| 716 | } |
| 717 | |
| 718 | async fn chat_legacy(&self, request: ChatRequest) -> Result<ChatResponse, ProviderError> { |
| 719 | let url = Self::chat_completions_url(self.config.base_url.as_deref()); |
| 720 | let mut request_body = Self::build_request_body(&request)?; |
| 721 | |
| 722 | // Ensure stream is disabled for non-streaming path. The caller may have |
| 723 | // set stream=true on the ChatRequest (e.g. prompt loop), but chat_legacy |
| 724 | // expects a single JSON response, not SSE chunks. |
| 725 | if let Value::Object(obj) = &mut request_body { |
| 726 | obj.remove("stream"); |
| 727 | obj.remove("stream_options"); |
| 728 | } |
| 729 | |
| 730 | let mut req_builder = self |
| 731 | .client |
| 732 | .post(&url) |
| 733 | .header("Authorization", format!("Bearer {}", self.config.api_key)) |
| 734 | .header("Content-Type", "application/json"); |
| 735 | |
| 736 | if let Some(org) = &self.config.organization { |
| 737 | req_builder = req_builder.header("OpenAI-Organization", org); |
| 738 | } |
| 739 | |
| 740 | let response = req_builder |
| 741 | .json(&request_body) |
| 742 | .send() |
| 743 | .await |
| 744 | .map_err(|e| ProviderError::NetworkError(e.to_string()))?; |
| 745 | |
| 746 | if !response.status().is_success() { |
| 747 | let status = response.status(); |
| 748 | let body = response.text().await.unwrap_or_default(); |
| 749 | return Err(ProviderError::ApiError(format!("{}: {}", status, body))); |
| 750 | } |
| 751 | |
| 752 | let body = response |
| 753 | .text() |
| 754 | .await |
| 755 | .map_err(|e| { |
| 756 | let mut msg = e.to_string(); |
| 757 | let mut source = std::error::Error::source(&e); |
| 758 | while let Some(cause) = source { |
| 759 | msg.push_str(": "); |
| 760 | msg.push_str(&cause.to_string()); |
| 761 | source = cause.source(); |
| 762 | } |
| 763 | ProviderError::ApiError(msg) |
| 764 | })?; |
| 765 | |
| 766 | // Some OpenAI-compatible providers (e.g. ZhipuAI) return SSE-formatted |
| 767 | // streaming data even for non-streaming requests. Detect and reassemble. |
| 768 | let raw: RawChatResponse = if body.trim_start().starts_with("data:") { |
| 769 | Self::reassemble_sse_chunks(&body)? |
| 770 | } else { |
| 771 | serde_json::from_str(&body).map_err(|e| { |
| 772 | let preview = if body.len() > 500 { |
| 773 | format!("{}...", &body[..500]) |
| 774 | } else { |
| 775 | body.clone() |