studio: reset Anthropic adapter cursor on auto-continue boundary

Codex P2 on #5549 flagged that the auto-continue branch yields only
a `{"type":"status","text":""}` event between turns; the Anthropic
streaming emitter (`AnthropicStreamEmitter`) and the non-streaming
tool path (`_anthropic_tool_non_streaming`) both ignore `status`
events, so their cumulative-text cursor still holds the previous
turn's full length when the continuation starts streaming. Shorter
continuations get dropped entirely and longer ones lose their prefix.

Treat the empty-text status as an auto-continue boundary in both
paths:

  - `AnthropicStreamEmitter`: close any open text block, open a fresh
    one (matches the `tool_end` reset pattern), and clear `_prev_text`.
  - `_anthropic_tool_non_streaming`: clear `prev_text` so the next
    `content` event's diff baseline is empty.

Non-empty status events (tool progress text) keep their existing
no-op semantics.
This commit is contained in:
danielhanchen 2026-05-18 21:42:31 +00:00
commit 25acc2062e
2 changed files with 27 additions and 1 deletions

View file

@ -253,9 +253,28 @@ class AnthropicStreamEmitter:
elif etype == "metadata":
self._usage = event.get("usage", {})
return []
# status events — no Anthropic equivalent
elif etype == "status" and not event.get("text"):
# Auto-continue boundary marker emitted by
# generate_chat_completion_with_tools — the next "content"
# event resets to a fresh cumulative baseline, so close any
# open text block and clear the diff cursor. Without this
# the next continuation gets diffed against the previous
# turn's length (shorter continuations are dropped, longer
# ones lose their prefix).
return self._handle_boundary()
# Other status events (tool progress text) have no Anthropic
# equivalent.
return []
def _handle_boundary(self) -> list[str]:
events = []
if self._text_block_open:
events.append(self._close_block())
self.block_index += 1
events.extend(self._open_text_block())
self._prev_text = ""
return events
def finish(self, stop_reason: str = "end_turn") -> list[str]:
"""Close any open block and emit message_delta + message_stop."""
events = []

View file

@ -4370,6 +4370,13 @@ async def _anthropic_tool_non_streaming(run_gen, message_id, model_name):
)
elif etype == "tool_end":
prev_text = ""
elif etype == "status" and not event.get("text"):
# Auto-continue boundary marker: the next content event
# restarts the cumulative diff baseline, so reset prev_text
# the same way tool_end does. Without this a shorter
# continuation gets dropped entirely and a longer one
# loses its prefix.
prev_text = ""
elif etype == "metadata":
usage = event.get("usage", {})