Releases: wjf1/commandcode-proxy
Release list
v4.11.0 — dashboard hotfix, protocol compat, budget alerts
CommandCode Proxy v4.11.0
修复
- 仪表盘在浏览器完全不可用(4.10.0 回归,紧急) — 4.10.0 重构时把 TypeScript 的
as HTMLElement断言写进了纯<script>,浏览器解析整个脚本块直接 SyntaxError,所有仪表盘交互全部失效(Node 侧 diff/冒烟抓不到,因为没人执行页面脚本)。已修复,并新增tests/dashboard-spa.test.ts三项守卫:内联脚本必须能作为纯 JS 解析、getElementById引用的元素 id 必须存在、不得引用外部 CDN。 start.cmd与 logger 双写同一日志文件 — 启动脚本>> logs\proxy.log与 4.10.0 起的日志落盘指向同一文件,两个写入者会交错/重复。控制台输出改到独立的logs/console.log。- CORS 预检被鉴权挡死 — 设置
PROXY_API_KEY后,浏览器的OPTIONS预检请求(不携带鉴权头)会 401,纯浏览器客户端无法使用。预检现已豁免(tests/auth.test.ts锁定)。
兼容性
POST /v1/messages/count_tokens— Anthropic SDK/工具会调用它做上下文预算,此前 404。现返回 CJK 感知的本地估算(不发起上游请求、不受引擎暂停影响)。- OpenAI
stream_options.include_usage— 流式收尾 chunk 现按 OpenAI 语义附带usage(上游 totalUsage 优先,缺失回落本地估算);未开启时行为不变。 .env转正 — 此前保存账号时会写出.env但应用从不读取(纯误导)。现在启动时自动加载(已存在的环境变量优先,docker/systemd 注入不受影响),与写入侧形成闭环:仪表盘添加账号重启即生效。
清理(行为不变)
- 删除从未生效的
permissionMode配置项(wire 契约强制 auto-accept,配置了也被覆盖);既有 config.json 里残留的该键会被静默忽略,部署无需改动。 - 删除无调用方的
estimateTokens、未采用的sseLineIterator、StreamEncoderState.promptTokens/completionTokens、AccountInfo.lastUsedAt/totalRequests、启动脚本里无消费者的COMMANDCODE_PROXY_DIR;version.ts复用utils/paths.ts(消除第三份副本)。
改进
- 每请求读盘归零 —
loadConfig与getUsageHistory增加 mtime+size 缓存,文件未变时不再重复读盘+解析。 - 优雅退出 — SIGINT/SIGTERM 先冲刷用量写队列再关闭服务,Ctrl+C 不丢最后一两条会话记录。
- 管理面 Origin 校验抽纯函数(
isSameOriginIfPresent)+fastify.inject集成测试进 CI(此前只有手测)。 - 每日预算告警 —
DAILY_BUDGET_USD设置后当日成本达阈值弹一次 toast;重启从历史回填当日金额,不误报。 - 版本更新检查 — 启动与每 24h 查询 GitHub Releases(api.github.com,只读无凭据,失败静默),有新版时仪表盘头部显示徽章。
- 用量导出 CSV — 会话明细一键导出(UTF-8 BOM,Excel 直开)。
- 上游并发上限 —
MAX_UPSTREAM_CONCURRENCY(默认不限制,兼容既有部署),超限以新错误码GATEWAY_BUSY(503) 快速失败。 GET /api/config— 只读运行配置视图(端口/绑定/上游参数/路径/限额,不含任何密钥)。
测试
-
新增
tests/guard.test.ts(6 项:Origin 纯函数 + 管理面异源 403/同源 200/无 Origin 放行)、tests/dashboard-spa.test.ts(3 项)、count_tokens 与 include_usage 集成测试。全量 228 项通过,tsc --noEmit无错误。 -
版本号
4.10.0→4.11.0。
v4.10.0 — admin auth, reliability hardening, dashboard SPA extraction
CommandCode Proxy v4.10.0
修复
- 燃烧速率预测 UI 从未显示 —
renderUsageForAccount引用window5hProjection元素,但 HTML 里从未定义它(if (pNote)静默跳过),"约 X 分钟后撞上限额"的核心预警实际是死代码。已在 5 小时窗口卡片补回该元素。 - 内联 thinking 标签剥离的定界符丢失 — 编码器用
' thinking'/' response'(空格前缀)做定界,系历史编辑事故中<think>类标签被剥掉的残留。后果:模型正常回复里任何含英文单词 "thinking" 的句子都会被误路由进reasoning_content,而真正的<think>…</think>块反而不被处理。现按真实标签<think>/<thinking>(含跨 delta 拆分)用状态机重写,并锁定"含 thinking 字样的普通英文不受影响"的回归测试。 - 上游用量请求无超时 —
fetchJson(whoami/credits/subscriptions/summary)此前为裸 fetch,上游挂起时登录、仪表盘聚合、5 分钟额度采样会无限等待。现统一 15s 超时(AbortSignal.timeout)。 npm test在干净克隆上必挂 — 集成测试 spawndist/index.js,无 dist 时必然 "Proxy did not become ready in time"。现在 dist 缺失时整个集成套件自动跳过并提示先npm run build。
安全
- 管理面纳入共享密钥鉴权 —
PROXY_API_KEY此前只保护/v1/*,绑定0.0.0.0时局域网内任何人仍可直连/api/*增删账号、切换 Key、清空历史。现在/api/*与/v1/*共用同一把密钥(常量时间比较);仪表盘首次收到 401 时弹出密钥输入框,提交后自动重试原请求,密钥仅存 sessionStorage(关标签页即清除)。并发 401 共享同一次输入,取消后 60 秒内不再打扰轮询。 - 非回环绑定且未鉴权时的醒目警示 —
/api/status新增boundNonLoopback字段;概览页"安全"卡片在"非回环 + 无密钥"时变红色"未鉴权暴露!"并悬停给出修复路径;启动日志与控制台同步输出警告。 - 管理接口防跨站驱动 — CORS 只能阻止"读响应"而非"发请求"。
/api/*的写操作现在校验Origin头与请求 host 一致(非浏览器客户端不带 Origin,不受影响)。 PROXY_API_KEY比较改为常量时间(crypto.timingSafeEqual)。- SSRF 网段覆盖补全 — 私有/保留地址判定补充 100.64.0.0/10(CGNAT)与 198.18.0.0/15。
改进
- 仪表盘静态资源本地化 — Tailwind / Font Awesome / Chart.js 从三个公共 CDN 改为随仓库
public/vendor/分发(经/assets/vendor/*服务,含字体,路径穿越防护),离线或 CDN 不可达时界面不再掉样式、丢图表;pkgassets 同步纳入。 usage-history.jsonl大小轮转 — 默认 20MB(USAGE_HISTORY_MAX_MB可调),超限保留较新一半;此前只追加不轮转,仪表盘每 30s 全量读取聚合会随文件增长持续变慢。- 日志持久化 — 全量日志追加到
logs/proxy.log(COMMANDCODE_LOG_PATH可改,超 5MB 轮转.old),控制台窗口关闭后仍可事后排查;路径解析收敛到utils/paths.ts(logger 复用,避免循环导入)。 - 错误可关联到用量记录 — 非流式 chat 请求现在预生成
traceId(响应 id、错误日志、usage 记录三者一致);流式/致命错误日志与上游错误日志带上Trace/Thread(threadId 即 x-session-id),一次失败可从报错串到归因明细。 - 用量统计缓存 — 新增
fetchLiveUsageStatsCached(45s TTL + 并发去重),仪表盘 overview/aggregate 共用;实测切用量页的重复上游请求(每账号 4 个/轮)从每轮 2.9s 降到毫秒级命中。登录与额度轮换仍走未缓存的原始拉取,保证新鲜度。 - 未捕获异常的退出策略 — 单次异常仍只记日志;但 5 分钟内累计 3 次(Exception/Rejection 合并计数)即主动
exit(1),交给服务管理器/看门狗重启,避免带病进程挂着僵死的上游连接。 - 仪表盘 SPA 迁出模板字符串 — 约 1000 行内嵌 HTML/JS 迁为静态文件
public/index.html,GET /改为按请求读取(dashboard.ts从 1400+ 行降到 404 行);前端代码从此可独立编辑、可在浏览器 devtools 直接调试源文件;pkgassets 更新为public/**/*。服务内容与迁出前逐字节一致(迁移即抓取运行时输出)。 - 原生 alert/confirm 全部替换 — 新增与面板风格一致的轻量 toast(成功/失败/信息,4s 自动消退)与确认模态(Esc 取消、Enter 确认):浏览器登录结果反馈、移除账号、清空会话历史三处流程;原生弹窗数量归零。
- 界面交互 — "实时日志"标签页现在真的实时(激活时每 5s 轮询);登录模态支持 Enter 提交 / Esc 关闭并自动聚焦;账号卡片按钮改为事件委托(去掉内联 onclick 拼 JS 字符串);页面切入后台时暂停状态/用量轮询;补充 favicon。
- 模型页人民币价格为折算参考 — 明示按 1 USD ≈ ¥6.72 折算(此前汇率硬编码无说明)。
- CJK 感知的 token 兜底估算 — 上游未回 usage 时,输入/输出量估算对中文文本从"4 字符=1 token"改为 CJK 字按 1 字 1 token 计,减少数倍低估。
- chat/messages 双出口助手收敛 — SSE 头、事件行解析、长连接加固、会话持久化收敛到
src/routes/sse-common.ts,消除双份实现。
工程化
- 新增 GitHub Actions CI(
.github/workflows/ci.yml):push/PR 上自动执行npm ci → typecheck → build → vitest run(先 build 是因为集成测试 spawndist/index.js)。 - 定价页解析契约锁定 —
parsePricingFromHtml导出并用合成 RSC fixture 锁定:多 chunk 拼接、静态价含缓存价、峰/谷分时价双保留(回归早期"峰时价被丢弃")、onGoPlan只看显式档位键不看all、缺 id 行跳过、无 payload 返回空。官方页面改版时会在这里变红而不是静默失效。 resolveModelName表驱动测试:精确 / 前缀剥离 / 后缀 / 展示名 / 部分包含 / 家族规则 / 未知透传 / 空输入共 10 组;为此新增setCachedModelsForTest测试挂钩。- 输出 token 兜底估算统一 CJK 感知 —
estimateTextTokens覆盖 adapter 编码器、chat/messages 非流式累计的全部输出路径(此前只换了输入侧)。
测试
-
新增:
<think>标签拆分(单 delta / 跨 delta)、普通英文含 "thinking" 不误判;定价页解析 5 项;模型解析 10 项;适配describe.skipIf跳过逻辑。新增tests/auth.test.ts(6 项,fastify.inject不监听端口):密钥开启时 /v1 与 /api 双面 401/200(Bearer 与 x-api-key)、错误密钥拒绝、/v1/messages返回 Anthropic 信封而其余返回 OpenAI 形态、非保护路径放行、未配置密钥时不注册钩子。全量 216 项通过(含 build 后 31 项集成),tsc --noEmit无错误。 -
版本号
4.9.2→4.9.3。
v4.9.2 — 通知静默失败可自诊断
修复
- 桌面通知的静默失败现在可自诊断 — 4.9.1 修掉 AUMID 未注册后,实机验证发现通知仍然不显示:通知平台事件日志(
Microsoft-Windows-PushNotification-Platform/Operational,事件 3150)显示PolicyReason [GlobalSettingDisabled]——系统通知总开关本身是关闭的(HKCU...PushNotifications\ToastEnabled=0),且所有应用(包括 ZCode 自己)的通知都在被同一策略拒绝。本代理无从修复用户的系统开关,但可以把它讲出来:- 启动时主动读取总开关(经 PowerShell
Get-ItemProperty,60 秒缓存),关闭则在日志中给出明确的修复路径(Windows 设置 → 系统 → 通知); notify()在总开关关闭时不再白白 spawn 一个注定被拒的 PowerShell 进程,直接跳过并在日志写明原因(同一原因 30 分钟去重,不刷日志);- 开关状态可随时变化,因此不永久缓存。
- 启动时主动读取总开关(经 PowerShell
- 改用 PowerShell 而非
reg.exe读注册表 — 本机实测reg.exe的命令行查询(无论带不带/v)均被以"无效语法"拒绝(status=1、无输出,疑似安全软件干扰),而Get-ItemProperty读同一键稳定可用。诊断同样依赖"工具可用性以实测为准",否则会得出"通知已开启"的错误结论(本过程实际发生过一次)。
验证
-
修复路径实测闭环:总开关关闭时启动日志输出"系统通知总开关已关闭,桌面通知将不会显示";用户打开开关后,toast 以自有 AUMID(
CommandCode.Proxy)真实弹出并收到——即 4.9.1(AUMID 注册)+ 本版(开关诊断)两层修复叠加后功能完整可用。 -
全量 193 项通过,
tsc --noEmit无错误。 -
版本号
4.9.1→4.9.2。
v4.9.1 — 修复桌面通知从未显示的问题
修复
- 桌面通知被 Windows 静默丢弃(从未真正显示过) — toast 必须以**已注册的应用标识(AUMID)**发出,此前随手用了
'CommandCode Proxy'这个未注册字符串,导致 PowerShell 调用成功返回(日志显示已发送)但 Win11 在展示层直接丢弃且不报任何错——即通知功能实际上从未生效。实测环境 Win11 25H2(build 26200)。- 现按本机其他应用(豆包/抖音/Steam++)的同一方式,把 AUMID 注册进
HKCU\Software\Classes\AppUserModelId\CommandCode.Proxy(DisplayName+ShowInSettings),无需管理员权限、无需打包,通知显示为 "CommandCode Proxy" 并可在 Windows 通知设置中单独管理。 - 注册幂等(已存在则不再写注册表),进程内缓存探测结果,并在启动时预注册(
setImmediate,不阻塞)。 - 注册失败时回退到 PowerShell 自身的 AUMID——通知显示名会变成 "Windows PowerShell",但至少能显示出来;日志中以
[own]/[fallback]区分实际使用的标识。 - 可选环境变量
COMMANDCODE_NOTIFY_ICON指定 .ico 路径,设置后通知带图标。
- 现按本机其他应用(豆包/抖音/Steam++)的同一方式,把 AUMID 注册进
- 头部注释中的错别字(
採接→拼接)。
测试
- 新增
tests/notifier.test.ts(8 项):事件去重(窗口期内只发一次、跨键互不影响、超窗重发)、COMMANDCODE_NOTIFY多种关闭写法、自有 AUMID 与回退标识的区分、通知正文 XML 转义(防拼进 PowerShell 脚本时注入)。 - 全量 193 项通过(基线 185),
tsc --noEmit无错误。
验证
-
注册表确认写入成功(
reg query可见DisplayName=CommandCode Proxy、ShowInSettings=1);启动日志输出AUMID registered;实发通知日志标记为[own],表示走的是自有标识而非回退。 -
版本号
4.9.0→4.9.1。
v4.9.0 — 端到端性能、额度预测与桌面通知
新增
- 端到端吞吐与延迟分布 — 面板新增"端到端性能"表:每模型的吞吐 P50/P95(t/s)、延迟 P50/P95、样本数;"会话明细"表新增吞吐列。
- 口径在界面与代码注释中显式标注:
timingMs覆盖整个请求生命周期(上游排队、重试、网络往返),因此这是端到端吞吐而非模型生成速度 —— 用来比较"体感等待"是准确的,用来评估模型快慢会失真。 - 只计
COMPLETED且有输出的请求;无输出/无耗时返回 null 而非 0。
- 口径在界面与代码注释中显式标注:
- 额度燃烧速率预测 — "账号与额度"页的 5 小时窗口卡片新增预测行:按当前速率多少分钟后撞上限额、是否早于官方重置时间(唯一需要行动的结论)。
- 关键口径:官方
windowLimits.fiveHour.used是全账号值,而本地用量历史只覆盖代理流量(实测约 18%),用本地速率外推会严重高估剩余时间、给出危险的反向预警。因此速率来自官方 used 的时间差分:新增src/utils/quota-tracker.ts,每 5 分钟采样一次credits端点(只拉这一个端点,开销最低),对采样做差分得燃烧速率。 - 采样跨度不足 10 分钟时自动放宽取更早样本以抑制噪声;官方计数回落(窗口重置/口径修正)时速率为 0、不外推;过期采样(>6h)丢弃;缺
resetAt时撞限判断为 null 而非猜测。 - 面板采样不足时显示"采样中",速率非正时显示"当前无消耗"。
- 关键口径:官方
- 桌面通知(Windows toast) — 新增
src/utils/notifier.ts,在三类用户多半不在面板前的时机主动提醒:5 小时窗口将早于重置耗尽、auto-quota 自动切换账号、引擎被暂停。- 用 PowerShell 原生
Windows.UI.Notificationstoast,零第三方依赖;同一事件 30 分钟去重限频(避免把用户烦到关闭通知权限);通知失败只记日志,绝不影响代理请求路径;COMMANDCODE_NOTIFY=0可整体关闭。
- 用 PowerShell 原生
变更
GET /api/usage/history新增byModelPerf与quotaProjection块;打开面板本身也会产生一个额度采样点。config.ts新增fetchWindowLimits():只拉credits的轻量采样接口,与完整的fetchLiveUsageStats(4 个端点)区分。
测试
- 新增
tests/perf-quota.test.ts(17 项):- 吞吐:500 tok/5s = 100 t/s,无输出/无耗时/非法值返回 null;
- 百分位:P50/P95 线性插值落点、单元素、空数组;
- 燃烧速率:线性外推、撞限早于重置 = true / 重置早于撞限 = false(这对结论的方向性被专门锁住)、计数回落时速率为 0、跨度不足自动放宽、过期采样丢弃、缺
resetAt返回 null、采样器容量上限与非法值拒绝。
验证
-
实机:
byModelPerf返回真实分布(deepseek-v4.1-flash377 样本、吞吐 P50 76.5 t/s / P95 171.5 t/s、延迟 P50 5.45s);quotaProjection首个采样正确给出余量与重置倒计时、速率待累计;Windows toast 独立进程实测弹出成功。 -
全量 185 项通过(基线 168),
tsc --noEmit无错误。 -
版本号
4.8.0→4.9.0。
v4.8.0 — 会话与项目归因
新增
- 会话维度聚合(客户端声明的标识) — 按会话归因每次请求的成本与 token。
- 会话 ID 取自客户端发送的
x-session-id头,实测值与磁盘上的会话目录名sess_<uuid>完全一致(独立交叉印证),属事实性标识而非推测。 - 三级回退:
x-session-id→ 请求体metadata.user_id(实测是被编码成字符串的 JSON,需二次解析)→ OpenAI 兼容客户端的user字段。全部失败则留空,不猜测填充。 - 同时采集
x-zcode-session-type(main / subagent)、x-zcode-agent。 - 面板新增会话排行表:会话 ID、所属项目、请求数、成本、agent、持续时长,并标注「声明值」徽章。
- 会话 ID 取自客户端发送的
- 项目维度聚合(文本推断,带置信度) — 按项目归因成本。
- 上游不提供该维度(
/alpha/usage/{projects,sessions,history,breakdown,daily,...}等 12 个候选端点实测全部 404),代理自身也没有调用方工作目录(原x-project-slug取自代理自己的process.cwd(),是无效值),因此只能从 system prompt 文本提取。 - 两级来源并逐条标注置信度:
label= 命中显式字段(实测 ZCode 的Primary working directory: <路径>,高置信);heuristic= 按路径父目录出现频次推断(低置信,≥2 次才采纳)。 - 护栏「宁可留空,不标错」:无标签且无可信频次 →
project为空,面板显示「未识别」。显式排除node_modules/AppData\Local\Temp/Windows/.zcode\cli\{plugins,skills,artifacts,exec,log,db}等噪声目录。 - 面板新增项目分布表,
推断徽章 + 每行标签/推测置信度标记,与「声明值」会话表视觉上明确区分,并写明「项目无权威字段来源」。
- 上游不提供该维度(
- 归因覆盖度披露 —
GET /api/usage/history新增attribution块(sessionsIdentified/projectsIdentified/projectsLabeled/totalRecords),面板显示"已归因 N/M 条"。 - 按客户端时区分组日期 — 此前
dayKey用服务器本地时区,跨时区调用方会看到日期错位(UTC+8 用户在本地 00:30 的请求被归到前一天)。现读取x-client-timezone(经Intl校验,非法值回退服务器时区)按其计算。
修复
- 头部大小写处理不完整 —
header()原先只查小写名,真实的原始大小写头部(如X-Session-Id)取不到值;现按小写归一后遍历匹配。 normalizeProjectPath误截断合法路径段 — 原先用split(/\\n/)处理提示词里的字面量\n,把C:\proj\node_modules\foo这类以n开头的路径段切成了C:\proj。现改为仅在字面量\n后紧跟字段标记(-或#)时才截断。- 频次启发式失效 — 原先按完整文件路径计数,而同一项目下各文件路径互不相同,导致计数永远为 1、推断永不生效;现改为按父目录计数,并在频次相同时取更浅(更接近项目根)的路径。
变更
UsageRecord新增sessionId/project/projectSource/sessionType/agent/timezone(均为可选,不确定则不写入)。getUsageStats()新增byProject/bySession/attribution;未识别项目单独成组而非静默丢弃。- 同一会话跨多次推断得到不同项目时保留首个非空值,避免抖动。
测试
- 新增
tests/attribution.test.ts(36 项):- 会话 ID 三级回退、双层编码 metadata、空白值、大小写头部、裸客户端返回 null;
- 时区校验(合法 IANA vs
Not/AZone); - 路径规范化:盘符小写、双重转义还原、引号剥离、噪声目录排除、相对路径拒绝、超长拒绝,以及三个已修缺陷的回归(
\node_modules误截断、父目录计数、字面量\n截断); - 项目推断:标签优先、无标签时频次推断、只出现一次必须返回 null、空/非法输入、标签值不可用时回落启发式;
- 聚合:项目按成本排序并保留置信度、未识别单独成组、会话仅计有 ID 的记录、
attribution计数、跨推断抖动保护、客户端时区分组生效(23:30 UTC 在上海时区归到次日)。
- 全量 168 项通过(基线 132),
tsc --noEmit无错误。
验证
-
实机:新记录正确落盘
sessionId=72c84a09-…、project=c:\Users\admin\.zcode\workspace\default、projectSource=label、agent=glm、timezone=Asia/Shanghai;/api/usage/history的byProject/bySession/attribution均返回正确数据。 -
版本号
4.7.0→4.8.0。
v4.7.0 — 缓存节省与峰谷时段提示
新增
- 缓存节省可视化 — 面板"用量与监控"页新增缓存节省卡片,显示缓存命中相比"全价输入"省下的金额,并给出它相对账面成本的倍数,回答"为什么我的账单远低于输入量×输入价"。
- 节省额 =
cacheReadTokens × (输入价 − 缓存读价),按每条记录自身的发生时刻取费率 —— 峰谷价不同,用当前时刻算历史记录会算错。 - 聚合层新增
savingsUsd/savingsMultiple(总计),以及byDay/byModel维度的savingsUsd。 - 护栏:缓存单价高于输入价(反常定价)时不报负节省,返回 0。
- 节省额 =
- 峰谷计费时段提示 — 面板顶部新增提示条,显示当前处于峰时还是谷时、多少小时后切换,并列出受分时价影响模型的当前生效费率。
- 新增
describeBillingWindow():给出isPeak/nextChangeAt/nextIsPeak/minutesUntilChange,切换点通过枚举官方边界(UTC 01/04/06/10)求得,因此周末与工作日交界能正确跨越(如周五 10:00 后一直谷时,直到下周一 01:00)。 - 新增
getTimeOfDayModels():列出带timeOfDay的模型及其当前档位费率(通常 4 个 deepseek 模型)。 GET /api/usage/history响应新增billing块(window+models)。
- 新增
测试
tests/cost-usage.test.ts从 19 项扩至 33 项,新增:- 缓存节省按 (输入价 − 缓存读价) 计算,峰时 $0.044819712(152,448 token × 0.294/M)等实测值;
- 峰时节省严格大于谷时同量(比值 2:1,对应费率差 0.294 vs 0.147);
- 零缓存、未知模型、缓存价高于输入价三种边界均返回 0;
- 峰谷窗口:峰时 02:00 → 切换点 04:00(120 分钟后)、06:30 → 10:00、谷时 00:30 → 01:00(30 分钟后)、周五 12:00 → 下周一 01:00 跨越周末、周六 → 下周一 01:00;
- 全部模型均为静态定价时
describeBillingWindow返回空窗口、getTimeOfDayModels返回空数组。
- 全量 132 项通过(基线 118),
tsc --noEmit无错误。
验证
-
实机复算:按记录时刻选档独立重算节省额得 $3.9725,与接口返回 $3.9055 偏差 1.72%,差额来自两次读取之间的新增实时请求(文件持续追加)。其中峰时记录贡献 $2.70、谷时 $1.27,与预期分时行为一致。
-
版本号
4.6.0→4.7.0。
v4.6.0 — 成本口径对齐官方账单
修复
- 面板成本与官方账单差约 15 倍(缓存命中被按全价输入计费) — 上游
finish事件的inputTokens是含缓存命中的总量,缓存明细在inputTokenDetails.cacheReadTokens。此前usage-store把整段输入统一按pricing.input计价,而 agent 场景的输入约 96%–99% 命中缓存,官方缓存读单价仅为输入价的 1/50(如deepseek-v4.1-flash谷时输入 $0.15/M、缓存读 $0.003/M)。- 实测对照:一条
input 153,993(缓存命中 152,448)/ output 176的请求,旧口径记 $0.023205,官方账单为 $0.001589388 —— 虚高 14.6 倍;缓存命中率更高时可达 29 倍。 - 现在按
nonCache×input + cacheRead×cacheRead + cacheWrite×cacheWrite + output×output分项计价。
- 实测对照:一条
- 峰时请求成本被低估一半 — 官方对 4 个 deepseek 模型设峰谷分时价(谷时 $0.15/$0.60、峰时 $0.30/$1.20,峰时为 UTC 周一至周五 01–04 与 06–10,共 7h/day)。此前解析定价页时只取
tiers[0].rates(= 谷时档),timeOfDay整块被丢弃,峰时请求一律按谷时价估算。- 现已解析
timeOfDay.peak/offPeak/windows/peakHoursPerDay并落盘,估算时按请求时刻选档(isPeakBillingTime)。
- 现已解析
- 类型声明与上游实际结构不符 —
CCEvent.totalUsage原先声明的是cacheReadTokens/cacheWriteTokens(顶层平铺),与上游实际的inputTokenDetails嵌套结构对不上,导致即使想读缓存量也读不到。现按实测结构重写并新增CCEventUsage类型。
新增
- 采集上游权威账单金额(
gateway.cost) — 上游provider-metadata事件带回网关已算好的账单字段(cost/marketCost/surchargeCost/gatewayCost/inferenceCost/inputInferenceCost/outputInferenceCost/generationId),此前该事件完全没有处理分支、整条被丢弃。新增src/adapters/commandcode/usage.ts统一采集:- 成本优先采用官方
gateway.cost,上游未给出时才回落到本地估算 —— 连峰谷价、缓存折扣与加成都不必自行维护; - 记录新增
costSource(official/estimated)与estimatedCostUsd,面板对本地估算值加~前缀,悬停可看两者对照,便于上游调价时及早发现偏差; - 采集对
finish-step/finish两个事件做覆盖而非累加 —— 上游同一轮会发两次相同 usage,累加会让 token 翻倍。
- 成本优先采用官方
- 缓存用量落盘 —
UsageRecord新增cacheReadTokens/cacheWriteTokens;聚合统计(getUsageStats)新增cacheReadTokens与cacheHitRate(累计缓存命中率)。 - 仪表盘"缓存命中"列与累计命中率 — "用量与监控"页的请求明细新增"缓存命中"列(显示命中量与占输入百分比,悬停看具体数值),"累计"卡片新增累计缓存命中率与命中 token 量,直观解释成本为何远低于"输入 × 输入价"。
变更
CCEvent.type新增provider-metadata;StreamEncoderState新增cacheReadTokens/noCacheTokens/upstreamCostUsd;ModelItem新增timeOfDay。pricing.json结构版本PRICING_SCHEMA_VERSION2 → 3,旧缓存自动失效并重新抓取,避免升级后timeOfDay静默为空。- 旧记录(无缓存字段)在聚合时按缓存 0 处理,历史成本数字无法回填。
测试
-
新增
tests/cost-usage.test.ts(19 项):- 实测数据回归——直接用真实上游响应的费率复算,断言本地估算与官方
gateway.cost一致到 1e-12:缓存命中探针320 非缓存 + 7,296 缓存读 + 13 输出 = 0.000155376(峰时)、零缓存探针64 + 162 = 0.0002136(峰时)、同量谷时0.0002136 / 2; - 缓存明细拆分的多种形态(完整
inputTokenDetails、仅cachedInputTokens、无缓存字段); finish-step+finish覆盖语义(防 token 翻倍);provider-metadata账单采集(字符串 / 数字 / 缺失);- 峰时窗口边界(01:00 / 03:59 / 04:00 / 06:00 / 09:59 / 10:00 UTC)与周末全天谷时;
- 无分时价的模型回落静态定价、未知模型
hasPricing=false。
- 实测数据回归——直接用真实上游响应的费率复算,断言本地估算与官方
-
全量 118 项通过(基线 99),
tsc --noEmit无错误。 -
版本号
4.5.0→4.6.0。