Repository navigation
fix(desktop): 补齐桌面端身份头 + chat 的 sse-gzip-recovery(修复 chat 410 freebuff_update_required) - #36
Conversation
上游对 chat 一跳加了客户端身份门禁: 缺少桌面端身份头时 /api/v1/chat/completions 一律 410 freebuff_update_required(official 通道下 body 为空), 而 session/admission 与 agent-runs 都正常. - officialSessionHeaders: 补 x-freebuff-client: desktop、 x-freebuff-client-version(取 FREEBUFF_APP_VERSION, 缺省按当前桌面版 0.0.173)与 x-freebuff-install-id(有值时); wallet-spend-limit 缺省改 'session' - cli-bridge chat 头: 补 x-freebuff-sse-gzip-recovery: 1 - upstream-contract: 登记两个新头 取值对齐官方桌面端 0.0.173 orchestrator 的 FREEBUFF_CLIENT_HEADER / FREEBUFF_CLIENT_VERSION_HEADER / FREEBUFF_INSTALL_ID_HEADER / FREEBUFF_SSE_GZIP_RECOVERY_HEADER, 并与同机同号抓包逐字段 diff 复核. 实测: /v1/chat/completions 200 + 完整流式回复; 日志无 upstream chat non-ok; npm run check:gates 全绿. 关联 HengXin666#35.
PR Summary by Qodo修复桌面端身份头缺失导致的聊天接口 410 错误
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo
1. 非 bun 回落路径的 chat 仍缺恢复标记
|
Qodo Fixer✅ Merged (0) · ☑ Fixed (0) Process
|
There was a problem hiding this comment.
All reported issues were addressed across 3 files
Reply to a comment to ask cubic a question or push back. It learns from your replies.
Click Fix with cubic on a comment, or reply @cubic-dev-ai fix this, and cubic writes the fix.
View guided diff | Re-trigger cubic
- note(2026-09-18-official-cli-fingerprint): 记录桌面端身份组的取值锚点 (orchestrator.js)、版本兜底与 wallet 缺省 'session', 并修正已过期的事实 (x-codebuff-api-key 已移除)。理由只在 note 里, 实现处只留指向本文的引用 - install-id: 新增 src/upstream/client/client-state.ts 的 resolveInstallId() (FREEBUFF_INSTALL_ID 优先, 回落官方客户端 state.json, 读不到整头省略); Node 会话装配与 bun 会话请求共用同一个值(此前 Node 准入只读 env, 与 bun 那一跳的身份源分叉) - 桥接准入(cli-bridge/lib/endpoints/session.ts): 补 x-freebuff-client-version, wallet-spend-limit 与桌面对齐为 'session' - smoke(指纹组): wallet 断言改为 'session', 并钉住桌面身份组 (client / client-version / install-id) - bun-channel 拆出 install-id 读取到 client-state.ts, 守住单文件 300 行上限 验证: gates 18/18 ALL PASS; agent-notes verify-all(all gates) 通过; typecheck rc=0; test:smoke rc=0; bun mock dump 确认桥接准入头集完整 (client / client-version / install-id / wallet=session)。
| - `proxy.js` 的 chat 改用 `officialChatHeaders()`,并按桌面形态补 | ||
| `x-freebuff-sse-gzip-recovery: 1`.鉴权头统一来自 `freebuffAuthHeaders()` | ||
| (只发 `Bearer`). |
There was a problem hiding this comment.
1. 非 bun 回落路径的 chat 仍缺恢复标记 🐞 Bug ≡ Correctness
本轮 note 写明 proxy.js 的 chat「按桌面形态补 x-freebuff-sse-gzip-recovery: 1」,但 src/
里没有任何地方发这个头:forward.ts 的 legacy upstream.raw('/api/v1/chat/completions') 用的是
filterRequestHeaders 加 officialChatHeaders(),两者都不带它,只有 cli-bridge 的 buildHeaders 补了。设
FREEBUFF_DISABLE_BUN=1,或 official 通道没拿到响应而走 legacy 回落时,chat 请求仍缺本次认定的桌面 chat 头。PR 自己也说没逐项
A/B,所以这条路径可能还是 410 freebuff_update_required,而 note 却说它已经补齐。
Agent Prompt
## Issue description
Node 侧 legacy chat 路径(official 通道未命中或 bun 被禁用时走的路径)没有发 `x-freebuff-sse-gzip-recovery: 1`,只有 cli-bridge 发了。note 却写着 proxy 的 chat 已经补上这个头。
## Fix Focus Areas
- src/upstream/fingerprint/official-fingerprint.ts[200-240]
- src/proxy/transport/forward.ts[79-100]
- .agents/notes/implemented/bug-fix/2026-09-18-official-cli-fingerprint.md[65-67]
## Recommended Fix
在 `officialChatHeaders()` 里加上 `[H_SSE_GZIP_RECOVERY]: '1'`(或在 forward.ts 的 headers 里显式加上),让 legacy chat 与 cli-bridge 的 `buildHeaders` 形态一致;同时在 smoke 测试里断言 legacy chat 带有这个头。
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
Code review by qodo was updated up to the latest commit c6b121a |
There was a problem hiding this comment.
2 issues found across 9 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="test/suites/entries/smoke/parts/api/core/fingerprint.ts">
<violation number="1" location="test/suites/entries/smoke/parts/api/core/fingerprint.ts:114">
P3: `resolveInstallId()` 可能返回空字符串,但会话头装配会把它视为缺失并省略该头;这里应把空值归一化为 `null`,否则 smoke 在空 `installId` 状态文件下失败。</violation>
</file>
<file name=".agents/notes/implemented/bug-fix/2026-09-18-official-cli-fingerprint.md">
<violation number="1" location=".agents/notes/implemented/bug-fix/2026-09-18-official-cli-fingerprint.md:139">
P3: 这段新增断言被归在不存在的 `test/smoke.mjs` 下,开发者无法按说明定位回归用例;请将测试引用更新为实际的 `test/suites/entries/smoke/parts/api/core/fingerprint.ts`。</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
| ) | ||
| assert.equal( | ||
| admitCall.headers['x-freebuff-install-id'] ?? null, | ||
| resolveInstallId(), |
There was a problem hiding this comment.
P3: resolveInstallId() 可能返回空字符串,但会话头装配会把它视为缺失并省略该头;这里应把空值归一化为 null,否则 smoke 在空 installId 状态文件下失败。
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At test/suites/entries/smoke/parts/api/core/fingerprint.ts, line 114:
<comment>`resolveInstallId()` 可能返回空字符串,但会话头装配会把它视为缺失并省略该头;这里应把空值归一化为 `null`,否则 smoke 在空 `installId` 状态文件下失败。</comment>
<file context>
@@ -89,9 +91,29 @@ import path from 'node:path'
+ )
+ assert.equal(
+ admitCall.headers['x-freebuff-install-id'] ?? null,
+ resolveInstallId(),
+ '准入的 install-id 必须与 resolveInstallId() 同一来源',
+ )
</file context>
| resolveInstallId(), | |
| resolveInstallId() || null, |
| first-tab-discount / `x-fb-timezone`;`x-codebuff-api-key` 断言为"仍带"(已知偏差). | ||
| `/1.0.0/`;POST 命中 `.../session/admission`;带 model / wallet-spend-limit | ||
| (= `session`)/ first-tab-discount / `x-fb-timezone`;桌面身份组断言 | ||
| `x-freebuff-client` = `desktop`,`client-version` = `desktopClientVersion()`, |
There was a problem hiding this comment.
P3: 这段新增断言被归在不存在的 test/smoke.mjs 下,开发者无法按说明定位回归用例;请将测试引用更新为实际的 test/suites/entries/smoke/parts/api/core/fingerprint.ts。
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At .agents/notes/implemented/bug-fix/2026-09-18-official-cli-fingerprint.md, line 139:
<comment>这段新增断言被归在不存在的 `test/smoke.mjs` 下,开发者无法按说明定位回归用例;请将测试引用更新为实际的 `test/suites/entries/smoke/parts/api/core/fingerprint.ts`。</comment>
<file context>
@@ -103,13 +134,16 @@ tar xzf freebuff-linux-x64.tar.gz && strings -n 6 freebuff > fbref-str.txt
- first-tab-discount / `x-fb-timezone`;`x-codebuff-api-key` 断言为"仍带"(已知偏差).
+ `/1.0.0/`;POST 命中 `.../session/admission`;带 model / wallet-spend-limit
+ (= `session`)/ first-tab-discount / `x-fb-timezone`;桌面身份组断言
+ `x-freebuff-client` = `desktop`,`client-version` = `desktopClientVersion()`,
+ `install-id` = `resolveInstallId()`;`x-codebuff-api-key` 断言为**不得再带**.
- 上表四处偏差的原文锚点:二进制 `strings` 提取,命令见本文 Problem 段.
</file context>
现象
POST /api/v1/chat/completions一律返回410(freebuff_update_required,official 通道下 body 为空),而 session/admission 与 agent-runs 都正常。同机、同账号用官方客户端(Desktop 0.0.173 / CLI 0.2.27)对话正常,说明不是账号或出口问题。根因
上游对 chat 一跳加了客户端身份门禁:请求缺少桌面端身份组时被判为过时客户端。与官方请求逐字段比对后,缺口三处:
x-freebuff-client: desktop、x-freebuff-client-version: <桌面版号>、x-freebuff-install-idx-freebuff-wallet-spend-limit: session0x-freebuff-sse-gzip-recovery: 1取值来源:官方桌面端 0.0.173 的
resources/orchestrator/orchestrator.js(常量FREEBUFF_CLIENT_HEADER/FREEBUFF_CLIENT_VERSION_HEADER/FREEBUFF_INSTALL_ID_HEADER/FREEBUFF_SSE_GZIP_RECOVERY_HEADER,其中 client-version 取FREEBUFF_APP_VERSION),并用同机同号的抓包逐字段 diff 复核。改动
src/upstream/fingerprint/official-fingerprint.ts:补身份组头与 install-id;wallet-spend-limit缺省'session'cli-bridge/lib/endpoints/chat-payload.ts:chat 补x-freebuff-sse-gzip-recovery: 1src/upstream/upstream-contract.ts:登记两个新头(过契约门禁)验证
/v1/chat/completions→200+ 完整流式回复;日志无upstream chat non-oknpm run check:gates全绿(18/18)、check-upstream-contract通过、typecheck通过备注
x-freebuff-client-version缺省值0.0.173是抓包时的桌面版号;桌面端发版后需同步更新该缺省(也可用FREEBUFF_APP_VERSION覆盖)。关联 #35。