Skip to content

fix(anthropic): forward round-2 stream with proper framing (vertical-text bug) - #126

Merged
ranxianglei merged 2 commits into
masterfrom
2026-08-12_fix-anthropic-round2-stream
Aug 12, 2026
Merged

ranxianglei merged 2 commits into
masterfrom
2026-08-12_fix-anthropic-round2-stream

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Problem

In ZCode (and any Anthropic client), after a proxy-tool re-request (e.g. acp_status), the model's round-2 answer rendered as vertical text — each ~2-char streaming chunk on its own line:

上下

文

状态

Root cause

PR #122 made round-2 streaming call emitText(ev.delta) per delta (core.ts:190). For the Anthropic adapter, emitText → buildTextBlock wraps each call in a full content block:

content_block_start (index N)
content_block_delta  (index N, delta)
content_block_stop   (index N)

So every tiny delta became its own complete block → the client rendered each block as a separate paragraph → vertical text. (OpenAI/Responses emitText only emits a delta chunk, so they were unaffected.)

The adapter also gated content_block_start/content_block_stop behind round === 1 (adapter-anthropic.ts:166/178/212), so round 2 emitted zero framing.

Fix

  1. adapter-anthropic.ts: forward round-2 content_block_start/delta/stop with new client indices (remove the round === 1 gate; use firstRoundOnly: round === 1). Round-2 text deltas now carry raw (the actual remapped content_block_delta event).
  2. core.ts: yield ev.raw in all rounds when the adapter provides it (was round === 1 && ev.raw). OpenAI/Responses round-2 (no raw) still falls back to emitText. textProtocol (marker) path unchanged.

Result: round 2 now streams ONE content_block_start + many inline content_block_delta + ONE content_block_stop → proper Anthropic framing → text renders inline. message_start stays round-1-only (one message per response).

Tests

  • New tests/anthropic-round2-stream.test.ts (2 tests): round-2 deltas carry raw (pure delta, not a full block) + content_block_start/stop forwarded + message_start not re-emitted.
  • Full suite 325/325 pass (323 + 2). typecheck clean. build OK.

Verification

Captured round-2 raw HTTP shows the model answer is now a single text block with inline deltas (was N separate blocks).

Sisyphus and others added 2 commits August 12, 2026 21:08
…text bug)

Round-2 text after a proxy-tool re-request (e.g. acp_status) was emitted
via emitText(ev.delta) per delta. For Anthropic, emitText/buildTextBlock
wraps EACH call in a full content_block (start+delta+stop), so every ~2-char
delta became its own block → clients rendered each chunk on its own line
(vertical text). The adapter also gated content_block_start/stop behind
'round === 1', so round 2 had no framing at all.

Fix: forward round-2 content_block_start/delta/stop with new client indices
(remove the round===1 gate; firstRoundOnly: round===1), and have core.ts
yield ev.raw in ALL rounds (not just round 1) when the adapter provides it.
OpenAI/Responses round-2 (no raw) still falls back to emitText. textProtocol
(marker) path unchanged.

Regression test: round-2 deltas carry raw (pure delta, not a full block) +
content_block_start/stop forwarded + message_start not re-emitted.
@ranxianglei
ranxianglei merged commit 4cf1396 into master Aug 12, 2026
3 checks passed
This was referenced Aug 12, 2026
@ranxianglei
ranxianglei deleted the 2026-08-12_fix-anthropic-round2-stream branch October 15, 2026 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant