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}
43
44#[derive(Debug, Clone, Deserialize)]
46#[serde(untagged)]
47pub enum ModelField {
48 Single(String),
50 Multiple(Vec<String>),
52}
53
54#[derive(Debug, Clone, Deserialize)]
56pub struct ChatCompletionMessage {
57 pub role: String,
59 pub content: Option<String>,
61 #[serde(default)]
63 pub tool_calls: Option<Vec<ToolCall>>,
64 #[serde(default)]
66 pub tool_call_id: Option<String>,
67 #[serde(default)]
69 pub name: Option<String>,
70}
71
72#[derive(Debug, Clone, Deserialize)]
78pub struct ToolDefinition {
79 #[serde(rename = "type")]
81 pub tool_type: String,
82 pub function: FunctionObject,
84}
85
86#[derive(Debug, Clone, Deserialize)]
88pub struct FunctionObject {
89 pub name: String,
91 #[serde(default)]
93 pub description: Option<String>,
94 #[serde(default)]
96 pub parameters: Option<serde_json::Value>,
97}
98
99#[derive(Debug, Clone, Deserialize)]
101#[serde(untagged)]
102pub enum ToolChoice {
103 Mode(String),
105 Specific(ToolChoiceSpecific),
107}
108
109#[derive(Debug, Clone, Deserialize)]
111pub struct ToolChoiceSpecific {
112 #[serde(rename = "type")]
114 pub tool_type: String,
115 pub function: ToolChoiceFunction,
117}
118
119#[derive(Debug, Clone, Deserialize)]
121pub struct ToolChoiceFunction {
122 pub name: String,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct ToolCall {
129 #[serde(default)]
131 pub index: usize,
132 pub id: String,
134 #[serde(rename = "type")]
136 pub tool_type: String,
137 pub function: ToolCallFunction,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct ToolCallFunction {
144 pub name: String,
146 pub arguments: String,
148}
149
150#[derive(Debug, Serialize)]
156pub struct ChatCompletionResponse {
157 pub id: String,
159 pub object: &'static str,
161 pub created: u64,
163 pub model: String,
165 pub choices: Vec<Choice>,
167 #[serde(skip_serializing_if = "Option::is_none")]
169 pub usage: Option<Usage>,
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub warnings: Option<Vec<String>>,
173}
174
175#[derive(Debug, Serialize)]
177pub struct Choice {
178 pub index: u32,
180 pub message: ResponseMessage,
182 pub finish_reason: Option<String>,
184}
185
186#[derive(Debug, Serialize)]
188pub struct ResponseMessage {
189 pub role: &'static str,
191 #[serde(skip_serializing_if = "Option::is_none")]
193 pub content: Option<String>,
194 #[serde(skip_serializing_if = "Option::is_none")]
196 pub tool_calls: Option<Vec<ToolCall>>,
197}
198
199#[derive(Debug, Serialize)]
201pub struct Usage {
202 #[serde(rename = "prompt_tokens")]
204 pub prompt: u32,
205 #[serde(rename = "completion_tokens")]
207 pub completion: u32,
208 #[serde(rename = "total_tokens")]
210 pub total: u32,
211}
212
213#[derive(Debug, Serialize)]
219pub struct ChatCompletionChunk {
220 pub id: String,
222 pub object: &'static str,
224 pub created: u64,
226 pub model: String,
228 pub choices: Vec<ChunkChoice>,
230}
231
232#[derive(Debug, Serialize)]
234pub struct ChunkChoice {
235 pub index: u32,
237 pub delta: Delta,
239 pub finish_reason: Option<String>,
241}
242
243#[derive(Debug, Serialize)]
245pub struct Delta {
246 #[serde(skip_serializing_if = "Option::is_none")]
248 pub role: Option<&'static str>,
249 #[serde(skip_serializing_if = "Option::is_none")]
251 pub content: Option<String>,
252 #[serde(skip_serializing_if = "Option::is_none")]
254 pub tool_calls: Option<Vec<ToolCall>>,
255}
256
257#[derive(Debug, Serialize)]
263pub struct MultiplexResponse {
264 pub id: String,
266 pub object: &'static str,
268 pub created: u64,
270 pub results: Vec<MultiplexProviderResult>,
272 pub summary: String,
274}
275
276#[derive(Debug, Serialize)]
278pub struct MultiplexProviderResult {
279 pub provider: String,
281 #[serde(skip_serializing_if = "Option::is_none")]
283 pub model: Option<String>,
284 #[serde(skip_serializing_if = "Option::is_none")]
286 pub content: Option<String>,
287 #[serde(skip_serializing_if = "Option::is_none")]
289 pub error: Option<String>,
290 pub duration_ms: u64,
292}
293
294#[derive(Debug, Serialize)]
300pub struct ModelsResponse {
301 pub object: &'static str,
303 pub data: Vec<ModelObject>,
305}
306
307#[derive(Debug, Serialize)]
309pub struct ModelObject {
310 pub id: String,
312 pub object: &'static str,
314 pub owned_by: String,
316}
317
318#[derive(Debug, Serialize)]
324pub struct HealthResponse {
325 pub status: &'static str,
327 pub providers: std::collections::HashMap<String, String>,
329}
330
331#[derive(Debug, Serialize)]
337pub struct ErrorResponse {
338 pub error: ErrorDetail,
340}
341
342#[derive(Debug, Serialize)]
344pub struct ErrorDetail {
345 pub message: String,
347 #[serde(rename = "type")]
349 pub error_type: String,
350 #[serde(skip_serializing_if = "Option::is_none")]
352 pub param: Option<String>,
353 #[serde(skip_serializing_if = "Option::is_none")]
355 pub code: Option<String>,
356}
357
358impl ErrorResponse {
359 pub fn new(error_type: impl Into<String>, message: impl Into<String>) -> Self {
361 Self {
362 error: ErrorDetail {
363 message: message.into(),
364 error_type: error_type.into(),
365 param: None,
366 code: None,
367 },
368 }
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[test]
377 fn deserialize_single_model() {
378 let json = r#"{"model":"copilot:gpt-4o","messages":[{"role":"user","content":"hi"}]}"#;
379 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
380 match req.model {
381 ModelField::Single(m) => assert_eq!(m, "copilot:gpt-4o"),
382 ModelField::Multiple(_) => panic!("expected single"),
383 }
384 assert!(!req.stream);
385 }
386
387 #[test]
388 fn deserialize_multiple_models() {
389 let json = r#"{"model":["copilot:gpt-4o","claude:opus"],"messages":[{"role":"user","content":"hi"}]}"#;
390 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
391 match req.model {
392 ModelField::Multiple(models) => {
393 assert_eq!(models.len(), 2);
394 assert_eq!(models[0], "copilot:gpt-4o");
395 assert_eq!(models[1], "claude:opus");
396 }
397 ModelField::Single(_) => panic!("expected multiple"),
398 }
399 }
400
401 #[test]
402 fn deserialize_with_stream_flag() {
403 let json =
404 r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"stream":true}"#;
405 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
406 assert!(req.stream);
407 }
408
409 #[test]
410 fn deserialize_message_with_null_content() {
411 let json = r#"{"model":"copilot","messages":[{"role":"assistant","content":null,"tool_calls":[{"id":"call_1","type":"function","function":{"name":"search","arguments":"{}"}}]}]}"#;
412 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
413 assert!(req.messages[0].content.is_none());
414 assert!(req.messages[0].tool_calls.is_some());
415 }
416
417 #[test]
418 fn deserialize_message_without_content_field() {
419 let json = r#"{"model":"copilot","messages":[{"role":"tool","tool_call_id":"call_1","name":"search","content":"{\"result\":\"found\"}"}]}"#;
420 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
421 assert_eq!(req.messages[0].role, "tool");
422 assert_eq!(req.messages[0].tool_call_id.as_deref(), Some("call_1"));
423 assert_eq!(req.messages[0].name.as_deref(), Some("search"));
424 }
425
426 #[test]
427 fn deserialize_tool_definitions() {
428 let json = r#"{
429 "model": "copilot",
430 "messages": [{"role": "user", "content": "hi"}],
431 "tools": [{
432 "type": "function",
433 "function": {
434 "name": "get_weather",
435 "description": "Get weather for a city",
436 "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
437 }
438 }]
439 }"#;
440 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
441 let tools = req.tools.expect("tools present");
442 assert_eq!(tools.len(), 1);
443 assert_eq!(tools[0].tool_type, "function");
444 assert_eq!(tools[0].function.name, "get_weather");
445 assert!(tools[0].function.parameters.is_some());
446 }
447
448 #[test]
449 fn deserialize_tool_choice_auto() {
450 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_choice":"auto"}"#;
451 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
452 match req.tool_choice.expect("tool_choice present") {
453 ToolChoice::Mode(m) => assert_eq!(m, "auto"),
454 ToolChoice::Specific(_) => panic!("expected mode"),
455 }
456 }
457
458 #[test]
459 fn deserialize_tool_choice_specific() {
460 let json = r#"{"model":"copilot","messages":[{"role":"user","content":"hi"}],"tool_choice":{"type":"function","function":{"name":"get_weather"}}}"#;
461 let req: ChatCompletionRequest = serde_json::from_str(json).expect("deserialize");
462 match req.tool_choice.expect("tool_choice present") {
463 ToolChoice::Specific(s) => assert_eq!(s.function.name, "get_weather"),
464 ToolChoice::Mode(_) => panic!("expected specific"),
465 }
466 }
467
468 #[test]
469 fn serialize_completion_response() {
470 let resp = ChatCompletionResponse {
471 id: "chatcmpl-test".to_owned(),
472 object: "chat.completion",
473 created: 1_700_000_000,
474 model: "copilot:gpt-4o".to_owned(),
475 choices: vec![Choice {
476 index: 0,
477 message: ResponseMessage {
478 role: "assistant",
479 content: Some("Hello!".to_owned()),
480 tool_calls: None,
481 },
482 finish_reason: Some("stop".to_owned()),
483 }],
484 usage: None,
485 warnings: None,
486 };
487 let json = serde_json::to_string(&resp).expect("serialize");
488 assert!(json.contains("chat.completion"));
489 assert!(json.contains("Hello!"));
490 assert!(!json.contains("tool_calls"));
491 }
492
493 #[test]
494 fn serialize_response_with_tool_calls() {
495 let resp = ChatCompletionResponse {
496 id: "chatcmpl-test".to_owned(),
497 object: "chat.completion",
498 created: 1_700_000_000,
499 model: "copilot:gpt-4o".to_owned(),
500 choices: vec![Choice {
501 index: 0,
502 message: ResponseMessage {
503 role: "assistant",
504 content: None,
505 tool_calls: Some(vec![ToolCall {
506 index: 0,
507 id: "call_abc123".to_owned(),
508 tool_type: "function".to_owned(),
509 function: ToolCallFunction {
510 name: "get_weather".to_owned(),
511 arguments: r#"{"city":"Paris"}"#.to_owned(),
512 },
513 }]),
514 },
515 finish_reason: Some("tool_calls".to_owned()),
516 }],
517 usage: None,
518 warnings: None,
519 };
520 let json = serde_json::to_string(&resp).expect("serialize");
521 assert!(json.contains("tool_calls"));
522 assert!(json.contains("call_abc123"));
523 assert!(json.contains("get_weather"));
524 assert!(!json.contains(r#""content""#));
525 }
526
527 #[test]
528 fn serialize_error_response() {
529 let resp = ErrorResponse::new("invalid_request_error", "Unknown model");
530 let json = serde_json::to_string(&resp).expect("serialize");
531 assert!(json.contains("invalid_request_error"));
532 assert!(json.contains("Unknown model"));
533 }
534
535 #[test]
536 fn serialize_chunk_response() {
537 let chunk = ChatCompletionChunk {
538 id: "chatcmpl-test".to_owned(),
539 object: "chat.completion.chunk",
540 created: 1_700_000_000,
541 model: "copilot".to_owned(),
542 choices: vec![ChunkChoice {
543 index: 0,
544 delta: Delta {
545 role: None,
546 content: Some("token".to_owned()),
547 tool_calls: None,
548 },
549 finish_reason: None,
550 }],
551 };
552 let json = serde_json::to_string(&chunk).expect("serialize");
553 assert!(json.contains("chat.completion.chunk"));
554 assert!(json.contains("token"));
555 assert!(!json.contains("tool_calls"));
556 }
557
558 #[test]
559 fn serialize_models_response() {
560 let resp = ModelsResponse {
561 object: "list",
562 data: vec![ModelObject {
563 id: "copilot:gpt-4o".to_owned(),
564 object: "model",
565 owned_by: "copilot".to_owned(),
566 }],
567 };
568 let json = serde_json::to_string(&resp).expect("serialize");
569 assert!(json.contains("copilot:gpt-4o"));
570 }
571}