diff --git a/src/providers/compatible.rs b/src/providers/compatible.rs index 0efef5d..e24f961 100644 --- a/src/providers/compatible.rs +++ b/src/providers/compatible.rs @@ -241,9 +241,9 @@ struct ResponsesContent { text: Option, } -// ═══════════════════════════════════════════════════════════════ +// --------------------------------------------------------------- // Streaming support (SSE parser) -// ═══════════════════════════════════════════════════════════════ +// --------------------------------------------------------------- /// Server-Sent Event stream chunk for OpenAI-compatible streaming. #[derive(Debug, Deserialize)] @@ -291,7 +291,9 @@ fn parse_sse_line(line: &str) -> StreamResult> { // Extract content from delta if let Some(choice) = chunk.choices.first() { if let Some(content) = &choice.delta.content { - return Ok(Some(content.clone())); + if !content.is_empty() { + return Ok(Some(content.clone())); + } } // Fallback to reasoning_content for thinking models if let Some(reasoning) = &choice.delta.reasoning_content { @@ -976,9 +978,9 @@ mod tests { ); } - // ══════════════════════════════════════════════════════════ + // ---------------------------------------------------------- // Custom endpoint path tests (Issue #114) - // ══════════════════════════════════════════════════════════ + // ---------------------------------------------------------- #[test] fn chat_completions_url_standard_openai() { @@ -1123,9 +1125,9 @@ mod tests { ); } - // ══════════════════════════════════════════════════════════ + // ---------------------------------------------------------- // Provider-specific endpoint tests (Issue #167) - // ══════════════════════════════════════════════════════════ + // ---------------------------------------------------------- #[test] fn chat_completions_url_zai() { @@ -1174,9 +1176,9 @@ mod tests { assert!(result.is_ok()); } - // ══════════════════════════════════════════════════════════ + // ---------------------------------------------------------- // Reasoning model fallback tests (reasoning_content) - // ══════════════════════════════════════════════════════════ + // ---------------------------------------------------------- #[test] fn reasoning_content_fallback_when_content_empty() { @@ -1217,7 +1219,7 @@ mod tests { #[test] fn reasoning_content_both_absent_returns_empty() { - // Neither content nor reasoning_content — returns empty string + // Neither content nor reasoning_content - returns empty string let json = r#"{"choices":[{"message":{}}]}"#; let resp: ApiChatResponse = serde_json::from_str(json).unwrap(); let msg = &resp.choices[0].message; @@ -1234,9 +1236,9 @@ mod tests { assert_eq!(msg.effective_content(), "Hello from Venice!"); } - // ══════════════════════════════════════════════════════════ + // ---------------------------------------------------------- // SSE streaming reasoning_content fallback tests - // ══════════════════════════════════════════════════════════ + // ---------------------------------------------------------- #[test] fn parse_sse_line_with_content() { @@ -1259,6 +1261,14 @@ mod tests { assert_eq!(result, Some("real answer".to_string())); } + #[test] + fn parse_sse_line_with_empty_content_falls_back_to_reasoning_content() { + let line = + r#"data: {"choices":[{"delta":{"content":"","reasoning_content":"thinking..."}}]}"#; + let result = parse_sse_line(line).unwrap(); + assert_eq!(result, Some("thinking...".to_string())); + } + #[test] fn parse_sse_line_done_sentinel() { let line = "data: [DONE]"; diff --git a/src/providers/openai.rs b/src/providers/openai.rs index 888eaa4..6474c01 100644 --- a/src/providers/openai.rs +++ b/src/providers/openai.rs @@ -460,9 +460,12 @@ mod tests { #[test] fn response_with_unicode() { - let json = r#"{"choices":[{"message":{"content":"こんにちは 🦀"}}]}"#; + let json = r#"{"choices":[{"message":{"content":"Hello \u03A9"}}]}"#; let resp: ChatResponse = serde_json::from_str(json).unwrap(); - assert_eq!(resp.choices[0].message.effective_content(), "こんにちは 🦀"); + assert_eq!( + resp.choices[0].message.effective_content(), + "Hello \u{03A9}" + ); } #[test] @@ -483,9 +486,9 @@ mod tests { assert!(result.is_ok()); } - // ══════════════════════════════════════════════════════════ + // ---------------------------------------------------------- // Reasoning model fallback tests (reasoning_content) - // ══════════════════════════════════════════════════════════ + // ---------------------------------------------------------- #[test] fn reasoning_content_fallback_empty_content() {