用量信息同步与管理
配额看服务端(CC /api/oauth/usage、Codex /api/codex/usage),token 消耗看本地 transcript——两层数据源各取所长
用量信息同步与管理
结论
用量数据有两层,harness 必须分开对待:配额/限流层只有服务端知道——CC 通过 OAuth 专属接口 /api/oauth/usage 返回 5 小时与 7 天窗口的利用率百分比,Codex 把 RateLimitSnapshot 直接嵌在每个 token_count 流事件里、另有 /api/codex/usage 可主动拉取;token 消耗层则完整落在本地 transcript——CC 每条 assistant 消息带增量 message.usage,Codex rollout 持久化累计值 total_token_usage。harness 聚合消耗只需解析本地文件(注意 CC 要按 message.id 去重、Codex 要做相邻差分),而配额数据绝不应自行估算——要么调真实接口、要么不显示,这是 Yoda 的两条实现路线。
研究问题
- 各 CLI 的用量数据落在哪里?transcript token 字段还是账户接口?
- 限流窗口(5h / 7d / credits)如何暴露?字段语义是什么?
- harness 如何不双重计数、不伪造数据地聚合多账户用量?
各 Agent 设计与实现
Claude Code
CC 源码为社区重建版(
src_2026-03-31),以下仅作架构描述与短引用。
配额接口:fetchUtilization() GET {BASE_API_URL}/api/oauth/usage,仅当 isClaudeAISubscriber() && hasProfileScope() 时调用(即订阅 OAuth 专属,API Key 用户拿不到)[一手源码 services/api/usage.ts:33-63]。返回结构:
type Utilization = {
five_hour?: RateLimit | null // { utilization: 0-100, resets_at: ISO8601 }
seven_day?: RateLimit | null
seven_day_opus?: RateLimit | null // Opus 单独的 7 天窗口
seven_day_sonnet?: RateLimit | null
seven_day_oauth_apps?: RateLimit | null
extra_usage?: ExtraUsage | null // { is_enabled, monthly_limit, used_credits, utilization }
}[一手源码 services/api/usage.ts:12-31]。/usage 命令即基于此,且 availability: ['claude-ai']——API Key 模式下命令本身不可见 [一手源码 commands/usage/index.ts:3-9]。
限流头:429/进行中响应携带 anthropic-ratelimit-unified-* 头族:-representative-claim(哪个窗口触顶)、-overage-status、-reset、-overage-reset、-overage-disabled-reason [一手源码 services/api/errors.ts:471-512,services/api/withRetry.ts:276, 815]。
订阅档位:fetchProfileInfo() 把 OAuth profile 的 organization.organization_type 映射为 max/pro/enterprise/team,并返回 rate_limit_tier、hasExtraUsageEnabled、billingType [一手源码 services/oauth/client.ts:355-401]。
本地消耗数据:transcript(~/.claude/projects/<dir>/<sessionId>.jsonl)的 assistant 行带 message.usage:input_tokens / output_tokens / cache_read_input_tokens / cache_creation_input_tokens,外加 service_tier、speed 等(本机实测)。同一条消息按 content block 写多行、重复同一 message.id 与 usage——消费方必须按 message id 去重 [一手源码(Yoda 侧验证)yoda/.../claude-usage-reader.ts:13-24]。子代理(Task 工具)的消耗在 <projectDir>/<sessionId>/subagents/*.jsonl 单独成文件,属真实成本。
企业级导出:除本地文件外,CC 还能以 OTel 形式把用量指标推到组织的可观测栈——CLAUDE_CODE_ENABLE_TELEMETRY=1 + OTEL_METRICS_EXPORTER / OTEL_LOGS_EXPORTER,支持 mTLS 与多团队自定义属性 [一手文档 claude-code-docs/docs/monitoring-usage.md:11-44]。对企业 harness 这是比抓 transcript 更正规的聚合通道,但对个人桌面产品(Yoda 场景)依赖用户开启,不可作为默认数据源。
Codex CLI
事件即数据:协议层 TokenCountEvent { info: Option<TokenUsageInfo>, rate_limits: Option<RateLimitSnapshot> } [一手源码 codex-rs/protocol/src/protocol.rs:1980-1984],且 EventMsg::TokenCount 在 rollout 持久化白名单中 [一手源码 rollout/src/policy.rs:76-94]——用量随会话写进 ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl。info.total_token_usage 是会话累计值(input/cached_input/output/reasoning_output/total,本机 rollout 实测字段一致)。
限流快照:RateLimitSnapshot { limit_id, limit_name, primary, secondary, credits, plan_type, rate_limit_reached_type },窗口为 RateLimitWindow { used_percent: 0-100, window_minutes, resets_at(unix秒) },credits 为 { has_credits, unlimited, balance } [一手源码 protocol.rs:1986-2046]。rate_limit_reached_type 还区分 workspace owner/member 的 credits 耗尽与 usage limit [同文件 :2001-2007]。
主动拉取:backend-client 提供 get_rate_limits(),按 path style 走 GET {base}/api/codex/usage(Codex API)或 {base}/wham/usage(ChatGPT API),多账户限流取 limit_id == "codex" 优先 [一手源码 backend-client/src/client.rs:287-305]。TUI 通过 app-server 同一通道展示(/status)[一手源码 tui/src/app_server_session.rs:1736]。
与 CC 的结构性差异:Codex 把限流快照作为事件流的一等公民下发(每次 token_count 都附带),客户端无需轮询即可实时显示余量;CC 的限流信息散落在响应头和独立 REST 接口两处,/usage 需要主动请求。对 harness 而言,Codex 的 rollout 文件同时是消耗数据和最近一次限流状态的载体——读文件尾部即可同时拿到两者。
差异矩阵
| 维度 | Claude Code | Codex CLI |
|---|---|---|
| 配额接口 | GET /api/oauth/usage(OAuth 订阅专属) | GET /api/codex/usage 或 /wham/usage |
| 配额单位 | utilization 百分比 + resets_at ISO 时间 | used_percent + window_minutes + unix resets_at |
| 窗口粒度 | 5h、7d、7d-opus、7d-sonnet、extra_usage | primary/secondary 双窗口 + credits + 个人限额 |
| 限流推送 | 响应头 anthropic-ratelimit-unified-* | 每个 token_count 事件内嵌 rate_limits |
| 本地 token 数据 | transcript assistant 行 message.usage(每消息增量,按 content block 重复行) | rollout token_count 事件 total_token_usage(会话累计) |
| 聚合时的坑 | 按 message.id 去重;subagents 子目录别漏 | 相邻事件差分;计数回落 = compaction 新基线 |
| 套餐探测 | profile 接口 organization_type → max/pro/team/enterprise | rate_limits.plan_type / id_token claim |
| 入口命令 | /usage(仅 claude-ai 模式)、/status | /status |
最小复现
# Codex rollout 中的 token_count 事件结构(本机实测,2026-06-03 会话)
f=$(grep -rl '"token_count"' ~/.codex/sessions/2026/06 | head -1)
grep -m1 '"token_count"' "$f" | jq '{usage: (.payload.info.total_token_usage|keys),
rate: (.payload.rate_limits|keys)}'
# => usage: ["cached_input_tokens","input_tokens","output_tokens",
# "reasoning_output_tokens","total_tokens"]
# rate: ["credits","limit_id","limit_name","plan_type","primary",
# "rate_limit_reached_type","secondary"]
# CC transcript 中的 usage 字段(本机实测)
f=$(find ~/.claude/projects -name "*.jsonl" | head -1)
grep -m1 '"usage"' "$f" | jq '{type, usage_keys: (.message.usage|keys)}'
# => type:"assistant", usage_keys 含 input_tokens / output_tokens /
# cache_read_input_tokens / cache_creation_input_tokens / service_tier ...Harness 接入建议(Yoda 实践)
Yoda 用三个互补组件做 usage 同步,全部只读 [一手源码 yoda/src/main/core/stats/ 与 core/settings/]:
-
Transcript 聚合(
getUsageOverview.ts):把每个会话的 transcript 解析为统一TokenBuckets(input/output/cacheRead/cacheCreation/reasoning),按日(heatmap)、按模型、按 runtime、按 auth 来源(official-subscription/official-api/yoda-maas)四维聚合;mtime 缓存让首次全量解析后续免费(:67-74)。无 auth 记录的旧会话回填为该 runtime 当前配置的认证模式——标注是估计值而非编造(:131-138)。 -
读法差异内置在 reader 里:CC reader 按
message.id去重、<synthetic>模型置 null(ccusage 口径对齐)、subagents транscript 并入 [一手源码claude-usage-reader.ts:13-24];Codex reader 对累计值做相邻差分,重复计数零增量、计数回落视为 compaction 新基线而不是负数 [一手源码codex-usage-reader.test.ts:17-84]。cached_input_tokens是input_tokens的子集,归一化时要扣除。 -
成本与档位:30 天窗口的成本用本地价格表估算(
local-usage-service.ts,未知模型列入unpricedModels而非算 0);订阅档位读凭证文件(见 auth-sync 章),不调用 CC 的/api/oauth/usage——那是 OAuth Bearer 专属接口,harness 冒用 CLI 的 token 调它会增加风控面。Codex 的 rollout 尾部 1MB 即含最新累计值(CODEX_TAIL_BYTES),无需全文件解析。 -
辅助目录纳入:用户在 Yoda 之外(research 目录、项目旧位置)跑的 CC 会话也可计入——project settings 的
statsAuxiliaryPaths配置额外扫描路径,已追踪的 conversation id 自动跳过避免双计 [一手源码getUsageOverview.ts:208-230]。
原则:消耗数据本地算(可复现、离线可用),配额数据要么用官方接口要么不展示,两者在 UI 上分开标注。多账户聚合的归因键 = runtimeId × authProvider,而不是只按 runtime——同一个 CC 既可能跑订阅也可能跑 BYOK,成本口径完全不同。
失效条件
- CC
Utilization字段集变化(如新增 per-model 窗口)或/api/oauth/usage路径迁移 - CC
/usage命令的availability限制放开到 API Key 模式(当前仅claude-ai) - Codex
get_rate_limits的limit_id == "codex"优先规则随多产品限流合并而改变 - Codex
total_token_usage从累计改为增量语义,或token_count退出 rollout 持久化白名单(rollout/src/policy.rs) - CC transcript 行格式变化(usage 移出
message.usage、message.id 去重假设失效) -
anthropic-ratelimit-unified-*头族更名 - Agent SDK 订阅额度独立计费上线(CC 文档预告 2026-06-15 起
claude -p走独立 credit),/usage口径可能拆分
参考资料
- CC 重建源码:
src_2026-03-31/services/api/usage.ts、services/api/errors.ts、services/oauth/client.ts、commands/usage/ - CC 文档:
claude-code-docs/docs/costs.md、monitoring-usage.md(OTel 指标导出)、authentication.md(SDK credit 预告) - Codex 源码:
codex-rs/protocol/src/protocol.rs(TokenCountEvent/RateLimitSnapshot)、rollout/src/policy.rs、backend-client/src/client.rs - Yoda:
src/main/core/stats/getUsageOverview.ts、stats/transcript-readers/{claude,codex}-usage-reader.ts、settings/local-usage-service.ts - ccusage(社区口径参照,Yoda reader 注释声明 parity)
第三方 Provider / Gateway
CC 是封闭四选一枚举(firstParty/bedrock/vertex/foundry)+ BASE_URL 重定向;Codex 是开放 model_providers 注册表——但 wire_api=chat 已被移除
PR Review / 代码评审
CC 的评审是三层产品(本地 /review、云端 ultrareview 多 agent 舰队、托管 GitHub Code Review 服务);Codex 是一条 review 任务管线贯穿 TUI /review、codex exec review 与 app-server review/start,外加给审批用的 guardian 自动评审。