From 25acc2062e766e794338d4cd1ffb2b0106452a17 Mon Sep 17 00:00:00 2001 From: danielhanchen Date: Mon, 18 May 2026 21:42:31 +0000 Subject: [PATCH] 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. --- .../core/inference/anthropic_compat.py | 21 ++++++++++++++++++- studio/backend/routes/inference.py | 7 +++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/studio/backend/core/inference/anthropic_compat.py b/studio/backend/core/inference/anthropic_compat.py index 263718c540..8e1c939e55 100644 --- a/studio/backend/core/inference/anthropic_compat.py +++ b/studio/backend/core/inference/anthropic_compat.py @@ -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 = [] diff --git a/studio/backend/routes/inference.py b/studio/backend/routes/inference.py index 607245467c..6b4c2f0268 100644 --- a/studio/backend/routes/inference.py +++ b/studio/backend/routes/inference.py @@ -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", {})