From bca24921e2660574c4ff2cbf178a0784d4b665a8 Mon Sep 17 00:00:00 2001 From: Stranmor Date: Fri, 13 Mar 2026 19:58:53 +0300 Subject: [PATCH] fix: prevent TransferEncodingError by clean SSE stream termination MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When SSE streams encounter errors (timeout, upstream connection drop), the Gemini handler uses yield Err(...) which causes hyper to abort HTTP/1.1 chunked transfer encoding without sending the final 0\r\n\r\n terminator. HTTP clients that validate chunk boundaries (aiohttp, httpx) then throw TransferEncodingError. Fix: convert stream errors to Ok(Bytes) SSE content (matching the pattern already used by the OpenAI handler), so the stream terminates cleanly and hyper sends the proper chunked encoding terminator. Affected path: Gemini streaming handler Already fixed: OpenAI streaming handler (errors wrapped as Ok) Partially mitigated: Claude handler (.map wrapper converts Err→Ok) --- src-tauri/src/proxy/handlers/gemini.rs | 8 +++++++- src-tauri/src/proxy/mappers/claude/mod.rs | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src-tauri/src/proxy/handlers/gemini.rs b/src-tauri/src/proxy/handlers/gemini.rs index 9fa2c2a33..77c7c0e73 100644 --- a/src-tauri/src/proxy/handlers/gemini.rs +++ b/src-tauri/src/proxy/handlers/gemini.rs @@ -343,7 +343,13 @@ pub async fn handle_generate( Some(Ok(b)) => b, Some(Err(e)) => { error!("[Gemini-SSE] Connection error: {}", e); - yield Err(format!("Stream error: {}", e)); + let error_json = serde_json::json!({ + "error": { + "message": format!("Stream error: {}", e), + "type": "stream_error" + } + }); + yield Ok::(Bytes::from(format!("data: {}\n\n", error_json))); break; } None => break, diff --git a/src-tauri/src/proxy/mappers/claude/mod.rs b/src-tauri/src/proxy/mappers/claude/mod.rs index 9ae853006..c90c04589 100644 --- a/src-tauri/src/proxy/mappers/claude/mod.rs +++ b/src-tauri/src/proxy/mappers/claude/mod.rs @@ -82,7 +82,13 @@ where } } Err(e) => { - yield Err(format!("Stream error: {}", e)); + let error_json = serde_json::json!({ + "error": { + "message": format!("Stream error: {}", e), + "type": "stream_error" + } + }); + yield Ok::(Bytes::from(format!("data: {}\n\n", error_json))); break; } }