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 #[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}