From db7d5ddd19645a7889c157510ccffc82a8f91db9 Mon Sep 17 00:00:00 2001 From: Bobo Date: Mon, 27 Jul 2026 20:07:31 +0800 Subject: [PATCH] Skip `[DONE]` stream terminator in `anthropic` and `google_ai` SSE parsers (#61627) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem When `api_url` points to a proxy or gateway that appends `data: [DONE]` to the SSE stream, every streaming completion fails with: ``` error deserializing Anthropic API response: expected value at line 1 column 2 ``` In the UI this surfaces as **"error deserializing Anthropic API response — Retrying (Attempt 3 of 3)"** followed by **"Connection Interrupted"**, even though the proxy returned a well-formed stream and the model's full response was already delivered before the terminator. The failure is 100% reproducible on multi-turn tool-use conversations (Zed reads the stream to completion after each turn, so it always reaches the trailing `[DONE]`) and intermittent on single-turn conversations (depending on whether the connection closes before the line is read). The same class of error appears in the `google_ai` crate as `Error parsing JSON: ... "[DONE]"`. Neither side is at fault here. Zed's current parser is a correct implementation of the Anthropic and Gemini specs, which terminate streams by connection close and never emit `[DONE]`; at the same time, appending a `[DONE]` terminator is a common, legitimate convention among SSE gateways. This PR is a compatibility improvement that makes Zed robust to that widely-used stream shape, so streaming works whether or not the upstream emits the terminator. ## Why Zed crashes but the Anthropic SDK does not The Anthropic TypeScript and Python SDKs handle this correctly **without** any explicit `[DONE]` check. Their SSE parser uses a two-layer architecture: 1. **SSE framing layer** — parses `event:` / `data:` fields into `{event, data}` objects without inspecting the data payload. 2. **Event dispatch layer** — only calls `JSON.parse` on events whose `event` name is in a known whitelist (`message_start`, `content_block_delta`, etc.). A bare `data:` line with no preceding `event:` line yields `event: null`, which is not in the whitelist and is silently ignored. So when a proxy sends `data: [DONE]`, the SDK produces `{event: null, data: "[DONE]"}`, the dispatch layer drops it, and `JSON.parse` is never run on `[DONE]`. Claude Code and Cline both use the Anthropic SDK under the hood, so they inherit this behavior and never crash on `[DONE]`. Zed's `anthropic` and `google_ai` crates use a simpler single-layer parser: strip the `data:` prefix, then unconditionally `serde_json::from_str` the remainder. There is no event-name dispatch, so `[DONE]` goes straight into the JSON parser — `[` looks like an array start, `D` is not a valid value, serde reports `expected value at line 1 column 2`, and the stream dies. ## Fix Add a one-line guard after prefix stripping: if the trimmed payload is `[DONE]`, return `None` from the `filter_map` closure. This is a lightweight equivalent of the SDK's whitelist-ignore behavior — same result (silently skip the terminator), without restructuring the parser into a full event-name dispatch architecture. Two commits, one per crate: 1. `anthropic` — guard in `stream_completion_with_rate_limit_info` 2. `google_ai` — identical guard in `stream_generate_content` ## Scope The other ten SSE-based providers in this repo (`open_ai`, `deepseek`, `mistral`, `open_router`, `lmstudio`, `llama_cpp`, `copilot_chat`) already handle `[DONE]` because their upstream specs (OpenAI-compatible) define it as a required stream terminator. `ollama` (NDJSON) and `bedrock` (AWS SDK event stream) do not use SSE. After this PR, every SSE parser in Zed handles `[DONE]` without error. ## Safety - Official Anthropic and Gemini APIs never send `[DONE]`; the guard is a no-op on direct connections. - `line.trim() == "[DONE]"` handles both `data: [DONE]` and `data:[DONE]`. - Returning `None` drops the line silently; the stream concludes on the next EOF as usual. Release Notes: - Fixed streaming completions failing with "error deserializing Anthropic API response" when the SSE endpoint appends a `[DONE]` stream terminator (affects custom `api_url` proxies for Anthropic and Google Gemini) --- crates/anthropic/src/anthropic.rs | 9 +++++++++ crates/google_ai/src/google_ai.rs | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/crates/anthropic/src/anthropic.rs b/crates/anthropic/src/anthropic.rs index c499e79e12..cc33e4f06a 100644 --- a/crates/anthropic/src/anthropic.rs +++ b/crates/anthropic/src/anthropic.rs @@ -560,6 +560,15 @@ pub async fn stream_completion_with_rate_limit_info( .strip_prefix("data: ") .or_else(|| line.strip_prefix("data:"))?; + // Some proxies and gateways append `data: [DONE]` as a + // stream-termination sentinel (an OpenAI convention). + // It is not part of the Anthropic streaming spec and is + // not valid JSON, so skip it to avoid a spurious + // deserialization error. + if line.trim() == "[DONE]" { + return None; + } + match serde_json::from_str(line) { Ok(response) => Some(Ok(response)), Err(error) => Some(Err(AnthropicError::DeserializeResponse(error))), diff --git a/crates/google_ai/src/google_ai.rs b/crates/google_ai/src/google_ai.rs index 9cf4fdc494..71c0d4e031 100644 --- a/crates/google_ai/src/google_ai.rs +++ b/crates/google_ai/src/google_ai.rs @@ -42,6 +42,15 @@ pub async fn stream_generate_content( match line { Ok(line) => { if let Some(line) = line.strip_prefix("data: ") { + // Some proxies and gateways append `data: [DONE]` as a + // stream-termination sentinel (an OpenAI convention). + // Google's streaming spec does not include this sentinel, + // but we tolerate it here for the same reason as the + // anthropic crate: proxies that inject it would otherwise + // cause a spurious JSON deserialization error. + if line.trim() == "[DONE]" { + return None; + } match serde_json::from_str(line) { Ok(response) => Some(Ok(response)), Err(error) => Some(Err(anyhow!(format!(