1use std::collections::HashMap;
8
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Deserialize)]
21pub struct ChatCompletionRequest {
22 pub model: ModelField,
24 pub messages: Vec<ChatCompletionMessage>,
26 #[serde(default)]
28 pub stream: bool,
29 #[serde(default)]
31 pub temperature: Option<f32>,
32 #[serde(default)]
34 pub max_tokens: Option<u32>,
35 #[serde(default)]
37 pub strict_capabilities: Option<bool>,
38 #[serde(default)]
40 pub tools: Option<Vec<ToolDefinition>>,
41 #[serde(default)]
43 pub tool_choice: Option<ToolChoice>,
44 #[serde(default)]
50 pub tool_execution: Option<ToolExecutionMode>,
51 #[serde(default)]
53 pub response_format: Option<ResponseFormatRequest>,
54 #[serde(default)]
56 pub top_p: Option<f32>,
57 #[serde(default)]
59 pub stop: Option<StopField>,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
64#[serde(rename_all = "lowercase")]
65pub enum ToolExecutionMode {
66 Client,
68 Server,
70}
71
72#[derive(Debug, Clone, Deserialize)]
77#[serde(untagged)]
78pub enum StopField {
79 Single(String),
81 Multiple(Vec<String>),
83}
84
85const MAX_STOP_SEQUENCES: usize = 4;
87
88impl StopField {
89 pub const fn len(&self) -> usize {
91 match self {
92 Self::Single(_) => 1,
93 Self::Multiple(v) => v.len(),
94 }
95 }
96
97 pub const fn is_empty(&self) -> bool {
99 matches!(self, Self::Multiple(v) if v.is_empty())
100 }
101
102 pub fn into_vec(self) -> Vec<String> {
105 match self {
106 Self::Single(s) => vec![s],
107 Self::Multiple(v) => v.into_iter().take(MAX_STOP_SEQUENCES).collect(),
108 }
109 }
110
111 pub fn to_bounded_vec(&self) -> Vec<String> {
114 match self {
115 Self::Single(s) => vec![s.clone()],
116 Self::Multiple(v) => v.iter().take(MAX_STOP_SEQUENCES).cloned().collect(),
117 }
118 }
119}
120
121#[derive(Debug, Clone, Deserialize)]
123#[serde(tag = "type")]
124pub enum ResponseFormatRequest {
125 #[serde(rename = "text")]
127 Text,
128 #[serde(rename = "json_object")]
130 JsonObject,
131 #[serde(rename = "json_schema")]
133 JsonSchema {
134 json_schema: JsonSchemaSpec,
136 },
137}
138
139#[derive(Debug, Clone, Deserialize)]
141pub struct JsonSchemaSpec {
142 pub name: String,
144 pub schema: serde_json::Value,
146}
147
148#[derive(Debug, Clone, Deserialize)]
150#[serde(untagged)]
151pub enum ModelField {
152 Single(String),
154 Multiple(Vec<String>),
156}
157
158#[derive(Debug, Clone, Deserialize)]
163#[serde(untagged)]
164pub enum MessageContent {
165 Text(String),
167 Parts(Vec<ContentPart>),
169}
170
171impl MessageContent {
172 pub fn as_text(&self) -> String {
174 match self {
175 Self::Text(s) => s.clone(),
176 Self::Parts(parts) => parts
177 .iter()
178 .filter_map(|p| match p {
179 ContentPart::Text { text } => Some(text.as_str()),
180 ContentPart::ImageUrl { .. } => None,
181 })
182 .collect::<Vec<_>>()
183 .join(""),
184 }
185 }
186}
187
188#[derive(Debug, Clone, Deserialize)]
190#[serde(tag = "type")]
191pub enum ContentPart {
192 #[serde(rename = "text")]
194 Text {
195 text: String,
197 },
198 #[serde(rename = "image_url")]
200 ImageUrl {
201 image_url: ImageUrlDetail,
203 },
204}
205
206#[derive(Debug, Clone, Deserialize)]
208pub struct ImageUrlDetail {
209 pub url: String,
211}
212
213#[derive(Debug, Clone, Deserialize)]
215pub struct ChatCompletionMessage {
216 pub role: String,
218 pub content: Option<MessageContent>,
220 #[serde(default)]
222 pub tool_calls: Option<Vec<ToolCall>>,
223 #[serde(default)]
225 pub tool_call_id: Option<String>,
226 #[serde(default)]
228 pub name: Option<String>,
229}
230
231#[derive(Debug, Clone, Deserialize)]
237pub struct ToolDefinition {
238 #[serde(rename = "type")]
240 pub tool_type: String,
241 pub function: FunctionObject,
243}
244
245#[derive(Debug, Clone, Deserialize)]
247pub struct FunctionObject {
248 pub name: String,
250 #[serde(default)]
252 pub description: Option<String>,
253 #[serde(default)]
255 pub parameters: Option<serde_json::Value>,
256}
257
258#[derive(Debug, Clone, Deserialize)]
260#[serde(untagged)]
261pub enum ToolChoice {
262 Mode(String),
264 Specific(ToolChoiceSpecific),
266}
267
268#[derive(Debug, Clone, Deserialize)]
270pub struct ToolChoiceSpecific {
271 #[serde(rename = "type")]
273 pub tool_type: String,
274 pub function: ToolChoiceFunction,
276}
277
278#[derive(Debug, Clone, Deserialize)]
280pub struct ToolChoiceFunction {
281 pub name: String,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct ToolCall {
288 #[serde(default)]
290 pub index: usize,
291 pub id: String,
293 #[serde(rename = "type")]
295 pub tool_type: String,
296 pub function: ToolCallFunction,
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct ToolCallFunction {
303 pub name: String,
305 pub arguments: String,
307}
308
309#[derive(Debug, Serialize)]
315pub struct ChatCompletionResponse {
316 pub id: String,
318 pub object: &'static str,
320 pub created: u64,
322 pub model: String,
324 pub choices: Vec<Choice>,
326 #[serde(skip_serializing_if = "Option::is_none")]
328 pub usage: Option<Usage>,
329 #[serde(skip_serializing_if = "Option::is_none")]
331 pub warnings: Option<Vec<String>>,
332}
333
334#[derive(Debug, Serialize)]
336pub struct Choice {
337 pub index: u32,
339 pub message: ResponseMessage,
341 pub finish_reason: Option<String>,
343}
344
345#[derive(Debug, Serialize)]
347pub struct ResponseMessage {
348 pub role: &'static str,
350 #[serde(skip_serializing_if = "Option::is_none")]
352 pub content: Option<String>,
353 #[serde(skip_serializing_if = "Option::is_none")]
355 pub tool_calls: Option<Vec<ToolCall>>,
356}
357
358#[derive(Debug, Serialize)]
360pub struct Usage {
361 #[serde(rename = "prompt_tokens")]
363 pub prompt: u32,
364 #[serde(rename = "completion_tokens")]
366 pub completion: u32,
367 #[serde(rename = "total_tokens")]
369 pub total: u32,
370}
371
372#[derive(Debug, Serialize)]
378pub struct ChatCompletionChunk {
379 pub id: String,
381 pub object: &'static str,
383 pub created: u64,
385 pub model: String,
387 pub choices: Vec<ChunkChoice>,
389}
390
391#[derive(Debug, Serialize)]
393pub struct ChunkChoice {
394 pub index: u32,
396 pub delta: Delta,
398 pub finish_reason: Option<String>,
400}
401
402#[derive(Debug, Serialize)]
404pub struct Delta {
405 #[serde(skip_serializing_if = "Option::is_none")]
407 pub role: Option<&'static str>,
408 #[serde(skip_serializing_if = "Option::is_none")]
410 pub content: Option<String>,
411 #[serde(skip_serializing_if = "Option::is_none")]
413 pub tool_calls: Option<Vec<ToolCall>>,
414}
415
416#[derive(Debug, Serialize)]
422pub struct MultiplexResponse {
423 pub id: String,
425 pub object: &'static str,
427 pub created: u64,
429 pub results: Vec<MultiplexProviderResult>,
431 pub summary: String,
433}
434
435#[derive(Debug, Serialize)]
437pub struct MultiplexProviderResult {
438 pub provider: String,
440 #[serde(skip_serializing_if = "Option::is_none")]
442 pub model: Option<String>,
443 #[serde(skip_serializing_if = "Option::is_none")]
445 pub content: Option<String>,
446 #[serde(skip_serializing_if = "Option::is_none")]
448 pub error: Option<String>,
449 pub duration_ms: u64,
451}
452
453#[derive(Debug, Serialize)]
459pub struct ModelsResponse {
460 pub object: &'static str,
462 pub data: Vec<ModelObject>,
464}
465
466#[derive(Debug, Serialize)]
468pub struct ModelObject {
469 pub id: String,
471 pub object: &'static str,
473 pub owned_by: String,
475}
476
477#[derive(Debug, Serialize)]
483pub struct HealthResponse {
484 pub status: &'static str,
486 pub providers: HashMap<String, String>,
488}
489
490#[derive(Debug, Serialize)]
496pub struct ErrorResponse {
497 pub error: ErrorDetail,
499}
500
501#[derive(Debug, Serialize)]
503pub struct ErrorDetail {
504 pub message: String,
506 #[serde(rename = "type")]
508 pub error_type: String,
509 #[serde(skip_serializing_if = "Option::is_none")]
511 pub param: Option<String>,
512 #[serde(skip_serializing_if = "Option::is_none")]
514 pub code: Option<String>,
515}
516
517impl ErrorResponse {
518 pub fn new(error_type: impl Into<String>, message: impl Into<String>) -> Self {
520 Self {
521 error: ErrorDetail {
522 message: message.into(),
523 error_type: error_type.into(),
524 param: None,
525 code: None,
526 },
527 }
528 }
529}
530
531#[cfg(test)]
532mod tests {
533 use super::*;
534
535 #[test]
536 fn deserialize_single_model() {
537 let json = r#"{"model":"copilot:gpt-4o","messages":[{"role":"user","content":"hi"}]}"#;
538 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); match req.model {
540 ModelField::Single(m) => assert_eq!(m, "copilot:gpt-4o"),
541 ModelField::Multiple(_) => unreachable!("expected single"), }
543 assert!(!req.stream);
544 }
545
546 #[test]
547 fn deserialize_multiple_models() {
548 let json = r#"{"model":["copilot:gpt-4o","claude:opus"],"messages":[{"role":"user","content":"hi"}]}"#;
549 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); match req.model {
551 ModelField::Multiple(models) => {
552 assert_eq!(models.len(), 2);
553 assert_eq!(models[0], "copilot:gpt-4o");
554 assert_eq!(models[1], "claude:opus");
555 }
556 ModelField::Single(_) => unreachable!("expected multiple"), }
558 }
559
560 #[test]
561 fn deserialize_with_stream_flag() {
562 let json =
563 r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"stream":true}"#;
564 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); assert!(req.stream);
566 }
567
568 #[test]
569 fn deserialize_message_with_null_content() {
570 let json = r#"{"model":"copilot","messages":[{"role":"assistant","content":null,"tool_calls":[{"id":"call_1","type":"function","function":{"name":"search","arguments":"{}"}}]}]}"#;
571 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); assert!(req.messages[0].content.is_none());
573 assert!(req.messages[0].tool_calls.is_some());
574 }
575
576 #[test]
577 fn deserialize_message_without_content_field() {
578 let json = r#"{"model":"copilot","messages":[{"role":"tool","tool_call_id":"call_1","name":"search","content":"{\"result\":\"found\"}"}]}"#;
579 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); assert_eq!(req.messages[0].role, "tool");
581 assert_eq!(req.messages[0].tool_call_id.as_deref(), Some("call_1"));
582 assert_eq!(req.messages[0].name.as_deref(), Some("search"));
583 }
584
585 #[test]
586 fn deserialize_multipart_content() {
587 let json = r#"{
588 "model": "copilot",
589 "messages": [{
590 "role": "user",
591 "content": [
592 {"type": "text", "text": "What is in this image?"},
593 {"type": "image_url", "image_url": {"url": "data:image/png;base64,aGVsbG8="}}
594 ]
595 }]
596 }"#;
597 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); let content = req.messages[0].content.as_ref().expect("content present"); match content {
600 MessageContent::Parts(parts) => {
601 assert_eq!(parts.len(), 2);
602 assert!(
603 matches!(&parts[0], ContentPart::Text { text } if text == "What is in this image?")
604 );
605 assert!(
606 matches!(&parts[1], ContentPart::ImageUrl { image_url } if image_url.url.contains("base64"))
607 );
608 }
609 MessageContent::Text(_) => unreachable!("expected Parts variant"), }
611 }
612
613 #[test]
614 fn message_content_as_text_plain_string() {
615 let content = MessageContent::Text("hello".to_owned());
616 assert_eq!(content.as_text(), "hello");
617 }
618
619 #[test]
620 fn message_content_as_text_multipart() {
621 let content = MessageContent::Parts(vec![
622 ContentPart::Text {
623 text: "describe ".to_owned(),
624 },
625 ContentPart::ImageUrl {
626 image_url: ImageUrlDetail {
627 url: "data:image/png;base64,abc".to_owned(),
628 },
629 },
630 ContentPart::Text {
631 text: "this image".to_owned(),
632 },
633 ]);
634 assert_eq!(content.as_text(), "describe this image");
635 }
636
637 #[test]
638 fn deserialize_plain_string_content_backward_compat() {
639 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}]}"#;
640 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); let content = req.messages[0].content.as_ref().expect("content present"); match content {
643 MessageContent::Text(s) => assert_eq!(s, "hi"),
644 MessageContent::Parts(_) => unreachable!("expected Text variant"), }
646 }
647
648 #[test]
649 fn deserialize_tool_definitions() {
650 let json = r#"{
651 "model": "copilot",
652 "messages": [{"role": "user", "content": "hi"}],
653 "tools": [{
654 "type": "function",
655 "function": {
656 "name": "get_weather",
657 "description": "Get weather for a city",
658 "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
659 }
660 }]
661 }"#;
662 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); let tools = req.tools.expect("tools present"); assert_eq!(tools.len(), 1);
665 assert_eq!(tools[0].tool_type, "function");
666 assert_eq!(tools[0].function.name, "get_weather");
667 assert!(tools[0].function.parameters.is_some());
668 }
669
670 #[test]
671 fn deserialize_tool_choice_auto() {
672 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_choice":"auto"}"#;
673 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); let tool_choice = req.tool_choice.expect("tool_choice present"); match tool_choice {
676 ToolChoice::Mode(m) => assert_eq!(m, "auto"),
677 ToolChoice::Specific(_) => unreachable!("expected mode"), }
679 }
680
681 #[test]
682 fn deserialize_tool_choice_specific() {
683 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_choice":{"type":"function","function":{"name":"get_weather"}}}"#;
684 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); let tool_choice = req.tool_choice.expect("tool_choice present"); match tool_choice {
687 ToolChoice::Specific(s) => assert_eq!(s.function.name, "get_weather"),
688 ToolChoice::Mode(_) => unreachable!("expected specific"), }
690 }
691
692 #[test]
693 fn serialize_completion_response() {
694 let resp = ChatCompletionResponse {
695 id: "chatcmpl-test".to_owned(),
696 object: "chat.completion",
697 created: 1_700_000_000,
698 model: "copilot:gpt-4o".to_owned(),
699 choices: vec![Choice {
700 index: 0,
701 message: ResponseMessage {
702 role: "assistant",
703 content: Some("Hello!".to_owned()),
704 tool_calls: None,
705 },
706 finish_reason: Some("stop".to_owned()),
707 }],
708 usage: None,
709 warnings: None,
710 };
711 let json = serde_json::to_string(&resp).expect("serialize"); assert!(json.contains("chat.completion"));
713 assert!(json.contains("Hello!"));
714 assert!(!json.contains("tool_calls"));
715 }
716
717 #[test]
718 fn serialize_response_with_tool_calls() {
719 let resp = ChatCompletionResponse {
720 id: "chatcmpl-test".to_owned(),
721 object: "chat.completion",
722 created: 1_700_000_000,
723 model: "copilot:gpt-4o".to_owned(),
724 choices: vec![Choice {
725 index: 0,
726 message: ResponseMessage {
727 role: "assistant",
728 content: None,
729 tool_calls: Some(vec![ToolCall {
730 index: 0,
731 id: "call_abc123".to_owned(),
732 tool_type: "function".to_owned(),
733 function: ToolCallFunction {
734 name: "get_weather".to_owned(),
735 arguments: r#"{"city":"Paris"}"#.to_owned(),
736 },
737 }]),
738 },
739 finish_reason: Some("tool_calls".to_owned()),
740 }],
741 usage: None,
742 warnings: None,
743 };
744 let json = serde_json::to_string(&resp).expect("serialize"); assert!(json.contains("tool_calls"));
746 assert!(json.contains("call_abc123"));
747 assert!(json.contains("get_weather"));
748 assert!(!json.contains(r#""content""#));
749 }
750
751 #[test]
752 fn serialize_error_response() {
753 let resp = ErrorResponse::new("invalid_request_error", "Unknown model");
754 let json = serde_json::to_string(&resp).expect("serialize"); assert!(json.contains("invalid_request_error"));
756 assert!(json.contains("Unknown model"));
757 }
758
759 #[test]
760 fn serialize_chunk_response() {
761 let chunk = ChatCompletionChunk {
762 id: "chatcmpl-test".to_owned(),
763 object: "chat.completion.chunk",
764 created: 1_700_000_000,
765 model: "copilot".to_owned(),
766 choices: vec![ChunkChoice {
767 index: 0,
768 delta: Delta {
769 role: None,
770 content: Some("token".to_owned()),
771 tool_calls: None,
772 },
773 finish_reason: None,
774 }],
775 };
776 let json = serde_json::to_string(&chunk).expect("serialize"); assert!(json.contains("chat.completion.chunk"));
778 assert!(json.contains("token"));
779 assert!(!json.contains("tool_calls"));
780 }
781
782 #[test]
783 fn deserialize_tool_execution_server() {
784 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_execution":"server"}"#;
785 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); assert_eq!(req.tool_execution, Some(ToolExecutionMode::Server));
787 }
788
789 #[test]
790 fn deserialize_tool_execution_client() {
791 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_execution":"client"}"#;
792 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); assert_eq!(req.tool_execution, Some(ToolExecutionMode::Client));
794 }
795
796 #[test]
797 fn tool_execution_defaults_to_none() {
798 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}]}"#;
799 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); assert_eq!(req.tool_execution, None);
801 }
802
803 #[test]
804 fn deserialize_tool_choice_none() {
805 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_choice":"none"}"#;
806 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); let tool_choice = req.tool_choice.expect("tool_choice present"); match tool_choice {
809 ToolChoice::Mode(m) => assert_eq!(m, "none"),
810 ToolChoice::Specific(_) => unreachable!("expected mode"), }
812 }
813
814 #[test]
815 fn deserialize_tool_choice_required() {
816 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_choice":"required"}"#;
817 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); let tool_choice = req.tool_choice.expect("tool_choice present"); match tool_choice {
820 ToolChoice::Mode(m) => assert_eq!(m, "required"),
821 ToolChoice::Specific(_) => unreachable!("expected mode"), }
823 }
824
825 #[test]
826 fn deserialize_response_format_text() {
827 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"response_format":{"type":"text"}}"#;
828 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); assert!(matches!(
830 req.response_format,
831 Some(ResponseFormatRequest::Text)
832 ));
833 }
834
835 #[test]
836 fn deserialize_response_format_json_object() {
837 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"response_format":{"type":"json_object"}}"#;
838 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); assert!(matches!(
840 req.response_format,
841 Some(ResponseFormatRequest::JsonObject)
842 ));
843 }
844
845 #[test]
846 fn deserialize_response_format_json_schema() {
847 let json = r#"{
848 "model": "copilot",
849 "messages": [{"role": "user", "content": "hi"}],
850 "response_format": {
851 "type": "json_schema",
852 "json_schema": {
853 "name": "weather",
854 "schema": {"type": "object", "properties": {"temp": {"type": "number"}}}
855 }
856 }
857 }"#;
858 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); match req.response_format {
860 Some(ResponseFormatRequest::JsonSchema { json_schema }) => {
861 assert_eq!(json_schema.name, "weather");
862 assert!(json_schema.schema["properties"]["temp"].is_object());
863 }
864 other => unreachable!("expected JsonSchema, got: {other:?}"), }
866 }
867
868 #[test]
869 fn deserialize_top_p() {
870 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"top_p":0.9}"#;
871 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); assert_eq!(req.top_p, Some(0.9));
873 }
874
875 #[test]
876 fn deserialize_stop_single() {
877 let json =
878 r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"stop":"END"}"#;
879 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); let stop = req.stop.expect("stop present"); assert_eq!(stop.into_vec(), vec!["END"]);
882 }
883
884 #[test]
885 fn deserialize_stop_array() {
886 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"stop":["END","STOP"]}"#;
887 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); let stop = req.stop.expect("stop present"); assert_eq!(stop.into_vec(), vec!["END", "STOP"]);
890 }
891
892 #[test]
893 fn stop_field_len() {
894 let single = StopField::Single("END".to_owned());
895 assert_eq!(single.len(), 1);
896 let multiple = StopField::Multiple(vec!["A".to_owned(), "B".to_owned(), "C".to_owned()]);
897 assert_eq!(multiple.len(), 3);
898 }
899
900 #[test]
901 fn stop_field_into_vec_truncates_at_four() {
902 let oversized = StopField::Multiple((0..10).map(|i| format!("stop_{i}")).collect());
903 let result = oversized.into_vec();
904 assert_eq!(result.len(), 4);
905 assert_eq!(result[0], "stop_0");
906 assert_eq!(result[3], "stop_3");
907 }
908
909 #[test]
910 fn deserialize_all_optional_fields() {
911 let json = r#"{
912 "model": "copilot",
913 "messages": [{"role": "user", "content": "hi"}],
914 "temperature": 0.7,
915 "max_tokens": 100,
916 "top_p": 0.95,
917 "stop": ["END"],
918 "stream": true
919 }"#;
920 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize"); assert_eq!(req.temperature, Some(0.7));
922 assert_eq!(req.max_tokens, Some(100));
923 assert_eq!(req.top_p, Some(0.95));
924 assert!(req.stop.is_some());
925 assert!(req.stream);
926 }
927
928 #[test]
929 fn serialize_models_response() {
930 let resp = ModelsResponse {
931 object: "list",
932 data: vec![ModelObject {
933 id: "copilot:gpt-4o".to_owned(),
934 object: "model",
935 owned_by: "copilot".to_owned(),
936 }],
937 };
938 let json = serde_json::to_string(&resp).expect("serialize"); assert!(json.contains("copilot:gpt-4o"));
940 }
941}