Skip to main content

gproxy_protocol/protocol/openai/generate_content/
response_items.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize, de};
4use serde_json::Value;
5
6use super::super::common::*;
7use super::response_tools::ResponseTool;
8
9#[derive(Debug, Clone, PartialEq, Serialize)]
10#[serde(untagged)]
11pub enum ResponseItem {
12    Message(ResponseMessageItem),
13    Typed(TypedResponseItem),
14    Unknown(UnknownResponseItem),
15}
16
17impl<'de> Deserialize<'de> for ResponseItem {
18    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
19    where
20        D: serde::Deserializer<'de>,
21    {
22        let value = Value::deserialize(deserializer)?;
23        let type_name = value.get("type").and_then(Value::as_str);
24
25        let Some(type_name) = type_name else {
26            if let Ok(message) = serde_json::from_value::<ResponseMessageItem>(value.clone()) {
27                return Ok(Self::Message(message));
28            }
29
30            if let Some(item_reference) = item_reference_without_type(&value) {
31                return Ok(Self::Typed(item_reference));
32            }
33
34            return serde_json::from_value(value)
35                .map(Self::Unknown)
36                .map_err(de::Error::custom);
37        };
38
39        let item_type =
40            serde_json::from_value::<ResponseItemType>(Value::String(type_name.to_owned()))
41                .map_err(de::Error::custom)?;
42
43        match item_type {
44            ResponseItemType::Known(ResponseItemTypeKnown::Message) => {
45                serde_json::from_value(value)
46                    .map(Self::Message)
47                    .map_err(de::Error::custom)
48            }
49            ResponseItemType::Known(_) => serde_json::from_value(value)
50                .map(Self::Typed)
51                .map_err(de::Error::custom),
52            ResponseItemType::Unknown(_) => serde_json::from_value(value)
53                .map(Self::Unknown)
54                .map_err(de::Error::custom),
55        }
56    }
57}
58
59fn item_reference_without_type(value: &Value) -> Option<TypedResponseItem> {
60    let object = value.as_object()?;
61    let id = object.get("id")?.as_str()?.to_owned();
62    let mut extra = Extra::new();
63
64    for (key, value) in object {
65        if key != "id" {
66            extra.insert(key.clone(), value.clone());
67        }
68    }
69
70    Some(TypedResponseItem::ItemReference { id, extra })
71}
72
73#[derive(Debug, Clone, PartialEq, Serialize)]
74#[serde(transparent)]
75pub struct ResponseOutputItem(pub ResponseItem);
76
77impl<'de> Deserialize<'de> for ResponseOutputItem {
78    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
79    where
80        D: serde::Deserializer<'de>,
81    {
82        let item = ResponseItem::deserialize(deserializer)?;
83        validate_response_output_item(&item).map_err(de::Error::custom)?;
84        Ok(Self(item))
85    }
86}
87
88fn validate_response_output_item(item: &ResponseItem) -> Result<(), &'static str> {
89    let ResponseItem::Typed(typed) = item else {
90        return Ok(());
91    };
92
93    match typed {
94        TypedResponseItem::ComputerCallOutput { id, status, .. } => {
95            require_some(id, "computer_call_output.id")?;
96            require_some(status, "computer_call_output.status")?;
97        }
98        TypedResponseItem::FunctionCallOutput { id, status, .. } => {
99            require_some(id, "function_call_output.id")?;
100            require_some(status, "function_call_output.status")?;
101        }
102        TypedResponseItem::ToolSearchCall {
103            id,
104            call_id,
105            execution,
106            status,
107            ..
108        } => {
109            require_some(id, "tool_search_call.id")?;
110            require_some(call_id, "tool_search_call.call_id")?;
111            require_some(execution, "tool_search_call.execution")?;
112            require_some(status, "tool_search_call.status")?;
113        }
114        TypedResponseItem::ToolSearchOutput {
115            id,
116            call_id,
117            execution,
118            status,
119            ..
120        } => {
121            require_some(id, "tool_search_output.id")?;
122            require_some(call_id, "tool_search_output.call_id")?;
123            require_some(execution, "tool_search_output.execution")?;
124            require_some(status, "tool_search_output.status")?;
125        }
126        TypedResponseItem::AdditionalTools { id, .. } => {
127            require_some(id, "additional_tools.id")?;
128        }
129        TypedResponseItem::ShellCall {
130            id,
131            environment,
132            status,
133            ..
134        } => {
135            require_some(id, "shell_call.id")?;
136            require_some(environment, "shell_call.environment")?;
137            require_some(status, "shell_call.status")?;
138        }
139        TypedResponseItem::ShellCallOutput {
140            id,
141            max_output_length,
142            status,
143            ..
144        } => {
145            require_some(id, "shell_call_output.id")?;
146            require_some(max_output_length, "shell_call_output.max_output_length")?;
147            require_some(status, "shell_call_output.status")?;
148        }
149        _ => {}
150    }
151
152    Ok(())
153}
154
155fn require_some<T>(value: &Option<T>, field: &'static str) -> Result<(), &'static str> {
156    value.as_ref().map(|_| ()).ok_or(field)
157}
158
159#[derive(Debug, Clone, PartialEq, Serialize)]
160#[serde(untagged)]
161pub enum ResponseMessageItem {
162    Output(ResponseOutputMessageItem),
163    Input(ResponseInputMessageItem),
164    EasyInput(ResponseEasyInputMessageItem),
165}
166
167impl<'de> Deserialize<'de> for ResponseMessageItem {
168    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
169    where
170        D: serde::Deserializer<'de>,
171    {
172        let value = Value::deserialize(deserializer)?;
173        let role = value.get("role").and_then(Value::as_str);
174        let has_id = value.get("id").is_some();
175        let has_status = value.get("status").is_some();
176
177        if role == Some("assistant") && has_id && has_status {
178            return serde_json::from_value(value)
179                .map(Self::Output)
180                .map_err(de::Error::custom);
181        }
182
183        if has_id || has_status {
184            return serde_json::from_value(value)
185                .map(Self::Input)
186                .map_err(de::Error::custom);
187        }
188
189        serde_json::from_value(value)
190            .map(Self::EasyInput)
191            .map_err(de::Error::custom)
192    }
193}
194
195#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
196pub struct ResponseOutputMessageItem {
197    #[serde(rename = "type")]
198    pub type_: ResponseMessageItemType,
199    pub id: String,
200    pub role: ResponseOutputMessageRole,
201    pub content: Vec<ResponseMessageOutputContentPart>,
202    pub status: ResponseItemLifecycleStatus,
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub phase: Option<ResponsePhase>,
205    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
206    pub extra: Extra,
207}
208
209#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
210pub struct ResponseInputMessageItem {
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub id: Option<String>,
213    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
214    pub type_: Option<ResponseMessageItemType>,
215    pub role: ResponseInputMessageRole,
216    pub content: Vec<ResponseInputContentPart>,
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub status: Option<ResponseItemLifecycleStatus>,
219    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
220    pub extra: Extra,
221}
222
223#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
224pub struct ResponseEasyInputMessageItem {
225    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
226    pub type_: Option<ResponseMessageItemType>,
227    pub role: ResponseEasyInputMessageRole,
228    pub content: ResponseEasyInputContent,
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub phase: Option<ResponsePhase>,
231    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
232    pub extra: Extra,
233}
234
235#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
236pub enum ResponseMessageItemType {
237    #[serde(rename = "message")]
238    Message,
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
242pub enum ResponseOutputMessageRole {
243    #[serde(rename = "assistant")]
244    Assistant,
245}
246
247#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
248pub enum ResponseInputMessageRole {
249    #[serde(rename = "user")]
250    User,
251    #[serde(rename = "system")]
252    System,
253    #[serde(rename = "developer")]
254    Developer,
255}
256
257#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
258pub enum ResponseEasyInputMessageRole {
259    #[serde(rename = "user")]
260    User,
261    #[serde(rename = "assistant")]
262    Assistant,
263    #[serde(rename = "system")]
264    System,
265    #[serde(rename = "developer")]
266    Developer,
267}
268
269#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
270pub struct ResponseAgent {
271    pub agent_name: String,
272    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
273    pub extra: Extra,
274}
275
276#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
277#[serde(tag = "type")]
278pub enum ResponseCaller {
279    #[serde(rename = "direct")]
280    Direct {
281        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
282        extra: Extra,
283    },
284    #[serde(rename = "program")]
285    Program {
286        caller_id: String,
287        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
288        extra: Extra,
289    },
290}
291
292#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
293#[serde(tag = "type")]
294pub enum TypedResponseItem {
295    #[serde(rename = "file_search_call")]
296    FileSearchCall {
297        id: String,
298        queries: Vec<String>,
299        status: ResponseFileSearchCallStatus,
300        #[serde(skip_serializing_if = "Option::is_none")]
301        results: Option<Vec<FileSearchResult>>,
302        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
303        extra: Extra,
304    },
305    #[serde(rename = "computer_call")]
306    ComputerCall {
307        id: String,
308        call_id: String,
309        pending_safety_checks: Vec<SafetyCheck>,
310        status: ResponseItemLifecycleStatus,
311        #[serde(skip_serializing_if = "Option::is_none")]
312        action: Option<ComputerAction>,
313        #[serde(skip_serializing_if = "Option::is_none")]
314        actions: Option<Vec<ComputerAction>>,
315        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
316        extra: Extra,
317    },
318    #[serde(rename = "computer_call_output")]
319    ComputerCallOutput {
320        call_id: String,
321        output: ComputerScreenshot,
322        #[serde(skip_serializing_if = "Option::is_none")]
323        id: Option<String>,
324        #[serde(skip_serializing_if = "Option::is_none")]
325        acknowledged_safety_checks: Option<Vec<SafetyCheck>>,
326        #[serde(skip_serializing_if = "Option::is_none")]
327        status: Option<ResponseComputerCallOutputStatus>,
328        #[serde(skip_serializing_if = "Option::is_none")]
329        created_by: Option<String>,
330        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
331        extra: Extra,
332    },
333    #[serde(rename = "web_search_call")]
334    WebSearchCall {
335        id: String,
336        action: WebSearchAction,
337        status: ResponseWebSearchCallStatus,
338        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
339        extra: Extra,
340    },
341    #[serde(rename = "function_call")]
342    FunctionCall {
343        arguments: String,
344        call_id: String,
345        name: String,
346        #[serde(skip_serializing_if = "Option::is_none")]
347        id: Option<String>,
348        #[serde(skip_serializing_if = "Option::is_none")]
349        caller: Option<ResponseCaller>,
350        #[serde(skip_serializing_if = "Option::is_none")]
351        namespace: Option<String>,
352        #[serde(skip_serializing_if = "Option::is_none")]
353        status: Option<ResponseItemLifecycleStatus>,
354        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
355        extra: Extra,
356    },
357    #[serde(rename = "function_call_output")]
358    FunctionCallOutput {
359        call_id: String,
360        output: ResponseOutput,
361        #[serde(skip_serializing_if = "Option::is_none")]
362        id: Option<String>,
363        #[serde(skip_serializing_if = "Option::is_none")]
364        caller: Option<ResponseCaller>,
365        #[serde(skip_serializing_if = "Option::is_none")]
366        status: Option<ResponseItemLifecycleStatus>,
367        #[serde(skip_serializing_if = "Option::is_none")]
368        created_by: Option<String>,
369        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
370        extra: Extra,
371    },
372    #[serde(rename = "tool_search_call")]
373    ToolSearchCall {
374        arguments: Value,
375        #[serde(skip_serializing_if = "Option::is_none")]
376        id: Option<String>,
377        #[serde(skip_serializing_if = "Option::is_none")]
378        call_id: Option<String>,
379        #[serde(skip_serializing_if = "Option::is_none")]
380        execution: Option<ToolSearchExecution>,
381        #[serde(skip_serializing_if = "Option::is_none")]
382        status: Option<ResponseItemLifecycleStatus>,
383        #[serde(skip_serializing_if = "Option::is_none")]
384        created_by: Option<String>,
385        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
386        extra: Extra,
387    },
388    #[serde(rename = "tool_search_output")]
389    ToolSearchOutput {
390        tools: Vec<ResponseTool>,
391        #[serde(skip_serializing_if = "Option::is_none")]
392        id: Option<String>,
393        #[serde(skip_serializing_if = "Option::is_none")]
394        call_id: Option<String>,
395        #[serde(skip_serializing_if = "Option::is_none")]
396        execution: Option<ToolSearchExecution>,
397        #[serde(skip_serializing_if = "Option::is_none")]
398        status: Option<ResponseItemLifecycleStatus>,
399        #[serde(skip_serializing_if = "Option::is_none")]
400        created_by: Option<String>,
401        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
402        extra: Extra,
403    },
404    #[serde(rename = "additional_tools")]
405    AdditionalTools {
406        role: AdditionalToolsRole,
407        tools: Vec<ResponseTool>,
408        #[serde(skip_serializing_if = "Option::is_none")]
409        id: Option<String>,
410        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
411        extra: Extra,
412    },
413    #[serde(rename = "reasoning")]
414    Reasoning {
415        #[serde(skip_serializing_if = "Option::is_none")]
416        id: Option<String>,
417        summary: Vec<ResponseReasoningSummaryPart>,
418        #[serde(skip_serializing_if = "Option::is_none")]
419        content: Option<Vec<ResponseReasoningTextPart>>,
420        #[serde(skip_serializing_if = "Option::is_none")]
421        encrypted_content: Option<String>,
422        #[serde(skip_serializing_if = "Option::is_none")]
423        status: Option<ResponseItemLifecycleStatus>,
424        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
425        extra: Extra,
426    },
427    #[serde(rename = "compaction")]
428    Compaction {
429        encrypted_content: String,
430        #[serde(skip_serializing_if = "Option::is_none")]
431        id: Option<String>,
432        #[serde(skip_serializing_if = "Option::is_none")]
433        created_by: Option<String>,
434        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
435        extra: Extra,
436    },
437    #[serde(rename = "image_generation_call")]
438    ImageGenerationCall {
439        id: String,
440        result: String,
441        status: ResponseImageGenerationCallStatus,
442        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
443        extra: Extra,
444    },
445    #[serde(rename = "code_interpreter_call")]
446    CodeInterpreterCall {
447        id: String,
448        code: Option<String>,
449        container_id: String,
450        outputs: Option<Vec<CodeInterpreterOutput>>,
451        status: ResponseCodeInterpreterCallStatus,
452        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
453        extra: Extra,
454    },
455    #[serde(rename = "local_shell_call")]
456    LocalShellCall {
457        id: String,
458        action: LocalShellAction,
459        call_id: String,
460        status: ResponseItemLifecycleStatus,
461        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
462        extra: Extra,
463    },
464    #[serde(rename = "local_shell_call_output")]
465    LocalShellCallOutput {
466        id: String,
467        output: String,
468        #[serde(skip_serializing_if = "Option::is_none")]
469        status: Option<ResponseItemLifecycleStatus>,
470        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
471        extra: Extra,
472    },
473    #[serde(rename = "shell_call")]
474    ShellCall {
475        action: ShellAction,
476        call_id: String,
477        #[serde(skip_serializing_if = "Option::is_none")]
478        id: Option<String>,
479        #[serde(skip_serializing_if = "Option::is_none")]
480        caller: Option<ResponseCaller>,
481        #[serde(skip_serializing_if = "Option::is_none")]
482        environment: Option<ShellEnvironment>,
483        #[serde(skip_serializing_if = "Option::is_none")]
484        status: Option<ResponseItemLifecycleStatus>,
485        #[serde(skip_serializing_if = "Option::is_none")]
486        created_by: Option<String>,
487        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
488        extra: Extra,
489    },
490    #[serde(rename = "shell_call_output")]
491    ShellCallOutput {
492        call_id: String,
493        output: Vec<ShellCallOutputContent>,
494        #[serde(skip_serializing_if = "Option::is_none")]
495        id: Option<String>,
496        #[serde(skip_serializing_if = "Option::is_none")]
497        caller: Option<ResponseCaller>,
498        #[serde(skip_serializing_if = "Option::is_none")]
499        max_output_length: Option<u32>,
500        #[serde(skip_serializing_if = "Option::is_none")]
501        status: Option<ResponseItemLifecycleStatus>,
502        #[serde(skip_serializing_if = "Option::is_none")]
503        created_by: Option<String>,
504        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
505        extra: Extra,
506    },
507    #[serde(rename = "apply_patch_call")]
508    ApplyPatchCall {
509        call_id: String,
510        operation: ApplyPatchOperation,
511        status: ResponseApplyPatchCallStatus,
512        #[serde(skip_serializing_if = "Option::is_none")]
513        id: Option<String>,
514        #[serde(skip_serializing_if = "Option::is_none")]
515        caller: Option<ResponseCaller>,
516        #[serde(skip_serializing_if = "Option::is_none")]
517        created_by: Option<String>,
518        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
519        extra: Extra,
520    },
521    #[serde(rename = "apply_patch_call_output")]
522    ApplyPatchCallOutput {
523        call_id: String,
524        status: ResponseApplyPatchCallOutputStatus,
525        #[serde(skip_serializing_if = "Option::is_none")]
526        id: Option<String>,
527        #[serde(skip_serializing_if = "Option::is_none")]
528        caller: Option<ResponseCaller>,
529        #[serde(skip_serializing_if = "Option::is_none")]
530        output: Option<String>,
531        #[serde(skip_serializing_if = "Option::is_none")]
532        created_by: Option<String>,
533        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
534        extra: Extra,
535    },
536    #[serde(rename = "mcp_list_tools")]
537    McpListTools {
538        id: String,
539        server_label: String,
540        tools: Vec<McpToolDescription>,
541        #[serde(skip_serializing_if = "Option::is_none")]
542        error: Option<String>,
543        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
544        extra: Extra,
545    },
546    #[serde(rename = "mcp_approval_request")]
547    McpApprovalRequest {
548        id: String,
549        arguments: String,
550        name: String,
551        server_label: String,
552        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
553        extra: Extra,
554    },
555    #[serde(rename = "mcp_approval_response")]
556    McpApprovalResponse {
557        approval_request_id: String,
558        approve: bool,
559        #[serde(skip_serializing_if = "Option::is_none")]
560        id: Option<String>,
561        #[serde(skip_serializing_if = "Option::is_none")]
562        reason: Option<String>,
563        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
564        extra: Extra,
565    },
566    #[serde(rename = "mcp_call")]
567    McpCall {
568        id: String,
569        arguments: String,
570        name: String,
571        server_label: String,
572        #[serde(skip_serializing_if = "Option::is_none")]
573        approval_request_id: Option<String>,
574        #[serde(skip_serializing_if = "Option::is_none")]
575        error: Option<String>,
576        #[serde(skip_serializing_if = "Option::is_none")]
577        output: Option<String>,
578        #[serde(skip_serializing_if = "Option::is_none")]
579        status: Option<ResponseMcpCallStatus>,
580        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
581        extra: Extra,
582    },
583    #[serde(rename = "custom_tool_call")]
584    CustomToolCall {
585        call_id: String,
586        input: String,
587        name: String,
588        #[serde(skip_serializing_if = "Option::is_none")]
589        id: Option<String>,
590        #[serde(skip_serializing_if = "Option::is_none")]
591        caller: Option<ResponseCaller>,
592        #[serde(skip_serializing_if = "Option::is_none")]
593        namespace: Option<String>,
594        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
595        extra: Extra,
596    },
597    #[serde(rename = "custom_tool_call_output")]
598    CustomToolCallOutput {
599        call_id: String,
600        output: ResponseOutput,
601        #[serde(skip_serializing_if = "Option::is_none")]
602        id: Option<String>,
603        #[serde(skip_serializing_if = "Option::is_none")]
604        caller: Option<ResponseCaller>,
605        #[serde(skip_serializing_if = "Option::is_none")]
606        status: Option<ResponseItemLifecycleStatus>,
607        #[serde(skip_serializing_if = "Option::is_none")]
608        created_by: Option<String>,
609        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
610        extra: Extra,
611    },
612    #[serde(rename = "program")]
613    Program {
614        id: String,
615        call_id: String,
616        code: String,
617        fingerprint: String,
618        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
619        extra: Extra,
620    },
621    #[serde(rename = "program_output")]
622    ProgramOutput {
623        id: String,
624        call_id: String,
625        result: String,
626        status: ResponseItemLifecycleStatus,
627        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
628        extra: Extra,
629    },
630    #[serde(rename = "multi_agent_call")]
631    MultiAgentCall {
632        #[serde(skip_serializing_if = "Option::is_none")]
633        id: Option<String>,
634        #[serde(skip_serializing_if = "Option::is_none")]
635        call_id: Option<String>,
636        #[serde(skip_serializing_if = "Option::is_none")]
637        action: Option<String>,
638        #[serde(skip_serializing_if = "Option::is_none")]
639        arguments: Option<String>,
640        #[serde(skip_serializing_if = "Option::is_none")]
641        agent: Option<ResponseAgent>,
642        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
643        extra: Extra,
644    },
645    #[serde(rename = "multi_agent_call_output")]
646    MultiAgentCallOutput {
647        #[serde(skip_serializing_if = "Option::is_none")]
648        id: Option<String>,
649        #[serde(skip_serializing_if = "Option::is_none")]
650        call_id: Option<String>,
651        #[serde(skip_serializing_if = "Option::is_none")]
652        action: Option<String>,
653        #[serde(skip_serializing_if = "Option::is_none")]
654        output: Option<Vec<ResponseOutputContentPart>>,
655        #[serde(skip_serializing_if = "Option::is_none")]
656        agent: Option<ResponseAgent>,
657        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
658        extra: Extra,
659    },
660    #[serde(rename = "agent_message")]
661    AgentMessage {
662        #[serde(skip_serializing_if = "Option::is_none")]
663        id: Option<String>,
664        #[serde(skip_serializing_if = "Option::is_none")]
665        author: Option<String>,
666        #[serde(skip_serializing_if = "Option::is_none")]
667        recipient: Option<String>,
668        #[serde(skip_serializing_if = "Option::is_none")]
669        content: Option<Vec<Value>>,
670        #[serde(skip_serializing_if = "Option::is_none")]
671        agent: Option<ResponseAgent>,
672        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
673        extra: Extra,
674    },
675    #[serde(rename = "compaction_trigger")]
676    CompactionTrigger {
677        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
678        extra: Extra,
679    },
680    #[serde(rename = "item_reference")]
681    ItemReference {
682        id: String,
683        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
684        extra: Extra,
685    },
686}
687
688#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
689pub struct UnknownResponseItem {
690    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
691    pub type_: Option<ResponseItemType>,
692    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
693    pub extra: Extra,
694}
695
696#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
697#[serde(untagged)]
698pub enum ResponseOutput {
699    Text(String),
700    Parts(Vec<ResponseToolOutputContentPart>),
701}
702
703#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
704#[serde(untagged)]
705pub enum ResponseEasyInputContent {
706    Text(String),
707    Parts(Vec<ResponseInputContentPart>),
708}
709
710#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
711#[serde(tag = "type")]
712pub enum ResponseInputContentPart {
713    #[serde(rename = "input_text")]
714    InputText {
715        text: String,
716        #[serde(skip_serializing_if = "Option::is_none")]
717        prompt_cache_breakpoint: Option<PromptCacheBreakpoint>,
718        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
719        extra: Extra,
720    },
721    #[serde(rename = "input_image")]
722    InputImage {
723        #[serde(skip_serializing_if = "Option::is_none")]
724        detail: Option<DetailLevel>,
725        #[serde(skip_serializing_if = "Option::is_none")]
726        file_id: Option<String>,
727        #[serde(skip_serializing_if = "Option::is_none")]
728        image_url: Option<String>,
729        #[serde(skip_serializing_if = "Option::is_none")]
730        prompt_cache_breakpoint: Option<PromptCacheBreakpoint>,
731        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
732        extra: Extra,
733    },
734    #[serde(rename = "input_file")]
735    InputFile {
736        #[serde(skip_serializing_if = "Option::is_none")]
737        detail: Option<InputFileDetailLevel>,
738        #[serde(skip_serializing_if = "Option::is_none")]
739        file_data: Option<String>,
740        #[serde(skip_serializing_if = "Option::is_none")]
741        file_id: Option<String>,
742        #[serde(skip_serializing_if = "Option::is_none")]
743        file_url: Option<String>,
744        #[serde(skip_serializing_if = "Option::is_none")]
745        filename: Option<String>,
746        #[serde(skip_serializing_if = "Option::is_none")]
747        prompt_cache_breakpoint: Option<PromptCacheBreakpoint>,
748        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
749        extra: Extra,
750    },
751    #[serde(rename = "input_audio")]
752    InputAudio {
753        input_audio: InputAudioContent,
754        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
755        extra: Extra,
756    },
757}
758
759#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
760#[serde(tag = "type")]
761pub enum ResponseToolOutputContentPart {
762    #[serde(rename = "input_text")]
763    InputText {
764        text: String,
765        #[serde(skip_serializing_if = "Option::is_none")]
766        prompt_cache_breakpoint: Option<PromptCacheBreakpoint>,
767        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
768        extra: Extra,
769    },
770    #[serde(rename = "input_image")]
771    InputImage {
772        #[serde(skip_serializing_if = "Option::is_none")]
773        detail: Option<DetailLevel>,
774        #[serde(skip_serializing_if = "Option::is_none")]
775        file_id: Option<String>,
776        #[serde(skip_serializing_if = "Option::is_none")]
777        image_url: Option<String>,
778        #[serde(skip_serializing_if = "Option::is_none")]
779        prompt_cache_breakpoint: Option<PromptCacheBreakpoint>,
780        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
781        extra: Extra,
782    },
783    #[serde(rename = "input_file")]
784    InputFile {
785        #[serde(skip_serializing_if = "Option::is_none")]
786        detail: Option<InputFileDetailLevel>,
787        #[serde(skip_serializing_if = "Option::is_none")]
788        file_data: Option<String>,
789        #[serde(skip_serializing_if = "Option::is_none")]
790        file_id: Option<String>,
791        #[serde(skip_serializing_if = "Option::is_none")]
792        file_url: Option<String>,
793        #[serde(skip_serializing_if = "Option::is_none")]
794        filename: Option<String>,
795        #[serde(skip_serializing_if = "Option::is_none")]
796        prompt_cache_breakpoint: Option<PromptCacheBreakpoint>,
797        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
798        extra: Extra,
799    },
800}
801
802#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
803#[serde(tag = "type")]
804pub enum ResponseMessageOutputContentPart {
805    #[serde(rename = "output_text")]
806    OutputText {
807        #[serde(default, skip_serializing_if = "Vec::is_empty")]
808        annotations: Vec<ResponseAnnotation>,
809        #[serde(skip_serializing_if = "Option::is_none")]
810        logprobs: Option<Vec<TokenLogprob>>,
811        text: String,
812        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
813        extra: Extra,
814    },
815    #[serde(rename = "refusal")]
816    Refusal {
817        refusal: String,
818        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
819        extra: Extra,
820    },
821}
822
823#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
824#[serde(tag = "type")]
825pub enum ResponseOutputContentPart {
826    #[serde(rename = "output_text")]
827    OutputText {
828        #[serde(default, skip_serializing_if = "Vec::is_empty")]
829        annotations: Vec<ResponseAnnotation>,
830        #[serde(skip_serializing_if = "Option::is_none")]
831        logprobs: Option<Vec<TokenLogprob>>,
832        text: String,
833        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
834        extra: Extra,
835    },
836    #[serde(rename = "refusal")]
837    Refusal {
838        refusal: String,
839        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
840        extra: Extra,
841    },
842    #[serde(rename = "reasoning_text")]
843    ReasoningText {
844        text: String,
845        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
846        extra: Extra,
847    },
848}
849
850pub type ResponseContentPart = ResponseOutputContentPart;
851
852#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
853pub struct InputAudioContent {
854    pub data: String,
855    pub format: InputAudioFormat,
856    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
857    pub extra: Extra,
858}
859
860#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
861#[serde(tag = "type")]
862pub enum ResponseAnnotation {
863    #[serde(rename = "file_citation")]
864    FileCitation {
865        file_id: String,
866        filename: String,
867        index: u32,
868        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
869        extra: Extra,
870    },
871    #[serde(rename = "url_citation")]
872    UrlCitation {
873        end_index: u32,
874        start_index: u32,
875        title: String,
876        url: String,
877        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
878        extra: Extra,
879    },
880    #[serde(rename = "container_file_citation")]
881    ContainerFileCitation {
882        container_id: String,
883        end_index: u32,
884        file_id: String,
885        filename: String,
886        start_index: u32,
887        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
888        extra: Extra,
889    },
890    #[serde(rename = "file_path")]
891    FilePath {
892        file_id: String,
893        index: u32,
894        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
895        extra: Extra,
896    },
897}
898
899#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
900pub struct FileSearchResult {
901    #[serde(skip_serializing_if = "Option::is_none")]
902    pub attributes: Option<FileSearchResultAttributes>,
903    #[serde(skip_serializing_if = "Option::is_none")]
904    pub file_id: Option<String>,
905    #[serde(skip_serializing_if = "Option::is_none")]
906    pub filename: Option<String>,
907    #[serde(skip_serializing_if = "Option::is_none")]
908    pub score: Option<f64>,
909    #[serde(skip_serializing_if = "Option::is_none")]
910    pub text: Option<String>,
911    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
912    pub extra: Extra,
913}
914
915pub type FileSearchResultAttributes = BTreeMap<String, FileSearchResultAttributeValue>;
916
917#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
918#[serde(untagged)]
919pub enum FileSearchResultAttributeValue {
920    String(String),
921    Number(f64),
922    Boolean(bool),
923}
924
925#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
926pub struct SafetyCheck {
927    pub id: String,
928    #[serde(skip_serializing_if = "Option::is_none")]
929    pub code: Option<String>,
930    #[serde(skip_serializing_if = "Option::is_none")]
931    pub message: Option<String>,
932    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
933    pub extra: Extra,
934}
935
936#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
937#[serde(tag = "type")]
938pub enum ComputerAction {
939    #[serde(rename = "click")]
940    Click {
941        button: ComputerMouseButton,
942        x: f64,
943        y: f64,
944        #[serde(skip_serializing_if = "Option::is_none")]
945        keys: Option<Vec<String>>,
946    },
947    #[serde(rename = "double_click")]
948    DoubleClick { keys: Vec<String>, x: f64, y: f64 },
949    #[serde(rename = "drag")]
950    Drag {
951        path: Vec<ComputerCoordinate>,
952        #[serde(skip_serializing_if = "Option::is_none")]
953        keys: Option<Vec<String>>,
954    },
955    #[serde(rename = "keypress")]
956    Keypress { keys: Vec<String> },
957    #[serde(rename = "move")]
958    Move {
959        x: f64,
960        y: f64,
961        #[serde(skip_serializing_if = "Option::is_none")]
962        keys: Option<Vec<String>>,
963    },
964    #[serde(rename = "screenshot")]
965    Screenshot {},
966    #[serde(rename = "scroll")]
967    Scroll {
968        scroll_x: f64,
969        scroll_y: f64,
970        x: f64,
971        y: f64,
972        #[serde(skip_serializing_if = "Option::is_none")]
973        keys: Option<Vec<String>>,
974    },
975    #[serde(rename = "type")]
976    Type { text: String },
977    #[serde(rename = "wait")]
978    Wait {},
979}
980
981#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
982pub enum ComputerMouseButton {
983    #[serde(rename = "left")]
984    Left,
985    #[serde(rename = "right")]
986    Right,
987    #[serde(rename = "wheel")]
988    Wheel,
989    #[serde(rename = "back")]
990    Back,
991    #[serde(rename = "forward")]
992    Forward,
993}
994
995#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
996pub struct ComputerCoordinate {
997    pub x: f64,
998    pub y: f64,
999}
1000
1001#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1002pub struct ComputerScreenshot {
1003    #[serde(rename = "type")]
1004    pub type_: ComputerScreenshotType,
1005    #[serde(skip_serializing_if = "Option::is_none")]
1006    pub file_id: Option<String>,
1007    #[serde(skip_serializing_if = "Option::is_none")]
1008    pub image_url: Option<String>,
1009    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
1010    pub extra: Extra,
1011}
1012
1013#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1014pub enum ComputerScreenshotType {
1015    #[serde(rename = "computer_screenshot")]
1016    ComputerScreenshot,
1017}
1018
1019#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1020#[serde(tag = "type")]
1021pub enum WebSearchAction {
1022    #[serde(rename = "search")]
1023    Search {
1024        #[serde(skip_serializing_if = "Option::is_none")]
1025        queries: Option<Vec<String>>,
1026        #[serde(skip_serializing_if = "Option::is_none")]
1027        query: Option<String>,
1028        #[serde(skip_serializing_if = "Option::is_none")]
1029        sources: Option<Vec<WebSearchSource>>,
1030    },
1031    #[serde(rename = "open_page")]
1032    OpenPage {
1033        #[serde(skip_serializing_if = "Option::is_none")]
1034        url: Option<String>,
1035    },
1036    #[serde(rename = "find_in_page")]
1037    FindInPage { pattern: String, url: String },
1038}
1039
1040#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1041pub struct WebSearchSource {
1042    #[serde(rename = "type")]
1043    pub type_: WebSearchSourceType,
1044    pub url: String,
1045    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
1046    pub extra: Extra,
1047}
1048
1049#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1050pub enum WebSearchSourceType {
1051    #[serde(rename = "url")]
1052    Url,
1053}
1054
1055#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1056pub enum AdditionalToolsRole {
1057    #[serde(rename = "unknown")]
1058    Unknown,
1059    #[serde(rename = "user")]
1060    User,
1061    #[serde(rename = "assistant")]
1062    Assistant,
1063    #[serde(rename = "system")]
1064    System,
1065    #[serde(rename = "critic")]
1066    Critic,
1067    #[serde(rename = "discriminator")]
1068    Discriminator,
1069    #[serde(rename = "developer")]
1070    Developer,
1071    #[serde(rename = "tool")]
1072    Tool,
1073}
1074
1075#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1076pub struct ResponseReasoningSummaryPart {
1077    pub text: String,
1078    #[serde(rename = "type")]
1079    pub type_: ResponseReasoningSummaryType,
1080    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
1081    pub extra: Extra,
1082}
1083
1084#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1085pub enum ResponseReasoningSummaryType {
1086    #[serde(rename = "summary_text")]
1087    SummaryText,
1088}
1089
1090#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1091pub struct ResponseReasoningTextPart {
1092    pub text: String,
1093    #[serde(rename = "type")]
1094    pub type_: ResponseReasoningTextType,
1095    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
1096    pub extra: Extra,
1097}
1098
1099#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1100pub enum ResponseReasoningTextType {
1101    #[serde(rename = "reasoning_text")]
1102    ReasoningText,
1103}
1104
1105#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1106#[serde(tag = "type")]
1107pub enum CodeInterpreterOutput {
1108    #[serde(rename = "logs")]
1109    Logs { logs: String },
1110    #[serde(rename = "image")]
1111    Image { url: String },
1112}
1113
1114#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1115pub struct LocalShellAction {
1116    pub command: Vec<String>,
1117    pub env: BTreeMap<String, String>,
1118    #[serde(rename = "type")]
1119    pub type_: LocalShellActionType,
1120    #[serde(skip_serializing_if = "Option::is_none")]
1121    pub timeout_ms: Option<u32>,
1122    #[serde(skip_serializing_if = "Option::is_none")]
1123    pub user: Option<String>,
1124    #[serde(skip_serializing_if = "Option::is_none")]
1125    pub working_directory: Option<String>,
1126    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
1127    pub extra: Extra,
1128}
1129
1130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1131pub enum LocalShellActionType {
1132    #[serde(rename = "exec")]
1133    Exec,
1134}
1135
1136#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1137pub struct ShellAction {
1138    pub commands: Vec<String>,
1139    #[serde(skip_serializing_if = "Option::is_none")]
1140    pub max_output_length: Option<u32>,
1141    #[serde(skip_serializing_if = "Option::is_none")]
1142    pub timeout_ms: Option<u32>,
1143    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
1144    pub extra: Extra,
1145}
1146
1147#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1148#[serde(tag = "type")]
1149pub enum ShellEnvironment {
1150    #[serde(rename = "local")]
1151    Local {
1152        #[serde(skip_serializing_if = "Option::is_none")]
1153        skills: Option<Vec<ShellSkillReference>>,
1154    },
1155    #[serde(rename = "container_reference")]
1156    ContainerReference { container_id: String },
1157}
1158
1159#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1160pub struct ShellSkillReference {
1161    #[serde(skip_serializing_if = "Option::is_none")]
1162    pub skill_id: Option<String>,
1163    #[serde(skip_serializing_if = "Option::is_none")]
1164    pub version: Option<String>,
1165    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
1166    pub extra: Extra,
1167}
1168
1169#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1170pub struct ShellCallOutputContent {
1171    pub outcome: ShellCallOutcome,
1172    pub stderr: String,
1173    pub stdout: String,
1174    #[serde(skip_serializing_if = "Option::is_none")]
1175    pub created_by: Option<String>,
1176    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
1177    pub extra: Extra,
1178}
1179
1180#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1181#[serde(tag = "type")]
1182pub enum ShellCallOutcome {
1183    #[serde(rename = "timeout")]
1184    Timeout {},
1185    #[serde(rename = "exit")]
1186    Exit { exit_code: i32 },
1187}
1188
1189#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1190#[serde(tag = "type")]
1191pub enum ApplyPatchOperation {
1192    #[serde(rename = "create_file")]
1193    CreateFile { diff: String, path: String },
1194    #[serde(rename = "delete_file")]
1195    DeleteFile { path: String },
1196    #[serde(rename = "update_file")]
1197    UpdateFile { diff: String, path: String },
1198}
1199
1200#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1201pub struct McpToolDescription {
1202    pub input_schema: Value,
1203    pub name: String,
1204    #[serde(skip_serializing_if = "Option::is_none")]
1205    pub annotations: Option<Value>,
1206    #[serde(skip_serializing_if = "Option::is_none")]
1207    pub description: Option<String>,
1208    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
1209    pub extra: Extra,
1210}
1211
1212#[cfg(test)]
1213mod tests {
1214    use super::*;
1215
1216    /// Regression: `ResponseItem`/`ResponseMessageItem` must serialize FLAT
1217    /// (untagged), matching their hand-written flat `Deserialize`. Before the
1218    /// `#[serde(untagged)]` fix the derived `Serialize` emitted
1219    /// `{"Message":{"EasyInput":{...}}}`, so the Responses backend saw no
1220    /// top-level `role` and rejected `input[0]` with role `''`.
1221    #[test]
1222    fn input_message_serializes_flat() {
1223        let flat = serde_json::json!({"type": "message", "role": "user", "content": "hi"});
1224        let item: ResponseItem = serde_json::from_value(flat.clone()).unwrap();
1225        let back = serde_json::to_value(&item).unwrap();
1226        assert!(
1227            back.get("Message").is_none() && back.get("EasyInput").is_none(),
1228            "must not be externally tagged: {back}"
1229        );
1230        assert_eq!(back["role"], "user", "{back}");
1231        assert_eq!(back, flat);
1232    }
1233}