Reassemble SSE `data:` chunks (streaming format) into a single `RawChatResponse`. Some OpenAI-compatible providers return SSE even for non-streaming requests.
(body: &str)
| 786 | /// Reassemble SSE `data:` chunks (streaming format) into a single `RawChatResponse`. |
| 787 | /// Some OpenAI-compatible providers return SSE even for non-streaming requests. |
| 788 | fn reassemble_sse_chunks(body: &str) -> Result<RawChatResponse, ProviderError> { |
| 789 | let mut content = String::new(); |
| 790 | let mut reasoning = String::new(); |
| 791 | let mut finish_reason: Option<String> = None; |
| 792 | let mut usage: Option<RawUsage> = None; |
| 793 | // tool_calls keyed by index: (id, name, arguments) |
| 794 | let mut tool_calls: HashMap<u32, (Option<String>, Option<String>, String)> = HashMap::new(); |
| 795 | |
| 796 | for line in body.lines() { |
| 797 | let line = line.trim(); |
| 798 | if !line.starts_with("data:") { |
| 799 | continue; |
| 800 | } |
| 801 | let data = line[5..].trim(); |
| 802 | if data == "[DONE]" { |
| 803 | break; |
| 804 | } |
| 805 | let chunk: Value = match serde_json::from_str(data) { |
| 806 | Ok(v) => v, |
| 807 | Err(_) => continue, |
| 808 | }; |
| 809 | if let Some(choices) = chunk.get("choices").and_then(|c| c.as_array()) { |
| 810 | for choice in choices { |
| 811 | if let Some(delta) = choice.get("delta") { |
| 812 | if let Some(text) = delta.get("content").and_then(|v| v.as_str()) { |
| 813 | content.push_str(text); |
| 814 | } |
| 815 | // ZhipuAI uses "reasoning_content"; OpenAI uses "reasoning_text" |
| 816 | let reasoning_val = delta |
| 817 | .get("reasoning_content") |
| 818 | .or_else(|| delta.get("reasoning_text")) |
| 819 | .and_then(|v| v.as_str()); |
| 820 | if let Some(r) = reasoning_val { |
| 821 | reasoning.push_str(r); |
| 822 | } |
| 823 | if let Some(tcs) = delta.get("tool_calls").and_then(|v| v.as_array()) { |
| 824 | for tc in tcs { |
| 825 | let idx = tc.get("index").and_then(|v| v.as_u64()).unwrap_or(0) as u32; |
| 826 | let entry = tool_calls.entry(idx).or_insert((None, None, String::new())); |
| 827 | if let Some(id) = tc.get("id").and_then(|v| v.as_str()) { |
| 828 | entry.0 = Some(id.to_string()); |
| 829 | } |
| 830 | if let Some(func) = tc.get("function") { |
| 831 | if let Some(name) = func.get("name").and_then(|v| v.as_str()) { |
| 832 | entry.1 = Some(name.to_string()); |
| 833 | } |
| 834 | if let Some(args) = func.get("arguments").and_then(|v| v.as_str()) { |
| 835 | entry.2.push_str(args); |
| 836 | } |
| 837 | } |
| 838 | } |
| 839 | } |
| 840 | } |
| 841 | if let Some(fr) = choice.get("finish_reason").and_then(|v| v.as_str()) { |
| 842 | finish_reason = Some(fr.to_string()); |
| 843 | } |
| 844 | } |
| 845 | } |