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!(