π File detail
utils/modelCost.ts
π― Use case
This file lives under βutils/β, which covers cross-cutting helpers (shell, tempfiles, settings, messages, process input, β¦). On the API surface it exposes ModelCosts, COST_TIER_3_15, COST_TIER_15_75, COST_TIER_5_25, and COST_TIER_30_150 (and more) β mainly types, interfaces, or factory objects. Dependencies touch @anthropic-ai and src. It composes internal code from bootstrap, fastMode, and model (relative imports).
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
import type { BetaUsage as Usage } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs' import type { AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS } from 'src/services/analytics/index.js' import { logEvent } from 'src/services/analytics/index.js' import { setHasUnknownModelCost } from '../bootstrap/state.js' import { isFastModeEnabled } from './fastMode.js'
π€ Exports (heuristic)
ModelCostsCOST_TIER_3_15COST_TIER_15_75COST_TIER_5_25COST_TIER_30_150COST_HAIKU_35COST_HAIKU_45getOpus46CostTierMODEL_COSTSgetModelCostscalculateUSDCostcalculateCostFromTokensformatModelPricinggetModelPricingString
π External import roots
Package roots from from "β¦" (relative paths omitted).
@anthropic-aisrc
π₯οΈ Source preview
import type { BetaUsage as Usage } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
import type { AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS } from 'src/services/analytics/index.js'
import { logEvent } from 'src/services/analytics/index.js'
import { setHasUnknownModelCost } from '../bootstrap/state.js'
import { isFastModeEnabled } from './fastMode.js'
import {
CLAUDE_3_5_HAIKU_CONFIG,
CLAUDE_3_5_V2_SONNET_CONFIG,
CLAUDE_3_7_SONNET_CONFIG,
CLAUDE_HAIKU_4_5_CONFIG,
CLAUDE_OPUS_4_1_CONFIG,
CLAUDE_OPUS_4_5_CONFIG,
CLAUDE_OPUS_4_6_CONFIG,
CLAUDE_OPUS_4_CONFIG,
CLAUDE_SONNET_4_5_CONFIG,
CLAUDE_SONNET_4_6_CONFIG,
CLAUDE_SONNET_4_CONFIG,
} from './model/configs.js'
import {
firstPartyNameToCanonical,
getCanonicalName,
getDefaultMainLoopModelSetting,
type ModelShortName,
} from './model/model.js'
// @see https://platform.claude.com/docs/en/about-claude/pricing
export type ModelCosts = {
inputTokens: number
outputTokens: number
promptCacheWriteTokens: number
promptCacheReadTokens: number
webSearchRequests: number
}
// Standard pricing tier for Sonnet models: $3 input / $15 output per Mtok
export const COST_TIER_3_15 = {
inputTokens: 3,
outputTokens: 15,
promptCacheWriteTokens: 3.75,
promptCacheReadTokens: 0.3,
webSearchRequests: 0.01,
} as const satisfies ModelCosts
// Pricing tier for Opus 4/4.1: $15 input / $75 output per Mtok
export const COST_TIER_15_75 = {
inputTokens: 15,
outputTokens: 75,
promptCacheWriteTokens: 18.75,
promptCacheReadTokens: 1.5,
webSearchRequests: 0.01,
} as const satisfies ModelCosts
// Pricing tier for Opus 4.5: $5 input / $25 output per Mtok
export const COST_TIER_5_25 = {
inputTokens: 5,
outputTokens: 25,
promptCacheWriteTokens: 6.25,
promptCacheReadTokens: 0.5,
webSearchRequests: 0.01,
} as const satisfies ModelCosts
// Fast mode pricing for Opus 4.6: $30 input / $150 output per Mtok
export const COST_TIER_30_150 = {
inputTokens: 30,
outputTokens: 150,
promptCacheWriteTokens: 37.5,
promptCacheReadTokens: 3,
webSearchRequests: 0.01,
} as const satisfies ModelCosts
// Pricing for Haiku 3.5: $0.80 input / $4 output per Mtok
export const COST_HAIKU_35 = {
inputTokens: 0.8,
outputTokens: 4,
promptCacheWriteTokens: 1,
promptCacheReadTokens: 0.08,
webSearchRequests: 0.01,
} as const satisfies ModelCosts
// Pricing for Haiku 4.5: $1 input / $5 output per Mtok
export const COST_HAIKU_45 = {
inputTokens: 1,
outputTokens: 5,
promptCacheWriteTokens: 1.25,
promptCacheReadTokens: 0.1,
webSearchRequests: 0.01,
} as const satisfies ModelCosts
const DEFAULT_UNKNOWN_MODEL_COST = COST_TIER_5_25
/**
* Get the cost tier for Opus 4.6 based on fast mode.
*/
export function getOpus46CostTier(fastMode: boolean): ModelCosts {
if (isFastModeEnabled() && fastMode) {
return COST_TIER_30_150
}
return COST_TIER_5_25
}
// @[MODEL LAUNCH]: Add a pricing entry for the new model below.
// Costs from https://platform.claude.com/docs/en/about-claude/pricing
// Web search cost: $10 per 1000 requests = $0.01 per request
export const MODEL_COSTS: Record<ModelShortName, ModelCosts> = {
[firstPartyNameToCanonical(CLAUDE_3_5_HAIKU_CONFIG.firstParty)]:
COST_HAIKU_35,
[firstPartyNameToCanonical(CLAUDE_HAIKU_4_5_CONFIG.firstParty)]:
COST_HAIKU_45,
[firstPartyNameToCanonical(CLAUDE_3_5_V2_SONNET_CONFIG.firstParty)]:
COST_TIER_3_15,
[firstPartyNameToCanonical(CLAUDE_3_7_SONNET_CONFIG.firstParty)]:
COST_TIER_3_15,
[firstPartyNameToCanonical(CLAUDE_SONNET_4_CONFIG.firstParty)]:
COST_TIER_3_15,
[firstPartyNameToCanonical(CLAUDE_SONNET_4_5_CONFIG.firstParty)]:
COST_TIER_3_15,
[firstPartyNameToCanonical(CLAUDE_SONNET_4_6_CONFIG.firstParty)]:
COST_TIER_3_15,
[firstPartyNameToCanonical(CLAUDE_OPUS_4_CONFIG.firstParty)]: COST_TIER_15_75,
[firstPartyNameToCanonical(CLAUDE_OPUS_4_1_CONFIG.firstParty)]:
COST_TIER_15_75,
[firstPartyNameToCanonical(CLAUDE_OPUS_4_5_CONFIG.firstParty)]:
COST_TIER_5_25,
[firstPartyNameToCanonical(CLAUDE_OPUS_4_6_CONFIG.firstParty)]:
COST_TIER_5_25,
}
/**
* Calculates the USD cost based on token usage and model cost configuration
*/
function tokensToUSDCost(modelCosts: ModelCosts, usage: Usage): number {
return (
(usage.input_tokens / 1_000_000) * modelCosts.inputTokens +
(usage.output_tokens / 1_000_000) * modelCosts.outputTokens +
((usage.cache_read_input_tokens ?? 0) / 1_000_000) *
modelCosts.promptCacheReadTokens +
((usage.cache_creation_input_tokens ?? 0) / 1_000_000) *
modelCosts.promptCacheWriteTokens +
(usage.server_tool_use?.web_search_requests ?? 0) *
modelCosts.webSearchRequests
)
}
export function getModelCosts(model: string, usage: Usage): ModelCosts {
const shortName = getCanonicalName(model)
// Check if this is an Opus 4.6 model with fast mode active.
if (
shortName === firstPartyNameToCanonical(CLAUDE_OPUS_4_6_CONFIG.firstParty)
) {
const isFastMode = usage.speed === 'fast'
return getOpus46CostTier(isFastMode)
}
const costs = MODEL_COSTS[shortName]
if (!costs) {
trackUnknownModelCost(model, shortName)
return (
MODEL_COSTS[getCanonicalName(getDefaultMainLoopModelSetting())] ??
DEFAULT_UNKNOWN_MODEL_COST
)
}
return costs
}
function trackUnknownModelCost(model: string, shortName: ModelShortName): void {
logEvent('tengu_unknown_model_cost', {
model: model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
shortName:
shortName as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
})
setHasUnknownModelCost()
}
// Calculate the cost of a query in US dollars.
// If the model's costs are not found, use the default model's costs.
export function calculateUSDCost(resolvedModel: string, usage: Usage): number {
const modelCosts = getModelCosts(resolvedModel, usage)
return tokensToUSDCost(modelCosts, usage)
}
/**
* Calculate cost from raw token counts without requiring a full BetaUsage object.
* Useful for side queries (e.g. classifier) that track token counts independently.
*/
export function calculateCostFromTokens(
model: string,
tokens: {
inputTokens: number
outputTokens: number
cacheReadInputTokens: number
cacheCreationInputTokens: number
},
): number {
const usage: Usage = {
input_tokens: tokens.inputTokens,
output_tokens: tokens.outputTokens,
cache_read_input_tokens: tokens.cacheReadInputTokens,
cache_creation_input_tokens: tokens.cacheCreationInputTokens,
} as Usage
return calculateUSDCost(model, usage)
}
function formatPrice(price: number): string {
// Format price: integers without decimals, others with 2 decimal places
// e.g., 3 -> "$3", 0.8 -> "$0.80", 22.5 -> "$22.50"
if (Number.isInteger(price)) {
return `$${price}`
}
return `$${price.toFixed(2)}`
}
/**
* Format model costs as a pricing string for display
* e.g., "$3/$15 per Mtok"
*/
export function formatModelPricing(costs: ModelCosts): string {
return `${formatPrice(costs.inputTokens)}/${formatPrice(costs.outputTokens)} per Mtok`
}
/**
* Get formatted pricing string for a model
* Accepts either a short name or full model name
* Returns undefined if model is not found
*/
export function getModelPricingString(model: string): string | undefined {
const shortName = getCanonicalName(model)
const costs = MODEL_COSTS[shortName]
if (!costs) return undefined
return formatModelPricing(costs)
}