Skip to content

[PR] fix: salvage bad-shape-A compress args at the execution layer, all lanes (#4889) - #4891

Open
ranxianglei wants to merge 12 commits into
masterfrom
2026-10-27_bare-content-salvage-all-lanes
Open

ranxianglei wants to merge 12 commits into
masterfrom
2026-10-27_bare-content-salvage-all-lanes

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Closes #4889

What this changes (plain language)

When the model writes compress args in bad shape A ({"content": <bare text>} with no opening quote), only the Anthropic plugin relay could rescue them before (#2591 wire rewrite). Every other path — proxy mode (loop / JSON rewrite, all four protocols) and third-party provider plugins where bili never touches the SSE bytes — fed the args to strict JSON parsing and failed: proxy mode lost one fold plus a retry; the plugin lane lost the whole turn. This PR moves the rescue into the shared execution-layer parser so every lane recovers, and exports the same repair entry point so provider plugins can fix args before the host's JSON.parse.

Behavior change (old → new)

Deliberately NOT done

Verification

  • Local: typecheck clean; targeted suites green — tests/issue4889-bare-content-execution-salvage.test.ts (new, 9 tests incl. the issue's verbatim incident sample + negative gates), tests/issue2580-compress-arg-salvage.test.ts, tests/issue1502-raw-args-salvage.test.ts (25/25 pass); full build + dist gates clean; built dist/salvage.js smoke-tested (bad sample → repaired strict JSON; valid input → null).
  • Full regression = CI lanes on this head.

Files

…nes (#4889)

parseCompressInput now wraps a bare line-form content value
({"content": <unquoted text>}) as a JSON string and re-parses it when
the kernel ladder recovers nothing — the same rescue the plugin relay
performed on the Anthropic wire only (#2591), now shared by every lane:
proxy-mode loop/JSON rewrite paths and any host that executes compress
through the proxy. Adoption discipline matches #3780/#1494: fires only
after a failed parse, adopted only when strictly more ranges result.

The wrap step is extracted from salvageCompressArgs into
tryBareContentWrap so both call sites run one implementation instead of
two regex copies (salvageCompressArgs behavior unchanged, pinned by the
#2580 suite).

New subpath export billion-context/salvage (repairCompressArgs):
provider plugins sitting between model and host can repair compress
arguments before the host's strict JSON.parse with the same ladder —
strict JSON in, null = leave as-is out (#1039 boundary preserved).
@ranxianglei ranxianglei changed the title fix: salvage bad-shape-A compress args at the execution layer, all lanes (#4889) [PR] fix: salvage bad-shape-A compress args at the execution layer, all lanes (#4889) Oct 27, 2026
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-27_bare-content-salvage-all-lanes (ba61c9a)

Latest build — install this exact version

npm install -g billion-context@0.0.7368-pr.4891.090de4e

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Recent builds (commit → version)

commit version build
ba61c9a billion-context@0.0.7368-pr.4891.090de4e build
9c39fa2 billion-context@0.0.7050-pr.4891.35fa2a9 build
612b02c billion-context@0.0.6844-pr.4891.387aca0 build
f198f79 billion-context@0.0.6391-pr.4891.afe2ea2 build
0b13709 billion-context@0.0.6360-pr.4891.cfcb850 build
697ce5e billion-context@0.0.6349-pr.4891.a2dad66 build
576ac9f billion-context@0.0.6343-pr.4891.6cde4a3 build
13d45a5 billion-context@0.0.6335-pr.4891.c284d13 build
4c6d761 billion-context@0.0.6334-pr.4891.95ac248 build

Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr4891.tgz
npm install -g package

Install instructions are refreshed on each push.

…graph gate (#4889)

check-test-import-graph.mjs keeps a hardcoded ENTRY_FILES list in sync with
tsup.config.ts (drift makes new entries look unreachable). The salvage PR
added src/salvage.ts to tsup.config.ts, package.json exports and
check-unused-exports.mjs but missed this gate's list, so the gate failed on
tests/issue4889-bare-content-execution-salvage.test.ts importing the module.
Master head is green — PR-specific failure, not inherited.
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

CI 修复完成:test-import-graph 红已修,新 head 13d45a5,等 CI 重跑。无需设计决策(单行守卫脚本登记修复),未开 decision request。

1. 是什么问题:CI 门禁 test-import-graph 报「测试文件导入了不在生产导入图里的模块」——本 PR 新增的 src/salvage.ts 被判为死代码,其测试 tests/issue4889-bare-content-execution-salvage.test.ts 因此违规。

2. 什么原因导致的:该门禁(scripts/check-test-import-graph.mjs)维护一份硬编码 ENTRY_FILES 列表,注释明确要求与 tsup.config.ts 的 entry 保持同步。本 PR 把新公开入口 ./salvage 登记到了三处(tsup.config.ts entry、package.json exports、check-unused-exports.mjs),唯独漏了这份列表 → 门禁把真实的生产入口当成了不可达模块。

3. 影响范围与严重程度:仅本 PR 的 CI 红;运行时行为零影响——repairCompressArgs 导出路径与 compress-tool 执行层 salvage 均不受这一行影响。已核实非继承性失败(master head 23158db 该 job 绿)。

4. 怎么修复的:在 check-test-import-graph.mjs 的 ENTRY_FILES 补一行 "src/salvage.ts"(与另两个守卫脚本及 tsup entry 对齐)。本地验证:门禁通过,production entries 18→19、reachable 263→264,其余白名单/retired-pin 判定输出不变。

5. 是否有回归问题:无。改动仅 CI 守卫脚本 +1 行,不触碰任何运行时代码;既有门禁语义(白名单、retired-pin 标记判定)逐条不变,本地输出与修复前除 salvage 条目外完全一致。

6. 是否破坏缓存:不涉及。

7. 是否改了设计方向:否。

8. 是否引入了新的配置:无。

已在 #4599(CI 总控)留一行结论。

@ranxianglei ranxianglei added bug Something isn't working dsh p2 scope/kernel labels Oct 27, 2026
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: qwen/qwen3.8-27b) Merge-guard sync done: merged origin/master into the PR branch — two clean merges (master moved mid-sync: 52cd3b8 → 0cf53c8), zero conflicts; pushed head 697ce5e. State after push: branch up-to-date with base (no longer behind), mergeable=true, mergeable_state=blocked awaiting the fresh CI run (6 success / 15 queued / 0 failures so far); local typecheck + build + targeted salvage tests (issue4889/1502/2580) all green before pushing.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: qwen/qwen3.8-27b) Sync round 3: merged origin/master e4cc685 (PR #4856/#4855 hermes compression-channel header) into the branch — zero conflicts (its only shared file src/plugin.ts is untouched by this PR's own commits) → pushed 0b13709; local verification green (typecheck/build/issue4889 suite + both import-graph gates); PR now mergeable=true against base e4cc685 with CI starting fresh (2 success / 19 queued / 0 failures so far).

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: qwen/qwen3.8-27b) Sync round 4: stale "behind" signal again — master tip is still e4cc685 and head 0b13709 already contains it (mergeable=true); no new push. CI on the current head is mid-run with zero failures so far (5 success / 6 skipped / 16 queued).

@koca2005

Copy link
Copy Markdown

来自 #4889 的报障方(DSH Desktop + 第三方 provider 插件 dsh-providers-hub / Jet Hub,provider buddy + deepseek-v4.1-flash)。我们就是 ② 要服务的那个 gateway,确认会接。下面是把你们挂的预构建包跑在本单逐字样本上的实测结果。

用 billion-context@0.0.6349-pr.4891.a2dad66 复核 ② 的行为(临时目录解包,未装进 profile)

输入 repairCompressArgs(raw) 判读
本单逐字坏样本(5713 字符,{"content": m00186–m00289 … 裸文本 + 42 个真实换行) 返回 5895 字符严格 JSON,JSON.parse 通过,content 为字符串(line-form 原样保留,没有强行转数组) 规则命中,与 issue 里的预期一致
合法 JSON {"content":"m00001–m00002 摘要\n正文"} null #1039 透传不变量成立
同一份合法 JSON 从 30% / 60% / 95% 处截断 null × 3 这条对我们最关键:gateway 侧要靠它把「截断」和「写坏」分开,截断仍走既有的 max-tokens,不会被误「修复」
单引号形态 {'content': 'm00186–m00289 …'} repaired #603 档也在这条入口里
与 compress 无关的坏 JSON({"command": "echo hi" ) null 不乱修,符合预期

(输入侧 JSON.parse 对坏样本是 Unexpected token 'm';输出侧 content 首 120 字符与原文逐字一致,截断位置/prob 都未改。)

gateway 侧集成计划(我们这半边,发版后做)

  1. 触发条件收窄到 classify === 'invalid'(结构闭合、括号配平、JSON.parse 仍失败)且工具名 == compress;truncated(参数半截/不配平)路径完全不动,继续报 max-tokens。
  2. 调 import('billion-context/salvage').repairCompressArgs(raw):非 null → 用修复后的串继续产出 tool_call(本轮不失败);null → 保持现在的 TOOL_ARGS_INVALID。
  3. 现装 0.1.208 没有 ./salvage 这个 subpath,所以会 try/catch 静默降级(导出不存在 = 退回今天的行为),不需要你们为兼容做任何事;升到含本 PR 的版本后自动生效。
  4. 发版后我们会回报实测:整轮丢失是否真的归零、命中/未命中比例、以及 rescue 前后的 usage 对照。

一条可选的 API 小建议(不阻塞、不影响合并)

repairCompressArgs 现在只在「内部救到 ≥1 range」时返回非 null,gateway 侧因此只能记「修好了」而无法知道救回 1 个还是 N 个 区间,日志/告警的信息量有限。如果改动成本低,返回 { json: string, ranges: number }(或加一个重载 repairCompressArgsDetailed)会更好;保持现状我们也能接。

再次确认:③(PREFERRED 翻成对象数组)被拒的理由我们认可——那是 kernel 决策、应单独拍板,不该塞进这个 PR。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: qwen/qwen3.8-27b) 感谢逐字样本的实测——五个 case 的判读(坏样本救回 / 合法 JSON 与截断一律 null / 单引号档命中 / 无关坏 JSON 不乱修)正是本 PR 要保证的行为,gateway 侧「只拦 classify=invalid 且工具名 compress、截断路径完全不动」的集成计划与我们这边的边界一致,可以按此推进。

关于那条 API 小建议:已采纳并合进本 PR(commit f198f79)。repairCompressArgs(raw, callId?) 的返回从 string | null 改为 { json: string; ranges: number } | null——json 就是原来返回的那个严格 JSON 串,ranges 是这次救回了几个区间。选改主函数而不是加 repairCompressArgsDetailed:这个 subpath 在已发布版本里还不存在(0.1.208 没有 ./salvage),发版前把形状定对没有兼容成本,留两个入口反而多一份要维护的表面。

⚠️ 一个对你们集成代码重要的点:你们实测的那份预构建包(a2dad66)还是旧的字符串返回;最终合入版本里拿到的是对象,请写 const r = repairCompressArgs(raw); if (r) { …用 r.json… 记 r.ranges },不要直接当字符串用。null 语义不变(合法 JSON / 截断 / 救不回 → null,走你们既有的 max-tokens / TOOL_ARGS_INVALID 路径)。回归测试已补:单区间样本断言 ranges=1,双区间样本断言 ranges=2。

③(PREFERRED 翻对象数组)维持拒绝不变——kernel 决策面,单独拍板。CI 正在新 head 上跑(mergeable=true,base e4cc685),绿了就可以合;合入后你们升版即自动生效,无需任何兼容处理。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working dsh p2 scope/kernel

Projects

None yet

2 participants