source dump of claude code
at main 231 lines 7.5 kB view raw
1import type { BetaUsage as Usage } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs' 2import type { AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS } from 'src/services/analytics/index.js' 3import { logEvent } from 'src/services/analytics/index.js' 4import { setHasUnknownModelCost } from '../bootstrap/state.js' 5import { isFastModeEnabled } from './fastMode.js' 6import { 7 CLAUDE_3_5_HAIKU_CONFIG, 8 CLAUDE_3_5_V2_SONNET_CONFIG, 9 CLAUDE_3_7_SONNET_CONFIG, 10 CLAUDE_HAIKU_4_5_CONFIG, 11 CLAUDE_OPUS_4_1_CONFIG, 12 CLAUDE_OPUS_4_5_CONFIG, 13 CLAUDE_OPUS_4_6_CONFIG, 14 CLAUDE_OPUS_4_CONFIG, 15 CLAUDE_SONNET_4_5_CONFIG, 16 CLAUDE_SONNET_4_6_CONFIG, 17 CLAUDE_SONNET_4_CONFIG, 18} from './model/configs.js' 19import { 20 firstPartyNameToCanonical, 21 getCanonicalName, 22 getDefaultMainLoopModelSetting, 23 type ModelShortName, 24} from './model/model.js' 25 26// @see https://platform.claude.com/docs/en/about-claude/pricing 27export type ModelCosts = { 28 inputTokens: number 29 outputTokens: number 30 promptCacheWriteTokens: number 31 promptCacheReadTokens: number 32 webSearchRequests: number 33} 34 35// Standard pricing tier for Sonnet models: $3 input / $15 output per Mtok 36export const COST_TIER_3_15 = { 37 inputTokens: 3, 38 outputTokens: 15, 39 promptCacheWriteTokens: 3.75, 40 promptCacheReadTokens: 0.3, 41 webSearchRequests: 0.01, 42} as const satisfies ModelCosts 43 44// Pricing tier for Opus 4/4.1: $15 input / $75 output per Mtok 45export const COST_TIER_15_75 = { 46 inputTokens: 15, 47 outputTokens: 75, 48 promptCacheWriteTokens: 18.75, 49 promptCacheReadTokens: 1.5, 50 webSearchRequests: 0.01, 51} as const satisfies ModelCosts 52 53// Pricing tier for Opus 4.5: $5 input / $25 output per Mtok 54export const COST_TIER_5_25 = { 55 inputTokens: 5, 56 outputTokens: 25, 57 promptCacheWriteTokens: 6.25, 58 promptCacheReadTokens: 0.5, 59 webSearchRequests: 0.01, 60} as const satisfies ModelCosts 61 62// Fast mode pricing for Opus 4.6: $30 input / $150 output per Mtok 63export const COST_TIER_30_150 = { 64 inputTokens: 30, 65 outputTokens: 150, 66 promptCacheWriteTokens: 37.5, 67 promptCacheReadTokens: 3, 68 webSearchRequests: 0.01, 69} as const satisfies ModelCosts 70 71// Pricing for Haiku 3.5: $0.80 input / $4 output per Mtok 72export const COST_HAIKU_35 = { 73 inputTokens: 0.8, 74 outputTokens: 4, 75 promptCacheWriteTokens: 1, 76 promptCacheReadTokens: 0.08, 77 webSearchRequests: 0.01, 78} as const satisfies ModelCosts 79 80// Pricing for Haiku 4.5: $1 input / $5 output per Mtok 81export const COST_HAIKU_45 = { 82 inputTokens: 1, 83 outputTokens: 5, 84 promptCacheWriteTokens: 1.25, 85 promptCacheReadTokens: 0.1, 86 webSearchRequests: 0.01, 87} as const satisfies ModelCosts 88 89const DEFAULT_UNKNOWN_MODEL_COST = COST_TIER_5_25 90 91/** 92 * Get the cost tier for Opus 4.6 based on fast mode. 93 */ 94export function getOpus46CostTier(fastMode: boolean): ModelCosts { 95 if (isFastModeEnabled() && fastMode) { 96 return COST_TIER_30_150 97 } 98 return COST_TIER_5_25 99} 100 101// @[MODEL LAUNCH]: Add a pricing entry for the new model below. 102// Costs from https://platform.claude.com/docs/en/about-claude/pricing 103// Web search cost: $10 per 1000 requests = $0.01 per request 104export const MODEL_COSTS: Record<ModelShortName, ModelCosts> = { 105 [firstPartyNameToCanonical(CLAUDE_3_5_HAIKU_CONFIG.firstParty)]: 106 COST_HAIKU_35, 107 [firstPartyNameToCanonical(CLAUDE_HAIKU_4_5_CONFIG.firstParty)]: 108 COST_HAIKU_45, 109 [firstPartyNameToCanonical(CLAUDE_3_5_V2_SONNET_CONFIG.firstParty)]: 110 COST_TIER_3_15, 111 [firstPartyNameToCanonical(CLAUDE_3_7_SONNET_CONFIG.firstParty)]: 112 COST_TIER_3_15, 113 [firstPartyNameToCanonical(CLAUDE_SONNET_4_CONFIG.firstParty)]: 114 COST_TIER_3_15, 115 [firstPartyNameToCanonical(CLAUDE_SONNET_4_5_CONFIG.firstParty)]: 116 COST_TIER_3_15, 117 [firstPartyNameToCanonical(CLAUDE_SONNET_4_6_CONFIG.firstParty)]: 118 COST_TIER_3_15, 119 [firstPartyNameToCanonical(CLAUDE_OPUS_4_CONFIG.firstParty)]: COST_TIER_15_75, 120 [firstPartyNameToCanonical(CLAUDE_OPUS_4_1_CONFIG.firstParty)]: 121 COST_TIER_15_75, 122 [firstPartyNameToCanonical(CLAUDE_OPUS_4_5_CONFIG.firstParty)]: 123 COST_TIER_5_25, 124 [firstPartyNameToCanonical(CLAUDE_OPUS_4_6_CONFIG.firstParty)]: 125 COST_TIER_5_25, 126} 127 128/** 129 * Calculates the USD cost based on token usage and model cost configuration 130 */ 131function tokensToUSDCost(modelCosts: ModelCosts, usage: Usage): number { 132 return ( 133 (usage.input_tokens / 1_000_000) * modelCosts.inputTokens + 134 (usage.output_tokens / 1_000_000) * modelCosts.outputTokens + 135 ((usage.cache_read_input_tokens ?? 0) / 1_000_000) * 136 modelCosts.promptCacheReadTokens + 137 ((usage.cache_creation_input_tokens ?? 0) / 1_000_000) * 138 modelCosts.promptCacheWriteTokens + 139 (usage.server_tool_use?.web_search_requests ?? 0) * 140 modelCosts.webSearchRequests 141 ) 142} 143 144export function getModelCosts(model: string, usage: Usage): ModelCosts { 145 const shortName = getCanonicalName(model) 146 147 // Check if this is an Opus 4.6 model with fast mode active. 148 if ( 149 shortName === firstPartyNameToCanonical(CLAUDE_OPUS_4_6_CONFIG.firstParty) 150 ) { 151 const isFastMode = usage.speed === 'fast' 152 return getOpus46CostTier(isFastMode) 153 } 154 155 const costs = MODEL_COSTS[shortName] 156 if (!costs) { 157 trackUnknownModelCost(model, shortName) 158 return ( 159 MODEL_COSTS[getCanonicalName(getDefaultMainLoopModelSetting())] ?? 160 DEFAULT_UNKNOWN_MODEL_COST 161 ) 162 } 163 return costs 164} 165 166function trackUnknownModelCost(model: string, shortName: ModelShortName): void { 167 logEvent('tengu_unknown_model_cost', { 168 model: model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, 169 shortName: 170 shortName as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, 171 }) 172 setHasUnknownModelCost() 173} 174 175// Calculate the cost of a query in US dollars. 176// If the model's costs are not found, use the default model's costs. 177export function calculateUSDCost(resolvedModel: string, usage: Usage): number { 178 const modelCosts = getModelCosts(resolvedModel, usage) 179 return tokensToUSDCost(modelCosts, usage) 180} 181 182/** 183 * Calculate cost from raw token counts without requiring a full BetaUsage object. 184 * Useful for side queries (e.g. classifier) that track token counts independently. 185 */ 186export function calculateCostFromTokens( 187 model: string, 188 tokens: { 189 inputTokens: number 190 outputTokens: number 191 cacheReadInputTokens: number 192 cacheCreationInputTokens: number 193 }, 194): number { 195 const usage: Usage = { 196 input_tokens: tokens.inputTokens, 197 output_tokens: tokens.outputTokens, 198 cache_read_input_tokens: tokens.cacheReadInputTokens, 199 cache_creation_input_tokens: tokens.cacheCreationInputTokens, 200 } as Usage 201 return calculateUSDCost(model, usage) 202} 203 204function formatPrice(price: number): string { 205 // Format price: integers without decimals, others with 2 decimal places 206 // e.g., 3 -> "$3", 0.8 -> "$0.80", 22.5 -> "$22.50" 207 if (Number.isInteger(price)) { 208 return `$${price}` 209 } 210 return `$${price.toFixed(2)}` 211} 212 213/** 214 * Format model costs as a pricing string for display 215 * e.g., "$3/$15 per Mtok" 216 */ 217export function formatModelPricing(costs: ModelCosts): string { 218 return `${formatPrice(costs.inputTokens)}/${formatPrice(costs.outputTokens)} per Mtok` 219} 220 221/** 222 * Get formatted pricing string for a model 223 * Accepts either a short name or full model name 224 * Returns undefined if model is not found 225 */ 226export function getModelPricingString(model: string): string | undefined { 227 const shortName = getCanonicalName(model) 228 const costs = MODEL_COSTS[shortName] 229 if (!costs) return undefined 230 return formatModelPricing(costs) 231}