1use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11pub use crate::core::types::codex::wire::{
12 CodexCustomTool, CodexCustomToolCall, CodexCustomToolCallOutput, CodexFunctionCall,
13 CodexFunctionCallOutput, CodexInternalChatMessageMetadataPassthrough, CodexUnsupportedWire,
14};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct ResponsesApiRequest {
21 pub model: String,
23
24 pub input: ResponseInput,
26
27 #[serde(skip_serializing_if = "Option::is_none")]
30 pub instructions: Option<String>,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub previous_response_id: Option<String>,
35
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub store: Option<bool>,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub tools: Option<Vec<ResponseTool>>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub additional_tools: Option<Vec<ResponseTool>>,
46
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub stream: Option<bool>,
50
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub background: Option<bool>,
54
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub max_output_tokens: Option<u32>,
58
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub temperature: Option<f32>,
62
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub top_p: Option<f32>,
66
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub user: Option<String>,
70
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub reasoning: Option<ReasoningParams>,
74
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub metadata: Option<std::collections::HashMap<String, String>>,
78
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub truncation: Option<String>,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86#[serde(untagged)]
87pub enum ResponseInput {
88 Text(String),
90 Items(Vec<ResponseInputItem>),
92}
93
94#[derive(Debug, Clone)]
96pub enum ResponseInputItem {
97 Message(ResponseInputMessage),
99 FunctionCall(CodexFunctionCall),
101 FunctionCallOutput(CodexFunctionCallOutput),
103 CustomToolCall(CodexCustomToolCall),
105 CustomToolCallOutput(CodexCustomToolCallOutput),
107 Unsupported(CodexUnsupportedWire),
108 Unknown(CodexUnsupportedWire),
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct ResponseInputMessage {
114 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub id: Option<String>,
117 #[serde(default, skip_serializing_if = "Option::is_none")]
118 pub phase: Option<String>,
119 #[serde(default, skip_serializing_if = "Option::is_none")]
120 pub internal_chat_message_metadata_passthrough:
121 Option<CodexInternalChatMessageMetadataPassthrough>,
122 pub role: String,
124 pub content: ResponseInputContent,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130#[serde(untagged)]
131pub enum ResponseInputContent {
132 Text(String),
134 Parts(Vec<ResponseInputContentPart>),
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140#[serde(tag = "type", rename_all = "snake_case")]
141pub enum ResponseInputContentPart {
142 InputText { text: String },
144 InputImage {
146 #[serde(skip_serializing_if = "Option::is_none")]
147 image_url: Option<String>,
148 #[serde(skip_serializing_if = "Option::is_none")]
149 detail: Option<String>,
150 },
151 InputAudio { audio_url: String },
153 OutputText { text: String },
155}
156
157#[derive(Debug, Clone)]
161pub enum ResponseTool {
162 WebSearch(WebSearchTool),
164 WebSearchPreview(WebSearchTool),
166 FileSearch(FileSearchTool),
168 CodeInterpreter(CodeInterpreterTool),
170 ComputerUsePreview(ComputerUseTool),
172 Mcp(McpTool),
174 Function(ResponseFunctionTool),
176 CodexFunction(ResponseFunctionDefinition),
177 Custom(CodexCustomTool),
179 Unsupported(CodexUnsupportedWire),
180 Unknown(CodexUnsupportedWire),
181}
182
183impl ResponseTool {
184 pub fn feature_name(&self) -> &str {
185 match self {
186 Self::WebSearch(_) | Self::WebSearchPreview(_) => "web_search",
187 Self::FileSearch(_) => "file_search",
188 Self::CodeInterpreter(_) => "code_interpreter",
189 Self::ComputerUsePreview(_) => "computer_use",
190 Self::Mcp(_) => "mcp",
191 Self::Function(_) | Self::CodexFunction(_) => "function",
192 Self::Custom(_) => "custom",
193 Self::Unsupported(value) | Self::Unknown(value) => &value.wire_type,
194 }
195 }
196}
197
198#[derive(Debug, Clone, Default, Serialize, Deserialize)]
200pub struct WebSearchTool {
201 #[serde(skip_serializing_if = "Option::is_none")]
203 pub user_location: Option<Value>,
204 #[serde(skip_serializing_if = "Option::is_none")]
206 pub search_context_size: Option<String>,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct FileSearchTool {
212 #[serde(skip_serializing_if = "Option::is_none")]
214 pub vector_store_ids: Option<Vec<String>>,
215 #[serde(skip_serializing_if = "Option::is_none")]
217 pub max_num_results: Option<u32>,
218 #[serde(skip_serializing_if = "Option::is_none")]
220 pub ranking_options: Option<Value>,
221 #[serde(skip_serializing_if = "Option::is_none")]
223 pub filters: Option<Value>,
224}
225
226#[derive(Debug, Clone, Default, Serialize, Deserialize)]
228pub struct CodeInterpreterTool {
229 #[serde(skip_serializing_if = "Option::is_none")]
231 pub container: Option<Value>,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct ComputerUseTool {
237 pub display_width: u32,
239 pub display_height: u32,
241 pub environment: String,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct McpTool {
248 pub server_label: String,
250 pub server_url: String,
252 #[serde(skip_serializing_if = "Option::is_none")]
254 pub allowed_tools: Option<Vec<String>>,
255 #[serde(skip_serializing_if = "Option::is_none")]
257 pub headers: Option<std::collections::HashMap<String, String>>,
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub require_approval: Option<Value>,
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct ResponseFunctionTool {
266 pub function: ResponseFunctionDefinition,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct ResponseFunctionDefinition {
273 pub name: String,
275 #[serde(skip_serializing_if = "Option::is_none")]
277 pub description: Option<String>,
278 #[serde(skip_serializing_if = "Option::is_none")]
280 pub parameters: Option<Value>,
281 #[serde(skip_serializing_if = "Option::is_none")]
283 pub strict: Option<bool>,
284 #[serde(skip_serializing_if = "Option::is_none")]
285 pub defer_loading: Option<bool>,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct ReasoningParams {
291 #[serde(skip_serializing_if = "Option::is_none")]
293 pub effort: Option<String>,
294 #[serde(skip_serializing_if = "Option::is_none")]
296 pub summary: Option<String>,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct ResponsesApiResponse {
304 pub id: String,
306 pub object: String,
308 pub created_at: i64,
310 pub status: String,
312 pub model: String,
314 pub output: Vec<ResponseOutputItem>,
316 #[serde(skip_serializing_if = "Option::is_none")]
318 pub usage: Option<ResponseUsage>,
319 #[serde(skip_serializing_if = "Option::is_none")]
321 pub error: Option<ResponseApiError>,
322 #[serde(skip_serializing_if = "Option::is_none")]
324 pub previous_response_id: Option<String>,
325 #[serde(skip_serializing_if = "Option::is_none")]
327 pub metadata: Option<std::collections::HashMap<String, String>>,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize)]
332#[serde(tag = "type", rename_all = "snake_case")]
333pub enum ResponseOutputItem {
334 Message(ResponseOutputMessage),
336 FunctionCall(ResponseFunctionCall),
338 CustomToolCall(CodexCustomToolCall),
340 WebSearchCall(ResponseToolCall),
342 FileSearchCall(ResponseToolCall),
344 CodeInterpreterCall(ResponseToolCall),
346 ComputerCall(ResponseToolCall),
348 McpCall(ResponseToolCall),
350 Reasoning(ResponseReasoningItem),
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct ResponseOutputMessage {
357 pub id: String,
359 pub role: String,
361 pub status: String,
363 pub content: Vec<ResponseOutputContent>,
365}
366
367#[derive(Debug, Clone, Serialize, Deserialize)]
369#[serde(tag = "type", rename_all = "snake_case")]
370pub enum ResponseOutputContent {
371 OutputText {
373 text: String,
375 #[serde(skip_serializing_if = "Option::is_none")]
377 annotations: Option<Vec<Value>>,
378 #[serde(skip_serializing_if = "Option::is_none")]
380 logprobs: Option<Vec<Value>>,
381 },
382 Refusal {
384 refusal: String,
386 },
387}
388
389#[derive(Debug, Clone, Serialize, Deserialize)]
391pub struct ResponseFunctionCall {
392 pub id: String,
394 pub name: String,
396 pub arguments: String,
398 pub status: String,
400 #[serde(skip_serializing_if = "Option::is_none")]
402 pub call_id: Option<String>,
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct ResponseToolCall {
408 pub id: String,
410 pub status: String,
412 #[serde(flatten)]
414 pub data: Value,
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize)]
419pub struct ResponseReasoningItem {
420 pub id: String,
422 pub status: String,
424 #[serde(skip_serializing_if = "Option::is_none")]
426 pub summary: Option<Vec<Value>>,
427}
428
429#[derive(Debug, Clone, Default, Serialize, Deserialize)]
431pub struct ResponseUsage {
432 pub input_tokens: u32,
434 pub output_tokens: u32,
436 pub total_tokens: u32,
438 #[serde(skip_serializing_if = "Option::is_none")]
440 pub input_tokens_details: Option<ResponseInputTokensDetails>,
441 #[serde(skip_serializing_if = "Option::is_none")]
443 pub output_tokens_details: Option<ResponseOutputTokensDetails>,
444}
445
446#[derive(Debug, Clone, Default, Serialize, Deserialize)]
448pub struct ResponseInputTokensDetails {
449 pub cached_tokens: u32,
451}
452
453#[derive(Debug, Clone, Default, Serialize, Deserialize)]
455pub struct ResponseOutputTokensDetails {
456 pub reasoning_tokens: u32,
458}
459
460#[derive(Debug, Clone, Serialize, Deserialize)]
462pub struct ResponseApiError {
463 pub code: String,
465 pub message: String,
467}
468
469#[derive(Debug, Clone, Serialize, Deserialize)]
473#[serde(tag = "type")]
474pub enum ResponseStreamEvent {
475 #[serde(rename = "response.created")]
477 ResponseCreated { response: Box<ResponsesApiResponse> },
478 #[serde(rename = "response.in_progress")]
480 ResponseInProgress { response: Box<ResponsesApiResponse> },
481 #[serde(rename = "response.output_item.added")]
483 ResponseOutputItemAdded {
484 output_index: u32,
485 item: ResponseOutputItem,
486 },
487 #[serde(rename = "response.content_part.added")]
489 ResponseContentPartAdded {
490 output_index: u32,
491 content_index: u32,
492 part: ResponseOutputContent,
493 },
494 #[serde(rename = "response.output_text.delta")]
496 ResponseOutputTextDelta {
497 output_index: u32,
498 content_index: u32,
499 delta: String,
500 },
501 #[serde(rename = "response.output_text.done")]
503 ResponseOutputTextDone {
504 output_index: u32,
505 content_index: u32,
506 text: String,
507 },
508 #[serde(rename = "response.content_part.done")]
510 ResponseContentPartDone {
511 output_index: u32,
512 content_index: u32,
513 part: ResponseOutputContent,
514 },
515 #[serde(rename = "response.output_item.done")]
517 ResponseOutputItemDone {
518 output_index: u32,
519 item: ResponseOutputItem,
520 },
521 #[serde(rename = "response.completed")]
523 ResponseCompleted { response: Box<ResponsesApiResponse> },
524 #[serde(rename = "response.failed")]
526 ResponseFailed { response: Box<ResponsesApiResponse> },
527 #[serde(rename = "response.function_call_arguments.delta")]
529 ResponseFunctionCallArgumentsDelta {
530 output_index: u32,
531 item_id: String,
532 delta: String,
533 },
534 #[serde(rename = "response.function_call_arguments.done")]
536 ResponseFunctionCallArgumentsDone {
537 output_index: u32,
538 item_id: String,
539 name: String,
540 arguments: String,
541 },
542 #[serde(rename = "response.custom_tool_call_input.delta")]
544 ResponseCustomToolCallInputDelta {
545 output_index: u32,
546 item_id: String,
547 delta: String,
548 },
549 #[serde(rename = "response.custom_tool_call_input.done")]
551 ResponseCustomToolCallInputDone {
552 output_index: u32,
553 item_id: String,
554 input: String,
555 },
556 #[serde(rename = "response.reasoning_summary_text.delta")]
563 ResponseReasoningSummaryTextDelta {
564 output_index: u32,
565 summary_index: u32,
566 delta: String,
567 },
568 #[serde(rename = "response.reasoning_summary_text.done")]
571 ResponseReasoningSummaryTextDone {
572 output_index: u32,
573 summary_index: u32,
574 text: String,
575 },
576 #[serde(other)]
578 Unknown,
579}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584
585 #[test]
586 fn test_request_text_input_deserialise() {
587 let json = r#"{"model":"gpt-4o","input":"Hello"}"#;
588 let req: ResponsesApiRequest = serde_json::from_str(json).unwrap();
589 assert_eq!(req.model, "gpt-4o");
590 assert!(matches!(req.input, ResponseInput::Text(_)));
591 }
592
593 #[test]
594 fn test_request_array_input_deserialise() {
595 let json = r#"{
596 "model": "gpt-4o",
597 "input": [
598 {"type": "message", "role": "user", "content": "Hello"}
599 ]
600 }"#;
601 let req: ResponsesApiRequest = serde_json::from_str(json).unwrap();
602 assert!(matches!(req.input, ResponseInput::Items(_)));
603 }
604
605 #[test]
606 fn test_request_with_instructions() {
607 let json = r#"{"model":"gpt-4o","input":"Hi","instructions":"Be concise"}"#;
608 let req: ResponsesApiRequest = serde_json::from_str(json).unwrap();
609 assert_eq!(req.instructions.as_deref(), Some("Be concise"));
610 }
611
612 #[test]
613 fn test_request_with_previous_response_id() {
614 let json = r#"{"model":"gpt-4o","input":"Follow up","previous_response_id":"resp_abc"}"#;
615 let req: ResponsesApiRequest = serde_json::from_str(json).unwrap();
616 assert_eq!(req.previous_response_id.as_deref(), Some("resp_abc"));
617 }
618
619 #[test]
620 fn test_request_with_store_flag() {
621 let json = r#"{"model":"gpt-4o","input":"Hi","store":true}"#;
622 let req: ResponsesApiRequest = serde_json::from_str(json).unwrap();
623 assert_eq!(req.store, Some(true));
624 }
625
626 #[test]
627 fn test_web_search_tool_deserialise() {
628 let json = r#"{"type":"web_search"}"#;
629 let tool: ResponseTool = serde_json::from_str(json).unwrap();
630 assert!(matches!(tool, ResponseTool::WebSearch(_)));
631 }
632
633 #[test]
634 fn test_file_search_tool_deserialise() {
635 let json = r#"{"type":"file_search","vector_store_ids":["vs_abc"]}"#;
636 let tool: ResponseTool = serde_json::from_str(json).unwrap();
637 assert!(matches!(tool, ResponseTool::FileSearch(_)));
638 }
639
640 #[test]
641 fn test_mcp_tool_deserialise() {
642 let json = r#"{
643 "type": "mcp",
644 "server_label": "my-server",
645 "server_url": "https://example.com/mcp"
646 }"#;
647 let tool: ResponseTool = serde_json::from_str(json).unwrap();
648 assert!(matches!(tool, ResponseTool::Mcp(_)));
649 }
650
651 #[test]
652 fn test_function_tool_deserialise() {
653 let json = r#"{
654 "type": "function",
655 "function": {"name": "get_weather", "description": "Get weather"}
656 }"#;
657 let tool: ResponseTool = serde_json::from_str(json).unwrap();
658 assert!(matches!(tool, ResponseTool::Function(_)));
659 }
660
661 #[test]
662 fn test_response_serialise() {
663 let resp = ResponsesApiResponse {
664 id: "resp_123".to_string(),
665 object: "response".to_string(),
666 created_at: 1_700_000_000,
667 status: "completed".to_string(),
668 model: "gpt-4o".to_string(),
669 output: vec![],
670 usage: None,
671 error: None,
672 previous_response_id: None,
673 metadata: None,
674 };
675 let json = serde_json::to_string(&resp).unwrap();
676 assert!(json.contains("\"object\":\"response\""));
677 assert!(json.contains("resp_123"));
678 }
679
680 #[test]
681 fn test_response_usage_defaults() {
682 let usage = ResponseUsage::default();
683 assert_eq!(usage.input_tokens, 0);
684 assert_eq!(usage.output_tokens, 0);
685 assert_eq!(usage.total_tokens, 0);
686 }
687
688 #[test]
689 fn test_stream_event_text_delta_serialise() {
690 let event = ResponseStreamEvent::ResponseOutputTextDelta {
691 output_index: 0,
692 content_index: 0,
693 delta: "Hello".to_string(),
694 };
695 let json = serde_json::to_string(&event).unwrap();
696 assert!(json.contains("response.output_text.delta"));
697 assert!(json.contains("Hello"));
698 }
699
700 #[test]
701 fn test_reasoning_params_deserialise() {
702 let json = r#"{"effort":"high","summary":"auto"}"#;
703 let params: ReasoningParams = serde_json::from_str(json).unwrap();
704 assert_eq!(params.effort.as_deref(), Some("high"));
705 }
706}