Spaces:
Sleeping
Sleeping
| /** | |
| * Usage tracking module β matches original rust/crates/runtime/src/usage.rs exactly. | |
| * | |
| * Provides: | |
| * - ModelPricing with per-model cost rates | |
| * - pricing_for_model() lookup | |
| * - TokenUsage with cache_creation/cache_read tokens | |
| * - UsageCostEstimate with total_cost_usd() | |
| * - UsageTracker with record(), cumulative_usage(), current_turn_usage(), turns() | |
| * - summary_lines_for_model() with pricing=estimated-default fallback | |
| * - format_usd() helper | |
| */ | |
| // βββ Constants (match original DEFAULT_*_COST_PER_MILLION) ββββββββββββββββββ | |
| const DEFAULT_INPUT_COST_PER_MILLION = 15.0; | |
| const DEFAULT_OUTPUT_COST_PER_MILLION = 75.0; | |
| const DEFAULT_CACHE_CREATION_COST_PER_MILLION = 18.75; | |
| const DEFAULT_CACHE_READ_COST_PER_MILLION = 1.5; | |
| // βββ ModelPricing βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| export interface ModelPricing { | |
| input_cost_per_million: number; | |
| output_cost_per_million: number; | |
| cache_creation_cost_per_million: number; | |
| cache_read_cost_per_million: number; | |
| } | |
| export function defaultSonnetTierPricing(): ModelPricing { | |
| return { | |
| input_cost_per_million: DEFAULT_INPUT_COST_PER_MILLION, | |
| output_cost_per_million: DEFAULT_OUTPUT_COST_PER_MILLION, | |
| cache_creation_cost_per_million: DEFAULT_CACHE_CREATION_COST_PER_MILLION, | |
| cache_read_cost_per_million: DEFAULT_CACHE_READ_COST_PER_MILLION, | |
| }; | |
| } | |
| /** | |
| * Matches original pricing_for_model() β returns pricing for known models, | |
| * null for unknown models (caller should use default). | |
| */ | |
| export function pricingForModel(model: string): ModelPricing | null { | |
| const normalized = model.toLowerCase(); | |
| if (normalized.includes("haiku")) { | |
| return { | |
| input_cost_per_million: 1.0, | |
| output_cost_per_million: 5.0, | |
| cache_creation_cost_per_million: 1.25, | |
| cache_read_cost_per_million: 0.1, | |
| }; | |
| } | |
| if (normalized.includes("opus")) { | |
| return { | |
| input_cost_per_million: 15.0, | |
| output_cost_per_million: 75.0, | |
| cache_creation_cost_per_million: 18.75, | |
| cache_read_cost_per_million: 1.5, | |
| }; | |
| } | |
| if (normalized.includes("sonnet")) { | |
| return defaultSonnetTierPricing(); | |
| } | |
| return null; | |
| } | |
| // βββ TokenUsage βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| export interface TokenUsage { | |
| input_tokens: number; | |
| output_tokens: number; | |
| cache_creation_input_tokens: number; | |
| cache_read_input_tokens: number; | |
| } | |
| export function emptyTokenUsage(): TokenUsage { | |
| return { | |
| input_tokens: 0, | |
| output_tokens: 0, | |
| cache_creation_input_tokens: 0, | |
| cache_read_input_tokens: 0, | |
| }; | |
| } | |
| export function totalTokens(usage: TokenUsage): number { | |
| return ( | |
| usage.input_tokens + | |
| usage.output_tokens + | |
| usage.cache_creation_input_tokens + | |
| usage.cache_read_input_tokens | |
| ); | |
| } | |
| // βββ UsageCostEstimate ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| export interface UsageCostEstimate { | |
| input_cost_usd: number; | |
| output_cost_usd: number; | |
| cache_creation_cost_usd: number; | |
| cache_read_cost_usd: number; | |
| } | |
| export function totalCostUsd(estimate: UsageCostEstimate): number { | |
| return ( | |
| estimate.input_cost_usd + | |
| estimate.output_cost_usd + | |
| estimate.cache_creation_cost_usd + | |
| estimate.cache_read_cost_usd | |
| ); | |
| } | |
| function costForTokens(tokens: number, costPerMillion: number): number { | |
| return (tokens / 1_000_000) * costPerMillion; | |
| } | |
| export function estimateCostUsd(usage: TokenUsage): UsageCostEstimate { | |
| return estimateCostUsdWithPricing(usage, defaultSonnetTierPricing()); | |
| } | |
| export function estimateCostUsdWithPricing( | |
| usage: TokenUsage, | |
| pricing: ModelPricing | |
| ): UsageCostEstimate { | |
| return { | |
| input_cost_usd: costForTokens(usage.input_tokens, pricing.input_cost_per_million), | |
| output_cost_usd: costForTokens(usage.output_tokens, pricing.output_cost_per_million), | |
| cache_creation_cost_usd: costForTokens( | |
| usage.cache_creation_input_tokens, | |
| pricing.cache_creation_cost_per_million | |
| ), | |
| cache_read_cost_usd: costForTokens( | |
| usage.cache_read_input_tokens, | |
| pricing.cache_read_cost_per_million | |
| ), | |
| }; | |
| } | |
| // βββ format_usd βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| export function formatUsd(amount: number): string { | |
| return `$${amount.toFixed(4)}`; | |
| } | |
| // βββ summary_lines_for_model ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| export function summaryLines(usage: TokenUsage, label: string): string[] { | |
| return summaryLinesForModel(usage, label, undefined); | |
| } | |
| export function summaryLinesForModel( | |
| usage: TokenUsage, | |
| label: string, | |
| model?: string | |
| ): string[] { | |
| const pricing = model ? pricingForModel(model) : null; | |
| const effectivePricing = pricing ?? defaultSonnetTierPricing(); | |
| const cost = estimateCostUsdWithPricing(usage, effectivePricing); | |
| const total = totalCostUsd(cost); | |
| const pricingLabel = pricing ? `model=${model}` : "pricing=estimated-default"; | |
| const line1 = [ | |
| `${label}:`, | |
| `estimated_cost=${formatUsd(total)}`, | |
| pricingLabel, | |
| `input=${formatUsd(cost.input_cost_usd)}`, | |
| `output=${formatUsd(cost.output_cost_usd)}`, | |
| ].join(" "); | |
| const line2 = [ | |
| ` tokens:`, | |
| `input=${usage.input_tokens}`, | |
| `output=${usage.output_tokens}`, | |
| `cache_creation=${usage.cache_creation_input_tokens}`, | |
| `cache_read=${usage.cache_read_input_tokens}`, | |
| `cache_creation=${formatUsd(cost.cache_creation_cost_usd)}`, | |
| `cache_read=${formatUsd(cost.cache_read_cost_usd)}`, | |
| ].join(" "); | |
| return [line1, line2]; | |
| } | |
| // βββ UsageTracker βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| export interface SessionMessage { | |
| usage?: TokenUsage | null; | |
| } | |
| export class UsageTracker { | |
| private latestTurn: TokenUsage = emptyTokenUsage(); | |
| private cumulative: TokenUsage = emptyTokenUsage(); | |
| private _turns: number = 0; | |
| static new(): UsageTracker { | |
| return new UsageTracker(); | |
| } | |
| /** | |
| * Matches original UsageTracker::from_session() β reconstructs tracker | |
| * from all messages that have usage data. | |
| */ | |
| static fromSession(messages: SessionMessage[]): UsageTracker { | |
| const tracker = new UsageTracker(); | |
| for (const msg of messages) { | |
| if (msg.usage) { | |
| tracker.record(msg.usage); | |
| } | |
| } | |
| return tracker; | |
| } | |
| record(usage: TokenUsage): void { | |
| this.latestTurn = usage; | |
| this.cumulative.input_tokens += usage.input_tokens; | |
| this.cumulative.output_tokens += usage.output_tokens; | |
| this.cumulative.cache_creation_input_tokens += usage.cache_creation_input_tokens; | |
| this.cumulative.cache_read_input_tokens += usage.cache_read_input_tokens; | |
| this._turns += 1; | |
| } | |
| currentTurnUsage(): TokenUsage { | |
| return { ...this.latestTurn }; | |
| } | |
| cumulativeUsage(): TokenUsage { | |
| return { ...this.cumulative }; | |
| } | |
| turns(): number { | |
| return this._turns; | |
| } | |
| } | |