compress 参数解析统一:前因后果与全栈收敛(tracking)
TL;DR :弱/本地模型 ~50% compress 参数不可解析。根因是 compress 参数解析在整条 stack 从来没有共享层 ——4 个实现各自为政,容错程度不一,同一份畸形输入全栈行为不一致。原 /compact 拦截器的 salvage 逻辑在 #74 删拦截器时蒸发。本 issue 记录前因后果 + 解决方案 + 现状 + 关联 ,供后来追查。
一、前因(起因)
弱/本地模型(vLLM/qwen3.8 等)是严格 JSON 参数生成的失败重灾区,实测 ~50% compress 参数不可解析 。
用户实测证据:billion-context proxy + vllm/qwen3.8 本地模型,2026-08-22 日志与截图([acp-compress-input] parsed 0 valid ranges. top keys: (空))。
失败形状(本仓库代码全部复现):
畸形形状
proxy
omp
pi
kernel rebuild
JSON 截断(提前收尾,括号不全)
静默丢弃
静默丢弃
错误串
静默丢弃
```json 围栏包裹
静默丢弃
静默丢弃
错误串
静默丢弃
尾逗号
静默丢弃
静默丢弃
错误串
静默丢弃
字符串内裸换行
静默丢弃
静默丢弃
错误串
静默丢弃
gateway stringify(vLLM)
部分
静默丢弃
错误串
静默丢全部块
2026-08-17,/compact 的容错解析系列在合并后 34 分钟内被整体删除 :
PR
commit
内容
#68
2992494
/compact 永不回退原生压缩——失败重试一次,再失败取消
#70
f71a9bc
parseSummary() 容错解析:① 剥 ```json 围栏;② JSON.parse 失败 → 纯 prose ≥50 字符直接作为 summary;③ 截断 JSON → 正则 /^\{\s*"summary"\s*:\s*"([\s\S]*)$/ 抽出残缺字符串值(≥50 字符)
#72
f573002
移除 summary 输出上限
#73
—
移除 summary 超时
#74
b41cf23
以「/compact 是宿主功能,ACP 经由 compress 工具拥有压缩」为由删除整个 /compact 拦截器 + src/auto-compress.ts(317 行) ,上述容错逻辑随之消失
#74 的架构决策本身没问题(等、单一职责),但容错解析的价值不依赖 /compact 通道 ——它应该随 compress 工具路径存活,而没有。能力跟着通道被删,而不是先提升到拥有它的层。
compress 工具参数解析在整条 stack 里从来没有共享层。现存 4 个各自为政的实现,容错程度各不相同:
实现
位置
对畸形输入
proxy
billion-context/src/util.ts:26-32 safeJsonParse + src/compress-tool.ts:54-83 parseCompressInput
catch → {} 静默吞;只打一行 parsed 0 valid ranges. top keys:(空 keys),模型收到 Compression FAILED: no valid ranges parsed
omp
billion-context-omp/src/messages.ts:105-126 compressToolArgs(host 流 + src/wire-fold.ts:318-321 共用)
JSON.parse catch → return null 静默丢弃,无任何日志
pi
billion-context-pi/src/compress-tool.ts:75-94 normalizeRanges
返回可行动错误串并 throw(唯一给模型自我纠正机会的,但只给机会不修数据)
kernel rebuild
acp-kernel/src/rebuild.ts:63-107 extractRanges
JSON.parse catch → continue 静默丢弃(fork-recovery 路径)
4. 新发现:kernel 侧 vLLM fork-recovery 缺口(超出 #121 描述)
src/rebuild.ts 的 extractRanges 只接受 Array.isArray(content) ,不支持 JSON-string content。vLLM 宿主会把整个 tool 参数 stringify(billion-context#176 ),此场景下 fork-recovery(session 恢复/重建)静默丢失全部压缩块 ——而 proxy 的 parseCompressInput 接受 JSON-string content。同一协议四个实现再次漂移。
二、后果(影响)
弱模型 compress 调用 ~50% 失败,模型收到 Compression FAILED: no valid ranges parsed 或静默无响应 → 上下文无法压缩 → 长会话 token 膨胀。
同一协议四个实现漂移,维护成本高,修复一处其他三处不复利。
vLLM fork-recovery 静默丢块(数据丢失风险 )。
三、解决方案
新增 parseCompressArgs(input: unknown, opts?: { callId?: string }) → { ranges: CompressRangeSpec[]; diagnostics }:
严格 JSON(对象,或整体被 stringify 的 JSON 串)/ 一层 double-stringification
剥 ```json 围栏 / 尾逗号 / 字符串字面量内裸换行 → 转义
截断数组 salvage :按字符串状态/括号深度 walk 原始文本,提取所有完整的顶层条目逐一解析 + 校验——能救多少救多少,不整份丢弃,partial 条目绝不发明
JSON-string content(vLLM #176 形状)
名字变体:startId / startRef / messageId(end 侧同理)
顶层单 range fallback + 顶层 topic/summaryMaxChars fallback (per-entry 优先)
diagnostics 是数据不是日志 :{ ok, kind, rawPrefix(≤800), length, keys, invalidItems },kind ∈ ok | empty-input | not-object | missing-content | content-not-array | malformed-json | truncated | no-valid-ranges。内核不打印日志(零 I/O 边界),adapter 决定怎么用(落盘 / 回喂模型 / UI 提示)
rebuild.ts 换用——顺带修 vLLM fork-recovery 缺口
三 adapter 收敛(消费 kernel diagnostics 数据)
仓库
改动
收益
billion-context (proxy)
parseCompressInput/safeJsonParse → kernel parseCompressArgs;diagnostics → loggerLog;删有害的 safeJsonParse 预解析 (截断串→{} 丢 salvage,改传 raw string)
~50% 失败率全在该链;salvage 后大量输入不再是失败
billion-context-omp
compressToolArgs → kernel parser;diagnostics → debug.event;live tool 路径不动 (typebox 类型化 + issue #47 错误契约)
消灭静默丢弃;wire-fold 路径同步受益
billion-context-pi
normalizeRanges → kernel parser;保留 throw 契约 (pi isError 依赖 throw);describeDiagnostics 用原始 content 类型区分 vLLM 串化形状
行为对齐 + 单一数据源
留 adapter(不进内核) :raw args 协议提取(openai SSE / responses / anthropic tool_use / omp stream / wire / pi host-parsed)、失败留证落点、失败回喂模型、nudge 注入点 + 提示文本、循环/轮次控制。omp host 侧 live tool-args 解析在 opencode 宿主内,三个 adapter 仓库都够不着,需宿主侧修。
重试策略协调 :不另起状态机。#73 (mid-band cadence + rejection escalation)已提议 kernel 侧 compressRejects: number + rejectionFeedback() helper。parse 失败重试 nudge 与 kernel reject 升级共用同一 failure-streak 机制 :streak 统计所有 compress 失败(reject + parse),adapter 按失败来源选择反馈文本。本 PR 的 parser 只产出数据(diagnostics + salvage ranges),retry 策略随 #73 落地,避免双归属。
四、时间线
五、现状(2026-08-22)
✅ 内核 PR feat: add parseCompressArgs — lenient compress-arg parsing with diagnostics #111 OPEN (2 commits:2d58a4b lenient parsing + diagnostics、70d46f2 top-level single range + topic/summaryMaxChars fallbacks),BEHIND master 需 rebase
⏸️ 三 adapter 本地改好未提交 (等内核发 v0.0.33 后 bump 版本再提交——现在提交会因 package.json pin 旧 0.0.32 导致 fresh install 拉到无 parseCompressArgs 的内核而 import 报错)
✅ 测试 1607 全绿 (内核 429 + proxy 512 + omp 257 + pi 409)
📄 影响面报告:/home/dog/tmp/impact-report.md
六、合并顺序
合并内核 PR #111 → 发 v0.0.33
三 adapter 各自 package.json bump 到 0.0.33 + 建分支 + 提交 + 推 + 开 PR
统一 review
七、关联
八、Acceptance
compress 参数解析统一:前因后果与全栈收敛(tracking)
一、前因(起因)
1. 用户侧症状 — billion-context-omp#121
[acp-compress-input] parsed 0 valid ranges. top keys: (空))。2. salvage 逻辑的蒸发 — #68/#70/#72/#73 → #74
2026-08-17,
/compact的容错解析系列在合并后 34 分钟内被整体删除:2992494f71a9bcparseSummary()容错解析:① 剥 ```json 围栏;② JSON.parse 失败 → 纯 prose ≥50 字符直接作为 summary;③ 截断 JSON → 正则/^\{\s*"summary"\s*:\s*"([\s\S]*)$/抽出残缺字符串值(≥50 字符)f573002b41cf23/compact拦截器 +src/auto-compress.ts(317 行),上述容错逻辑随之消失#74 的架构决策本身没问题(等、单一职责),但容错解析的价值不依赖 /compact 通道——它应该随 compress 工具路径存活,而没有。能力跟着通道被删,而不是先提升到拥有它的层。
3. 结构性问题 — acp-kernel#108
compress 工具参数解析在整条 stack 里从来没有共享层。现存 4 个各自为政的实现,容错程度各不相同:
billion-context/src/util.ts:26-32safeJsonParse+src/compress-tool.ts:54-83parseCompressInputcatch → {}静默吞;只打一行parsed 0 valid ranges. top keys:(空 keys),模型收到Compression FAILED: no valid ranges parsedbillion-context-omp/src/messages.ts:105-126compressToolArgs(host 流 +src/wire-fold.ts:318-321共用)JSON.parsecatch →return null静默丢弃,无任何日志billion-context-pi/src/compress-tool.ts:75-94normalizeRangesacp-kernel/src/rebuild.ts:63-107extractRangesJSON.parsecatch →continue静默丢弃(fork-recovery 路径)4. 新发现:kernel 侧 vLLM fork-recovery 缺口(超出 #121 描述)
src/rebuild.ts的extractRanges只接受Array.isArray(content),不支持 JSON-string content。vLLM 宿主会把整个 tool 参数 stringify(billion-context#176),此场景下 fork-recovery(session 恢复/重建)静默丢失全部压缩块——而 proxy 的parseCompressInput接受 JSON-string content。同一协议四个实现再次漂移。二、后果(影响)
Compression FAILED: no valid ranges parsed或静默无响应 → 上下文无法压缩 → 长会话 token 膨胀。三、解决方案
内核 — acp-kernel PR #111
新增
parseCompressArgs(input: unknown, opts?: { callId?: string }) → { ranges: CompressRangeSpec[]; diagnostics }:startId/startRef/messageId(end 侧同理)topic/summaryMaxCharsfallback(per-entry 优先){ ok, kind, rawPrefix(≤800), length, keys, invalidItems },kind ∈ ok | empty-input | not-object | missing-content | content-not-array | malformed-json | truncated | no-valid-ranges。内核不打印日志(零 I/O 边界),adapter 决定怎么用(落盘 / 回喂模型 / UI 提示)rebuild.ts换用——顺带修 vLLM fork-recovery 缺口三 adapter 收敛(消费 kernel diagnostics 数据)
parseCompressInput/safeJsonParse→ kernelparseCompressArgs;diagnostics →loggerLog;删有害的safeJsonParse预解析(截断串→{}丢 salvage,改传 raw string)compressToolArgs→ kernel parser;diagnostics →debug.event;live tool 路径不动(typebox 类型化 + issue #47 错误契约)normalizeRanges→ kernel parser;保留 throw 契约(piisError依赖 throw);describeDiagnostics用原始content类型区分 vLLM 串化形状留 adapter(不进内核):raw args 协议提取(openai SSE / responses / anthropic tool_use / omp stream / wire / pi host-parsed)、失败留证落点、失败回喂模型、nudge 注入点 + 提示文本、循环/轮次控制。omp host 侧 live tool-args 解析在 opencode 宿主内,三个 adapter 仓库都够不着,需宿主侧修。
重试策略协调:不另起状态机。#73(mid-band cadence + rejection escalation)已提议 kernel 侧
compressRejects: number+rejectionFeedback()helper。parse 失败重试 nudge 与 kernel reject 升级共用同一 failure-streak 机制:streak 统计所有 compress 失败(reject + parse),adapter 按失败来源选择反馈文本。本 PR 的 parser 只产出数据(diagnostics+ salvage ranges),retry 策略随 #73 落地,避免双归属。四、时间线
b41cf23) 删除整个拦截器,salvage 蒸发parseCompressArgs)五、现状(2026-08-22)
2d58a4blenient parsing + diagnostics、70d46f2top-level single range + topic/summaryMaxChars fallbacks),BEHIND master 需 rebasepackage.jsonpin 旧 0.0.32 导致 fresh install 拉到无parseCompressArgs的内核而 import 报错)/home/dog/tmp/impact-report.md六、合并顺序
v0.0.33package.jsonbump 到0.0.33+ 建分支 + 提交 + 推 + 开 PR七、关联
git show f71a9bc:src/auto-compress.ts的parseSummary()b41cf23(feat: wire/ submodule — protocol codecs (anthropic/openai/responses) + content-hash message identity, extracted from billion-context proxy #74)八、Acceptance
parseCompressArgs通过畸形语料:截断 / 围栏 / 尾逗号 / 裸换行 / JSON-string content / vLLM content 串 / 名字变体 / 截断 salvage / garbage / 空输入 / 缺 content / 无效项计数 / callId 盖章rebuildCompressionState在 vLLM JSON-string content 下不再静默丢弃(fork-recovery 回归测试)npm test/npm run typecheck/npm run build全绿