| import type { BetaUsage as Usage } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs' |
| import chalk from 'chalk' |
| import { |
| addToTotalCostState, |
| addToTotalLinesChanged, |
| getCostCounter, |
| getModelUsage, |
| getSdkBetas, |
| getSessionId, |
| getTokenCounter, |
| getTotalAPIDuration, |
| getTotalAPIDurationWithoutRetries, |
| getTotalCacheCreationInputTokens, |
| getTotalCacheReadInputTokens, |
| getTotalCostUSD, |
| getTotalDuration, |
| getTotalInputTokens, |
| getTotalLinesAdded, |
| getTotalLinesRemoved, |
| getTotalOutputTokens, |
| getTotalToolDuration, |
| getTotalWebSearchRequests, |
| getUsageForModel, |
| hasUnknownModelCost, |
| resetCostState, |
| resetStateForTests, |
| setCostStateForRestore, |
| setHasUnknownModelCost, |
| } from './bootstrap/state.js' |
| import type { ModelUsage } from './entrypoints/agentSdkTypes.js' |
| import { |
| type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| logEvent, |
| } from './services/analytics/index.js' |
| import { getAdvisorUsage } from './utils/advisor.js' |
| import { |
| getCurrentProjectConfig, |
| saveCurrentProjectConfig, |
| } from './utils/config.js' |
| import { |
| getContextWindowForModel, |
| getModelMaxOutputTokens, |
| } from './utils/context.js' |
| import { isFastModeEnabled } from './utils/fastMode.js' |
| import { formatDuration, formatNumber } from './utils/format.js' |
| import type { FpsMetrics } from './utils/fpsTracker.js' |
| import { getCanonicalName } from './utils/model/model.js' |
| import { calculateUSDCost } from './utils/modelCost.js' |
| export { |
| getTotalCostUSD as getTotalCost, |
| getTotalDuration, |
| getTotalAPIDuration, |
| getTotalAPIDurationWithoutRetries, |
| addToTotalLinesChanged, |
| getTotalLinesAdded, |
| getTotalLinesRemoved, |
| getTotalInputTokens, |
| getTotalOutputTokens, |
| getTotalCacheReadInputTokens, |
| getTotalCacheCreationInputTokens, |
| getTotalWebSearchRequests, |
| formatCost, |
| hasUnknownModelCost, |
| resetStateForTests, |
| resetCostState, |
| setHasUnknownModelCost, |
| getModelUsage, |
| getUsageForModel, |
| } |
|
|
| type StoredCostState = { |
| totalCostUSD: number |
| totalAPIDuration: number |
| totalAPIDurationWithoutRetries: number |
| totalToolDuration: number |
| totalLinesAdded: number |
| totalLinesRemoved: number |
| lastDuration: number | undefined |
| modelUsage: { [modelName: string]: ModelUsage } | undefined |
| } |
|
|
| |
| |
| |
| |
| |
| export function getStoredSessionCosts( |
| sessionId: string, |
| ): StoredCostState | undefined { |
| const projectConfig = getCurrentProjectConfig() |
|
|
| |
| if (projectConfig.lastSessionId !== sessionId) { |
| return undefined |
| } |
|
|
| |
| let modelUsage: { [modelName: string]: ModelUsage } | undefined |
| if (projectConfig.lastModelUsage) { |
| modelUsage = Object.fromEntries( |
| Object.entries(projectConfig.lastModelUsage).map(([model, usage]) => [ |
| model, |
| { |
| ...usage, |
| contextWindow: getContextWindowForModel(model, getSdkBetas()), |
| maxOutputTokens: getModelMaxOutputTokens(model).default, |
| }, |
| ]), |
| ) |
| } |
|
|
| return { |
| totalCostUSD: projectConfig.lastCost ?? 0, |
| totalAPIDuration: projectConfig.lastAPIDuration ?? 0, |
| totalAPIDurationWithoutRetries: |
| projectConfig.lastAPIDurationWithoutRetries ?? 0, |
| totalToolDuration: projectConfig.lastToolDuration ?? 0, |
| totalLinesAdded: projectConfig.lastLinesAdded ?? 0, |
| totalLinesRemoved: projectConfig.lastLinesRemoved ?? 0, |
| lastDuration: projectConfig.lastDuration, |
| modelUsage, |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| export function restoreCostStateForSession(sessionId: string): boolean { |
| const data = getStoredSessionCosts(sessionId) |
| if (!data) { |
| return false |
| } |
| setCostStateForRestore(data) |
| return true |
| } |
|
|
| |
| |
| |
| |
| export function saveCurrentSessionCosts(fpsMetrics?: FpsMetrics): void { |
| saveCurrentProjectConfig(current => ({ |
| ...current, |
| lastCost: getTotalCostUSD(), |
| lastAPIDuration: getTotalAPIDuration(), |
| lastAPIDurationWithoutRetries: getTotalAPIDurationWithoutRetries(), |
| lastToolDuration: getTotalToolDuration(), |
| lastDuration: getTotalDuration(), |
| lastLinesAdded: getTotalLinesAdded(), |
| lastLinesRemoved: getTotalLinesRemoved(), |
| lastTotalInputTokens: getTotalInputTokens(), |
| lastTotalOutputTokens: getTotalOutputTokens(), |
| lastTotalCacheCreationInputTokens: getTotalCacheCreationInputTokens(), |
| lastTotalCacheReadInputTokens: getTotalCacheReadInputTokens(), |
| lastTotalWebSearchRequests: getTotalWebSearchRequests(), |
| lastFpsAverage: fpsMetrics?.averageFps, |
| lastFpsLow1Pct: fpsMetrics?.low1PctFps, |
| lastModelUsage: Object.fromEntries( |
| Object.entries(getModelUsage()).map(([model, usage]) => [ |
| model, |
| { |
| inputTokens: usage.inputTokens, |
| outputTokens: usage.outputTokens, |
| cacheReadInputTokens: usage.cacheReadInputTokens, |
| cacheCreationInputTokens: usage.cacheCreationInputTokens, |
| webSearchRequests: usage.webSearchRequests, |
| costUSD: usage.costUSD, |
| }, |
| ]), |
| ), |
| lastSessionId: getSessionId(), |
| })) |
| } |
|
|
| function formatCost(cost: number, maxDecimalPlaces: number = 4): string { |
| return `$${cost > 0.5 ? round(cost, 100).toFixed(2) : cost.toFixed(maxDecimalPlaces)}` |
| } |
|
|
| function formatModelUsage(): string { |
| const modelUsageMap = getModelUsage() |
| if (Object.keys(modelUsageMap).length === 0) { |
| return 'Usage: 0 input, 0 output, 0 cache read, 0 cache write' |
| } |
|
|
| |
| const usageByShortName: { [shortName: string]: ModelUsage } = {} |
| for (const [model, usage] of Object.entries(modelUsageMap)) { |
| const shortName = getCanonicalName(model) |
| if (!usageByShortName[shortName]) { |
| usageByShortName[shortName] = { |
| inputTokens: 0, |
| outputTokens: 0, |
| cacheReadInputTokens: 0, |
| cacheCreationInputTokens: 0, |
| webSearchRequests: 0, |
| costUSD: 0, |
| contextWindow: 0, |
| maxOutputTokens: 0, |
| } |
| } |
| const accumulated = usageByShortName[shortName] |
| accumulated.inputTokens += usage.inputTokens |
| accumulated.outputTokens += usage.outputTokens |
| accumulated.cacheReadInputTokens += usage.cacheReadInputTokens |
| accumulated.cacheCreationInputTokens += usage.cacheCreationInputTokens |
| accumulated.webSearchRequests += usage.webSearchRequests |
| accumulated.costUSD += usage.costUSD |
| } |
|
|
| let result = 'Usage by model:' |
| for (const [shortName, usage] of Object.entries(usageByShortName)) { |
| const usageString = |
| ` ${formatNumber(usage.inputTokens)} input, ` + |
| `${formatNumber(usage.outputTokens)} output, ` + |
| `${formatNumber(usage.cacheReadInputTokens)} cache read, ` + |
| `${formatNumber(usage.cacheCreationInputTokens)} cache write` + |
| (usage.webSearchRequests > 0 |
| ? `, ${formatNumber(usage.webSearchRequests)} web search` |
| : '') + |
| ` (${formatCost(usage.costUSD)})` |
| result += `\n` + `${shortName}:`.padStart(21) + usageString |
| } |
| return result |
| } |
|
|
| export function formatTotalCost(): string { |
| const costDisplay = |
| formatCost(getTotalCostUSD()) + |
| (hasUnknownModelCost() |
| ? ' (costs may be inaccurate due to usage of unknown models)' |
| : '') |
|
|
| const modelUsageDisplay = formatModelUsage() |
|
|
| return chalk.dim( |
| `Total cost: ${costDisplay}\n` + |
| `Total duration (API): ${formatDuration(getTotalAPIDuration())} |
| Total duration (wall): ${formatDuration(getTotalDuration())} |
| Total code changes: ${getTotalLinesAdded()} ${getTotalLinesAdded() === 1 ? 'line' : 'lines'} added, ${getTotalLinesRemoved()} ${getTotalLinesRemoved() === 1 ? 'line' : 'lines'} removed |
| ${modelUsageDisplay}`, |
| ) |
| } |
|
|
| function round(number: number, precision: number): number { |
| return Math.round(number * precision) / precision |
| } |
|
|
| function addToTotalModelUsage( |
| cost: number, |
| usage: Usage, |
| model: string, |
| ): ModelUsage { |
| const modelUsage = getUsageForModel(model) ?? { |
| inputTokens: 0, |
| outputTokens: 0, |
| cacheReadInputTokens: 0, |
| cacheCreationInputTokens: 0, |
| webSearchRequests: 0, |
| costUSD: 0, |
| contextWindow: 0, |
| maxOutputTokens: 0, |
| } |
|
|
| modelUsage.inputTokens += usage.input_tokens |
| modelUsage.outputTokens += usage.output_tokens |
| modelUsage.cacheReadInputTokens += usage.cache_read_input_tokens ?? 0 |
| modelUsage.cacheCreationInputTokens += usage.cache_creation_input_tokens ?? 0 |
| modelUsage.webSearchRequests += |
| usage.server_tool_use?.web_search_requests ?? 0 |
| modelUsage.costUSD += cost |
| modelUsage.contextWindow = getContextWindowForModel(model, getSdkBetas()) |
| modelUsage.maxOutputTokens = getModelMaxOutputTokens(model).default |
| return modelUsage |
| } |
|
|
| export function addToTotalSessionCost( |
| cost: number, |
| usage: Usage, |
| model: string, |
| ): number { |
| const modelUsage = addToTotalModelUsage(cost, usage, model) |
| addToTotalCostState(cost, modelUsage, model) |
|
|
| const attrs = |
| isFastModeEnabled() && usage.speed === 'fast' |
| ? { model, speed: 'fast' } |
| : { model } |
|
|
| getCostCounter()?.add(cost, attrs) |
| getTokenCounter()?.add(usage.input_tokens, { ...attrs, type: 'input' }) |
| getTokenCounter()?.add(usage.output_tokens, { ...attrs, type: 'output' }) |
| getTokenCounter()?.add(usage.cache_read_input_tokens ?? 0, { |
| ...attrs, |
| type: 'cacheRead', |
| }) |
| getTokenCounter()?.add(usage.cache_creation_input_tokens ?? 0, { |
| ...attrs, |
| type: 'cacheCreation', |
| }) |
|
|
| let totalCost = cost |
| for (const advisorUsage of getAdvisorUsage(usage)) { |
| const advisorCost = calculateUSDCost(advisorUsage.model, advisorUsage) |
| logEvent('tengu_advisor_tool_token_usage', { |
| advisor_model: |
| advisorUsage.model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| input_tokens: advisorUsage.input_tokens, |
| output_tokens: advisorUsage.output_tokens, |
| cache_read_input_tokens: advisorUsage.cache_read_input_tokens ?? 0, |
| cache_creation_input_tokens: |
| advisorUsage.cache_creation_input_tokens ?? 0, |
| cost_usd_micros: Math.round(advisorCost * 1_000_000), |
| }) |
| totalCost += addToTotalSessionCost( |
| advisorCost, |
| advisorUsage, |
| advisorUsage.model, |
| ) |
| } |
| return totalCost |
| } |
|
|