fix: strip model-echoed render tags from tool-call arguments on every wire path - #934
ranxianglei wants to merge 1 commit into
Conversation
[bot] 🏷 Reviewing PR #934 now. Initial state:
Per repo convention (§7.2: rebase to CURRENT master before claiming mergeable), I'm rebasing the branch onto |
8695aee to
a757033
Compare
📦 Built Package ArtifactBranch: Option A — Install from npm PR tag (recommended)npm install -g billion-context@pr-934Each push to this PR publishes a new version under the Option B — Download artifact
tar xzf billion-context-pr934.tgz
npm install -g packageInstall instructions are refreshed on each push. |
|
This branch now conflicts with master and can't merge as-is. A clean rebase preserving the full change (plus conflict resolution: master's done-family split in |
Problem (#933)
Verified chain: kernel
renderMessageinjects<acp …>mNNNNN</acp>refs into message text (text-only) → model echoes them in output → response-side stripping covered only text fields (content/reasoning/thinking) → tags echoed into tool-call arguments passed through verbatim on every wire format, entered client session storage, and rendered raw in the host TUI. The issue's "move tags to a header channel" main fix is infeasible — the model must read refs from prompt text to cite them in compress calls — so the actionable fix is making the response-side strip structurally complete.Changes
src/loop/tag-echo-filter.ts): new exportsstripAcpTagsDeep/findAcpLikeTagSnippetsDeepwalk every string leaf of any payload object. Non-streaming strips now also cover tool args: OpenAIfunction.arguments, Anthropictool_use.input(+ streamingpartial_json), Responses itemargumentsand reasoningsummary[].text.src/plugin.ts, opencode/pi/omp mode): per-tool-call argument streams for OpenAIdelta.tool_calls[].function.argumentsand Anthropicinput_json_delta.partial_json; per-item streams for Responsesfunction_call_arguments.delta/reasoning_summary_text.delta, plus one-shot strip on.donepayloads. Held mid-tag tails flush before done-family/EOF/error events. Also fixed a real bug found while writing the tests: Responses fc delta fragments live indelta, notarguments— the old-style field read would have passed them through raw.src/loop/adapter-*.ts): strip buffered-complete arguments at emission time. adapter-openai additionally reconstructs fragmented raw replays when accumulated args are tag-tainted (a tag spanning fragment boundaries cannot be cut by per-fragment stripping).src/server.ts):ACP_RENDER_NONE=1was previously honored only on the Responses path + compact rebuilds — exactly the wires opencode 插件模式下 render tag 注入消息正文,模型回显漏网后原样渲染进 TUI(元数据与正文同层问题) #933 hit (chat paths) ignored it. Now honored on all paths (OpenAI chat, Anthropic, count_tokens, [bug] ACP 压缩轮以 stop 终止任务:模型输出压缩摘要后零 toolCalls,需用户手动「继续」(#361 同族) #422 fold-refresh re-request). Documented in CONFIGURATION.md.tests/fix-933-tag-echo-toolargs.test.ts, 6 cases): deep-scan invariant over every carrier field × 3 protocols; mid-tag-split streaming through each pipe (tag broken across fragments); non-streaming JSON × 3 protocols; faithful-client replay loop (assistant turn rebuilt from the clean client stream ⇒ next-turn request carries no tags).Verification
npm run typecheck✓npm test: 1674 pass / 0 fail / 2 skipped (gated e2e) ✓npm run build✓Both compression modes reasoned: plugin mode (carrier = agent's compress tool call) gets the pipe-level strips; proxy mode (carrier = acp_summary) gets the adapter-level strips.
Fixes #933