Repository navigation
fix(#2374): visibility marker 的 message item id 改用 msg 命名空间,并治愈 marker- 中毒会话 (WC-015) - #2375
Conversation
…间,并治愈 marker- 中毒会话 (WC-015) emitMarker 产出的是 type:"message" 的 item,却用 marker-<ts>-<index> 作 id;上游按 msg 前缀 校验 message item id,整请求 400。该 id 会被 pi-ai 系客户端经 textSignature 回放,而入口清洗 只认 msg-proxy-,于是会话此后每轮必 400、无法自愈。 - emitText / emitMarker 统一走 proxyMessageItemId(),铸造在 msg-proxy- 命名空间下 - sanitizeResponsesInputIds 增认历史 marker- 前缀,救治已被旧版本写坏的 rollout (只放宽 assistant-message 的删除分支;>64 字符改写分支仍只认 msg-proxy-,ranxianglei#1474 保护不变) - 新增 wire-contract 台账条目 WC-015(台账 + 校验等价 fake 子句 + 闸门探针 + 定向测试) - 两个夹具文件随契约收紧同步修正 - 顺带把 sanitizeResponsesInputIds 的文档注释从 normalizeResponsesMessageItems 移到它真正描述的函数上 生产实证:一个被 0.1.187 毒化的会话(session-5f30b295…),其 rollout 里的 marker- id 始终未被 客户端丢弃(textSignature 回放载体仍在),换用本构建后同一会话即刻恢复正常,无需任何客户端侧处理。
…h PROXY_MESSAGE_ID_PREFIX Reviewer nit on top of the PR head: WC-015 single-sources the msg-proxy- namespace, but this third client-visible mint site (round>1 upstream message remap) still hand-wrote the literal. String is byte-identical - no behavior change.
[bot] 🏷 PR #2375 Review(fix #2374)— 判定:可合(人工评审,CI 把关全量) 我做了什么:拉取 PR head 七问:
两种压缩模式:proxy 模式产出侧 + 入口治愈双生效;plugin 模式产出侧生效(同一 adapter,不再有任何路径铸坏 id),plugin 直通本就没有入站 id 清洗、本 PR 也不新增——PR 关于「修复前毒化的 plugin rollout 因 id 留在客户端侧而不可治」的边界说明准确。 测试有效性:三个新回归都有牙——visibility-markers 断言发出的 marker item id 以 diff 整洁度:7 文件全部与本 PR 目的相关;两个测试文件是必要的夹具修正(WC-015 下原夹具已属 wire 非法),其中 responses-proxy-id-replay 把「 自动合并判定:触及 message-id 逻辑 = §7.4 gate 8,必须人工评审、不可自动合并——PR 自己也是这么标的,正确。 结论:可合(待人工批准 + CI 全量绿)。方向、根因定位层次、修法层次、既有行为回归清单、测试有效性、diff 整洁度我都过了;未本地跑的是全量套件(交给 CI,与作者一致)。没有发现需要顺手小修的小问题。 |
[bot] 🏷 补充:PR head 我 review 时留的唯一开放项(「待 CI 全量绿」)现已闭合。判定维持:可合,剩人工批准即可。 |
|
ci失败 |
[bot] 🏷 查了,CI 不是全挂,是单个 check-run 红: **归因:几乎可以断定是这条腿的偶发 flake,不是本 PR 引入的回归。**依据:
我的限制(说清楚):我拉不到原始日志——check-run logs 端点返回 404(要 admin scope),annotations 只到 建议动作:
我这边判定不变:代码可合(两个提交都过审,第 2 个是零风险重构);当前唯一挡路的是这条 flaky 腿,重跑即可。 |
📦 Merged Build PublishedThis merge to master was published as npm install -g billion-context@0.0.147-master.cc368fe # this exact build
npm install -g billion-context@master # always the newest merged build
|
问题
emitMarker(Responses adapter)产出的 message item,其 id 不以msg开头:这个 id 会穿过客户端往返(pi-ai 系客户端把 message item 的 id 存进响应文本块的
textSignature,再作为 input item 的
id回放),所以一旦某轮压缩发出过一个,该会话此后每个请求都 400。sanitizeResponsesInputIds——bili 针对这个往返的入口治愈——只认msg-proxy-命名空间,于是历史的
marker-id 穿了过去,会话无法自愈。根因,且修在产出侧(疏,不是堵)
缺陷的本质是 item 类型与铸造它的 id 命名空间不匹配。
emitText对同样的 item 形状一直铸msg-proxy-…;emitMarker铸marker-…,这个名字描述的是 marker 的内容而不是它的 wire 类型。现在两者共用一个 helper:
因此未来的 emit 路径不可能漂到上游不接受的命名空间之外,而入口治愈认识的分母恰好就是 bili 铸造的命名空间。
治愈另外放宽到历史的
marker-前缀。这不是"堵症状":产出侧在同一个改动里已经修掉,治愈的存在是为了修复已发布版本已经写到用户磁盘上的 rollout(与它在 #242 的超长 id、#1474 的收窄里扮演的角色相同)。
没有它,升级 bili 会把每一个已被毒化的会话永久留在坏状态。
改动
src/loop/adapter-responses.tsPROXY_MESSAGE_ID_PREFIX/LEGACY_MARKER_MESSAGE_ID_PREFIX常量、emitText与emitMarker共用的proxyMessageItemId()、sanitizeResponsesInputIds的 assistant-message 治愈分支改用isBiliMessageId();顺带把该治愈的文档注释(本次改动本就要重写它)从normalizeResponsesMessageItems头上移到它真正描述的函数上tests/visibility-markers.test.tsmsg开头tests/responses-round2-lifecycle.test.tsmarker-assistant-message id 在入口被删除,同时 provider id(msg_…、rs_…)保持字节原样tests/wire-contract-fakes.tsvalidateResponsesBody里对应的校验等价子句tests/wire-contract.test.tstests/issue1733-config-update.test.tsmsg_前缀。这些测试考的是 WC-011 相邻性,而它们原先的m1/a1id 在 WC-015 下已属 wire 非法(fake 现在会拦)tests/responses-proxy-id-replay.test.tsmarker-123-0原本被放在"原生 id 必须原样保留"一组里,但marker-是 bili 自己铸造的历史命名空间、不是原生的。换成原生 id;该历史命名空间由上面的治愈回归覆盖因此有两个测试文件需要改夹具。其中之一(
responses-proxy-id-replay.test.ts)此前把"
marker-id 不可触碰"这个修复前的假设写死了——那个假设就是本 bug 的适用范围在测试里的形态,这也正是这条台账子句值得加的原因:它让夹具的 wire 非法性显式暴露,而不是默认隐含。
内部重请求路径已核查,无需改动:kernel 的
coreToResponses投影 message/function_call item 时不带 item id,所以入口处由客户端回放的 id 是 bili 合成 message id 能到达上游请求的唯一途径。
wire-contract 台账(#1304)
按 AGENTS.md §7.3 新增的永久条目:
WIRE_RULES——WC-015/ wireresponses/ "a supplied message input item id must begin with msg",provenance 指向生产拒绝现场、往返机制,以及 Codex backend 自身的同类报告;
validateResponsesBody拒绝任何 message 形状(带 type 的,或 omp 的无 type 形态)且 id不以
msg开头的 input item;所有经校验等价 fake 转发的泳道都会在 CI 拦住回归;该条目不可能不被执法地存在;
两种压缩模式(§6)
sanitizeResponsesInputIds),且
fork-adoption对被收养的克隆继承放宽后的治愈。id 清洗环节,本改动也不新增一个。边界说明:被修复前版本毒化的 plugin 模式 rollout,其 id 留在
客户端一侧,in-plugin 无物可治——因为该 id 从不进入 bili 构造的请求体。
配置面
无。没有新增、改名或改语义任何配置字段、环境变量或 CLI 开关;既有的
compress.visibilityMarkers语义不变(它仍然只决定是否发出 marker)。
相关工作(开 PR 前已核对 tracker)
没有任何现存 issue 或 PR 报告过本缺陷——已检索 open + closed 的 issue/PR,包括
emitMarker/visibilityMarkers/sanitizeResponsesInputIds三个词以及上游错误原文。最接近的条目及其为何不覆盖本问题:
每轮 400。同一缺陷类(bili 铸造的 id 往返后违反上游规则);本 PR 的产出侧修复走的是同一个
"铸在规则之内"的方向。
namespace"。正是这次收窄造成了本 bug:
marker-同样是 bili 铸造的,却被留在了白名单之外。非回归:>64 字符的改写分支仍然只认
msg-proxy-,且 Preserve provider-issued reasoning IDs in Responses input sanitization #1474 自己的不变量测试(
provider-issued over-64 ids stay byte-identical across item types)依然通过——只有assistant-message 的删除分支新增了历史前缀。
本 bug 正是"marker 进入客户端历史并被回放"的下游后果,所以 Proposal: model-only delivery mode for visibility markers — proxy internal ACKs are not included in the client conversation history | proposal: visibility markers 的 model-only 投递模式——proxy 内部回执不进客户端对话历史 #1701 是整类问题的结构性解法;
本 PR 是窄口径的正确性修复,既不与它冲突也不阻塞它。
msg-proxy-*"一句会在本改动合入后过期。此处只作提示,不去改动别人的 open PR。
msg前缀规则是 Codex backend 自身的硬校验、并非 bili 的推断,这一点有独立佐证:同样的错误在Codex CLI 本地合成的 id 上也被报告(openai/codex#27928 的
review_rollout_user;openai/codex#20783的一个裸 UUID)。该佐证同样记录在 WC-015 的 provenance 里。
验证
tests/visibility-markers.test.ts—— 新回归在修复前的产出侧(marker-…)失败,修复后通过。tests/responses-round2-lifecycle.test.ts—— 新增治愈回归(历史前缀被删、provider id 不受影响)。tests/wire-contract.test.ts—— WC-015 子句 + 定向测试 + 台账自检。npm run typecheck在当前 master head 上全绿(见下)。代理环境噪声)。
session-5f30b295…,真实 ChatGPT Codex backend):Date.now()= 1791393957942turn/end/PI_AI_ERROR/Invalid 'input[59].id';代理日志← upstream 400turn/start seq=110;代理日志该会话恢复活动[acp-usage] round 1 input=79287、request: 99 msgs、forward POST → …/codex/responses,无 400三条佐证排除了"碰巧好了":
marker-1791393957942-2出现 4 次,其中两次正是回放载体
"textSignature":"{\"v\":1,\"id\":\"marker-1791393957942-2\"}",且该文件在正常工作期间(03:25)仍在写入。客户端没有丢弃它。
nudge idle ... reason="max compressible 43822 < threshold 50000",历史未被压缩改写,所以毒项确实仍在被原样回放的数组里。即:毒 id 由已发布的旧版本写到了用户磁盘上,新版本在不改动客户端任何数据的前提下把它治好,
无需任何客户端侧会话手术。
是否可自动合并
本改动触及 message id 逻辑,属 AGENTS.md §7.4 列明的必须人工评审范围(gate 8)——请求人工评审,
不申请自动合并。
Closes #2374