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
66impl StopField {
67 pub fn into_vec(self) -> Vec<String> {
69 match self {
70 Self::Single(s) => vec![s],
71 Self::Multiple(v) => v,
72 }
73 }
74}
75
76#[derive(Debug, Clone, Deserialize)]
78#[serde(tag = "type")]
79pub enum ResponseFormatRequest {
80 #[serde(rename = "text")]
82 Text,
83 #[serde(rename = "json_object")]
85 JsonObject,
86 #[serde(rename = "json_schema")]
88 JsonSchema {
89 json_schema: JsonSchemaSpec,
91 },
92}
93
94#[derive(Debug, Clone, Deserialize)]
96pub struct JsonSchemaSpec {
97 pub name: String,
99 pub schema: serde_json::Value,
101}
102
103#[derive(Debug, Clone, Deserialize)]
105#[serde(untagged)]
106pub enum ModelField {
107 Single(String),
109 Multiple(Vec<String>),
111}
112
113#[derive(Debug, Clone, Deserialize)]
115pub struct ChatCompletionMessage {
116 pub role: String,
118 pub content: Option<String>,
120 #[serde(default)]
122 pub tool_calls: Option<Vec<ToolCall>>,
123 #[serde(default)]
125 pub tool_call_id: Option<String>,
126 #[serde(default)]
128 pub name: Option<String>,
129}
130
131#[derive(Debug, Clone, Deserialize)]
137pub struct ToolDefinition {
138 #[serde(rename = "type")]
140 pub tool_type: String,
141 pub function: FunctionObject,
143}
144
145#[derive(Debug, Clone, Deserialize)]
147pub struct FunctionObject {
148 pub name: String,
150 #[serde(default)]
152 pub description: Option<String>,
153 #[serde(default)]
155 pub parameters: Option<serde_json::Value>,
156}
157
158#[derive(Debug, Clone, Deserialize)]
160#[serde(untagged)]
161pub enum ToolChoice {
162 Mode(String),
164 Specific(ToolChoiceSpecific),
166}
167
168#[derive(Debug, Clone, Deserialize)]
170pub struct ToolChoiceSpecific {
171 #[serde(rename = "type")]
173 pub tool_type: String,
174 pub function: ToolChoiceFunction,
176}
177
178#[derive(Debug, Clone, Deserialize)]
180pub struct ToolChoiceFunction {
181 pub name: String,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct ToolCall {
188 #[serde(default)]
190 pub index: usize,
191 pub id: String,
193 #[serde(rename = "type")]
195 pub tool_type: String,
196 pub function: ToolCallFunction,
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct ToolCallFunction {
203 pub name: String,
205 pub arguments: String,
207}
208
209#[derive(Debug, Serialize)]
215pub struct ChatCompletionResponse {
216 pub id: String,
218 pub object: &'static str,
220 pub created: u64,
222 pub model: String,
224 pub choices: Vec<Choice>,
226 #[serde(skip_serializing_if = "Option::is_none")]
228 pub usage: Option<Usage>,
229 #[serde(skip_serializing_if = "Option::is_none")]
231 pub warnings: Option<Vec<String>>,
232}
233
234#[derive(Debug, Serialize)]
236pub struct Choice {
237 pub index: u32,
239 pub message: ResponseMessage,
241 pub finish_reason: Option<String>,
243}
244
245#[derive(Debug, Serialize)]
247pub struct ResponseMessage {
248 pub role: &'static str,
250 #[serde(skip_serializing_if = "Option::is_none")]
252 pub content: Option<String>,
253 #[serde(skip_serializing_if = "Option::is_none")]
255 pub tool_calls: Option<Vec<ToolCall>>,
256}
257
258#[derive(Debug, Serialize)]
260pub struct Usage {
261 #[serde(rename = "prompt_tokens")]
263 pub prompt: u32,
264 #[serde(rename = "completion_tokens")]
266 pub completion: u32,
267 #[serde(rename = "total_tokens")]
269 pub total: u32,
270}
271
272#[derive(Debug, Serialize)]
278pub struct ChatCompletionChunk {
279 pub id: String,
281 pub object: &'static str,
283 pub created: u64,
285 pub model: String,
287 pub choices: Vec<ChunkChoice>,
289}
290
291#[derive(Debug, Serialize)]
293pub struct ChunkChoice {
294 pub index: u32,
296 pub delta: Delta,
298 pub finish_reason: Option<String>,
300}
301
302#[derive(Debug, Serialize)]
304pub struct Delta {
305 #[serde(skip_serializing_if = "Option::is_none")]
307 pub role: Option<&'static str>,
308 #[serde(skip_serializing_if = "Option::is_none")]
310 pub content: Option<String>,
311 #[serde(skip_serializing_if = "Option::is_none")]
313 pub tool_calls: Option<Vec<ToolCall>>,
314}
315
316#[derive(Debug, Serialize)]
322pub struct MultiplexResponse {
323 pub id: String,
325 pub object: &'static str,
327 pub created: u64,
329 pub results: Vec<MultiplexProviderResult>,
331 pub summary: String,
333}
334
335#[derive(Debug, Serialize)]
337pub struct MultiplexProviderResult {
338 pub provider: String,
340 #[serde(skip_serializing_if = "Option::is_none")]
342 pub model: Option<String>,
343 #[serde(skip_serializing_if = "Option::is_none")]
345 pub content: Option<String>,
346 #[serde(skip_serializing_if = "Option::is_none")]
348 pub error: Option<String>,
349 pub duration_ms: u64,
351}
352
353#[derive(Debug, Serialize)]
359pub struct ModelsResponse {
360 pub object: &'static str,
362 pub data: Vec<ModelObject>,
364}
365
366#[derive(Debug, Serialize)]
368pub struct ModelObject {
369 pub id: String,
371 pub object: &'static str,
373 pub owned_by: String,
375}
376
377#[derive(Debug, Serialize)]
383pub struct HealthResponse {
384 pub status: &'static str,
386 pub providers: std::collections::HashMap<String, String>,
388}
389
390#[derive(Debug, Serialize)]
396pub struct ErrorResponse {
397 pub error: ErrorDetail,
399}
400
401#[derive(Debug, Serialize)]
403pub struct ErrorDetail {
404 pub message: String,
406 #[serde(rename = "type")]
408 pub error_type: String,
409 #[serde(skip_serializing_if = "Option::is_none")]
411 pub param: Option<String>,
412 #[serde(skip_serializing_if = "Option::is_none")]
414 pub code: Option<String>,
415}
416
417impl ErrorResponse {
418 pub fn new(error_type: impl Into<String>, message: impl Into<String>) -> Self {
420 Self {
421 error: ErrorDetail {
422 message: message.into(),
423 error_type: error_type.into(),
424 param: None,
425 code: None,
426 },
427 }
428 }
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434
435 #[test]
436 fn deserialize_single_model() {
437 let json = r#"{"model":"copilot:gpt-4o","messages":[{"role":"user","content":"hi"}]}"#;
438 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
439 match req.model {
440 ModelField::Single(m) => assert_eq!(m, "copilot:gpt-4o"),
441 ModelField::Multiple(_) => panic!("expected single"),
442 }
443 assert!(!req.stream);
444 }
445
446 #[test]
447 fn deserialize_multiple_models() {
448 let json = r#"{"model":["copilot:gpt-4o","claude:opus"],"messages":[{"role":"user","content":"hi"}]}"#;
449 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
450 match req.model {
451 ModelField::Multiple(models) => {
452 assert_eq!(models.len(), 2);
453 assert_eq!(models[0], "copilot:gpt-4o");
454 assert_eq!(models[1], "claude:opus");
455 }
456 ModelField::Single(_) => panic!("expected multiple"),
457 }
458 }
459
460 #[test]
461 fn deserialize_with_stream_flag() {
462 let json =
463 r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"stream":true}"#;
464 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
465 assert!(req.stream);
466 }
467
468 #[test]
469 fn deserialize_message_with_null_content() {
470 let json = r#"{"model":"copilot","messages":[{"role":"assistant","content":null,"tool_calls":[{"id":"call_1","type":"function","function":{"name":"search","arguments":"{}"}}]}]}"#;
471 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
472 assert!(req.messages[0].content.is_none());
473 assert!(req.messages[0].tool_calls.is_some());
474 }
475
476 #[test]
477 fn deserialize_message_without_content_field() {
478 let json = r#"{"model":"copilot","messages":[{"role":"tool","tool_call_id":"call_1","name":"search","content":"{\"result\":\"found\"}"}]}"#;
479 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
480 assert_eq!(req.messages[0].role, "tool");
481 assert_eq!(req.messages[0].tool_call_id.as_deref(), Some("call_1"));
482 assert_eq!(req.messages[0].name.as_deref(), Some("search"));
483 }
484
485 #[test]
486 fn deserialize_tool_definitions() {
487 let json = r#"{
488 "model": "copilot",
489 "messages": [{"role": "user", "content": "hi"}],
490 "tools": [{
491 "type": "function",
492 "function": {
493 "name": "get_weather",
494 "description": "Get weather for a city",
495 "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
496 }
497 }]
498 }"#;
499 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
500 let tools = req.tools.expect("tools present");
501 assert_eq!(tools.len(), 1);
502 assert_eq!(tools[0].tool_type, "function");
503 assert_eq!(tools[0].function.name, "get_weather");
504 assert!(tools[0].function.parameters.is_some());
505 }
506
507 #[test]
508 fn deserialize_tool_choice_auto() {
509 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_choice":"auto"}"#;
510 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
511 match req.tool_choice.expect("tool_choice present") {
512 ToolChoice::Mode(m) => assert_eq!(m, "auto"),
513 ToolChoice::Specific(_) => panic!("expected mode"),
514 }
515 }
516
517 #[test]
518 fn deserialize_tool_choice_specific() {
519 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_choice":{"type":"function","function":{"name":"get_weather"}}}"#;
520 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
521 match req.tool_choice.expect("tool_choice present") {
522 ToolChoice::Specific(s) => assert_eq!(s.function.name, "get_weather"),
523 ToolChoice::Mode(_) => panic!("expected specific"),
524 }
525 }
526
527 #[test]
528 fn serialize_completion_response() {
529 let resp = ChatCompletionResponse {
530 id: "chatcmpl-test".to_owned(),
531 object: "chat.completion",
532 created: 1_700_000_000,
533 model: "copilot:gpt-4o".to_owned(),
534 choices: vec![Choice {
535 index: 0,
536 message: ResponseMessage {
537 role: "assistant",
538 content: Some("Hello!".to_owned()),
539 tool_calls: None,
540 },
541 finish_reason: Some("stop".to_owned()),
542 }],
543 usage: None,
544 warnings: None,
545 };
546 let json = serde_json::to_string(&resp).expect("serialize");
547 assert!(json.contains("chat.completion"));
548 assert!(json.contains("Hello!"));
549 assert!(!json.contains("tool_calls"));
550 }
551
552 #[test]
553 fn serialize_response_with_tool_calls() {
554 let resp = ChatCompletionResponse {
555 id: "chatcmpl-test".to_owned(),
556 object: "chat.completion",
557 created: 1_700_000_000,
558 model: "copilot:gpt-4o".to_owned(),
559 choices: vec![Choice {
560 index: 0,
561 message: ResponseMessage {
562 role: "assistant",
563 content: None,
564 tool_calls: Some(vec![ToolCall {
565 index: 0,
566 id: "call_abc123".to_owned(),
567 tool_type: "function".to_owned(),
568 function: ToolCallFunction {
569 name: "get_weather".to_owned(),
570 arguments: r#"{"city":"Paris"}"#.to_owned(),
571 },
572 }]),
573 },
574 finish_reason: Some("tool_calls".to_owned()),
575 }],
576 usage: None,
577 warnings: None,
578 };
579 let json = serde_json::to_string(&resp).expect("serialize");
580 assert!(json.contains("tool_calls"));
581 assert!(json.contains("call_abc123"));
582 assert!(json.contains("get_weather"));
583 assert!(!json.contains(r#""content""#));
584 }
585
586 #[test]
587 fn serialize_error_response() {
588 let resp = ErrorResponse::new("invalid_request_error", "Unknown model");
589 let json = serde_json::to_string(&resp).expect("serialize");
590 assert!(json.contains("invalid_request_error"));
591 assert!(json.contains("Unknown model"));
592 }
593
594 #[test]
595 fn serialize_chunk_response() {
596 let chunk = ChatCompletionChunk {
597 id: "chatcmpl-test".to_owned(),
598 object: "chat.completion.chunk",
599 created: 1_700_000_000,
600 model: "copilot".to_owned(),
601 choices: vec![ChunkChoice {
602 index: 0,
603 delta: Delta {
604 role: None,
605 content: Some("token".to_owned()),
606 tool_calls: None,
607 },
608 finish_reason: None,
609 }],
610 };
611 let json = serde_json::to_string(&chunk).expect("serialize");
612 assert!(json.contains("chat.completion.chunk"));
613 assert!(json.contains("token"));
614 assert!(!json.contains("tool_calls"));
615 }
616
617 #[test]
618 fn deserialize_tool_choice_none() {
619 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_choice":"none"}"#;
620 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
621 match req.tool_choice.expect("tool_choice present") {
622 ToolChoice::Mode(m) => assert_eq!(m, "none"),
623 ToolChoice::Specific(_) => panic!("expected mode"),
624 }
625 }
626
627 #[test]
628 fn deserialize_tool_choice_required() {
629 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_choice":"required"}"#;
630 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
631 match req.tool_choice.expect("tool_choice present") {
632 ToolChoice::Mode(m) => assert_eq!(m, "required"),
633 ToolChoice::Specific(_) => panic!("expected mode"),
634 }
635 }
636
637 #[test]
638 fn deserialize_response_format_text() {
639 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"response_format":{"type":"text"}}"#;
640 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
641 assert!(matches!(
642 req.response_format,
643 Some(ResponseFormatRequest::Text)
644 ));
645 }
646
647 #[test]
648 fn deserialize_response_format_json_object() {
649 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"response_format":{"type":"json_object"}}"#;
650 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
651 assert!(matches!(
652 req.response_format,
653 Some(ResponseFormatRequest::JsonObject)
654 ));
655 }
656
657 #[test]
658 fn deserialize_response_format_json_schema() {
659 let json = r#"{
660 "model": "copilot",
661 "messages": [{"role": "user", "content": "hi"}],
662 "response_format": {
663 "type": "json_schema",
664 "json_schema": {
665 "name": "weather",
666 "schema": {"type": "object", "properties": {"temp": {"type": "number"}}}
667 }
668 }
669 }"#;
670 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
671 match req.response_format {
672 Some(ResponseFormatRequest::JsonSchema { json_schema }) => {
673 assert_eq!(json_schema.name, "weather");
674 assert!(json_schema.schema["properties"]["temp"].is_object());
675 }
676 other => panic!("expected JsonSchema, got: {other:?}"),
677 }
678 }
679
680 #[test]
681 fn deserialize_top_p() {
682 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"top_p":0.9}"#;
683 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
684 assert_eq!(req.top_p, Some(0.9));
685 }
686
687 #[test]
688 fn deserialize_stop_single() {
689 let json =
690 r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"stop":"END"}"#;
691 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
692 let stop = req.stop.expect("stop present");
693 assert_eq!(stop.into_vec(), vec!["END"]);
694 }
695
696 #[test]
697 fn deserialize_stop_array() {
698 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"stop":["END","STOP"]}"#;
699 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
700 let stop = req.stop.expect("stop present");
701 assert_eq!(stop.into_vec(), vec!["END", "STOP"]);
702 }
703
704 #[test]
705 fn deserialize_all_optional_fields() {
706 let json = r#"{
707 "model": "copilot",
708 "messages": [{"role": "user", "content": "hi"}],
709 "temperature": 0.7,
710 "max_tokens": 100,
711 "top_p": 0.95,
712 "stop": ["END"],
713 "stream": true
714 }"#;
715 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
716 assert_eq!(req.temperature, Some(0.7));
717 assert_eq!(req.max_tokens, Some(100));
718 assert_eq!(req.top_p, Some(0.95));
719 assert!(req.stop.is_some());
720 assert!(req.stream);
721 }
722
723 #[test]
724 fn serialize_models_response() {
725 let resp = ModelsResponse {
726 object: "list",
727 data: vec![ModelObject {
728 id: "copilot:gpt-4o".to_owned(),
729 object: "model",
730 owned_by: "copilot".to_owned(),
731 }],
732 };
733 let json = serde_json::to_string(&resp).expect("serialize");
734 assert!(json.contains("copilot:gpt-4o"));
735 }
736}