跳转到内容

订阅档位计量器(Claude Code Pro + ChatGPT Plus 上的 Codex)

Claude Code Pro/Max 以及 ChatGPT Plus/Pro 上的 Codex 用的是 OAuth 订阅。流量照样打到 api.anthropic.com/v1/messageschatgpt.com/backend-api/codex/responses,所以代理能看到这些调用 —— 但 Anthropic 和 OpenAI 是按固定月费在内部扣配额的。如果 SpendGuard 的 BYOK 账本对每个 token 按零售价计费,那就等于在 $20/mo 套餐之上又凭空 造出一笔幻影美元。

为此,subscription_meter 模式的做法是:

  • 在开启账本事务之前,先把每个请求分类成 BYOK 还是订阅。
  • 对订阅请求,只跑一次纯计量估算(tokens × 零售单价 ÷ 1M),并给 审计行打上 reservation_source = subscription_meter 标记。
  • 跳过 ledger_entriesreservations 两张表的写入 —— 计量行本身 就是权威记录。

| 模式 | 默认 | 行为 | |-------------|:----:|--------------------------------------------------| | meter | ✅ | 仅估算;不告警、不拦截。 | | soft_cap | | 到达 alert_at_atomic 时发出 subscription_soft_cap_alert;CONTINUE。 | | hard_cap | | 到达 hard_cap_at_atomic 时短路 DENY + 合成 429。 |

模式按租户配置,走 subscription_caps 表(cap-aware SQL 下个切片才上; 目前 sidecar 从 DecisionRequest.runtime_metadata.subscription 读阈值)。

分类器要求两个信号同时命中,才把一个请求判定为订阅:

| 信号 | Claude Code Pro | Codex / ChatGPT-OAuth | |--------|------------------|------------------------| | Authorization 前缀 | sk-ant-oat01-… | eyJ…(JWT header) | | User-Agent | claude-cli/<ver>claude-code/<ver> | codex_cli_rs/<ver> | | BYOK 区分标识 | sk-ant-api03-… | sk-proj-… / sk-… |

单看 UA 是可伪造的(运维经常拿 claude-cli 配一把 BYOK key 用); 单看 token 也不够,因为 OAuth token 比 CLI 二进制更容易泄露。两个都得 命中 —— 见 classifier.rs

hard_cap 模式开启,且预估的计量消耗越过 hard_cap_at_atomic 时, sidecar 会在进账本之前就短路:

{
"error": {
"type": "rate_limit_exceeded",
"message": "spendguard subscription cap reached",
"code": "spendguard_subscription_cap"
}
}

这个结构是对齐提供商口径的,所以 claude-cli / codex_cli_rs 会把它 当成提供商返回的 429 一视同仁地处理,然后干净退出。专门留的那个 code = "spendguard_subscription_cap" 让运维能在自己的 dashboard 里 区分:哪些 429 是 SpendGuard 注入的、哪些是提供商注入的。

Retry-After 上限封到 86 400 s(24 h)—— 防止配错的 cap 窗口让 CLI 干等超过一天。

D13 上了三条增量迁移:

  • 0044_subscription_meter.sql —— subscription_meters 表 + audit_outbox.reservation_source 字段。
  • 0045_subscription_alerts.sql —— subscription_alerts 表,带冷却期 (默认 1 h,跟 stats_aggregator 的 drift_alert 对齐)。
  • 0046_subscription_importer.sql —— subscription_import_jobs 表 + audit_outbox.import_source 字段。

三条都开了 RLS,用标准的 tenant-id 隔离策略。见 services/ledger/migrations/

固定月费计量器是尽力而为的。要拿提供商的账单报表去校准它,D13 上了一批 导入器 crate,先把 Day-2 对账的契约锁死:

pub fn import_record_to_audit_row(rec: &ImportRecord) -> AuditRowDraft {
AuditRowDraft {
tenant_id: rec.tenant_id.clone(),
reservation_source: "subscription_meter",
import_source: rec.importer_kind.as_str(),
..
}
}

D14 / D15 / D16 点亮 Devin / Manus / Genspark 的实时导入器。Anthropic Console Usage 和 OpenAI Admin Usage 则一直保持桩状态,等各自的 Admin API 上线再说。

Terminal window
make -C deploy/demo demo-up DEMO_MODE=subscription_meter

跑三个 cap 决策场景(PASS / SOFT / HARD),断言硬上限响应带的 Retry-After ∈ [1, 86_400],并校验那条最关键的不变式:在计量租户下 ledger_entries 行、reservations行。

deploy/demo/subscription_meter/README.md