← Back to Blog

CodeGateway 计费精度口径白皮书

October 24, 2026

CodeGateway 计费精度口径白皮书

这份白皮书公开 CodeGateway 的完整计费口径:用什么单位记账、在哪里取整、流式请求怎么计量、中途断开怎么结算、tier 加价怎么算、充值与退款怎么入账。每一个口径都是生产环境的实际行为,附代码落点(文件:符号,行号为 2026-10 参考值)与迁移号佐证;历史上出过的精度事故也如实列出,包括已修复、已退还与仍在跟踪中的。

我们只描述已经上线的行为,不描述计划中的能力。

1. 计价单位:整数 micro-dollars 双层体系

CodeGateway 不用浮点数记账。整个计费链路有两层单位,换算的单一事实源(SoT)是 backend/src/balance-units.ts:

层

单位

换算

落点

账本与热路径

整数 micro-dollars(µ)

1¢ = 10⁸ µ;$1 = 10¹⁰ µ

balance-units.ts:38-41(MICRO_PER_CENT / MICRO_PER_DOLLAR)

展示与钱包

整数美分(cents),micro 的 floor 镜像

$1 = 100¢

workspaces.balance_usd_cents(0057_workspaces_init.sql:50 建列;同一条 UPDATE 内写 floor(micro / 10⁸),workspace-balance.ts:46-48)。workspaces.balance_usd_micro 由迁移 0172:217 新增

为什么是 10⁸ 而不是 10⁴:1 美分 = 100,000,000 micro。历史上两次把换算系数写成 10_000(差 10,000 倍)的事故(见 §8 事故披露)之后,所有跨 cents↔micro 边界的换算都必须经过 balance-units.ts 的常量与函数,不允许各写各的——git grep MICRO_PER_CENT 可以列出全部换算点。大额运算走 bigint,避免超过 2⁵³ 后丢精度(balance-units.ts:27-29)。

取整规则三分法

三个方向各司其职,这是本白皮书最重要的口径(balance-units.ts:66-149):

函数

规则

用途

为什么

microToCents

向零截断(truncate)

一般换算

迁移 0141 的 legacy 余额回填同样采用向零截断(0141:116);负数残差上与 floor 不同(−1.55¢ → −1¢,floor 会给 −2¢)

microToCentsFloor

向下取整(floor,toward −∞)

客户可见余额镜像

客户可见的 cents 绝不能高于真实 micro 余额——这是 #2818 修复定下的原则(见 §6、§8)

microToCentsCeil

向上取整(ceil)

仅遗留兼容入口

亚分成本至少收 1¢,防长尾漏扣;当前扣费主路径已不经过它(见下)

扣减不再经过 cents 取整

#2818 之后,逐请求扣费传 costMicro 原值、直接对 micro 账本做原子扣减(workspace-balance.ts:210 deductWorkspaceBalanceMicro,单条 SQL UPDATE 带 WHERE balance >= cost 守卫)——亚分精度全程不丢失。"逐请求 ceil 到分"这个曾造成系统性多收的环节在主路径上已不存在(workspace-balance.ts:31-32 注释)。

2. 价格来源与成本公式

双源一致:模型价格的事实源是 D1 的 model_catalog / model_costs(由 admin portal 管理);backend/src/pricing.ts 的硬编码 MODEL_PRICES 是计费兜底。CI(model-prices-parity.yml)强制每个公开 model id 两边都有且一致。

成本公式(pricing.ts:1186-1201 calculateCostMicro):

usd       = (inputTokens × rate.input
+ outputTokens × rate.output
+ cacheCreationTokens × rate.cacheCreate
+ cacheReadTokens × rate.cacheRead) / 1,000,000
costMicro = Math.round(usd × markup × 10¹⁰)

四路 token 分别计价(输入 / 输出 / 缓存写 / 缓存读),费率按每百万 token 美元计。最终取整是 Math.round(四舍五入),不是向上取整——代码注释原文 "no ceil overcharge"。旧版 cents 入口 calculateCostCents 用 Math.ceil,仅保留给旧测试,生产路径不走(pricing.ts:1204-1219)。

长上下文加价(pricing.ts:1158-1183 applyLongContextSurcharge):当次请求的总提示 token(input + cacheRead + cacheCreation)超过该模型的 thresholdTokens 时,按三元组(thresholdTokens / inputMultiplier / outputMultiplier)对 input、output、cacheRead 以及 cacheCreate 四路费率乘系数(WHI-1387)。逐模型的长上下文阈值在价格页与 D1 费率中同源。

3. Tier 加价与 90 天滚动重算

当前档位表(tier-config.ts:73-117 DEFAULT_CONFIG,须与线上 operational_config 行一致):

档

90 天消费门槛

token 加价

限流(rpm/tpm)

tier1

$0+

1.07×

各档独立上限(#1390)

tier2

$20+

1.07×

↑

tier3

$50+

1.07×

↑

tier4

$200+

1.07×

↑

tier5

$500+

1.07×

↑

2026-10-05「方案3」起,token 侧加价拉平为 1.07×,货币化移到充值侧 2% 服务费(见 §7);tier 保留的意义是限流档位(tier-config.ts:61-65)。档位事实源是 D1 operational_config(admin portal 维护),KV 缓存 300 秒,代码内默认表仅作配置面不可达时的兜底,且必须与线上行一致(tier-config.ts:56-60,#2514 教训)。

90 天口径:滚动窗口 windowDays=90,消费 = SUM(request_logs.cost_micro)——只由实际请求成本构成,充值本身不改变它(90 天聚合函数 get90DaySpend 在 tier-config.ts:537-545,聚合 SUM(cost_micro);tier-writeback.ts:151 是同义注释)。

重算的两条路径(tier-writeback.ts 头注释):

  • 充值后即时重算(recomputeTierOnTopup,Stripe webhook 触发):只升不降。应该档位高于当前档 → 立即升级;永不在这里降级。

  • 每日 02:00 扫描(dailyTierSweep):升级立即生效;降级先挂起、过 7 天宽限才生效(TIER_DEMOTION_GRACE_DAYS=7)。宽限期内档位进一步下降时只更新目标档、不重置计时器(防计时器滥用)。宽限期内消费回升则自动清除挂起。

折扣(#940):支持按档与全局两类促销折扣,同时生效时取对用户更优的一档(tier-config.ts:20-42)。

配置漂移监测(tier-config-drift.ts,#2516):绕开 admin 的 200 包装直接读 D1,区分"行存在但与兜底值分叉"(row_diverges)与"行丢失"(row_missing)两种静默漂移;该模块只读、不自动修复。另有workspace 池账本漂移事项(属另一类,不是 tier 配置漂移)按 GitHub #3472 跟踪,见 §8。

4. 流式与非流式计量

流式(SSE):metering/stream.ts 的 createMeteringStream 逐事件解析上游 SSE,增量累积 token usage:

  • 每个 message_delta 触发 onProgress——用于渐进释放预扣留(见 §5);

  • message_stop 触发 onUsage 最终结算;

  • 每 15 秒注入心跳注释防客户端超时,同时 onHeartbeat 让预留保活(#1356,防 Durable Object 回收在途流的预留)。

非流式:从响应体的 usage 字段一次结算,同一套 calculateCostMicro 公式。两条路径的差别只在 usage 的获取方式,成本公式与单位完全一致。

5. 预扣留、中止路径与错误路径结算

一次请求的结算生命周期:

  1. 预扣留(preDeduct):请求进入流水线前按预估成本预留余额(computePreDeductEstimate 在 backend/src/preDeduct.ts:254:input 按实测 token 计价,剩余额度换算成最大输出 token)。余额不足返回 402,payload 附「把 max_tokens 降到 N 可通过」提示——提示用与预留同一公式、同一实测 input token 计算(#2536),不会出现"照做仍然 402"。

  2. 渐进释放:流式过程中每个 message_delta 释放一部分预留。

  3. 恰好一次结算(#2501):无论流以哪种方式结束——正常 message_stop、上游截断关闭、管道错误、客户端 abort——都结算且仅结算一次,按当时已累积的 usage 计费(metering/stream.ts:23-50、metering/stream-settle.ts)。客户端断开由请求 signal 捕获,是唯一可靠的断连信号。

诚实披露:#2501 之前,中止或出错的流完全不入账(不写 balance_events 也不写 request_logs)——上游 token 我们已经付了钱,但没有向用户计费。修复后按已流出的 tokens 正常结算。

错误路径:成本公式由 usage 驱动,错误响应没有 usage 就不计费(cost = 0)。例如我方上游批发额度耗尽(CF AI Gateway code 2021)产生的 402,生产实测全部 0 计费(upstream-error-rate-alert.ts:14 的取证记录)。流失败同时释放限流 TPM 预留,不留幻影占用(#1668)。

6. 池钱包与账本镜像

  • 账本(事实层):用户层 balance_events、workspace 层 workspace_balance_events,以及 micro 余额缓存(users.balance_cache_micro、workspaces.balance_usd_micro),全部整数 micro;D2(#2860)起 workspace 的 micro 列是写侧权威。

  • 钱包(workspace 层):workspaces.balance_usd_micro 是权威值;workspaces.balance_usd_cents 是客户可见的 cents 镜像,在同一条 UPDATE 内写入(Sprint 9 workspace 多租户起,余额按 workspace 隔离)。

  • 镜像规则:cents = floor(micro / 10⁸)(balance-units.ts:115-138)。迁移 0172:217 新增 balance_usd_micro 列,0172:222-227 由 balance_usd_cents 回填 micro;此后 cents 是 floor 镜像而非事实源(workspace-balance.ts:46-48)。原则:展示值永远不高于真实余额——宁可显示少一分的保守值。

  • 原子性:扣减是单条 UPDATE … WHERE balance >= cost 带守卫的原子操作,并发请求不会把余额扣穿。

  • 对账兜底:每日对账 cron 复核取整与扣减异常(balance-units.ts:90-92 注释承诺的兜底路径)。

7. 充值、赠送金与退款

三条充值通道——Stripe 一次性支付、自动充值(SetupIntent 绑卡 + cron 触发)、银行转账——全部入账 workspaces.balance_usd_micro(D2/#2860 起的写侧权威);cents 列是同一条 UPDATE 内写入的 floor 镜像。

2% 充值服务费(方案3,2026-10-05 起):token 侧加价拉平 1.07× 后,货币化移到充值侧,platformFeeRate = 0.02(dashboard/lib/payment-config.ts:20、backend/src/payment-config.ts:33)。实收金额 = 入账面额 + 服务费 + Stripe 手续费 gross-up;余额入账 = 面额 + 赠送金,平台服务费与 Stripe 手续费不入账(stripe-handlers/manual-checkout.ts:231-235、:598-601 传 amountMicro;auto-topup-charge.ts:661)。

预付赠送:预付更高档位得赠送金——充 $100 得 10%、充 $200 得 10%、充 $500 得 12%(dashboard/lib/topup-bonus.ts:39-43)。赠送金与充值同通道入账。

首充加赠(已上线):用户首次成功充值在面额之外额外赠送——充 $10 赠 $2、充 $20 赠 $5、充 $50 赠 $12(FIRST_TOPUP_BONUS_TIERS,dashboard/lib/topup-bonus.ts:71-75);与上述预付档位不重叠。

自动充值:

  • webhook 是绑卡事实源(SetupIntent 以 POST /api/webhooks/stripe/auto-topup 落库为准,浏览器侧保存只是乐观路径,auto-topup-webhook.ts 头注释);

  • 扣款入账幂等:以 auto_topup_events.balance_event_id IS NULL 判重,重复 webhook 不会重复入账;

  • 连续失败达阈值自动暂停配置并邮件告知;3DS 挑战时挂起(suspended_3ds)直到用户在 dashboard 完成 SCA。

退款与纠偏:

  • 平台侧纠偏性退还直接写池账本:迁移 0197_pool_ceil_overcharge_credit.sql 按请求复算 #2818 多收(ceil(cost_micro/1e8)×1e8 − cost_micro,0197:43),按 workspace 汇总结转(0197:213-220,同时回写 cents floor 镜像)。这是池账本内部结转,不是对外退款;迁移文件头明确披露 class-(a) 行在池被抽干、带守卫的扣减被静默丢弃时可能高估金额(见 §8);

  • 退款/扣减可产生负的亚分残差(balance-units.ts:69-71)——此时 truncate 与 floor 结果不同(−0.5¢ 截断为 0¢、floor 为 −1¢),账本以 micro 原值保存,展示层按 §1 的规则换算;

  • 前端余额显示用 microToUsdDisplay(截断到分、负数带号,balance-units.ts:159-166)。

8. 事故披露与已知事项(如实记录)

透明度不是"从没出过错",而是错了什么、怎么修的、钱怎么退的,全都可查:

事故

内容

状态

#1345

自动充值阈值的 cents→micro 换算写成 ×10⁴(差 10,000 倍),候选集几乎恒空,自动充值长期静默停摆

已修复:换算统一收口到 balance-units.ts SoT 常量

#2075

workspace shadow-deduct 用本地 MICRO_PER_CENT=10_000,逐笔多换算 10,000×

已修复:同一 SoT 收口

#2818

逐请求把成本 ceil 到分再扣,系统性多收(每请求至多多收不到 1¢,长尾累积)

已修复:扣减改传 costMicro 原值;迁移 0197 按请求复算多收、按 workspace 汇总结转池账本(生产已执行)

#2861

写侧早已切 micro,但读侧(402 闸门 / dashboard 展示 / balance-drift)仍按 cents 换算,出现跨单位偏差(D3)

已修复:读路径统一切到 micro,消除跨单位不一致

#2514

线上 operational_config 档位行($50/$200)与代码兜底值($100/$300)静默分叉

已修复:代码兜底值对齐线上行($20/$50/$200/$500);只读漂移监测由 #2516 另行加入

#3472

workspace 池账本与账户账本之间的漂移

部分修复:迁移 0192_workspace_pool_drift_decrease.sql 已于 2026-10-06 在生产执行,纠正了唯一 hazardous 条目;其余非 hazardous 条目与一笔尚无法归因的小额残差(#3480)仍在跟踪,如实披露

附:复算口径(数据声明如何验证)

本白皮书正文只声明机制与常数,不引用即时业务数据。如需复算任何消费类数据,统一口径为:

sql
-- 某用户 90 天消费(USD):request_logs 按 workspace/user 聚合
SELECT SUM(cost_micro) / 1e10 AS spend_usd FROM request_logs WHERE ...;

-- 验证取整镜像:派生 cents 必须等于 floor(micro/1e8)。
-- 除数写成 100000000.0(浮点):若用整数除法,SQLite 会先向零截断再 FLOOR,负亚分余额会算错。
SELECT balance_usd_cents, FLOOR(balance_usd_micro / 100000000.0) FROM workspaces WHERE ...;

任何对外发布的量化声明(如事故影响笔数、退还总额)发布前须经独立复算并在发布页附取数口径。


口径变更随代码走:本文每个声明都有上述代码/迁移锚点,代码变了本文必须同 PR 更新。代码锚点指向我们的内部仓库,issue 编号为内部引用。来源:内部 issue GH#2294。

Author:CodeGateway Team

Resuelve los límites de claves de API y de facturación con CodeGateway

¿Sin clave de API oficial o con problemas para pagar con tarjeta internacional? Crea una clave sk-cg- al instante. Una sola clave para Claude, GPT, Gemini y DeepSeek: los modelos de texto de Claude y Gemini usan el protocolo Anthropic Messages (/v1/messages); los endpoints de texto compatibles con OpenAI no los aceptan. Incluye enrutamiento inteligente y créditos de prueba.

Crear cuenta gratis