1use anyhow::Result;
2use futures_util::StreamExt;
3use reqwest::Client;
4use serde::{Deserialize, Serialize};
5use std::time::Duration;
6
7use super::template_processor::TemplateProcessor;
8
9#[derive(Debug, Serialize)]
10pub struct ChatRequest {
11 pub model: String,
12 pub messages: Vec<Message>,
13 pub max_tokens: Option<u32>,
14 pub temperature: Option<f32>,
15 #[serde(skip_serializing_if = "Option::is_none")]
16 pub tools: Option<Vec<Tool>>,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 pub stream: Option<bool>,
19}
20
21#[derive(Debug, Serialize)]
23pub struct ChatRequestWithoutModel {
24 pub messages: Vec<Message>,
25 pub max_tokens: Option<u32>,
26 pub temperature: Option<f32>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub tools: Option<Vec<Tool>>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub stream: Option<bool>,
31}
32
33impl From<&ChatRequest> for ChatRequestWithoutModel {
34 fn from(request: &ChatRequest) -> Self {
35 Self {
36 messages: request.messages.clone(),
37 max_tokens: request.max_tokens,
38 temperature: request.temperature,
39 tools: request.tools.clone(),
40 stream: request.stream,
41 }
42 }
43}
44
45
46#[derive(Debug, Serialize)]
47pub struct EmbeddingRequest {
48 pub model: String,
49 pub input: String,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub encoding_format: Option<String>,
52}
53
54#[derive(Debug, Serialize)]
55pub struct ImageGenerationRequest {
56 pub prompt: String,
57 pub model: Option<String>,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub n: Option<u32>,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub size: Option<String>,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub quality: Option<String>,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub style: Option<String>,
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub response_format: Option<String>,
68}
69
70#[derive(Debug, Deserialize)]
71pub struct ImageGenerationResponse {
72 pub data: Vec<ImageData>,
73}
74
75#[derive(Debug, Deserialize, Clone)]
76pub struct ImageData {
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub url: Option<String>,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 pub b64_json: Option<String>,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub revised_prompt: Option<String>,
83}
84
85#[derive(Debug, Deserialize)]
86pub struct EmbeddingResponse {
87 pub data: Vec<EmbeddingData>,
88 pub usage: EmbeddingUsage,
89}
90
91#[derive(Debug, Deserialize, Clone)]
92pub struct EmbeddingData {
93 pub embedding: Vec<f64>,
94}
95
96#[derive(Debug, Deserialize, Clone)]
97pub struct EmbeddingUsage {
98 pub total_tokens: u32,
99}
100
101#[derive(Debug, Serialize, Clone)]
102pub struct Tool {
103 #[serde(rename = "type")]
104 pub tool_type: String,
105 pub function: Function,
106}
107
108#[derive(Debug, Serialize, Clone)]
109pub struct Function {
110 pub name: String,
111 pub description: String,
112 pub parameters: serde_json::Value,
113}
114
115#[derive(Debug, Serialize, Deserialize, Clone)]
117pub struct Message {
118 pub role: String,
119 #[serde(flatten)]
120 pub content_type: MessageContent,
121 #[serde(skip_serializing_if = "Option::is_none")]
122 pub tool_calls: Option<Vec<ToolCall>>,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub tool_call_id: Option<String>,
125}
126
127#[derive(Debug, Serialize, Deserialize, Clone)]
129#[serde(untagged)]
130pub enum MessageContent {
131 Text { content: Option<String> },
132 Multimodal { content: Vec<ContentPart> },
133}
134
135#[derive(Debug, Serialize, Deserialize, Clone)]
137#[serde(tag = "type")]
138pub enum ContentPart {
139 #[serde(rename = "text")]
140 Text { text: String },
141 #[serde(rename = "image_url")]
142 ImageUrl { image_url: ImageUrl },
143}
144
145#[derive(Debug, Serialize, Deserialize, Clone)]
146pub struct ImageUrl {
147 pub url: String,
148 #[serde(skip_serializing_if = "Option::is_none")]
149 pub detail: Option<String>, }
151
152impl Message {
153 pub fn user(content: String) -> Self {
154 Self {
155 role: "user".to_string(),
156 content_type: MessageContent::Text {
157 content: Some(content),
158 },
159 tool_calls: None,
160 tool_call_id: None,
161 }
162 }
163
164 #[allow(dead_code)]
165 pub fn user_with_image(text: String, image_data: String, detail: Option<String>) -> Self {
166 Self {
167 role: "user".to_string(),
168 content_type: MessageContent::Multimodal {
169 content: vec![
170 ContentPart::Text { text },
171 ContentPart::ImageUrl {
172 image_url: ImageUrl {
173 url: image_data,
174 detail,
175 },
176 },
177 ],
178 },
179 tool_calls: None,
180 tool_call_id: None,
181 }
182 }
183
184 pub fn assistant(content: String) -> Self {
185 Self {
186 role: "assistant".to_string(),
187 content_type: MessageContent::Text {
188 content: Some(content),
189 },
190 tool_calls: None,
191 tool_call_id: None,
192 }
193 }
194
195 pub fn assistant_with_tool_calls(tool_calls: Vec<ToolCall>) -> Self {
196 Self {
197 role: "assistant".to_string(),
198 content_type: MessageContent::Text { content: None },
199 tool_calls: Some(tool_calls),
200 tool_call_id: None,
201 }
202 }
203
204 pub fn tool_result(tool_call_id: String, content: String) -> Self {
205 Self {
206 role: "tool".to_string(),
207 content_type: MessageContent::Text {
208 content: Some(content),
209 },
210 tool_calls: None,
211 tool_call_id: Some(tool_call_id),
212 }
213 }
214
215 pub fn get_text_content(&self) -> Option<&String> {
217 match &self.content_type {
218 MessageContent::Text { content } => content.as_ref(),
219 MessageContent::Multimodal { content } => {
220 content.iter().find_map(|part| match part {
222 ContentPart::Text { text } => Some(text),
223 _ => None,
224 })
225 }
226 }
227 }
228}
229
230#[derive(Debug, Deserialize)]
231pub struct ChatResponse {
232 pub choices: Vec<Choice>,
233}
234
235#[derive(Debug, Deserialize)]
236pub struct Choice {
237 pub message: ResponseMessage,
238}
239
240#[derive(Debug, Deserialize)]
241pub struct ResponseMessage {
242 #[allow(dead_code)]
243 pub role: String,
244 pub content: Option<String>,
245 pub tool_calls: Option<Vec<ToolCall>>,
246}
247
248#[derive(Debug, Serialize, Deserialize, Clone)]
249pub struct ToolCall {
250 pub id: String,
251 #[serde(rename = "type")]
252 pub call_type: String,
253 pub function: FunctionCall,
254}
255
256#[derive(Debug, Serialize, Deserialize, Clone)]
257pub struct FunctionCall {
258 pub name: String,
259 pub arguments: String,
260}
261
262
263#[derive(Debug, Deserialize)]
264pub struct ModelsResponse {
265 #[serde(alias = "models")]
266 pub data: Vec<Model>,
267}
268
269#[derive(Debug, Deserialize)]
270pub struct Provider {
271 pub provider: String,
272 #[allow(dead_code)]
273 pub status: String,
274 #[serde(default)]
275 #[allow(dead_code)]
276 pub supports_tools: bool,
277 #[serde(default)]
278 #[allow(dead_code)]
279 pub supports_structured_output: bool,
280}
281
282#[derive(Debug, Deserialize)]
283pub struct Model {
284 pub id: String,
285 #[serde(default = "default_object_type")]
286 pub object: String,
287 #[serde(default)]
288 pub providers: Vec<Provider>,
289}
290
291fn default_object_type() -> String {
292 "model".to_string()
293}
294
295#[derive(Debug, Deserialize)]
296pub struct TokenResponse {
297 pub token: String,
298 pub expires_at: i64, }
300
301pub struct OpenAIClient {
302 client: Client,
303 streaming_client: Client, base_url: String,
305 api_key: String,
306 models_path: String,
307 chat_path: String,
308 custom_headers: std::collections::HashMap<String, String>,
309 provider_config: Option<crate::config::ProviderConfig>,
310 template_processor: Option<TemplateProcessor>,
311}
312
313impl OpenAIClient {
314 pub fn new_with_headers(
315 base_url: String,
316 api_key: String,
317 models_path: String,
318 chat_path: String,
319 custom_headers: std::collections::HashMap<String, String>,
320 ) -> Self {
321 use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
322
323 let mut default_headers = HeaderMap::new();
325 default_headers.insert(
326 HeaderName::from_static("http-referer"),
327 HeaderValue::from_static("https://lc.viwq.dev/"),
328 );
329 default_headers.insert(
330 HeaderName::from_static("x-title"),
331 HeaderValue::from_static("lc"),
332 );
333
334 let client = Client::builder()
337 .pool_max_idle_per_host(10) .pool_idle_timeout(Duration::from_secs(90)) .tcp_keepalive(Duration::from_secs(60)) .timeout(Duration::from_secs(60)) .connect_timeout(Duration::from_secs(10)) .user_agent(concat!(
343 env!("CARGO_PKG_NAME"),
344 "/",
345 env!("CARGO_PKG_VERSION")
346 ))
347 .default_headers(default_headers.clone())
348 .build()
349 .expect("Failed to create optimized HTTP client");
350
351 let streaming_client = Client::builder()
353 .timeout(Duration::from_secs(300)) .default_headers(default_headers)
355 .build()
356 .expect("Failed to create streaming-optimized HTTP client");
357
358 Self {
359 client,
360 streaming_client,
361 base_url: base_url.trim_end_matches('/').to_string(),
362 api_key,
363 models_path,
364 chat_path,
365 custom_headers,
366 provider_config: None,
367 template_processor: None,
368 }
369 }
370
371 pub fn new_with_provider_config(
372 base_url: String,
373 api_key: String,
374 models_path: String,
375 chat_path: String,
376 custom_headers: std::collections::HashMap<String, String>,
377 provider_config: crate::config::ProviderConfig,
378 ) -> Self {
379 use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
380
381 let mut default_headers = HeaderMap::new();
383 default_headers.insert(
384 HeaderName::from_static("http-referer"),
385 HeaderValue::from_static("https://lc.viwq.dev/"),
386 );
387 default_headers.insert(
388 HeaderName::from_static("x-title"),
389 HeaderValue::from_static("lc"),
390 );
391
392 let client = Client::builder()
395 .pool_max_idle_per_host(10) .pool_idle_timeout(Duration::from_secs(90)) .tcp_keepalive(Duration::from_secs(60)) .timeout(Duration::from_secs(60)) .connect_timeout(Duration::from_secs(10)) .user_agent(concat!(
401 env!("CARGO_PKG_NAME"),
402 "/",
403 env!("CARGO_PKG_VERSION")
404 ))
405 .default_headers(default_headers.clone())
406 .build()
407 .expect("Failed to create optimized HTTP client");
408
409 let streaming_client = Client::builder()
411 .timeout(Duration::from_secs(300)) .default_headers(default_headers)
413 .build()
414 .expect("Failed to create streaming-optimized HTTP client");
415
416 let template_processor = if provider_config.chat_templates.is_some()
418 || provider_config.images_templates.is_some()
419 || provider_config.embeddings_templates.is_some()
420 || provider_config.models_templates.is_some() {
421 match TemplateProcessor::new() {
422 Ok(processor) => Some(processor),
423 Err(e) => {
424 eprintln!("Warning: Failed to create template processor: {}", e);
425 None
426 }
427 }
428 } else {
429 None
430 };
431
432 Self {
433 client,
434 streaming_client,
435 base_url: base_url.trim_end_matches('/').to_string(),
436 api_key,
437 models_path,
438 chat_path,
439 custom_headers,
440 provider_config: Some(provider_config),
441 template_processor,
442 }
443 }
444
445 fn get_chat_url(&self, model: &str) -> String {
447 if let Some(ref config) = self.provider_config {
448 config.get_chat_url(model)
450 } else {
451 if self.chat_path.starts_with("https://") {
453 self.chat_path
455 .replace("{model_name}", model)
456 .replace("{model}", model)
457 } else {
458 format!("{}{}", self.base_url, self.chat_path)
460 }
461 }
462 }
463
464
465
466 pub async fn chat(&self, request: &ChatRequest) -> Result<String> {
467 let url = self.get_chat_url(&request.model);
468
469 let mut req = self
470 .client
471 .post(&url)
472 .header("Content-Type", "application/json");
473
474 if request.stream == Some(true) {
476 req = req.header("Accept-Encoding", "identity");
477 }
478
479 if self.custom_headers.is_empty() {
482 req = req.header("Authorization", format!("Bearer {}", self.api_key));
483 }
484
485 for (name, value) in &self.custom_headers {
487 req = req.header(name, value);
488 }
489
490 let request_body = if let Some(ref config) = &self.provider_config {
492 if let Some(ref processor) = &self.template_processor {
493 let template = config.get_endpoint_template("chat", &request.model);
495
496 if let Some(template_str) = template {
497 let mut processor_clone = processor.clone();
499 match processor_clone.process_request(request, &template_str, &config.vars) {
501 Ok(json_value) => Some(json_value),
502 Err(e) => {
503 eprintln!("Warning: Failed to process request template: {}. Falling back to default.", e);
504 None
505 }
506 }
507 } else {
508 None
509 }
510 } else {
511 None
512 }
513 } else {
514 None
515 };
516
517 let response = if let Some(json_body) = request_body {
519 req.json(&json_body).send().await?
520 } else {
521 let should_exclude_model = if let Some(ref config) = self.provider_config {
524 config.chat_path.contains("{model}")
525 } else {
526 self.chat_path.contains("{model}")
527 };
528
529 if should_exclude_model {
530 let request_without_model = ChatRequestWithoutModel::from(request);
532 req.json(&request_without_model).send().await?
533 } else {
534 req.json(request).send().await?
535 }
536 };
537
538 if !response.status().is_success() {
539 let status = response.status();
540 let text = response.text().await.unwrap_or_default();
541 anyhow::bail!("API request failed with status {}: {}", status, text);
542 }
543
544 let response_text = response.text().await?;
546
547 if let Some(ref config) = &self.provider_config {
549 if let Some(ref processor) = &self.template_processor {
550 let template = config.get_endpoint_response_template("chat", &request.model);
552
553 if let Some(template_str) = template {
554 if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
556 let mut processor_clone = processor.clone();
558 match processor_clone.process_response(&response_json, &template_str) {
560 Ok(extracted) => {
561 if let Some(content) = extracted.get("content").and_then(|v| v.as_str()) {
563 return Ok(content.to_string());
564 } else if let Some(tool_calls) = extracted.get("tool_calls").and_then(|v| v.as_array()) {
565 if !tool_calls.is_empty() {
566 let mut response = String::new();
567 response.push_str("🔧 **Tool Calls Made:**\n\n");
568 response.push_str(&format!("Tool calls: {:?}\n\n", tool_calls));
569 response.push_str("*Tool calls detected - execution handled by chat module*\n\n");
570 return Ok(response);
571 }
572 }
573 }
574 Err(e) => {
575 eprintln!("Warning: Failed to process response template: {}. Falling back to default parsing.", e);
576 }
577 }
578 }
579 }
580 }
581 }
582
583 if let Ok(chat_response) = serde_json::from_str::<ChatResponse>(&response_text) {
586 if let Some(choice) = chat_response.choices.first() {
587 if let Some(tool_calls) = &choice.message.tool_calls {
589 if !tool_calls.is_empty() {
590 let mut response = String::new();
591 response.push_str("🔧 **Tool Calls Made:**\n\n");
592
593 for tool_call in tool_calls {
594 response.push_str(&format!(
595 "**Function:** `{}`\n",
596 tool_call.function.name
597 ));
598 response.push_str(&format!(
599 "**Arguments:** `{}`\n\n",
600 tool_call.function.arguments
601 ));
602
603 response.push_str(
605 "*Tool calls detected - execution handled by chat module*\n\n",
606 );
607 }
608
609 return Ok(response);
610 }
611 }
613
614 if let Some(content) = &choice.message.content {
616 return Ok(content.clone());
617 } else {
618 anyhow::bail!("No content or tool calls in response");
619 }
620 } else {
621 anyhow::bail!("No response from API");
622 }
623 }
624
625
626
627 anyhow::bail!("Failed to parse chat response. Response: {}", response_text);
629 }
630
631 pub async fn list_models(&self) -> Result<Vec<Model>> {
632 let url = format!("{}{}", self.base_url, self.models_path);
633
634 let mut req = self
635 .client
636 .get(&url)
637 .header("Content-Type", "application/json");
638
639 if self.custom_headers.is_empty() {
641 req = req.header("Authorization", format!("Bearer {}", self.api_key));
642 }
643
644 for (name, value) in &self.custom_headers {
646 req = req.header(name, value);
647 }
648
649 let response = req.send().await?;
650
651 if !response.status().is_success() {
652 let status = response.status();
653 let text = response.text().await.unwrap_or_default();
654 anyhow::bail!("API request failed with status {}: {}", status, text);
655 }
656
657 let response_text = response.text().await?;
659
660 let models =
662 if let Ok(models_response) = serde_json::from_str::<ModelsResponse>(&response_text) {
663 models_response.data
664 } else if let Ok(parsed_models) = serde_json::from_str::<Vec<Model>>(&response_text) {
665 parsed_models
667 } else {
668 anyhow::bail!(
670 "Failed to parse models response. Response: {}",
671 response_text
672 );
673 };
674
675 let mut expanded_models = Vec::new();
677
678 for model in models {
679 if model.providers.is_empty() {
680 expanded_models.push(model);
682 } else {
683 for provider in &model.providers {
685 let expanded_model = Model {
686 id: format!("{}:{}", model.id, provider.provider),
687 object: model.object.clone(),
688 providers: vec![], };
690 expanded_models.push(expanded_model);
691 }
692 }
693 }
694
695 Ok(expanded_models)
696 }
697
698 pub async fn chat_with_tools(&self, request: &ChatRequest) -> Result<ChatResponse> {
700 let url = self.get_chat_url(&request.model);
701
702 let mut req = self
703 .client
704 .post(&url)
705 .header("Content-Type", "application/json");
706
707 if request.stream == Some(true) {
709 req = req.header("Accept-Encoding", "identity");
710 }
711
712 if self.custom_headers.is_empty() {
714 req = req.header("Authorization", format!("Bearer {}", self.api_key));
715 }
716
717 for (name, value) in &self.custom_headers {
719 req = req.header(name, value);
720 }
721
722 let should_exclude_model = if let Some(ref config) = self.provider_config {
724 config.chat_path.contains("{model}")
725 } else {
726 self.chat_path.contains("{model}")
727 };
728
729 let response = if should_exclude_model {
730 let request_without_model = ChatRequestWithoutModel::from(request);
732 req.json(&request_without_model).send().await?
733 } else {
734 req.json(request).send().await?
735 };
736
737 if !response.status().is_success() {
738 let status = response.status();
739 let text = response.text().await.unwrap_or_default();
740 anyhow::bail!("API request failed with status {}: {}", status, text);
741 }
742
743 let response_text = response.text().await?;
745
746 if let Ok(chat_response) = serde_json::from_str::<ChatResponse>(&response_text) {
748 return Ok(chat_response);
749 }
750
751 anyhow::bail!("Failed to parse chat response. Response: {}", response_text);
753 }
754
755 pub async fn get_token_from_url(&self, token_url: &str) -> Result<TokenResponse> {
756 let mut req = self
757 .client
758 .get(token_url)
759 .header("Authorization", format!("token {}", self.api_key))
760 .header("Content-Type", "application/json");
761
762 for (name, value) in &self.custom_headers {
764 req = req.header(name, value);
765 }
766
767 let response = req.send().await?;
768
769 if !response.status().is_success() {
770 let status = response.status();
771 let text = response.text().await.unwrap_or_default();
772 anyhow::bail!("Token request failed with status {}: {}", status, text);
773 }
774
775 let token_response: TokenResponse = response.json().await?;
776 Ok(token_response)
777 }
778
779 pub async fn embeddings(&self, request: &EmbeddingRequest) -> Result<EmbeddingResponse> {
780 let url = format!("{}/embeddings", self.base_url);
781
782 let mut req = self
783 .client
784 .post(&url)
785 .header("Content-Type", "application/json");
786
787 if self.custom_headers.is_empty() {
789 req = req.header("Authorization", format!("Bearer {}", self.api_key));
790 }
791
792 for (name, value) in &self.custom_headers {
794 req = req.header(name, value);
795 }
796
797 let response = req.json(request).send().await?;
798
799 if !response.status().is_success() {
800 let status = response.status();
801 let text = response.text().await.unwrap_or_default();
802 anyhow::bail!(
803 "Embeddings API request failed with status {}: {}",
804 status,
805 text
806 );
807 }
808
809 let embedding_response: EmbeddingResponse = response.json().await?;
810 Ok(embedding_response)
811 }
812
813 pub async fn generate_images(
814 &self,
815 request: &ImageGenerationRequest,
816 ) -> Result<ImageGenerationResponse> {
817 let url = if let Some(ref config) = self.provider_config {
819 format!("{}{}", self.base_url, config.images_path.as_deref().unwrap_or("/images/generations"))
820 } else {
821 format!("{}/images/generations", self.base_url)
822 };
823
824 let mut req = self
825 .client
826 .post(&url)
827 .header("Content-Type", "application/json");
828
829 if self.custom_headers.is_empty() {
831 req = req.header("Authorization", format!("Bearer {}", self.api_key));
832 }
833
834 for (name, value) in &self.custom_headers {
836 req = req.header(name, value);
837 }
838
839 let request_body = if let Some(ref config) = &self.provider_config {
841 if let Some(ref processor) = &self.template_processor {
842 let model_name = request.model.as_deref().unwrap_or("");
844 let template = config.get_endpoint_template("images", model_name);
845
846 if let Some(template_str) = template {
847 let mut processor_clone = processor.clone();
849 match processor_clone.process_image_request(request, &template_str, &config.vars) {
851 Ok(json_value) => Some(json_value),
852 Err(e) => {
853 eprintln!("Warning: Failed to process image request template: {}. Falling back to default.", e);
854 None
855 }
856 }
857 } else {
858 None
859 }
860 } else {
861 None
862 }
863 } else {
864 None
865 };
866
867 let response = if let Some(json_body) = request_body {
869 req.json(&json_body).send().await?
870 } else {
871 req.json(request).send().await?
872 };
873
874 if !response.status().is_success() {
875 let status = response.status();
876 let text = response.text().await.unwrap_or_default();
877 anyhow::bail!(
878 "Image generation API request failed with status {}: {}",
879 status,
880 text
881 );
882 }
883
884 let response_text = response.text().await?;
886
887 if let Some(ref config) = &self.provider_config {
889 if let Some(ref processor) = &self.template_processor {
890 let model_name = request.model.as_deref().unwrap_or("");
892 let template = config.get_endpoint_response_template("images", model_name);
893
894 if let Some(template_str) = template {
895 if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
897 let mut processor_clone = processor.clone();
899 match processor_clone.process_response(&response_json, &template_str) {
901 Ok(transformed) => {
902 if let Ok(image_response) = serde_json::from_value::<ImageGenerationResponse>(transformed) {
904 return Ok(image_response);
905 }
906 }
907 Err(e) => {
908 eprintln!("Warning: Failed to process image response template: {}. Falling back to default parsing.", e);
909 }
910 }
911 }
912 }
913 }
914 }
915
916 let image_response: ImageGenerationResponse = serde_json::from_str(&response_text)?;
918 Ok(image_response)
919 }
920
921 pub async fn chat_stream(&self, request: &ChatRequest) -> Result<()> {
922 use std::io::{stdout, Write};
923
924 let url = self.get_chat_url(&request.model);
925
926 let mut req = self
928 .streaming_client
929 .post(&url)
930 .header("Content-Type", "application/json")
931 .header("Accept", "text/event-stream") .header("Cache-Control", "no-cache") .header("Accept-Encoding", "identity"); let stdout = stdout();
937 let mut handle = std::io::BufWriter::new(stdout.lock());
938
939 if self.custom_headers.is_empty() {
941 req = req.header("Authorization", format!("Bearer {}", self.api_key));
942 }
943
944 for (name, value) in &self.custom_headers {
946 req = req.header(name, value);
947 }
948
949 let should_exclude_model = if let Some(ref config) = self.provider_config {
951 config.chat_path.contains("{model}")
952 } else {
953 self.chat_path.contains("{model}")
954 };
955
956 let response = if should_exclude_model {
957 let request_without_model = ChatRequestWithoutModel::from(request);
959 req.json(&request_without_model).send().await?
960 } else {
961 req.json(request).send().await?
962 };
963
964 if !response.status().is_success() {
965 let status = response.status();
966 let text = response.text().await.unwrap_or_default();
967 anyhow::bail!("API request failed with status {}: {}", status, text);
968 }
969
970 let headers = response.headers();
972 if headers.get("content-encoding").is_some() {
973 }
975
976 let mut stream = response.bytes_stream();
977
978 let mut buffer = String::new();
979
980 while let Some(chunk) = stream.next().await {
981 let chunk = chunk?;
982
983 let chunk_str = String::from_utf8_lossy(&chunk);
984 buffer.push_str(&chunk_str);
985
986 while let Some(newline_pos) = buffer.find('\n') {
988 let line = buffer[..newline_pos].to_string();
989 buffer.drain(..=newline_pos);
990
991 if line.starts_with("data: ") {
993 let data = &line[6..]; if data.trim() == "[DONE]" {
996 handle.write_all(b"\n")?;
997 handle.flush()?;
998 return Ok(());
999 }
1000
1001 if let Ok(json) = serde_json::from_str::<serde_json::Value>(data) {
1002 if let Some(response) = json.get("response") {
1004 if let Some(text) = response.as_str() {
1005 if !text.is_empty() {
1006 handle.write_all(text.as_bytes())?;
1007 handle.flush()?;
1008 }
1009 }
1010 }
1011 else if let Some(choices) = json.get("choices") {
1013 if let Some(choice) = choices.get(0) {
1014 if let Some(delta) = choice.get("delta") {
1015 if let Some(content) = delta.get("content") {
1016 if let Some(text) = content.as_str() {
1017 handle.write_all(text.as_bytes())?;
1019 handle.flush()?;
1020 }
1021 }
1022 }
1023 }
1024 }
1025 }
1026 } else if line.trim().is_empty() {
1027 continue;
1029 } else {
1030 if let Ok(json) = serde_json::from_str::<serde_json::Value>(&line) {
1032 if let Some(response) = json.get("response") {
1034 if let Some(text) = response.as_str() {
1035 if !text.is_empty() {
1036 handle.write_all(text.as_bytes())?;
1037 handle.flush()?;
1038 }
1039 }
1040 }
1041 else if let Some(choices) = json.get("choices") {
1043 if let Some(choice) = choices.get(0) {
1044 if let Some(delta) = choice.get("delta") {
1045 if let Some(content) = delta.get("content") {
1046 if let Some(text) = content.as_str() {
1047 handle.write_all(text.as_bytes())?;
1048 handle.flush()?;
1049 }
1050 }
1051 }
1052 }
1053 }
1054 }
1055 }
1056 }
1057 }
1058
1059 if !buffer.trim().is_empty() {
1061 if let Ok(json) = serde_json::from_str::<serde_json::Value>(&buffer) {
1062 if let Some(response) = json.get("response") {
1064 if let Some(text) = response.as_str() {
1065 if !text.is_empty() {
1066 handle.write_all(text.as_bytes())?;
1067 handle.flush()?;
1068 }
1069 }
1070 }
1071 else if let Some(choices) = json.get("choices") {
1073 if let Some(choice) = choices.get(0) {
1074 if let Some(delta) = choice.get("delta") {
1075 if let Some(content) = delta.get("content") {
1076 if let Some(text) = content.as_str() {
1077 handle.write_all(text.as_bytes())?;
1078 handle.flush()?;
1079 }
1080 }
1081 }
1082 }
1083 }
1084 }
1085 }
1086
1087 handle.write_all(b"\n")?;
1089 handle.flush()?;
1090 Ok(())
1091 }
1092}