目录
  1. 1. 一、系统架构总览
  2. 2. 二、模型定价体系 (modelCost.ts)
    1. 2.1. 2.1 定价层级定义
    2. 2.2. 2.2 MODEL_COSTS 映射表
    3. 2.3. 2.3 Opus 4.6 Fast模式特殊定价
    4. 2.4. 2.4 费用计算核心函数
      1. 2.4.1. calculateUSDCost(model: string, usage: TokenUsage): number
  3. 3. 三、会话成本追踪 (cost-tracker.ts)
    1. 3.1. 3.1 状态管理架构
    2. 3.2. 3.2 成本累加:addToTotalSessionCost()
      1. 3.2.1. Advisor递归追踪
    3. 3.3. 3.3 会话持久化
      1. 3.3.1. saveCurrentSessionCosts(fpsMetrics?)
      2. 3.3.2. getStoredSessionCosts()
      3. 3.3.3. restoreCostStateForSession(sessionId)
    4. 3.4. 3.4 格式化输出:formatTotalCost()
  4. 4. 四、React集成:costHook.ts
    1. 4.1. 4.1 useCostSummary() Hook
  5. 5. 五、OpenTelemetry遥测集成
    1. 5.1. 5.1 两个计数器
    2. 5.2. 5.2 遥测用途
  6. 6. 六、缓存经济学
    1. 6.1. 6.1 缓存定价模型
    2. 6.2. 6.2 经济分析
  7. 7. 七、完整数据流追踪
  8. 8. 八、边界情况与容错设计
    1. 8.1. 8.1 未知模型处理
    2. 8.2. 8.2 会话恢复的一致性
    3. 8.3. 8.3 并发安全
  9. 9. 九、设计哲学总结
  10. 10. 涉及源文件
【Claude Code源码剖析】14-成本追踪与Token管理系统

⚠️ 学习声明:本文档基于 Claude Code 2.1.88 源码分析整理,仅供个人学习研究使用,不做任何商业用途。

职责: 模型调用计费、Token消耗统计、会话成本持久化、OpenTelemetry遥测上报


一、系统架构总览

成本追踪系统由三个文件组成,职责清晰分离:

modelCost.ts          cost-tracker.ts              costHook.ts
┌─────────────┐ ┌──────────────────────┐ ┌──────────────┐
│ 定价表定义 │ │ 状态累加 & 持久化 │ │ React Hook │
│ MODEL_COSTS │──────>│ addToTotalSessionCost│ │ 进程退出时 │
│ calculateUSD │ │ saveCurrentSession │<────│ 格式化+保存 │
│ Cost() │ │ formatTotalCost() │ │ │
└─────────────┘ │ OpenTelemetry上报 │ └──────────────┘
└──────────────────────┘

数据流: API调用完成 → calculateUSDCost() 计算单次费用 → addToTotalSessionCost() 累加到会话状态 → 进程退出时 saveCurrentSessionCosts() 写入磁盘 → 下次恢复时 restoreCostStateForSession() 读取


二、模型定价体系 (modelCost.ts)

2.1 定价层级定义

系统定义了 6个定价层级,每个层级是一个 { input, output } 对象,单位是 **美元/百万Token ($/MTok)**:

COST_TIER_3_15    = { input: 3,    output: 15   }  // Sonnet系列
COST_TIER_15_75 = { input: 15, output: 75 } // Opus 4 / Opus 4.1
COST_TIER_5_25 = { input: 5, output: 25 } // Opus 4.5 / Opus 4.6(常规)
COST_TIER_30_150 = { input: 30, output: 150 } // Opus 4.6 fast模式
COST_HAIKU_35 = { input: 0.80, output: 4 } // Haiku 3.5
COST_HAIKU_45 = { input: 1, output: 5 } // Haiku 4.5

2.2 MODEL_COSTS 映射表

MODEL_COSTS 是一个 Map<string, ModelCostEntry>,键为模型的 规范化名称(canonical name),值为对应的定价层级。源码中完整的映射:

模型名称 定价层级 Input $/MTok Output $/MTok
claude-sonnet-4-20250514 COST_TIER_3_15 $3 $15
claude-sonnet-4-0 COST_TIER_3_15 $3 $15
claude-3-5-sonnet-20241022 COST_TIER_3_15 $3 $15
claude-3-7-sonnet-20250219 COST_TIER_3_15 $3 $15
claude-opus-4-20250918 COST_TIER_15_75 $15 $75
claude-opus-4-0 COST_TIER_15_75 $15 $75
claude-4-1-opus-20250620 COST_TIER_15_75 $15 $75
claude-opus-4-5-20250601 COST_TIER_5_25 $5 $25
claude-opus-4-6-20250822 COST_TIER_5_25 (常规) / COST_TIER_30_150 (fast) $5/$30 $25/$150
claude-3-5-haiku-20241022 COST_HAIKU_35 $0.80 $4
claude-haiku-4-5-20250601 COST_HAIKU_45 $1 $5

注意: 表中每个模型可能有多个别名(如 claude-sonnet-4-0claude-sonnet-4-20250514 指向同一定价),MAP 中都有独立条目。

2.3 Opus 4.6 Fast模式特殊定价

Opus 4.6 是唯一一个有 双定价层级 的模型。源码中通过 getOpus46CostTier() 函数判断:

function getOpus46CostTier(usage: TokenUsage): ModelCostEntry {
// 检查 usage 中是否有 speed: "fast" 标记
if (usage?.speed === "fast") {
return COST_TIER_30_150; // $30/$150 per MTok
}
return COST_TIER_5_25; // $5/$25 per MTok (默认/常规)
}

设计原因: Opus 4.6 fast模式牺牲成本换取更低延迟,定价是常规模式的6倍。系统根据API返回的 usage.speed 字段自动选择定价。

2.4 费用计算核心函数

calculateUSDCost(model: string, usage: TokenUsage): number

这是对外暴露的唯一计算入口:

calculateUSDCost(model, usage)
└── tokensToUSDCost(getModelCosts(model, usage), usage)

内部流程:

  1. getModelCosts(model, usage):

    • MODEL_COSTS 中查找模型名称
    • 如果是 Opus 4.6,进入 getOpus46CostTier(usage) 判断 fast/常规
    • 找不到模型 → 调用 setHasUnknownModelCost() 标记(全局状态),然后使用 默认模型 的定价作为 fallback
    • 默认模型通过 getDefaultModel() 获取(通常是 Sonnet 系列)
  2. tokensToUSDCost(costs, usage):

    cost = (usage.inputTokens * costs.input / 1_000_000)
    + (usage.outputTokens * costs.output / 1_000_000)
    + (usage.cacheCreationInputTokens * costs.input * 1.25 / 1_000_000)
    + (usage.cacheReadInputTokens * costs.input * 0.10 / 1_000_000)

    关键定价规则:

    • 缓存创建 (cache creation): 比普通输入贵 25% (乘以 1.25)
    • 缓存读取 (cache read): 只需普通输入的 10% (乘以 0.10)
    • 这就是为什么上下文缓存能大幅降低成本的原因
  3. setHasUnknownModelCost():

    • 设置全局标志 hasUnknownModelCost = true
    • 当会话结束格式化输出时,如果此标志为 true,会附加 “cost is estimated” 的提示
    • 这确保了系统在遇到新模型时不会崩溃,但会告知用户费用仅为估算值

三、会话成本追踪 (cost-tracker.ts)

3.1 状态管理架构

成本状态存储在全局 Store 中(getState()/setState()),涉及以下字段:

// State中的成本相关字段
{
totalCost: number, // 累计美元费用
totalAPIDuration: number, // 累计API调用耗时(ms)
totalModelUsage: Record<string, { // 每个模型的token使用量
inputTokens: number,
outputTokens: number,
cacheCreationInputTokens: number,
cacheReadInputTokens: number,
}>,
fpsMetrics: FPSMetrics, // 渲染帧率指标
}

3.2 成本累加:addToTotalSessionCost()

这是每次API调用返回后的核心累加函数。源码逻辑按顺序

addToTotalSessionCost(cost, duration, modelUsage, message)

├── 1. setState({ totalCost: prev + cost })
│ // 累加本次费用到总费用

├── 2. setState({ totalAPIDuration: prev + duration })
│ // 累加API耗时

├── 3. addToTotalModelUsage(modelUsage)
│ // 按模型名称累加各类Token数
│ // 内部遍历 modelUsage 的每个 [model, tokens] 对
│ // 对 totalModelUsage[model] 做字段级累加

├── 4. OpenTelemetry 成本计数器上报
│ // otelCostCounter.add(cost, {
│ // model: modelName,
│ // speed: usage.speed || "normal"
│ // })

├── 5. OpenTelemetry Token计数器上报
│ // 对每种 token 类型分别上报:
│ // otelTokenCounter.add(inputTokens, { type: "input", model })
│ // otelTokenCounter.add(outputTokens, { type: "output", model })
│ // otelTokenCounter.add(cacheCreation, { type: "cache_creation", model })
│ // otelTokenCounter.add(cacheRead, { type: "cache_read", model })

└── 6. Advisor递归处理
// if (message?.advisorUsage) {
// getAdvisorUsage(message) → 提取advisor的子调用费用
// 递归调用 addToTotalSessionCost() 处理advisor费用
// }

Advisor递归追踪

getAdvisorUsage() 函数从 API 响应消息中提取 advisor(副手/子Agent) 的资源消耗。Advisor 是 Claude Code 内部用于处理某些特殊任务的辅助模型调用(比如安全审查、代码审查等)。

递归结构确保无论 advisor 嵌套多少层,所有费用都会被正确累加到总费用中。

3.3 会话持久化

saveCurrentSessionCosts(fpsMetrics?)

在进程退出时调用,将当前会话的所有成本数据写入 项目配置文件(通过 setProjectConfig()):

保存的数据结构 (per session key):
{
cost: number, // 总美元费用
duration: number, // 总API耗时
durationMs: number, // 总壁钟耗时 (Date.now() - sessionStartTime)
modelUsage: Record<string, TokenUsage>, // 每模型token明细
linesAdded: number, // git diff统计: 新增行数
linesRemoved: number, // git diff统计: 删除行数
fpsMetrics?: FPSMetrics, // 可选: UI渲染帧率
}

存储键: 使用 getSessionId() 生成,每个会话有唯一 ID,存储在项目级别配置中(~/.claude/projects/<project-hash>/config.json)。

Git统计: 保存前会执行 git diff --stat 获取当前会话的代码变更量(新增/删除行数),记录到持久化数据中。

getStoredSessionCosts()

读取指定会话的历史成本数据,用于展示历史或恢复。

restoreCostStateForSession(sessionId)

在恢复一个已有会话时调用(如 --resume 模式),从磁盘读取之前保存的成本状态,恢复到内存 State 中,确保费用累计是连续的。

3.4 格式化输出:formatTotalCost()

在会话结束时生成人类可读的成本报告。源码中使用 chalk.dim() 渲染为灰色文本。

输出格式(实际源码):

Total cost: $1.23
Duration: 45s API time, 2m 30s wall time
Code: +123 -45 lines changed
Model usage:
claude-sonnet-4-20250514: 50,000 input, 12,000 output, 30,000 cache_creation, 200,000 cache_read
claude-3-5-haiku-20241022: 5,000 input, 2,000 output

formatModelUsage() 内部函数:遍历 totalModelUsage Map,对每个模型生成一行,列出各类Token数量。只有非零的Token类型才会显示。

未知模型费用提示: 如果 hasUnknownModelCost 标志为 true(在计算时遇到了不在 MODEL_COSTS 表中的模型),输出末尾会附加 (cost is estimated) 提示。


四、React集成:costHook.ts

4.1 useCostSummary() Hook

这是整个成本系统与 React UI 层的唯一连接点。完整源码仅23行:

export function useCostSummary(): void {
useEffect(() => {
const handler = () => {
const summary = formatTotalCost();
if (summary) {
// 输出成本摘要到终端(仅对有计费权限的用户)
process.stderr.write(summary + "\n");
}
saveCurrentSessionCosts(getState().fpsMetrics);
};
process.on("exit", handler);
return () => {
process.removeListener("exit", handler);
};
}, []);
}

工作原理:

  1. 使用 React useEffect 注册一次性的 process.exit 事件监听器
  2. 退出时做两件事:
    • formatTotalCost() → 格式化输出到 stderr(灰色文本)
    • saveCurrentSessionCosts() → 持久化到磁盘
  3. cleanup函数移除监听器,防止内存泄漏

为什么写到 stderr: 因为 stdout 可能被管道使用(如 claude | grep ...),成本信息属于诊断信息,应该输出到 stderr。

formatTotalCost() 的条件性: 只有 hasBillingAccess() 为 true 时才返回格式化字符串。API Key用户有计费权限,但某些免费试用或内部用户可能没有,此时返回空字符串,不显示成本。


五、OpenTelemetry遥测集成

5.1 两个计数器

成本系统向 OpenTelemetry 上报两个 Counter(单调递增计数器):

  1. otelCostCounter: 费用计数器

    • 值: 美元金额(浮点数)
    • 属性: { model: string, speed: "normal" | "fast" }
  2. otelTokenCounter: Token数量计数器

    • 值: Token数量(整数)
    • 属性: { type: "input" | "output" | "cache_creation" | "cache_read", model: string }

5.2 遥测用途

这些指标被发送到 Anthropic 的后端,用于:

  • 监控不同模型的使用分布
  • 追踪缓存命中率(cache_read 占比越高说明缓存效果越好)
  • 检测 Opus 4.6 fast模式的使用频率
  • 计费审计和异常检测

六、缓存经济学

理解缓存定价对优化 Claude Code 使用成本至关重要:

6.1 缓存定价模型

普通输入:       1.00x 基础价格
缓存创建: 1.25x 基础价格 (首次写入缓存)
缓存读取: 0.10x 基础价格 (后续命中缓存)

6.2 经济分析

以 Sonnet 模型($3/MTok input)为例:

  • 普通输入 100K tokens: $0.30
  • 缓存创建 100K tokens: $0.375 (贵25%)
  • 缓存读取 100K tokens: $0.03 (便宜90%)

盈亏平衡点: 如果一段上下文被缓存后至少读取 2次,总成本就低于每次都直接输入:

  • 2次直接输入: $0.30 × 2 = $0.60
  • 1次缓存创建 + 1次缓存读取: $0.375 + $0.03 = $0.405 ✓

这就是为什么 Claude Code 的上下文压缩系统(compact)会尽量保留系统提示等高复用内容在缓存中。


七、完整数据流追踪

从一次API调用到最终持久化的完整路径:

1. QueryEngine 发起API调用
└── @anthropic-ai/sdk.messages.create({model, messages, ...})

2. API返回 response.usage
└── { input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens }

3. 计算费用
└── calculateUSDCost(model, usage)
├── getModelCosts(model, usage) // 查表
│ └── Opus 4.6? → getOpus46CostTier(usage)
└── tokensToUSDCost(costs, usage) // 四项相乘相加

4. 累加到会话状态
└── addToTotalSessionCost(cost, duration, {[model]: usage}, message)
├── setState({ totalCost: +=cost })
├── setState({ totalAPIDuration: +=duration })
├── addToTotalModelUsage(...)
├── otelCostCounter.add(cost, attrs)
├── otelTokenCounter.add(tokens, attrs) // 四种类型各一次
└── advisor递归? → 再次 addToTotalSessionCost()

5. 进程退出(useCostSummary hook触发)
├── formatTotalCost() // 格式化输出
│ └── chalk.dim(cost + duration + codeChanges + modelBreakdown)
└── saveCurrentSessionCosts(fpsMetrics) // 持久化
├── git diff --stat → linesAdded/Removed
└── setProjectConfig(sessionKey, data)
└── ~/.claude/projects/<hash>/config.json

6. 会话恢复
└── restoreCostStateForSession(sessionId)
└── getStoredSessionCosts() → setState(restored)

八、边界情况与容错设计

8.1 未知模型处理

当遇到 MODEL_COSTS 中没有的模型名称时:

  1. 使用默认模型的定价作为 fallback(不崩溃)
  2. 设置全局标志 hasUnknownModelCost = true
  3. 输出成本时附加 “(cost is estimated)” 提示
  4. 这使得系统在 Anthropic 发布新模型但代码尚未更新时仍能正常工作

8.2 会话恢复的一致性

restoreCostStateForSession() 确保 --resume 恢复的会话费用是连续累加的,不会因为进程重启而丢失之前的费用记录。

8.3 并发安全

所有状态更新通过 setState() 的原子操作完成。由于 Node.js 单线程模型,不存在并发写入问题。但 advisor 递归调用是同步的,确保嵌套费用在主调用返回前就已累加完毕。


九、设计哲学总结

  1. 分层清晰: 定价(modelCost) → 追踪(cost-tracker) → 展示(costHook),每层单一职责
  2. 可扩展: 新增模型只需在 MODEL_COSTS Map 中添加一行
  3. 容错优先: 未知模型不崩溃,用 fallback + 提示
  4. 双重持久化: 内存 State 保证实时性,磁盘 ProjectConfig 保证重启后可恢复
  5. 遥测内建: OpenTelemetry 从设计之初就集成,不是事后追加
  6. 缓存感知: 定价公式内建缓存创建/读取的差异化系数,与上下文管理系统联动优化成本

涉及源文件

  • src/cost-tracker.ts
  • src/costHook.ts
  • src/services/modelCost.ts
打赏
  • 微信
  • 支付宝

评论