Yoda
参考Agent 设计指南可观测

成本与 Token 追踪

CC 端侧算 USD(pricing 表内置、/cost、statusline、OTel cost 指标三处出口);Codex 只算 token 与限额百分比(TokenCount 累计值落盘、/status、零 USD)——harness 算钱的两套姿势

成本与 Token 追踪

证据标签:[一手源码] [一手文档] [推断]。CC 源码为重建快照(src_2026-03-31);Codex codex-rs @ b89ce9a (2026-06-06)。

结论

CC 在客户端本地把 token 折算成 USD:每次 API 响应的 usage 块经 calculateUSDCost(内置模型单价表)累进会话级状态,再从三个出口吐出——/cost 命令、statusline stdin JSON 的 cost.total_cost_usd、OTel 指标 claude_code.cost.usage;token 明细按模型分桶(input/output/cacheRead/cacheCreation/webSearch 五维)并随 lastSessionId 持久化到 project config 以支持 resume 续算。Codex 全程不出现美元——源码无任何 pricing 表,它追踪的是 TokenUsage 五元组(含 reasoning tokens)与服务端下发的 RateLimitSnapshot(5h/周限额百分比、credits、plan_type),TokenCount 事件以会话累计值形式白名单落盘 rollout,/status 渲染限额进度条(且对 ChatGPT 订阅用户隐藏 token 数)。给 harness 的含义:算 CC 的钱可以直接吃它算好的数(statusline/OTel/transcript usage 自算三选一);算 Codex 的钱必须自备单价表 × 自己从 rollout 的 token_count 累计值差分出增量——且订阅模式下「钱」本身是伪命题,应改算限额消耗。

研究问题

  • token 用量在哪产生、在哪累计、在哪持久化?
  • USD 折算谁来做?订阅(无单价)与 API(有单价)两种计费态如何分别呈现?
  • 外部 harness 如何重算 per-session / per-account 花费?哪些坑(去重、缓存 token、累计 vs 增量)?

各 Agent 设计与实现

Claude Code

累计管线[一手源码] cost-tracker.ts:278-323 addToTotalSessionCost(cost, usage, model):把 calculateUSDCostutils/modelCost.js)算出的单次成本与 API usage 块(input/output/cache_read/cache_creation/web_search 五维)累进按模型分桶的 ModelUsage,同时打 OTel 计数器:

getCostCounter()?.add(cost, attrs)                                  // claude_code.cost.usage
getTokenCounter()?.add(usage.input_tokens, { ...attrs, type: 'input' })  // claude_code.token.usage

counter 即 telemetry 章的 claude_code.cost.usage / token.usagebootstrap/state.ts:968-975 创建)。advisor 等辅助请求的用量也递归并入并单独打 tengu_advisor_tool_token_usage 内部事件(:304-321)。

持久化与 resume 续算[一手源码] cost-tracker.ts:143-175 saveCurrentSessionCostslastCost / lastAPIDuration / lastLinesAdded / lastModelUsage / lastSessionId 等写进 project config;resume 时 restoreCostStateForSession(:130-137) 仅在 lastSessionId 匹配时恢复——成本口径是「会话」而非「进程」

三个对外出口

  1. /cost 命令 [一手源码] commands/cost/cost.ts:6-23:订阅用户(isClaudeAISubscriber())看不到美元,只看到「You are currently using your subscription…」(或 overage 提示);API 计费用户才看 formatTotalCost() 的四行汇总(Total cost / API 时长 / wall 时长 / 代码行变更 + 按模型分桶用量)。[一手文档] costs.md:17-31 同口径:/usage 的 Session 块美元数是「locally computed estimate,may differ from your actual bill」。
  2. statusline stdin JSON [一手文档] statusline.md:165-168:cost.total_cost_usd / total_duration_ms / total_api_duration_ms / total_lines_added / total_lines_removed——每次状态更新推给用户脚本,是零解析成本的成本推送通道
  3. transcript:assistant 行内嵌 API 原样 message.usage(四类 token;无 USD)——离线重算的原料(见 Yoda 节)。

团队/机群口径 [一手文档] costs.md:企业均值约 $13/活跃日、$150-250/月;OTel cost.usage 指标带 model/query_source/agent.name/skill.name/mcp_server.name 等归因属性(monitoring-usage.md:466-482),可按 skill/插件/MCP server 切成本。后台功能(resume 摘要等)即使闲置也烧 token(典型 <$0.04/会话,costs.md:196-203)。

Codex CLI

协议层[一手源码] protocol/src/protocol.rs:1900-1911

pub struct TokenUsage {
    pub input_tokens: i64,          // 注意:包含 cached_input_tokens
    pub cached_input_tokens: i64,
    pub output_tokens: i64,
    pub reasoning_output_tokens: i64,
    pub total_tokens: i64,
}

TokenUsageInfo { total_token_usage, last_token_usage, model_context_window }:1914-1920)——total 是会话累计,last 是当前上下文占用。事件载体 TokenCountEvent { info, rate_limits: Option<RateLimitSnapshot> }:1981-1984);RateLimitSnapshot:1987-1997)带 primary/secondary 限额窗口、creditsindividual_limit(spend control)、plan_typerate_limit_reached_type(区分个人限额/工作区 credits 耗尽等 5 种,:2001-2007)。

落盘TokenCount 在 rollout 持久化白名单内 [一手源码] rollout/src/policy.rs:85——token 用量是磁盘可离线读的;resume 时从 rollout 尾部的 TokenCount 恢复 token_usage_infocore/src/session/mod.rs:1339)。

展示。TUI /status 卡片渲染限额进度条(20 段 █░,tui/src/status/rate_limits.rs:23-25),行如 "5h limit / Monthly limit / Credits";token 汇总用 blended_total = 非缓存 input + outputtui/src/token_usage.rs:33-35);ChatGPT 订阅用户默认隐藏 Token usage 行tui/src/status/card.rs:855-857 注释 "Hide token usage only for ChatGPT subscribers")——与 CC /cost 对订阅用户藏美元同构。

没有 USD[一手源码] 全 workspace 检索无模型单价表、无成本折算函数;token→钱的换算完全留给服务端(credits/限额百分比由 rate_limits 下发)与外部工具。厂商侧归因走 analytics:track_turn_token_usage(TurnTokenUsageFact { turn_id, thread_id, token_usage })analytics/src/facts.rs:101-105)逐 turn 回传。

差异矩阵

维度Claude CodeCodex CLI
USD 折算✅ 端侧内置单价表(utils/modelCost),实时累计❌ 全源码无 pricing;只有 token / 百分比 / credits
token 口径API usage 四类(input 不含 cache)+ webSearch,按模型分桶五元组,input 含 cached,含 reasoning tokens
落盘形式assistant 行 message.usage(每行=每次响应,增量)TokenCount 事件(会话累计值,需差分)
会话内持久化project config(lastCost+lastSessionId,resume 续算)rollout 尾部 TokenCount(resume 重放恢复)
用户出口/cost/usage、statusline JSON、OTel cost.usage/status(限额进度条 + token 汇总)
订阅态处理/cost 藏美元,显示订阅/overage 文案/status 藏 Token usage 行,只显限额百分比
限额可见性/usage 计划用量条 + 24h/7d 归因(本机近似)RateLimitSnapshot 协议化:双窗口/credits/plan_type/触顶类型
推送通道statusline stdin(含 cost);OTel metricsapp-server TokenCount 通知(仅连接内)
厂商归因回传tengu_*(如 advisor token 事件)+ OTel 归因属性TurnTokenUsageFact 逐 turn 回传

最小复现

# CC:statusline 是最便宜的成本探针——脚本里
# input=$(cat); echo "$input" | jq -r '.cost.total_cost_usd'   (statusline.md:509)

# CC:transcript 离线重算原料
grep '"usage"' ~/.claude/projects/<slug>/<sid>.jsonl | tail -1 | jq '.message.usage'

# Codex:rollout 里的累计 token(取最后一条即会话总量)
grep '"token_count"' ~/.codex/sessions/Y/M/D/rollout-*.jsonl | tail -1 \
  | jq '.payload.info.total_token_usage'

Harness 接入建议(Yoda 实践)

Yoda 的 Usage 视图完全靠离线解析两种日志重算 tokenyoda/src/main/core/stats/transcript-readers/),实现里埋着四条硬规则:

  1. CC 去重必须按 message.idclaude-usage-reader.ts:14-24 注释):CC 给同一条 assistant 消息的每个 content block 各写一行,重复携带相同 message.id + usage——「dedupe by message id (last row wins)」,否则成倍虚报。subagent 烧的 token 在 subagents/*.jsonl 独立文件里,是真实成本必须并入;实测其 message id 与父文件不重叠,per-file 去重即可(ccusage 对齐)。<synthetic> model 行是本地生成、无真实模型,计零价。
  2. Codex 必须差分累计值codex-usage-reader.ts:16-21 注释):token_countinfo.total_token_usage 是会话累计,「diff consecutive events so repeated mid-turn updates never double-count」;且 input_tokenscached_input_tokens,要先归一成「非缓存 input」才能与 CC 口径对齐。模型归因取最近一条 turn_context 行的 model
  3. 性能:rollout 路径解析只走 state DB(state_5.sqlite),不做目录扫描兜底(codex-usage-reader.ts:23-26);全量 parse 用 mtime 缓存(usage-cache.ts)+ 8 路并发,首算贵、后续廉。
  4. per-account 归因要在 spawn 时落账:用量该记在订阅还是 API key 上,取决于会话启动时的认证模式——Yoda 在每次 spawn/resume 时把 authProvider 写到 conversation 行(conversations/session-stats-hooks.ts:9-26),事后从日志里是推不出来的。
  5. 算钱:CC 可信任端侧数(statusline total_cost_usd),但跨 runtime 统一口径时 Yoda 选择只展示 token 分桶、把单价表留作显示层换算——Codex 无 USD 是结构性的,伪造精确美元数反而误导(订阅模式下应展示限额消耗,即 RateLimitSnapshot 百分比)。

反例 / 边界

  • CC 的端侧美元数 ≠ 账单:文档两处明示是本地估算(costs.md、statusline.md:165 "May differ from your actual bill");权威数据在 Console Usage 页。harness 对外展示时应保留「估算」措辞。
  • 订阅模式下两边都主动藏数(CC 藏美元、Codex 藏 token)——harness 给订阅用户看「精确成本」是产品错误,应改看限额/credits 消耗。
  • CC OTel cost.usage 与 transcript 自算可能有微小出入:OTel 在每次 API 请求后累计(含重试归因差异),transcript 去重按 message.id——两条管道口径不同,勿混合相加。
  • Codex last_token_usage.total_tokens 是「当前上下文占用」不是「本 turn 消耗」(token_usage.rs:37-40 注释)——做上下文水位监控用它,做花费统计必须用 total 差分。

失效条件

  • CC 单价表内置在客户端(utils/modelCost),新模型发布后旧版本 CC 会把成本算错并置 hasUnknownModelCost 警示(cost-tracker.ts:230-233)——版本升级后回归。
  • Codex TokenUsage.input_tokens 含 cached 的口径若改(TODO 注释暗示 schema 仍在动,protocol.rs:1917),所有差分/归一逻辑需重验。
  • CC「同 message.id 多行」的写入模式是观测所得(Yoda 注释自述 verified against local data)而非文档契约——transcript 写入逻辑改版需重验去重假设。
  • RateLimitSnapshot 字段(credits/plan_type/触顶类型)随 OpenAI 计费产品快速演进,b89ce9a 后可能增删。

参考资料

  • [一手源码] CC:cost-tracker.ts(全文)、commands/cost/cost.tsbootstrap/state.ts:955-983
  • [一手文档] claude-code-docs/docs/costs.md、statusline.md:160-230、monitoring-usage.md:466-497(cost/token 指标归因属性)、agent-sdk__cost-tracking.md(SDK 侧逐消息 usage 契约)
  • [一手源码] Codex:protocol/src/protocol.rs:1900-2007rollout/src/policy.rs:85core/src/session/mod.rs:1133-1160,3037-3066tui/src/token_usage.rstui/src/status/{card,rate_limits}.rsanalytics/src/facts.rs:101-105
  • Yoda:src/main/core/stats/transcript-readers/{claude,codex}-usage-reader.tsstats/getUsageOverview.tsconversations/session-stats-hooks.ts
  • 社区对照:ccusage(Yoda 两个 reader 的注释均以其为 parity 基准)

On this page