Repository navigation
fix: preserve prompt cache tokens across providers and salvage streams on terminal finish_reason - #293
fix: preserve prompt cache tokens across providers and salvage streams on terminal finish_reason#293longlixiao-ong wants to merge 5 commits into
Conversation
…s on terminal finish_reason
…erminal chunks for qoder and trae
caigee-cmd
left a comment
There was a problem hiding this comment.
审查结论:Approved(我已替你修掉 3 处会阻塞或影响发布的问题,见下)
这是四个 PR 里内容最好的一个。salvage 逻辑和缓存 token 回退链都写得很扎实,我先说为什么认可,再说我改了什么。
认可的部分
salvage 的判定是安全的。 classifyStreamSSEError 在帧级先拦错误帧,只有干净终态才补 [DONE];条件要求 stats.FinishReason != "" 且已有 token 或事件,不会把空流误判为成功。这消掉的是一整类虚假 502 —— 上游发了 finish_reason 就直接 EOF、而严格的 [DONE] 检查会判流不完整。
stats.FinishReason 的赋值顺序是对的。 在 ParseStreamUsageLine 用 stats 覆盖局部变量之前先写入,且覆盖时又回传 usage.FinishReason = stats.FinishReason,不会丢。这个细节容易写错。
devin 的 auth 分类扩展有真实依据。 isDevinAuthError 覆盖的是 Codeium Connect-RPC 和 seat management 的真实错误形状(permission_denied + an internal error occurred 是过期 session token 的表现),并且 Classify 的 case 顺序保证 isDevinMCPConfigDenial 仍然最先命中 —— MCP 工具转储造成的 permission_denied 不会被误升级成 auth、不会把账号错误地冷却。测试还带了一个负向 case(bare permission_denied 仍归 invalid_request),这是对的。
normalizeTraeUsage 合成 total_tokens 解决的是 Trae 不回总 token 导致用量统计缺一项的问题,方向正确。
我修掉的 3 处
1. deploy/docker-compose.yml — 宿主特定拓扑,对公共用户是破坏性的(最重要)
原 diff:
- build:
- context: ..
- dockerfile: deploy/Dockerfile
+ networks:
+ - default
+ - new-api-net
+ new-api-net:
+ external: true
+ name: new-api-deploy_default删掉本地 build: 段意味着别人 docker compose up 时不会构建镜像;而引入 external: true 的 new-api-net(name: new-api-deploy_default)更严重 —— 该网络不存在时 docker compose up 直接失败。这是你自己宿主机上配合 new-api 网关的拓扑,PR 描述里对此只字未提。
我已把 deploy/docker-compose.yml 恢复成 main 的原样。你自己的部署覆盖请放到 deploy/docker-compose.override.yml(该文件已被 .gitignore,正是为本地覆盖准备的),不要进公共仓库。
2. internal/providers/trae/client.go — gofmt 不干净
usage 结构体的 tag 对齐不符合 gofmt(CI 不跑 gofmt -l 所以没拦住)。已 gofmt -w 修好,现在 gofmt -l 只剩 internal/store/migrations.go(main 既有基线,与本 PR 无关)。
3. changelog fragment — 20 个反斜杠会原样渲染
changelog/unreleased/prompt-cache-and-stream-salvage.md 里 \cache_read_tokens\、\[DONE]\、\finish_reason\ 这种写法,反斜杠会在生成的 release notes 里原样显示。显然是想用反引号包裹代码标识符,已全部改为 `...`。release-notes.py validate 仍 exit 0。
另外两点(不阻塞,供参考)
- 这个分支落后 main 一个提交(
f0f05a8,v0.6.21 changelog 归档)。它自带的checkin-row-align.md和devin-rate-limit-reset.md两个 fragment 已被 main 归档进 v0.6.21,合并后会自动清掉,不会重复发布。它带的 frontend 侧改动则会被 main 的 #294/#290/#291 完全取代 —— 我核对过 merge 后的 blob 与 main 一致,所以保留 main 的 frontend 源码和internal/webui/static静态资源即可,不需要额外处理。 - head commit 的 CI 从未验证过(最后一次成功 CI 停在
ce173cb,head 已是27585ba7)。我已代为批准了 fork 的 pending workflow run,CI 正在跑。合并前我会等它出结果。
验证
我打上上述修正后完整跑过:go build ./...、go test ./...(全量通过)、go vet ./...、gofmt -l(仅 main 基线)、release-notes.py validate(exit 0),以及 -race 覆盖 gateway / executor / providers/... / runtime / store,全部通过。
CI 通过后我就地合并。
caigee-cmd
left a comment
There was a problem hiding this comment.
Thanks for merging main in — CI is now fully green (all 6 jobs). But the three fixes from my earlier review are not in this head yet. Re-checking 27585ba7:
| Fix | Status on 27585ba7 |
|---|---|
deploy/docker-compose.yml |
❌ still diverges from main — build: block deleted, new-api-net / new-api-deploy_default hardcoded as external: true |
gofmt on internal/providers/trae/client.go |
❌ still unformatted |
changelog \token\ → `token` |
❌ still backslash-wrapped |
I know these are annoying to hand-apply, so here they are as suggestions you can commit in one click each.
1. deploy/docker-compose.yml — please revert to main's version
This is the one that actually breaks people. Your version deletes the build: stanza, so docker compose build no longer works from this file — it only pulls a prebuilt image. And it declares:
networks:
default:
new-api-net:
external: true
name: new-api-deploy_defaultexternal: true means Docker will refuse to start the container unless a network literally named new-api-deploy_default already exists on the host. That is a host-specific artifact from whoever ran this, not something that belongs in a repo others clone. Every other user gets network new-api-net declared as external, but could not be found.
Please take main's file byte-for-byte:
build:
context: ..
dockerfile: deploy/Dockerfile
and drop the networks: key on the service plus the whole networks: block at the bottom, and drop the trailing newline difference.
2. internal/providers/trae/client.go — run gofmt -w
CI does not gate on gofmt (only go vet / go test), so this slipped through, but the repo's convention is gofmt-clean. One-line fix:
gofmt -w internal/providers/trae/client.goIt's a struct-tag alignment in the usage struct. No behavior change.
3. changelog/unreleased/prompt-cache-and-stream-salvage.md — use backticks, not \token\
The fragment currently reads \cache_read_tokens\, \prompt_cache_hit_tokens\, \prompt_tokens_details\, \inish_reason\, \[DONE]\. Two problems:
- The repo convention is backticks. Every archived fragment in
CHANGELOG.mduses`finish_reason`,`tool_calls`,`codex`. Zero fragments use the backslash style. \inish_reason\is actually mangled — thefgot eaten, so the released note would literally readinish_reason. Same class of bug as the earlier\token\round.
- Preserve and normalize prompt cache token metrics (`cache_read_tokens`, `prompt_cache_hit_tokens`, and `prompt_tokens_details`) across Trae, Qoder, and WorkBuddy providers in both streaming and non-streaming responses.
- Gracefully salvage upstream streams that finish with a terminal `finish_reason` before EOF without emitting literal `[DONE]`, preventing artificial 502 incomplete stream errors.
- 修复并标准化 Trae、Qoder 与 WorkBuddy 提供商在流式与非流式响应中的 Prompt 缓存 Token 指标(统一解析并透传 `cache_read_tokens`、`prompt_cache_hit_tokens` 与 `prompt_tokens_details`)。
- 容错处理上游流式响应在带有终态 `finish_reason` 正常结束但未显式发送 `[DONE]` 的边界情况,自动补齐结束帧,避免下游误判为 502 流异常中断。
scripts/release-notes.py validate passes either way, so this is a convention fix rather than a gate failure — but the \inish_reason\ typo means the text is currently wrong, which is why I'm asking for it.
Once those three are in I'll merge. Nothing else is blocking — the substantive change (prompt-cache normalization + stream salvage + Devin error classification) reviewed clean and CI is green.
Summary
Test plan