1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Deserialize)]
19pub struct ChatCompletionRequest {
20 pub model: ModelField,
22 pub messages: Vec<ChatCompletionMessage>,
24 #[serde(default)]
26 pub stream: bool,
27 #[serde(default)]
29 pub temperature: Option<f32>,
30 #[serde(default)]
32 pub max_tokens: Option<u32>,
33 #[serde(default)]
35 pub strict_capabilities: Option<bool>,
36 #[serde(default)]
38 pub tools: Option<Vec<ToolDefinition>>,
39 #[serde(default)]
41 pub tool_choice: Option<ToolChoice>,
42 #[serde(default)]
44 pub response_format: Option<ResponseFormatRequest>,
45 #[serde(default)]
47 pub top_p: Option<f32>,
48 #[serde(default)]
50 pub stop: Option<StopField>,
51}
52
53#[derive(Debug, Clone, Deserialize)]
58#[serde(untagged)]
59pub enum StopField {
60 Single(String),
62 Multiple(Vec<String>),
64}
65
66const MAX_STOP_SEQUENCES: usize = 4;
68
69impl StopField {
70 pub const fn len(&self) -> usize {
72 match self {
73 Self::Single(_) => 1,
74 Self::Multiple(v) => v.len(),
75 }
76 }
77
78 pub const fn is_empty(&self) -> bool {
80 matches!(self, Self::Multiple(v) if v.is_empty())
81 }
82
83 pub fn into_vec(self) -> Vec<String> {
86 match self {
87 Self::Single(s) => vec![s],
88 Self::Multiple(v) => v.into_iter().take(MAX_STOP_SEQUENCES).collect(),
89 }
90 }
91
92 pub fn to_bounded_vec(&self) -> Vec<String> {
95 match self {
96 Self::Single(s) => vec![s.clone()],
97 Self::Multiple(v) => v.iter().take(MAX_STOP_SEQUENCES).cloned().collect(),
98 }
99 }
100}
101
102#[derive(Debug, Clone, Deserialize)]
104#[serde(tag = "type")]
105pub enum ResponseFormatRequest {
106 #[serde(rename = "text")]
108 Text,
109 #[serde(rename = "json_object")]
111 JsonObject,
112 #[serde(rename = "json_schema")]
114 JsonSchema {
115 json_schema: JsonSchemaSpec,
117 },
118}
119
120#[derive(Debug, Clone, Deserialize)]
122pub struct JsonSchemaSpec {
123 pub name: String,
125 pub schema: serde_json::Value,
127}
128
129#[derive(Debug, Clone, Deserialize)]
131#[serde(untagged)]
132pub enum ModelField {
133 Single(String),
135 Multiple(Vec<String>),
137}
138
139#[derive(Debug, Clone, Deserialize)]
141pub struct ChatCompletionMessage {
142 pub role: String,
144 pub content: Option<String>,
146 #[serde(default)]
148 pub tool_calls: Option<Vec<ToolCall>>,
149 #[serde(default)]
151 pub tool_call_id: Option<String>,
152 #[serde(default)]
154 pub name: Option<String>,
155}
156
157#[derive(Debug, Clone, Deserialize)]
163pub struct ToolDefinition {
164 #[serde(rename = "type")]
166 pub tool_type: String,
167 pub function: FunctionObject,
169}
170
171#[derive(Debug, Clone, Deserialize)]
173pub struct FunctionObject {
174 pub name: String,
176 #[serde(default)]
178 pub description: Option<String>,
179 #[serde(default)]
181 pub parameters: Option<serde_json::Value>,
182}
183
184#[derive(Debug, Clone, Deserialize)]
186#[serde(untagged)]
187pub enum ToolChoice {
188 Mode(String),
190 Specific(ToolChoiceSpecific),
192}
193
194#[derive(Debug, Clone, Deserialize)]
196pub struct ToolChoiceSpecific {
197 #[serde(rename = "type")]
199 pub tool_type: String,
200 pub function: ToolChoiceFunction,
202}
203
204#[derive(Debug, Clone, Deserialize)]
206pub struct ToolChoiceFunction {
207 pub name: String,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct ToolCall {
214 #[serde(default)]
216 pub index: usize,
217 pub id: String,
219 #[serde(rename = "type")]
221 pub tool_type: String,
222 pub function: ToolCallFunction,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct ToolCallFunction {
229 pub name: String,
231 pub arguments: String,
233}
234
235#[derive(Debug, Serialize)]
241pub struct ChatCompletionResponse {
242 pub id: String,
244 pub object: &'static str,
246 pub created: u64,
248 pub model: String,
250 pub choices: Vec<Choice>,
252 #[serde(skip_serializing_if = "Option::is_none")]
254 pub usage: Option<Usage>,
255 #[serde(skip_serializing_if = "Option::is_none")]
257 pub warnings: Option<Vec<String>>,
258}
259
260#[derive(Debug, Serialize)]
262pub struct Choice {
263 pub index: u32,
265 pub message: ResponseMessage,
267 pub finish_reason: Option<String>,
269}
270
271#[derive(Debug, Serialize)]
273pub struct ResponseMessage {
274 pub role: &'static str,
276 #[serde(skip_serializing_if = "Option::is_none")]
278 pub content: Option<String>,
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub tool_calls: Option<Vec<ToolCall>>,
282}
283
284#[derive(Debug, Serialize)]
286pub struct Usage {
287 #[serde(rename = "prompt_tokens")]
289 pub prompt: u32,
290 #[serde(rename = "completion_tokens")]
292 pub completion: u32,
293 #[serde(rename = "total_tokens")]
295 pub total: u32,
296}
297
298#[derive(Debug, Serialize)]
304pub struct ChatCompletionChunk {
305 pub id: String,
307 pub object: &'static str,
309 pub created: u64,
311 pub model: String,
313 pub choices: Vec<ChunkChoice>,
315}
316
317#[derive(Debug, Serialize)]
319pub struct ChunkChoice {
320 pub index: u32,
322 pub delta: Delta,
324 pub finish_reason: Option<String>,
326}
327
328#[derive(Debug, Serialize)]
330pub struct Delta {
331 #[serde(skip_serializing_if = "Option::is_none")]
333 pub role: Option<&'static str>,
334 #[serde(skip_serializing_if = "Option::is_none")]
336 pub content: Option<String>,
337 #[serde(skip_serializing_if = "Option::is_none")]
339 pub tool_calls: Option<Vec<ToolCall>>,
340}
341
342#[derive(Debug, Serialize)]
348pub struct MultiplexResponse {
349 pub id: String,
351 pub object: &'static str,
353 pub created: u64,
355 pub results: Vec<MultiplexProviderResult>,
357 pub summary: String,
359}
360
361#[derive(Debug, Serialize)]
363pub struct MultiplexProviderResult {
364 pub provider: String,
366 #[serde(skip_serializing_if = "Option::is_none")]
368 pub model: Option<String>,
369 #[serde(skip_serializing_if = "Option::is_none")]
371 pub content: Option<String>,
372 #[serde(skip_serializing_if = "Option::is_none")]
374 pub error: Option<String>,
375 pub duration_ms: u64,
377}
378
379#[derive(Debug, Serialize)]
385pub struct ModelsResponse {
386 pub object: &'static str,
388 pub data: Vec<ModelObject>,
390}
391
392#[derive(Debug, Serialize)]
394pub struct ModelObject {
395 pub id: String,
397 pub object: &'static str,
399 pub owned_by: String,
401}
402
403#[derive(Debug, Serialize)]
409pub struct HealthResponse {
410 pub status: &'static str,
412 pub providers: std::collections::HashMap<String, String>,
414}
415
416#[derive(Debug, Serialize)]
422pub struct ErrorResponse {
423 pub error: ErrorDetail,
425}
426
427#[derive(Debug, Serialize)]
429pub struct ErrorDetail {
430 pub message: String,
432 #[serde(rename = "type")]
434 pub error_type: String,
435 #[serde(skip_serializing_if = "Option::is_none")]
437 pub param: Option<String>,
438 #[serde(skip_serializing_if = "Option::is_none")]
440 pub code: Option<String>,
441}
442
443impl ErrorResponse {
444 pub fn new(error_type: impl Into<String>, message: impl Into<String>) -> Self {
446 Self {
447 error: ErrorDetail {
448 message: message.into(),
449 error_type: error_type.into(),
450 param: None,
451 code: None,
452 },
453 }
454 }
455}
456
457#[cfg(test)]
458mod tests {
459 use super::*;
460
461 #[test]
462 fn deserialize_single_model() {
463 let json = r#"{"model":"copilot:gpt-4o","messages":[{"role":"user","content":"hi"}]}"#;
464 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
465 match req.model {
466 ModelField::Single(m) => assert_eq!(m, "copilot:gpt-4o"),
467 ModelField::Multiple(_) => panic!("expected single"),
468 }
469 assert!(!req.stream);
470 }
471
472 #[test]
473 fn deserialize_multiple_models() {
474 let json = r#"{"model":["copilot:gpt-4o","claude:opus"],"messages":[{"role":"user","content":"hi"}]}"#;
475 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
476 match req.model {
477 ModelField::Multiple(models) => {
478 assert_eq!(models.len(), 2);
479 assert_eq!(models[0], "copilot:gpt-4o");
480 assert_eq!(models[1], "claude:opus");
481 }
482 ModelField::Single(_) => panic!("expected multiple"),
483 }
484 }
485
486 #[test]
487 fn deserialize_with_stream_flag() {
488 let json =
489 r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"stream":true}"#;
490 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
491 assert!(req.stream);
492 }
493
494 #[test]
495 fn deserialize_message_with_null_content() {
496 let json = r#"{"model":"copilot","messages":[{"role":"assistant","content":null,"tool_calls":[{"id":"call_1","type":"function","function":{"name":"search","arguments":"{}"}}]}]}"#;
497 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
498 assert!(req.messages[0].content.is_none());
499 assert!(req.messages[0].tool_calls.is_some());
500 }
501
502 #[test]
503 fn deserialize_message_without_content_field() {
504 let json = r#"{"model":"copilot","messages":[{"role":"tool","tool_call_id":"call_1","name":"search","content":"{\"result\":\"found\"}"}]}"#;
505 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
506 assert_eq!(req.messages[0].role, "tool");
507 assert_eq!(req.messages[0].tool_call_id.as_deref(), Some("call_1"));
508 assert_eq!(req.messages[0].name.as_deref(), Some("search"));
509 }
510
511 #[test]
512 fn deserialize_tool_definitions() {
513 let json = r#"{
514 "model": "copilot",
515 "messages": [{"role": "user", "content": "hi"}],
516 "tools": [{
517 "type": "function",
518 "function": {
519 "name": "get_weather",
520 "description": "Get weather for a city",
521 "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
522 }
523 }]
524 }"#;
525 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
526 let tools = req.tools.expect("tools present");
527 assert_eq!(tools.len(), 1);
528 assert_eq!(tools[0].tool_type, "function");
529 assert_eq!(tools[0].function.name, "get_weather");
530 assert!(tools[0].function.parameters.is_some());
531 }
532
533 #[test]
534 fn deserialize_tool_choice_auto() {
535 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_choice":"auto"}"#;
536 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
537 match req.tool_choice.expect("tool_choice present") {
538 ToolChoice::Mode(m) => assert_eq!(m, "auto"),
539 ToolChoice::Specific(_) => panic!("expected mode"),
540 }
541 }
542
543 #[test]
544 fn deserialize_tool_choice_specific() {
545 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_choice":{"type":"function","function":{"name":"get_weather"}}}"#;
546 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
547 match req.tool_choice.expect("tool_choice present") {
548 ToolChoice::Specific(s) => assert_eq!(s.function.name, "get_weather"),
549 ToolChoice::Mode(_) => panic!("expected specific"),
550 }
551 }
552
553 #[test]
554 fn serialize_completion_response() {
555 let resp = ChatCompletionResponse {
556 id: "chatcmpl-test".to_owned(),
557 object: "chat.completion",
558 created: 1_700_000_000,
559 model: "copilot:gpt-4o".to_owned(),
560 choices: vec![Choice {
561 index: 0,
562 message: ResponseMessage {
563 role: "assistant",
564 content: Some("Hello!".to_owned()),
565 tool_calls: None,
566 },
567 finish_reason: Some("stop".to_owned()),
568 }],
569 usage: None,
570 warnings: None,
571 };
572 let json = serde_json::to_string(&resp).expect("serialize");
573 assert!(json.contains("chat.completion"));
574 assert!(json.contains("Hello!"));
575 assert!(!json.contains("tool_calls"));
576 }
577
578 #[test]
579 fn serialize_response_with_tool_calls() {
580 let resp = ChatCompletionResponse {
581 id: "chatcmpl-test".to_owned(),
582 object: "chat.completion",
583 created: 1_700_000_000,
584 model: "copilot:gpt-4o".to_owned(),
585 choices: vec![Choice {
586 index: 0,
587 message: ResponseMessage {
588 role: "assistant",
589 content: None,
590 tool_calls: Some(vec![ToolCall {
591 index: 0,
592 id: "call_abc123".to_owned(),
593 tool_type: "function".to_owned(),
594 function: ToolCallFunction {
595 name: "get_weather".to_owned(),
596 arguments: r#"{"city":"Paris"}"#.to_owned(),
597 },
598 }]),
599 },
600 finish_reason: Some("tool_calls".to_owned()),
601 }],
602 usage: None,
603 warnings: None,
604 };
605 let json = serde_json::to_string(&resp).expect("serialize");
606 assert!(json.contains("tool_calls"));
607 assert!(json.contains("call_abc123"));
608 assert!(json.contains("get_weather"));
609 assert!(!json.contains(r#""content""#));
610 }
611
612 #[test]
613 fn serialize_error_response() {
614 let resp = ErrorResponse::new("invalid_request_error", "Unknown model");
615 let json = serde_json::to_string(&resp).expect("serialize");
616 assert!(json.contains("invalid_request_error"));
617 assert!(json.contains("Unknown model"));
618 }
619
620 #[test]
621 fn serialize_chunk_response() {
622 let chunk = ChatCompletionChunk {
623 id: "chatcmpl-test".to_owned(),
624 object: "chat.completion.chunk",
625 created: 1_700_000_000,
626 model: "copilot".to_owned(),
627 choices: vec![ChunkChoice {
628 index: 0,
629 delta: Delta {
630 role: None,
631 content: Some("token".to_owned()),
632 tool_calls: None,
633 },
634 finish_reason: None,
635 }],
636 };
637 let json = serde_json::to_string(&chunk).expect("serialize");
638 assert!(json.contains("chat.completion.chunk"));
639 assert!(json.contains("token"));
640 assert!(!json.contains("tool_calls"));
641 }
642
643 #[test]
644 fn deserialize_tool_choice_none() {
645 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_choice":"none"}"#;
646 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
647 match req.tool_choice.expect("tool_choice present") {
648 ToolChoice::Mode(m) => assert_eq!(m, "none"),
649 ToolChoice::Specific(_) => panic!("expected mode"),
650 }
651 }
652
653 #[test]
654 fn deserialize_tool_choice_required() {
655 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_choice":"required"}"#;
656 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
657 match req.tool_choice.expect("tool_choice present") {
658 ToolChoice::Mode(m) => assert_eq!(m, "required"),
659 ToolChoice::Specific(_) => panic!("expected mode"),
660 }
661 }
662
663 #[test]
664 fn deserialize_response_format_text() {
665 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"response_format":{"type":"text"}}"#;
666 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
667 assert!(matches!(
668 req.response_format,
669 Some(ResponseFormatRequest::Text)
670 ));
671 }
672
673 #[test]
674 fn deserialize_response_format_json_object() {
675 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"response_format":{"type":"json_object"}}"#;
676 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
677 assert!(matches!(
678 req.response_format,
679 Some(ResponseFormatRequest::JsonObject)
680 ));
681 }
682
683 #[test]
684 fn deserialize_response_format_json_schema() {
685 let json = r#"{
686 "model": "copilot",
687 "messages": [{"role": "user", "content": "hi"}],
688 "response_format": {
689 "type": "json_schema",
690 "json_schema": {
691 "name": "weather",
692 "schema": {"type": "object", "properties": {"temp": {"type": "number"}}}
693 }
694 }
695 }"#;
696 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
697 match req.response_format {
698 Some(ResponseFormatRequest::JsonSchema { json_schema }) => {
699 assert_eq!(json_schema.name, "weather");
700 assert!(json_schema.schema["properties"]["temp"].is_object());
701 }
702 other => panic!("expected JsonSchema, got: {other:?}"),
703 }
704 }
705
706 #[test]
707 fn deserialize_top_p() {
708 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"top_p":0.9}"#;
709 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
710 assert_eq!(req.top_p, Some(0.9));
711 }
712
713 #[test]
714 fn deserialize_stop_single() {
715 let json =
716 r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"stop":"END"}"#;
717 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
718 let stop = req.stop.expect("stop present");
719 assert_eq!(stop.into_vec(), vec!["END"]);
720 }
721
722 #[test]
723 fn deserialize_stop_array() {
724 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"stop":["END","STOP"]}"#;
725 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
726 let stop = req.stop.expect("stop present");
727 assert_eq!(stop.into_vec(), vec!["END", "STOP"]);
728 }
729
730 #[test]
731 fn stop_field_len() {
732 let single = StopField::Single("END".to_owned());
733 assert_eq!(single.len(), 1);
734 let multiple = StopField::Multiple(vec!["A".to_owned(), "B".to_owned(), "C".to_owned()]);
735 assert_eq!(multiple.len(), 3);
736 }
737
738 #[test]
739 fn stop_field_into_vec_truncates_at_four() {
740 let oversized = StopField::Multiple((0..10).map(|i| format!("stop_{i}")).collect());
741 let result = oversized.into_vec();
742 assert_eq!(result.len(), 4);
743 assert_eq!(result[0], "stop_0");
744 assert_eq!(result[3], "stop_3");
745 }
746
747 #[test]
748 fn deserialize_all_optional_fields() {
749 let json = r#"{
750 "model": "copilot",
751 "messages": [{"role": "user", "content": "hi"}],
752 "temperature": 0.7,
753 "max_tokens": 100,
754 "top_p": 0.95,
755 "stop": ["END"],
756 "stream": true
757 }"#;
758 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
759 assert_eq!(req.temperature, Some(0.7));
760 assert_eq!(req.max_tokens, Some(100));
761 assert_eq!(req.top_p, Some(0.95));
762 assert!(req.stop.is_some());
763 assert!(req.stream);
764 }
765
766 #[test]
767 fn serialize_models_response() {
768 let resp = ModelsResponse {
769 object: "list",
770 data: vec![ModelObject {
771 id: "copilot:gpt-4o".to_owned(),
772 object: "model",
773 owned_by: "copilot".to_owned(),
774 }],
775 };
776 let json = serde_json::to_string(&resp).expect("serialize");
777 assert!(json.contains("copilot:gpt-4o"));
778 }
779}