Skip to main content

gproxy_protocol/transform/claude/generate_content/openai_response/
request.rs

1use crate::claude::count_tokens::types::{
2    BetaContextManagementEdit, BetaMessageRole, BetaOutputEffort, BetaThinkingConfigParam,
3    BetaToolChoice, BetaToolInputSchema, BetaToolInputSchemaType, BetaToolUnion,
4};
5use crate::claude::create_message::request::ClaudeCreateMessageRequest;
6use crate::claude::create_message::types::{BetaServiceTierParam, BetaSpeed};
7use crate::openai::count_tokens::types::{
8    HttpMethod, ResponseApplyPatchTool, ResponseApplyPatchToolType, ResponseApproximateLocation,
9    ResponseApproximateLocationType, ResponseCodeInterpreterContainer, ResponseCodeInterpreterTool,
10    ResponseCodeInterpreterToolAuto, ResponseCodeInterpreterToolAutoType,
11    ResponseCodeInterpreterToolType, ResponseComputerEnvironment, ResponseComputerTool,
12    ResponseComputerToolType, ResponseFormatTextJsonSchemaConfig,
13    ResponseFormatTextJsonSchemaConfigType, ResponseFunctionShellTool,
14    ResponseFunctionShellToolType, ResponseFunctionTool, ResponseFunctionToolType, ResponseInput,
15    ResponseInputItem, ResponseInputMessage, ResponseInputMessageContent, ResponseInputMessageRole,
16    ResponseInputMessageType, ResponseMcpAllowedTools, ResponseMcpTool, ResponseMcpToolType,
17    ResponseReasoning, ResponseReasoningEffort, ResponseTextConfig, ResponseTextFormatConfig,
18    ResponseTextVerbosity, ResponseTool, ResponseToolChoice, ResponseToolChoiceFunction,
19    ResponseToolChoiceFunctionType, ResponseToolChoiceOptions, ResponseTruncation,
20    ResponseWebSearchFilters, ResponseWebSearchTool, ResponseWebSearchToolType,
21};
22use crate::openai::create_response::request::{
23    OpenAiCreateResponseRequest, PathParameters, QueryParameters, RequestBody, RequestHeaders,
24};
25use crate::openai::create_response::types::{
26    Metadata, ResponseContextManagementEntry, ResponseContextManagementType, ResponseServiceTier,
27};
28use crate::transform::claude::generate_content::utils::{
29    beta_system_prompt_to_text, claude_model_to_string,
30};
31use crate::transform::utils::TransformError;
32use serde_json::{Map, Value};
33use std::collections::BTreeMap;
34
35fn tool_input_schema_to_json_object(
36    input_schema: BetaToolInputSchema,
37) -> std::collections::BTreeMap<String, Value> {
38    let mut parameters = std::collections::BTreeMap::new();
39    let schema_type = match input_schema.type_ {
40        BetaToolInputSchemaType::Object => "object",
41    };
42    parameters.insert("type".to_string(), Value::String(schema_type.to_string()));
43    if let Some(properties) = input_schema.properties {
44        let properties_object = properties.into_iter().collect::<Map<String, Value>>();
45        parameters.insert("properties".to_string(), Value::Object(properties_object));
46    }
47    if let Some(required) = input_schema.required {
48        parameters.insert(
49            "required".to_string(),
50            Value::Array(required.into_iter().map(Value::String).collect()),
51        );
52    }
53    parameters
54}
55
56use crate::claude::count_tokens::types as ct;
57use crate::openai::count_tokens::types as ot;
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60enum ClaudeToolKind {
61    Function,
62    Custom,
63    Mcp,
64    CodeInterpreter,
65    Computer,
66    WebSearch,
67    WebFetch,
68    Shell,
69    ApplyPatch,
70    FileSearch,
71}
72
73#[derive(Debug, Clone, Copy)]
74struct RecordedToolCall {
75    item_index: usize,
76    kind: ClaudeToolKind,
77}
78
79fn json_string<T: serde::Serialize>(value: &T) -> String {
80    serde_json::to_string(value).unwrap_or_else(|_| "{}".to_string())
81}
82
83fn input_text_content(text: String) -> ot::ResponseInputContent {
84    ot::ResponseInputContent::Text(ot::ResponseInputText {
85        text,
86        type_: ot::ResponseInputTextType::InputText,
87    })
88}
89
90fn flush_input_parts(
91    input_items: &mut Vec<ResponseInputItem>,
92    role: ResponseInputMessageRole,
93    parts: &mut Vec<ot::ResponseInputContent>,
94) {
95    if parts.is_empty() {
96        return;
97    }
98
99    let content = if parts.len() == 1 {
100        match parts.pop() {
101            Some(ot::ResponseInputContent::Text(text_part)) => {
102                ResponseInputMessageContent::Text(text_part.text)
103            }
104            Some(part) => ResponseInputMessageContent::List(vec![part]),
105            None => ResponseInputMessageContent::Text(String::new()),
106        }
107    } else {
108        ResponseInputMessageContent::List(std::mem::take(parts))
109    };
110
111    input_items.push(ResponseInputItem::Message(ResponseInputMessage {
112        content,
113        role,
114        phase: None,
115        status: None,
116        type_: Some(ResponseInputMessageType::Message),
117    }));
118}
119
120fn output_message_item(id: String, text: String) -> ResponseInputItem {
121    ResponseInputItem::OutputMessage(ot::ResponseOutputMessage {
122        id,
123        content: vec![ot::ResponseOutputContent::Text(ot::ResponseOutputText {
124            annotations: Vec::new(),
125            logprobs: None,
126            text,
127            type_: ot::ResponseOutputTextType::OutputText,
128        })],
129        role: ot::ResponseOutputMessageRole::Assistant,
130        phase: None,
131        status: Some(ot::ResponseItemStatus::Completed),
132        type_: Some(ot::ResponseOutputMessageType::Message),
133    })
134}
135
136fn image_media_type(media_type: ct::BetaImageMediaType) -> &'static str {
137    match media_type {
138        ct::BetaImageMediaType::ImageJpeg => "image/jpeg",
139        ct::BetaImageMediaType::ImagePng => "image/png",
140        ct::BetaImageMediaType::ImageGif => "image/gif",
141        ct::BetaImageMediaType::ImageWebp => "image/webp",
142    }
143}
144
145fn image_block_to_input_content(
146    block: ct::BetaImageBlockParam,
147) -> Option<ot::ResponseInputContent> {
148    match block.source {
149        ct::BetaImageSource::Base64(source) => {
150            Some(ot::ResponseInputContent::Image(ot::ResponseInputImage {
151                detail: None,
152                type_: ot::ResponseInputImageType::InputImage,
153                file_id: None,
154                image_url: Some(format!(
155                    "data:{};base64,{}",
156                    image_media_type(source.media_type),
157                    source.data
158                )),
159            }))
160        }
161        ct::BetaImageSource::Url(source) => {
162            Some(ot::ResponseInputContent::Image(ot::ResponseInputImage {
163                detail: None,
164                type_: ot::ResponseInputImageType::InputImage,
165                file_id: None,
166                image_url: Some(source.url),
167            }))
168        }
169        ct::BetaImageSource::File(source) => {
170            Some(ot::ResponseInputContent::Image(ot::ResponseInputImage {
171                detail: None,
172                type_: ot::ResponseInputImageType::InputImage,
173                file_id: Some(source.file_id),
174                image_url: None,
175            }))
176        }
177    }
178}
179
180fn document_block_to_input_content(
181    block: ct::BetaRequestDocumentBlock,
182) -> Option<ot::ResponseInputContent> {
183    let filename = block.title;
184    match block.source {
185        ct::BetaDocumentSource::Base64Pdf(source) => {
186            Some(ot::ResponseInputContent::File(ot::ResponseInputFile {
187                type_: ot::ResponseInputFileType::InputFile,
188                detail: None,
189                file_data: Some(source.data),
190                file_id: None,
191                file_url: None,
192                filename,
193            }))
194        }
195        ct::BetaDocumentSource::PlainText(source) => {
196            Some(ot::ResponseInputContent::File(ot::ResponseInputFile {
197                type_: ot::ResponseInputFileType::InputFile,
198                detail: None,
199                file_data: Some(source.data),
200                file_id: None,
201                file_url: None,
202                filename,
203            }))
204        }
205        ct::BetaDocumentSource::UrlPdf(source) => {
206            Some(ot::ResponseInputContent::File(ot::ResponseInputFile {
207                type_: ot::ResponseInputFileType::InputFile,
208                detail: None,
209                file_data: None,
210                file_id: None,
211                file_url: Some(source.url),
212                filename,
213            }))
214        }
215        ct::BetaDocumentSource::File(source) => {
216            Some(ot::ResponseInputContent::File(ot::ResponseInputFile {
217                type_: ot::ResponseInputFileType::InputFile,
218                detail: None,
219                file_data: None,
220                file_id: Some(source.file_id),
221                file_url: None,
222                filename,
223            }))
224        }
225        ct::BetaDocumentSource::Content(source) => {
226            let text = match source.content {
227                ct::BetaContentBlockSourceContentPayload::Text(text) => text,
228                ct::BetaContentBlockSourceContentPayload::Blocks(parts) => parts
229                    .into_iter()
230                    .filter_map(|part| match part {
231                        ct::BetaContentBlockSourceContent::Text(text) => Some(text.text),
232                        ct::BetaContentBlockSourceContent::Image(_) => None,
233                    })
234                    .collect::<Vec<_>>()
235                    .join("\n"),
236            };
237            if text.is_empty() {
238                None
239            } else {
240                Some(ot::ResponseInputContent::File(ot::ResponseInputFile {
241                    type_: ot::ResponseInputFileType::InputFile,
242                    detail: None,
243                    file_data: Some(text),
244                    file_id: None,
245                    file_url: None,
246                    filename,
247                }))
248            }
249        }
250    }
251}
252
253fn user_message_part_from_block(
254    block: ct::BetaContentBlockParam,
255) -> Option<ot::ResponseInputContent> {
256    match block {
257        ct::BetaContentBlockParam::Text(block) => Some(input_text_content(block.text)),
258        ct::BetaContentBlockParam::Image(block) => image_block_to_input_content(block),
259        ct::BetaContentBlockParam::RequestDocument(block) => document_block_to_input_content(block),
260        ct::BetaContentBlockParam::SearchResult(block) => {
261            let text = block
262                .content
263                .into_iter()
264                .map(|entry| entry.text)
265                .filter(|text| !text.is_empty())
266                .collect::<Vec<_>>()
267                .join("\n");
268            let summary = if text.is_empty() {
269                format!("{}\n{}", block.title, block.source)
270            } else {
271                format!("{}\n{}\n{}", block.title, block.source, text)
272            };
273            Some(input_text_content(summary))
274        }
275        ct::BetaContentBlockParam::ContainerUpload(block) => {
276            Some(ot::ResponseInputContent::File(ot::ResponseInputFile {
277                type_: ot::ResponseInputFileType::InputFile,
278                detail: None,
279                file_data: None,
280                file_id: Some(block.file_id),
281                file_url: None,
282                filename: None,
283            }))
284        }
285        _ => None,
286    }
287}
288
289fn tool_result_block_to_text(block: ct::BetaToolResultContentBlockParam) -> String {
290    match block {
291        ct::BetaToolResultContentBlockParam::Text(part) => part.text,
292        ct::BetaToolResultContentBlockParam::Image(part) => match part.source {
293            ct::BetaImageSource::Base64(source) => {
294                format!(
295                    "data:{};base64,{}",
296                    image_media_type(source.media_type),
297                    source.data
298                )
299            }
300            ct::BetaImageSource::Url(source) => source.url,
301            ct::BetaImageSource::File(source) => format!("file_id:{}", source.file_id),
302        },
303        ct::BetaToolResultContentBlockParam::SearchResult(part) => {
304            let content = part
305                .content
306                .into_iter()
307                .map(|entry| entry.text)
308                .collect::<Vec<_>>()
309                .join("\n");
310            if content.is_empty() {
311                format!("{}\n{}", part.title, part.source)
312            } else {
313                format!("{}\n{}\n{}", part.title, part.source, content)
314            }
315        }
316        ct::BetaToolResultContentBlockParam::Document(part) => {
317            document_block_to_input_content(part)
318                .and_then(|content| match content {
319                    ot::ResponseInputContent::File(file) => file
320                        .file_url
321                        .or(file.file_id)
322                        .or(file.filename)
323                        .or(file.file_data),
324                    _ => None,
325                })
326                .unwrap_or_default()
327        }
328        ct::BetaToolResultContentBlockParam::ToolReference(part) => part.tool_name,
329    }
330}
331
332fn tool_result_blocks_to_text(parts: Vec<ct::BetaToolResultContentBlockParam>) -> String {
333    parts
334        .into_iter()
335        .map(tool_result_block_to_text)
336        .filter(|text| !text.is_empty())
337        .collect::<Vec<_>>()
338        .join("\n")
339}
340
341fn tool_result_blocks_to_input_contents(
342    parts: Vec<ct::BetaToolResultContentBlockParam>,
343) -> Option<Vec<ot::ResponseInputContent>> {
344    let mut converted = Vec::new();
345    for part in parts {
346        match part {
347            ct::BetaToolResultContentBlockParam::Text(part) => {
348                converted.push(input_text_content(part.text));
349            }
350            ct::BetaToolResultContentBlockParam::Image(part) => {
351                converted.push(image_block_to_input_content(part)?);
352            }
353            ct::BetaToolResultContentBlockParam::Document(part) => {
354                converted.push(document_block_to_input_content(part)?);
355            }
356            ct::BetaToolResultContentBlockParam::SearchResult(_)
357            | ct::BetaToolResultContentBlockParam::ToolReference(_) => return None,
358        }
359    }
360    Some(converted)
361}
362
363fn tool_result_content_to_text(content: Option<ct::BetaToolResultBlockParamContent>) -> String {
364    match content {
365        Some(ct::BetaToolResultBlockParamContent::Text(text)) => text,
366        Some(ct::BetaToolResultBlockParamContent::Blocks(parts)) => {
367            tool_result_blocks_to_text(parts)
368        }
369        None => String::new(),
370    }
371}
372
373fn tool_result_content_to_function_output(
374    content: Option<ct::BetaToolResultBlockParamContent>,
375) -> ot::ResponseFunctionCallOutputContent {
376    match content {
377        Some(ct::BetaToolResultBlockParamContent::Text(text)) => {
378            ot::ResponseFunctionCallOutputContent::Text(text)
379        }
380        Some(ct::BetaToolResultBlockParamContent::Blocks(parts)) => {
381            if let Some(contents) = tool_result_blocks_to_input_contents(parts.clone()) {
382                ot::ResponseFunctionCallOutputContent::Content(contents)
383            } else {
384                ot::ResponseFunctionCallOutputContent::Text(tool_result_blocks_to_text(parts))
385            }
386        }
387        None => ot::ResponseFunctionCallOutputContent::Text(String::new()),
388    }
389}
390
391fn tool_result_content_to_custom_output(
392    content: Option<ct::BetaToolResultBlockParamContent>,
393) -> ot::ResponseCustomToolCallOutputContent {
394    match content {
395        Some(ct::BetaToolResultBlockParamContent::Text(text)) => {
396            ot::ResponseCustomToolCallOutputContent::Text(text)
397        }
398        Some(ct::BetaToolResultBlockParamContent::Blocks(parts)) => {
399            if let Some(contents) = tool_result_blocks_to_input_contents(parts.clone()) {
400                ot::ResponseCustomToolCallOutputContent::Content(contents)
401            } else {
402                ot::ResponseCustomToolCallOutputContent::Text(tool_result_blocks_to_text(parts))
403            }
404        }
405        None => ot::ResponseCustomToolCallOutputContent::Text(String::new()),
406    }
407}
408
409fn mcp_result_text(content: Option<ct::BetaMcpToolResultBlockParamContent>) -> String {
410    match content {
411        Some(ct::BetaMcpToolResultBlockParamContent::Text(text)) => text,
412        Some(ct::BetaMcpToolResultBlockParamContent::Blocks(parts)) => parts
413            .into_iter()
414            .map(|part| part.text)
415            .filter(|text| !text.is_empty())
416            .collect::<Vec<_>>()
417            .join("\n"),
418        None => String::new(),
419    }
420}
421
422fn shell_output_text(stdout: String, stderr: String) -> String {
423    if stderr.is_empty() {
424        stdout
425    } else if stdout.is_empty() {
426        stderr
427    } else {
428        format!("stdout: {stdout}\nstderr: {stderr}")
429    }
430}
431
432fn string_list(value: Option<&serde_json::Value>) -> Vec<String> {
433    match value {
434        Some(serde_json::Value::Array(values)) => values
435            .iter()
436            .filter_map(|value| value.as_str().map(ToString::to_string))
437            .collect(),
438        Some(serde_json::Value::String(value)) => vec![value.clone()],
439        _ => Vec::new(),
440    }
441}
442
443fn f64_field(input: &ct::JsonObject, key: &str) -> Option<f64> {
444    input.get(key).and_then(|value| value.as_f64())
445}
446
447fn str_field<'a>(input: &'a ct::JsonObject, key: &str) -> Option<&'a str> {
448    input.get(key).and_then(|value| value.as_str())
449}
450
451fn computer_mouse_button(input: &ct::JsonObject) -> ot::ResponseComputerMouseButton {
452    match str_field(input, "button").unwrap_or("left") {
453        "right" => ot::ResponseComputerMouseButton::Right,
454        "wheel" => ot::ResponseComputerMouseButton::Wheel,
455        "back" => ot::ResponseComputerMouseButton::Back,
456        "forward" => ot::ResponseComputerMouseButton::Forward,
457        _ => ot::ResponseComputerMouseButton::Left,
458    }
459}
460
461fn computer_action_from_input(input: &ct::JsonObject) -> Option<ot::ResponseComputerAction> {
462    let action = str_field(input, "action")
463        .or_else(|| str_field(input, "type"))?
464        .to_string();
465
466    match action.as_str() {
467        "click" => Some(ot::ResponseComputerAction::Click {
468            button: computer_mouse_button(input),
469            x: f64_field(input, "x")?,
470            y: f64_field(input, "y")?,
471        }),
472        "double_click" => Some(ot::ResponseComputerAction::DoubleClick {
473            x: f64_field(input, "x")?,
474            y: f64_field(input, "y")?,
475        }),
476        "drag" => {
477            let path = input
478                .get("path")
479                .and_then(|value| value.as_array())
480                .map(|values| {
481                    values
482                        .iter()
483                        .filter_map(|value| {
484                            let x = value.get("x")?.as_f64()?;
485                            let y = value.get("y")?.as_f64()?;
486                            Some(ot::ResponseComputerPoint { x, y })
487                        })
488                        .collect::<Vec<_>>()
489                })
490                .unwrap_or_default();
491            if path.is_empty() {
492                None
493            } else {
494                Some(ot::ResponseComputerAction::Drag { path })
495            }
496        }
497        "keypress" => {
498            let mut keys = string_list(input.get("keys"));
499            if keys.is_empty()
500                && let Some(key) = str_field(input, "key")
501            {
502                keys.push(key.to_string());
503            }
504            if keys.is_empty() {
505                None
506            } else {
507                Some(ot::ResponseComputerAction::Keypress { keys })
508            }
509        }
510        "move" => Some(ot::ResponseComputerAction::Move {
511            x: f64_field(input, "x")?,
512            y: f64_field(input, "y")?,
513        }),
514        "screenshot" => Some(ot::ResponseComputerAction::Screenshot),
515        "scroll" => Some(ot::ResponseComputerAction::Scroll {
516            scroll_x: f64_field(input, "scroll_x")
517                .or_else(|| f64_field(input, "delta_x"))
518                .unwrap_or_default(),
519            scroll_y: f64_field(input, "scroll_y")
520                .or_else(|| f64_field(input, "delta_y"))
521                .unwrap_or_default(),
522            x: f64_field(input, "x").unwrap_or_default(),
523            y: f64_field(input, "y").unwrap_or_default(),
524        }),
525        "type" => Some(ot::ResponseComputerAction::Type {
526            text: str_field(input, "text")?.to_string(),
527        }),
528        "wait" => Some(ot::ResponseComputerAction::Wait),
529        _ => None,
530    }
531}
532
533fn computer_screenshot_from_tool_result_content(
534    content: Option<ct::BetaToolResultBlockParamContent>,
535) -> Option<ot::ResponseComputerToolCallOutputScreenshot> {
536    let ct::BetaToolResultBlockParamContent::Blocks(parts) = content? else {
537        return None;
538    };
539
540    for part in parts {
541        let ct::BetaToolResultContentBlockParam::Image(image) = part else {
542            continue;
543        };
544        return match image.source {
545            ct::BetaImageSource::Base64(source) => {
546                Some(ot::ResponseComputerToolCallOutputScreenshot {
547                    type_: ot::ResponseComputerToolCallOutputScreenshotType::ComputerScreenshot,
548                    file_id: None,
549                    image_url: Some(format!(
550                        "data:{};base64,{}",
551                        image_media_type(source.media_type),
552                        source.data
553                    )),
554                })
555            }
556            ct::BetaImageSource::Url(source) => {
557                Some(ot::ResponseComputerToolCallOutputScreenshot {
558                    type_: ot::ResponseComputerToolCallOutputScreenshotType::ComputerScreenshot,
559                    file_id: None,
560                    image_url: Some(source.url),
561                })
562            }
563            ct::BetaImageSource::File(source) => {
564                Some(ot::ResponseComputerToolCallOutputScreenshot {
565                    type_: ot::ResponseComputerToolCallOutputScreenshotType::ComputerScreenshot,
566                    file_id: Some(source.file_id),
567                    image_url: None,
568                })
569            }
570        };
571    }
572
573    None
574}
575
576fn file_search_queries(input: &ct::JsonObject) -> Vec<String> {
577    let mut queries = string_list(input.get("queries"));
578    if queries.is_empty() {
579        for key in ["query", "pattern", "term"] {
580            if let Some(value) = str_field(input, key) {
581                queries.push(value.to_string());
582                break;
583            }
584        }
585    }
586    queries
587}
588
589fn shell_commands(input: &ct::JsonObject) -> Vec<String> {
590    let mut commands = string_list(input.get("commands"));
591    if commands.is_empty() {
592        for key in ["command", "cmd"] {
593            if let Some(value) = str_field(input, key) {
594                commands.push(value.to_string());
595                break;
596            }
597        }
598    }
599    commands
600}
601
602fn apply_claude_tool_choice(
603    tool_choice: Option<BetaToolChoice>,
604    tool_registry: &BTreeMap<String, ClaudeToolKind>,
605) -> Option<ResponseToolChoice> {
606    match tool_choice {
607        Some(BetaToolChoice::Auto(_)) => {
608            Some(ResponseToolChoice::Options(ResponseToolChoiceOptions::Auto))
609        }
610        Some(BetaToolChoice::Any(_)) => Some(ResponseToolChoice::Options(
611            ResponseToolChoiceOptions::Required,
612        )),
613        Some(BetaToolChoice::None(_)) => {
614            Some(ResponseToolChoice::Options(ResponseToolChoiceOptions::None))
615        }
616        Some(BetaToolChoice::Tool(choice)) => match tool_registry.get(&choice.name) {
617            Some(ClaudeToolKind::Custom) => {
618                Some(ResponseToolChoice::Custom(ot::ResponseToolChoiceCustom {
619                    name: choice.name,
620                    type_: ot::ResponseToolChoiceCustomType::Custom,
621                }))
622            }
623            Some(ClaudeToolKind::Mcp) => Some(ResponseToolChoice::Mcp(ot::ResponseToolChoiceMcp {
624                server_label: choice.name,
625                type_: ot::ResponseToolChoiceMcpType::Mcp,
626                name: None,
627            })),
628            Some(ClaudeToolKind::ApplyPatch) => Some(ResponseToolChoice::ApplyPatch(
629                ot::ResponseToolChoiceApplyPatch {
630                    type_: ot::ResponseToolChoiceApplyPatchType::ApplyPatch,
631                },
632            )),
633            Some(ClaudeToolKind::Shell) => {
634                Some(ResponseToolChoice::Shell(ot::ResponseToolChoiceShell {
635                    type_: ot::ResponseToolChoiceShellType::Shell,
636                }))
637            }
638            Some(ClaudeToolKind::FileSearch) => {
639                Some(ResponseToolChoice::Types(ot::ResponseToolChoiceTypes {
640                    type_: ot::ResponseToolChoiceBuiltinType::FileSearch,
641                }))
642            }
643            Some(ClaudeToolKind::Computer) => {
644                Some(ResponseToolChoice::Types(ot::ResponseToolChoiceTypes {
645                    type_: ot::ResponseToolChoiceBuiltinType::ComputerUsePreview,
646                }))
647            }
648            Some(ClaudeToolKind::CodeInterpreter) => {
649                Some(ResponseToolChoice::Types(ot::ResponseToolChoiceTypes {
650                    type_: ot::ResponseToolChoiceBuiltinType::CodeInterpreter,
651                }))
652            }
653            Some(ClaudeToolKind::WebSearch | ClaudeToolKind::WebFetch) => {
654                Some(ResponseToolChoice::Types(ot::ResponseToolChoiceTypes {
655                    type_: ot::ResponseToolChoiceBuiltinType::WebSearchPreview,
656                }))
657            }
658            _ => Some(ResponseToolChoice::Function(ResponseToolChoiceFunction {
659                name: choice.name,
660                type_: ResponseToolChoiceFunctionType::Function,
661            })),
662        },
663        None => None,
664    }
665}
666
667impl TryFrom<ClaudeCreateMessageRequest> for OpenAiCreateResponseRequest {
668    type Error = TransformError;
669
670    fn try_from(value: ClaudeCreateMessageRequest) -> Result<Self, TransformError> {
671        let body = value.body;
672        let model = claude_model_to_string(&body.model);
673
674        let instructions = beta_system_prompt_to_text(body.system.clone());
675        let parallel_tool_calls = match body.tool_choice.as_ref() {
676            Some(BetaToolChoice::Auto(choice)) => choice.disable_parallel_tool_use.map(|v| !v),
677            Some(BetaToolChoice::Any(choice)) => choice.disable_parallel_tool_use.map(|v| !v),
678            Some(BetaToolChoice::Tool(choice)) => choice.disable_parallel_tool_use.map(|v| !v),
679            Some(BetaToolChoice::None(_)) | None => None,
680        };
681
682        let mut tool_registry = BTreeMap::new();
683        if let Some(tools) = body.tools.as_ref() {
684            for tool in tools {
685                match tool {
686                    BetaToolUnion::Custom(tool) => {
687                        tool_registry.insert(
688                            tool.name.clone(),
689                            if matches!(tool.type_, Some(ct::BetaCustomToolType::Custom)) {
690                                ClaudeToolKind::Custom
691                            } else {
692                                ClaudeToolKind::Function
693                            },
694                        );
695                    }
696                    BetaToolUnion::CodeExecution20250522(_)
697                    | BetaToolUnion::CodeExecution20250825(_) => {
698                        tool_registry.insert(
699                            "code_execution".to_string(),
700                            ClaudeToolKind::CodeInterpreter,
701                        );
702                    }
703                    BetaToolUnion::ComputerUse20241022(_)
704                    | BetaToolUnion::ComputerUse20250124(_)
705                    | BetaToolUnion::ComputerUse20251124(_) => {
706                        tool_registry.insert("computer".to_string(), ClaudeToolKind::Computer);
707                    }
708                    BetaToolUnion::WebSearch20250305(_) => {
709                        tool_registry.insert("web_search".to_string(), ClaudeToolKind::WebSearch);
710                    }
711                    BetaToolUnion::WebFetch20250910(_) => {
712                        tool_registry.insert("web_fetch".to_string(), ClaudeToolKind::WebFetch);
713                    }
714                    BetaToolUnion::Bash20241022(_) | BetaToolUnion::Bash20250124(_) => {
715                        tool_registry.insert("bash".to_string(), ClaudeToolKind::Shell);
716                    }
717                    BetaToolUnion::ToolSearchBm25_20251119(_) => {
718                        tool_registry.insert(
719                            "tool_search_tool_bm25".to_string(),
720                            ClaudeToolKind::FileSearch,
721                        );
722                    }
723                    BetaToolUnion::ToolSearchRegex20251119(_) => {
724                        tool_registry.insert(
725                            "tool_search_tool_regex".to_string(),
726                            ClaudeToolKind::FileSearch,
727                        );
728                    }
729                    BetaToolUnion::TextEditor20241022(_) => {
730                        tool_registry
731                            .insert("str_replace_editor".to_string(), ClaudeToolKind::ApplyPatch);
732                    }
733                    BetaToolUnion::TextEditor20250124(_)
734                    | BetaToolUnion::TextEditor20250429(_)
735                    | BetaToolUnion::TextEditor20250728(_) => {
736                        tool_registry.insert(
737                            "str_replace_based_edit_tool".to_string(),
738                            ClaudeToolKind::ApplyPatch,
739                        );
740                    }
741                    BetaToolUnion::McpToolset(tool) => {
742                        tool_registry.insert(tool.mcp_server_name.clone(), ClaudeToolKind::Mcp);
743                    }
744                    BetaToolUnion::Memory20250818(_) => {}
745                }
746            }
747        }
748        if let Some(servers) = body.mcp_servers.as_ref() {
749            for server in servers {
750                tool_registry.insert(server.name.clone(), ClaudeToolKind::Mcp);
751            }
752        }
753        let tool_choice = apply_claude_tool_choice(body.tool_choice.clone(), &tool_registry);
754
755        let reasoning_effort_from_thinking = match body.thinking.clone() {
756            Some(BetaThinkingConfigParam::Enabled(config)) => Some(if config.budget_tokens == 0 {
757                ResponseReasoningEffort::None
758            } else if config.budget_tokens <= 4096 {
759                ResponseReasoningEffort::Minimal
760            } else if config.budget_tokens <= 8192 {
761                ResponseReasoningEffort::Low
762            } else if config.budget_tokens <= 16384 {
763                ResponseReasoningEffort::Medium
764            } else if config.budget_tokens <= 32768 {
765                ResponseReasoningEffort::High
766            } else {
767                ResponseReasoningEffort::XHigh
768            }),
769            Some(BetaThinkingConfigParam::Disabled(_)) => Some(ResponseReasoningEffort::None),
770            Some(BetaThinkingConfigParam::Adaptive(_)) => Some(ResponseReasoningEffort::Medium),
771            None => None,
772        };
773        let reasoning = reasoning_effort_from_thinking.map(|effort| ResponseReasoning {
774            effort: Some(effort),
775            generate_summary: None,
776            summary: None,
777        });
778        let output_schema = body
779            .output_config
780            .as_ref()
781            .and_then(|config| config.format.as_ref());
782        let text_format = output_schema.map(|schema| {
783            ResponseTextFormatConfig::JsonSchema(ResponseFormatTextJsonSchemaConfig {
784                name: "output".to_string(),
785                schema: schema.schema.clone(),
786                type_: ResponseFormatTextJsonSchemaConfigType::JsonSchema,
787                description: None,
788                strict: None,
789            })
790        });
791        let text_verbosity = body
792            .output_config
793            .as_ref()
794            .and_then(|config| config.effort.as_ref())
795            .map(|effort| match effort {
796                BetaOutputEffort::Low => ResponseTextVerbosity::Low,
797                BetaOutputEffort::Medium => ResponseTextVerbosity::Medium,
798                BetaOutputEffort::High | BetaOutputEffort::XHigh | BetaOutputEffort::Max => {
799                    ResponseTextVerbosity::High
800                }
801            });
802        let text = if text_format.is_some() || text_verbosity.is_some() {
803            Some(ResponseTextConfig {
804                format: text_format,
805                verbosity: text_verbosity,
806            })
807        } else {
808            None
809        };
810        let context_management = body.context_management.as_ref().and_then(|config| {
811            let mut entries = Vec::new();
812            if let Some(edits) = config.edits.as_ref() {
813                for edit in edits {
814                    if let BetaContextManagementEdit::Compact(compact) = edit {
815                        entries.push(ResponseContextManagementEntry {
816                            type_: ResponseContextManagementType::Compaction,
817                            compact_threshold: compact
818                                .trigger
819                                .as_ref()
820                                .map(|trigger| trigger.value),
821                        });
822                    }
823                }
824            }
825
826            if entries.is_empty() {
827                None
828            } else {
829                Some(entries)
830            }
831        });
832        let truncation = body
833            .context_management
834            .as_ref()
835            .map(|_| ResponseTruncation::Auto);
836
837        let mut input_items = Vec::new();
838        let mut recorded_calls = BTreeMap::<String, RecordedToolCall>::new();
839        let mut assistant_message_index = 0u64;
840        let mut reasoning_index = 0u64;
841
842        for message in body.messages {
843            match (message.role, message.content) {
844                (BetaMessageRole::User, ct::BetaMessageContent::Text(text)) => {
845                    if !text.is_empty() {
846                        input_items.push(ResponseInputItem::Message(ResponseInputMessage {
847                            content: ResponseInputMessageContent::Text(text),
848                            role: ResponseInputMessageRole::User,
849                            phase: None,
850                            status: None,
851                            type_: Some(ResponseInputMessageType::Message),
852                        }));
853                    }
854                }
855                (BetaMessageRole::User, ct::BetaMessageContent::Blocks(blocks)) => {
856                    let mut message_parts = Vec::new();
857                    for block in blocks {
858                        if let Some(part) = user_message_part_from_block(block.clone()) {
859                            message_parts.push(part);
860                            continue;
861                        }
862
863                        match block {
864                            ct::BetaContentBlockParam::ToolResult(block) => {
865                                flush_input_parts(
866                                    &mut input_items,
867                                    ResponseInputMessageRole::User,
868                                    &mut message_parts,
869                                );
870                                let kind = recorded_calls
871                                    .get(&block.tool_use_id)
872                                    .map(|record| record.kind)
873                                    .unwrap_or(ClaudeToolKind::Function);
874                                let is_error = block.is_error.unwrap_or(false);
875                                match kind {
876                                    ClaudeToolKind::Function => {
877                                        input_items.push(ResponseInputItem::FunctionCallOutput(
878                                            ot::ResponseFunctionCallOutput {
879                                                call_id: block.tool_use_id,
880                                                output: tool_result_content_to_function_output(
881                                                    block.content,
882                                                ),
883                                                type_: ot::ResponseFunctionCallOutputType::FunctionCallOutput,
884                                                id: None,
885                                                status: Some(if is_error {
886                                                    ot::ResponseItemStatus::Incomplete
887                                                } else {
888                                                    ot::ResponseItemStatus::Completed
889                                                }),
890                                            },
891                                        ));
892                                    }
893                                    ClaudeToolKind::Custom | ClaudeToolKind::ApplyPatch => {
894                                        input_items.push(ResponseInputItem::CustomToolCallOutput(
895                                            ot::ResponseCustomToolCallOutput {
896                                                call_id: block.tool_use_id,
897                                                output: tool_result_content_to_custom_output(
898                                                    block.content,
899                                                ),
900                                                type_: ot::ResponseCustomToolCallOutputType::CustomToolCallOutput,
901                                                id: None,
902                                            },
903                                        ));
904                                    }
905                                    ClaudeToolKind::Mcp => {
906                                        if let Some(record) = recorded_calls.get(&block.tool_use_id)
907                                            && let Some(ResponseInputItem::McpCall(call)) =
908                                                input_items.get_mut(record.item_index)
909                                        {
910                                            let text = tool_result_content_to_text(block.content);
911                                            call.output = (!is_error && !text.is_empty())
912                                                .then_some(text.clone());
913                                            call.error = if is_error {
914                                                Some(if text.is_empty() {
915                                                    "mcp_tool_result_error".to_string()
916                                                } else {
917                                                    text
918                                                })
919                                            } else {
920                                                None
921                                            };
922                                            call.status = Some(if is_error {
923                                                ot::ResponseToolCallStatus::Failed
924                                            } else {
925                                                ot::ResponseToolCallStatus::Completed
926                                            });
927                                        }
928                                    }
929                                    ClaudeToolKind::CodeInterpreter => {
930                                        let output_text =
931                                            tool_result_content_to_text(block.content);
932                                        if let Some(record) = recorded_calls.get(&block.tool_use_id)
933                                            && let Some(ResponseInputItem::CodeInterpreterToolCall(
934                                                call,
935                                            )) = input_items.get_mut(record.item_index)
936                                        {
937                                            call.outputs =
938                                                (!output_text.is_empty()).then_some(vec![
939                                                    ot::ResponseCodeInterpreterOutputItem::Logs {
940                                                        logs: output_text,
941                                                    },
942                                                ]);
943                                            call.status = if is_error {
944                                                ot::ResponseCodeInterpreterToolCallStatus::Failed
945                                            } else {
946                                                ot::ResponseCodeInterpreterToolCallStatus::Completed
947                                            };
948                                        }
949                                    }
950                                    ClaudeToolKind::Shell => {
951                                        let output_text =
952                                            tool_result_content_to_text(block.content);
953                                        input_items.push(ResponseInputItem::ShellCallOutput(
954                                            ot::ResponseShellCallOutput {
955                                                call_id: block.tool_use_id,
956                                                output: if output_text.is_empty() {
957                                                    Vec::new()
958                                                } else {
959                                                    vec![ot::ResponseFunctionShellCallOutputContent {
960                                                        outcome: ot::ResponseShellCallOutcome::Exit {
961                                                            exit_code: if is_error { 1 } else { 0 },
962                                                        },
963                                                        stderr: if is_error {
964                                                            output_text.clone()
965                                                        } else {
966                                                            String::new()
967                                                        },
968                                                        stdout: if is_error {
969                                                            String::new()
970                                                        } else {
971                                                            output_text
972                                                        },
973                                                    }]
974                                                },
975                                                type_: ot::ResponseShellCallOutputType::ShellCallOutput,
976                                                id: None,
977                                                max_output_length: None,
978                                                status: Some(if is_error {
979                                                    ot::ResponseItemStatus::Incomplete
980                                                } else {
981                                                    ot::ResponseItemStatus::Completed
982                                                }),
983                                            },
984                                        ));
985                                    }
986                                    ClaudeToolKind::FileSearch => {
987                                        let output_text =
988                                            tool_result_content_to_text(block.content);
989                                        if let Some(record) = recorded_calls.get(&block.tool_use_id)
990                                            && let Some(ResponseInputItem::FileSearchToolCall(call)) =
991                                                input_items.get_mut(record.item_index)
992                                        {
993                                            call.results =
994                                                (!output_text.is_empty()).then_some(vec![
995                                                    ot::ResponseFileSearchResult {
996                                                        text: Some(output_text),
997                                                        ..Default::default()
998                                                    },
999                                                ]);
1000                                            call.status = if is_error {
1001                                                ot::ResponseFileSearchToolCallStatus::Failed
1002                                            } else {
1003                                                ot::ResponseFileSearchToolCallStatus::Completed
1004                                            };
1005                                        }
1006                                    }
1007                                    ClaudeToolKind::Computer => {
1008                                        if let Some(screenshot) =
1009                                            computer_screenshot_from_tool_result_content(
1010                                                block.content,
1011                                            )
1012                                        {
1013                                            input_items.push(ResponseInputItem::ComputerCallOutput(
1014                                                ot::ResponseComputerCallOutput {
1015                                                    call_id: block.tool_use_id,
1016                                                    output: screenshot,
1017                                                    type_: ot::ResponseComputerCallOutputType::ComputerCallOutput,
1018                                                    id: None,
1019                                                    acknowledged_safety_checks: None,
1020                                                    status: Some(if is_error {
1021                                                        ot::ResponseItemStatus::Incomplete
1022                                                    } else {
1023                                                        ot::ResponseItemStatus::Completed
1024                                                    }),
1025                                                },
1026                                            ));
1027                                        }
1028                                    }
1029                                    ClaudeToolKind::WebSearch | ClaudeToolKind::WebFetch => {}
1030                                }
1031                            }
1032                            ct::BetaContentBlockParam::McpToolResult(block) => {
1033                                flush_input_parts(
1034                                    &mut input_items,
1035                                    ResponseInputMessageRole::User,
1036                                    &mut message_parts,
1037                                );
1038                                let output_text = mcp_result_text(block.content);
1039                                if let Some(record) = recorded_calls.get(&block.tool_use_id)
1040                                    && let Some(ResponseInputItem::McpCall(call)) =
1041                                        input_items.get_mut(record.item_index)
1042                                {
1043                                    call.output = (!block.is_error.unwrap_or(false)
1044                                        && !output_text.is_empty())
1045                                    .then_some(output_text.clone());
1046                                    call.error = if block.is_error.unwrap_or(false) {
1047                                        Some(if output_text.is_empty() {
1048                                            "mcp_tool_result_error".to_string()
1049                                        } else {
1050                                            output_text
1051                                        })
1052                                    } else {
1053                                        None
1054                                    };
1055                                    call.status = Some(if block.is_error.unwrap_or(false) {
1056                                        ot::ResponseToolCallStatus::Failed
1057                                    } else {
1058                                        ot::ResponseToolCallStatus::Completed
1059                                    });
1060                                }
1061                            }
1062                            ct::BetaContentBlockParam::WebSearchToolResult(block) => {
1063                                flush_input_parts(
1064                                    &mut input_items,
1065                                    ResponseInputMessageRole::User,
1066                                    &mut message_parts,
1067                                );
1068                                let status = match block.content {
1069                                    ct::BetaWebSearchToolResultBlockParamContent::Results(
1070                                        results,
1071                                    ) => {
1072                                        let sources = results
1073                                            .into_iter()
1074                                            .map(|result| ot::ResponseFunctionWebSearchSource {
1075                                                type_: ot::ResponseFunctionWebSearchSourceType::Url,
1076                                                url: result.url,
1077                                            })
1078                                            .collect::<Vec<_>>();
1079                                        if let Some(record) = recorded_calls.get(&block.tool_use_id)
1080                                            && let Some(ResponseInputItem::FunctionWebSearch(call)) =
1081                                                input_items.get_mut(record.item_index)
1082                                        {
1083                                            let (query, queries) = match &call.action {
1084                                                ot::ResponseFunctionWebSearchAction::Search {
1085                                                    query,
1086                                                    queries,
1087                                                    ..
1088                                                } => (query.clone(), queries.clone()),
1089                                                _ => (None, None),
1090                                            };
1091                                            call.action =
1092                                                ot::ResponseFunctionWebSearchAction::Search {
1093                                                    query,
1094                                                    queries,
1095                                                    sources: (!sources.is_empty())
1096                                                        .then_some(sources),
1097                                                };
1098                                            call.status =
1099                                                ot::ResponseFunctionWebSearchStatus::Completed;
1100                                        }
1101                                        ot::ResponseFunctionWebSearchStatus::Completed
1102                                    }
1103                                    ct::BetaWebSearchToolResultBlockParamContent::Error(_) => {
1104                                        if let Some(record) = recorded_calls.get(&block.tool_use_id)
1105                                            && let Some(ResponseInputItem::FunctionWebSearch(call)) =
1106                                                input_items.get_mut(record.item_index)
1107                                        {
1108                                            call.status =
1109                                                ot::ResponseFunctionWebSearchStatus::Failed;
1110                                        }
1111                                        ot::ResponseFunctionWebSearchStatus::Failed
1112                                    }
1113                                };
1114                                if !recorded_calls.contains_key(&block.tool_use_id) {
1115                                    input_items.push(ResponseInputItem::FunctionWebSearch(
1116                                        ot::ResponseFunctionWebSearch {
1117                                            id: Some(block.tool_use_id),
1118                                            action: ot::ResponseFunctionWebSearchAction::Search {
1119                                                query: None,
1120                                                queries: None,
1121                                                sources: None,
1122                                            },
1123                                            status,
1124                                            type_: ot::ResponseFunctionWebSearchType::WebSearchCall,
1125                                        },
1126                                    ));
1127                                }
1128                            }
1129                            ct::BetaContentBlockParam::WebFetchToolResult(block) => {
1130                                flush_input_parts(
1131                                    &mut input_items,
1132                                    ResponseInputMessageRole::User,
1133                                    &mut message_parts,
1134                                );
1135                                match block.content {
1136                                    ct::BetaWebFetchToolResultBlockParamContent::Result(result) => {
1137                                        if let Some(record) = recorded_calls.get(&block.tool_use_id)
1138                                            && let Some(ResponseInputItem::FunctionWebSearch(call)) =
1139                                                input_items.get_mut(record.item_index)
1140                                        {
1141                                            call.action =
1142                                                ot::ResponseFunctionWebSearchAction::OpenPage {
1143                                                    url: Some(result.url.clone()),
1144                                                };
1145                                            call.status =
1146                                                ot::ResponseFunctionWebSearchStatus::Completed;
1147                                        } else {
1148                                            input_items.push(ResponseInputItem::FunctionWebSearch(
1149                                                ot::ResponseFunctionWebSearch {
1150                                                    id: Some(block.tool_use_id),
1151                                                    action: ot::ResponseFunctionWebSearchAction::OpenPage {
1152                                                        url: Some(result.url),
1153                                                    },
1154                                                    status: ot::ResponseFunctionWebSearchStatus::Completed,
1155                                                    type_: ot::ResponseFunctionWebSearchType::WebSearchCall,
1156                                                },
1157                                            ));
1158                                        }
1159                                    }
1160                                    ct::BetaWebFetchToolResultBlockParamContent::Error(_) => {
1161                                        if let Some(record) = recorded_calls.get(&block.tool_use_id)
1162                                            && let Some(ResponseInputItem::FunctionWebSearch(call)) =
1163                                                input_items.get_mut(record.item_index)
1164                                        {
1165                                            call.status =
1166                                                ot::ResponseFunctionWebSearchStatus::Failed;
1167                                        } else {
1168                                            input_items.push(ResponseInputItem::FunctionWebSearch(
1169                                                ot::ResponseFunctionWebSearch {
1170                                                    id: Some(block.tool_use_id),
1171                                                    action: ot::ResponseFunctionWebSearchAction::OpenPage {
1172                                                        url: None,
1173                                                    },
1174                                                    status: ot::ResponseFunctionWebSearchStatus::Failed,
1175                                                    type_: ot::ResponseFunctionWebSearchType::WebSearchCall,
1176                                                },
1177                                            ));
1178                                        }
1179                                    }
1180                                }
1181                            }
1182                            ct::BetaContentBlockParam::CodeExecutionToolResult(block) => {
1183                                flush_input_parts(
1184                                    &mut input_items,
1185                                    ResponseInputMessageRole::User,
1186                                    &mut message_parts,
1187                                );
1188                                let (output_text, status) = match block.content {
1189                                    ct::BetaCodeExecutionToolResultBlockParamContent::Result(
1190                                        result,
1191                                    ) => (
1192                                        shell_output_text(result.stdout, result.stderr),
1193                                        ot::ResponseCodeInterpreterToolCallStatus::Completed,
1194                                    ),
1195                                    ct::BetaCodeExecutionToolResultBlockParamContent::Error(
1196                                        err,
1197                                    ) => (
1198                                        format!("code_execution_error:{:?}", err.error_code),
1199                                        ot::ResponseCodeInterpreterToolCallStatus::Failed,
1200                                    ),
1201                                };
1202                                if let Some(record) = recorded_calls.get(&block.tool_use_id)
1203                                    && let Some(ResponseInputItem::CodeInterpreterToolCall(call)) =
1204                                        input_items.get_mut(record.item_index)
1205                                {
1206                                    call.outputs = (!output_text.is_empty()).then_some(vec![
1207                                        ot::ResponseCodeInterpreterOutputItem::Logs {
1208                                            logs: output_text,
1209                                        },
1210                                    ]);
1211                                    call.status = status;
1212                                } else {
1213                                    input_items.push(ResponseInputItem::CodeInterpreterToolCall(
1214                                        ot::ResponseCodeInterpreterToolCall {
1215                                            id: block.tool_use_id,
1216                                            code: String::new(),
1217                                            container_id: String::new(),
1218                                            outputs: (!output_text.is_empty()).then_some(vec![
1219                                                ot::ResponseCodeInterpreterOutputItem::Logs {
1220                                                    logs: output_text,
1221                                                },
1222                                            ]),
1223                                            status,
1224                                            type_: ot::ResponseCodeInterpreterToolCallType::CodeInterpreterCall,
1225                                        },
1226                                    ));
1227                                }
1228                            }
1229                            ct::BetaContentBlockParam::BashCodeExecutionToolResult(block) => {
1230                                flush_input_parts(
1231                                    &mut input_items,
1232                                    ResponseInputMessageRole::User,
1233                                    &mut message_parts,
1234                                );
1235                                let (stdout, stderr, outcome) = match block.content {
1236                                    ct::BetaBashCodeExecutionToolResultBlockParamContent::Result(result) => (
1237                                        result.stdout,
1238                                        result.stderr,
1239                                        ot::ResponseShellCallOutcome::Exit { exit_code: 0 },
1240                                    ),
1241                                    ct::BetaBashCodeExecutionToolResultBlockParamContent::Error(err) => (
1242                                        String::new(),
1243                                        format!("bash_code_execution_error:{:?}", err.error_code),
1244                                        if matches!(
1245                                            err.error_code,
1246                                            ct::BetaBashCodeExecutionToolResultErrorCode::ExecutionTimeExceeded
1247                                        ) {
1248                                            ot::ResponseShellCallOutcome::Timeout
1249                                        } else {
1250                                            ot::ResponseShellCallOutcome::Exit { exit_code: 1 }
1251                                        },
1252                                    ),
1253                                };
1254                                input_items.push(ResponseInputItem::ShellCallOutput(
1255                                    ot::ResponseShellCallOutput {
1256                                        call_id: block.tool_use_id,
1257                                        output: vec![ot::ResponseFunctionShellCallOutputContent {
1258                                            outcome,
1259                                            stderr,
1260                                            stdout,
1261                                        }],
1262                                        type_: ot::ResponseShellCallOutputType::ShellCallOutput,
1263                                        id: None,
1264                                        max_output_length: None,
1265                                        status: Some(ot::ResponseItemStatus::Completed),
1266                                    },
1267                                ));
1268                            }
1269                            ct::BetaContentBlockParam::TextEditorCodeExecutionToolResult(block) => {
1270                                flush_input_parts(
1271                                    &mut input_items,
1272                                    ResponseInputMessageRole::User,
1273                                    &mut message_parts,
1274                                );
1275                                let output = match block.content {
1276                                    ct::BetaTextEditorCodeExecutionToolResultBlockParamContent::View(view) => view.content,
1277                                    ct::BetaTextEditorCodeExecutionToolResultBlockParamContent::Create(create) => {
1278                                        format!("file_updated:{}", create.is_file_update)
1279                                    }
1280                                    ct::BetaTextEditorCodeExecutionToolResultBlockParamContent::StrReplace(replace) => {
1281                                        replace.lines.unwrap_or_default().join("\n")
1282                                    }
1283                                    ct::BetaTextEditorCodeExecutionToolResultBlockParamContent::Error(err) => err
1284                                        .error_message
1285                                        .unwrap_or_else(|| {
1286                                            format!(
1287                                                "text_editor_code_execution_error:{:?}",
1288                                                err.error_code
1289                                            )
1290                                        }),
1291                                };
1292                                input_items.push(ResponseInputItem::CustomToolCallOutput(
1293                                    ot::ResponseCustomToolCallOutput {
1294                                        call_id: block.tool_use_id,
1295                                        output: ot::ResponseCustomToolCallOutputContent::Text(output),
1296                                        type_: ot::ResponseCustomToolCallOutputType::CustomToolCallOutput,
1297                                        id: None,
1298                                    },
1299                                ));
1300                            }
1301                            ct::BetaContentBlockParam::ToolSearchToolResult(block) => {
1302                                flush_input_parts(
1303                                    &mut input_items,
1304                                    ResponseInputMessageRole::User,
1305                                    &mut message_parts,
1306                                );
1307                                match block.content {
1308                                    ct::BetaToolSearchToolResultBlockParamContent::Result(
1309                                        result,
1310                                    ) => {
1311                                        let results = result
1312                                            .tool_references
1313                                            .into_iter()
1314                                            .map(|reference| ot::ResponseFileSearchResult {
1315                                                filename: Some(reference.tool_name.clone()),
1316                                                text: Some(reference.tool_name),
1317                                                ..Default::default()
1318                                            })
1319                                            .collect::<Vec<_>>();
1320                                        if let Some(record) = recorded_calls.get(&block.tool_use_id)
1321                                            && let Some(ResponseInputItem::FileSearchToolCall(call)) =
1322                                                input_items.get_mut(record.item_index)
1323                                        {
1324                                            call.results = Some(results);
1325                                            call.status =
1326                                                ot::ResponseFileSearchToolCallStatus::Completed;
1327                                        } else {
1328                                            input_items.push(ResponseInputItem::FileSearchToolCall(
1329                                                ot::ResponseFileSearchToolCall {
1330                                                    id: block.tool_use_id,
1331                                                    queries: Vec::new(),
1332                                                    status: ot::ResponseFileSearchToolCallStatus::Completed,
1333                                                    type_: ot::ResponseFileSearchToolCallType::FileSearchCall,
1334                                                    results: Some(results),
1335                                                },
1336                                            ));
1337                                        }
1338                                    }
1339                                    ct::BetaToolSearchToolResultBlockParamContent::Error(err) => {
1340                                        if let Some(record) = recorded_calls.get(&block.tool_use_id)
1341                                            && let Some(ResponseInputItem::FileSearchToolCall(call)) =
1342                                                input_items.get_mut(record.item_index)
1343                                        {
1344                                            call.status =
1345                                                ot::ResponseFileSearchToolCallStatus::Failed;
1346                                            call.results =
1347                                                Some(vec![ot::ResponseFileSearchResult {
1348                                                    text: Some(format!(
1349                                                        "tool_search_error:{:?}",
1350                                                        err.error_code
1351                                                    )),
1352                                                    ..Default::default()
1353                                                }]);
1354                                        } else {
1355                                            input_items.push(ResponseInputItem::FileSearchToolCall(
1356                                                ot::ResponseFileSearchToolCall {
1357                                                    id: block.tool_use_id,
1358                                                    queries: Vec::new(),
1359                                                    status: ot::ResponseFileSearchToolCallStatus::Failed,
1360                                                    type_: ot::ResponseFileSearchToolCallType::FileSearchCall,
1361                                                    results: Some(vec![ot::ResponseFileSearchResult {
1362                                                        text: Some(format!(
1363                                                            "tool_search_error:{:?}",
1364                                                            err.error_code
1365                                                        )),
1366                                                        ..Default::default()
1367                                                    }]),
1368                                                },
1369                                            ));
1370                                        }
1371                                    }
1372                                }
1373                            }
1374                            ct::BetaContentBlockParam::Compaction(block) => {
1375                                flush_input_parts(
1376                                    &mut input_items,
1377                                    ResponseInputMessageRole::User,
1378                                    &mut message_parts,
1379                                );
1380                                input_items.push(ResponseInputItem::CompactionItem(
1381                                    ot::ResponseCompactionItemParam {
1382                                        encrypted_content: block.content.unwrap_or_default(),
1383                                        type_: ot::ResponseCompactionItemType::Compaction,
1384                                        id: None,
1385                                        created_by: None,
1386                                    },
1387                                ));
1388                            }
1389                            other => {
1390                                message_parts.push(input_text_content(json_string(&other)));
1391                            }
1392                        }
1393                    }
1394                    flush_input_parts(
1395                        &mut input_items,
1396                        ResponseInputMessageRole::User,
1397                        &mut message_parts,
1398                    );
1399                }
1400                (BetaMessageRole::Assistant, ct::BetaMessageContent::Text(text)) => {
1401                    if !text.is_empty() {
1402                        input_items.push(output_message_item(
1403                            format!("msg_{assistant_message_index}"),
1404                            text,
1405                        ));
1406                        assistant_message_index += 1;
1407                    }
1408                }
1409                (BetaMessageRole::Assistant, ct::BetaMessageContent::Blocks(blocks)) => {
1410                    for block in blocks {
1411                        match block {
1412                            ct::BetaContentBlockParam::Text(block) => {
1413                                if !block.text.is_empty() {
1414                                    input_items.push(output_message_item(
1415                                        format!("msg_{assistant_message_index}"),
1416                                        block.text,
1417                                    ));
1418                                    assistant_message_index += 1;
1419                                }
1420                            }
1421                            ct::BetaContentBlockParam::Thinking(block) => {
1422                                input_items.push(ResponseInputItem::ReasoningItem(
1423                                    ot::ResponseReasoningItem {
1424                                        id: Some(block.signature),
1425                                        summary: vec![ot::ResponseSummaryTextContent {
1426                                            text: block.thinking,
1427                                            type_: ot::ResponseSummaryTextContentType::SummaryText,
1428                                        }],
1429                                        type_: ot::ResponseReasoningItemType::Reasoning,
1430                                        content: None,
1431                                        encrypted_content: None,
1432                                        status: Some(ot::ResponseItemStatus::Completed),
1433                                    },
1434                                ));
1435                            }
1436                            ct::BetaContentBlockParam::RedactedThinking(block) => {
1437                                input_items.push(ResponseInputItem::ReasoningItem(
1438                                    ot::ResponseReasoningItem {
1439                                        id: Some(format!("redacted_reasoning_{reasoning_index}")),
1440                                        summary: Vec::new(),
1441                                        type_: ot::ResponseReasoningItemType::Reasoning,
1442                                        content: None,
1443                                        encrypted_content: Some(block.data),
1444                                        status: Some(ot::ResponseItemStatus::Completed),
1445                                    },
1446                                ));
1447                                reasoning_index += 1;
1448                            }
1449                            ct::BetaContentBlockParam::ToolUse(block) => {
1450                                let tool_name = block.name.clone();
1451                                let call_id = block.id.clone();
1452                                let input_json = json_string(&block.input);
1453                                let mut actual_kind = tool_registry
1454                                    .get(&tool_name)
1455                                    .copied()
1456                                    .unwrap_or(ClaudeToolKind::Function);
1457                                let item = match actual_kind {
1458                                    ClaudeToolKind::Function => ResponseInputItem::FunctionToolCall(
1459                                        ot::ResponseFunctionToolCall {
1460                                            arguments: input_json,
1461                                            call_id: call_id.clone(),
1462                                            name: tool_name,
1463                                            type_: ot::ResponseFunctionToolCallType::FunctionCall,
1464                                            id: Some(call_id.clone()),
1465                                            status: Some(ot::ResponseItemStatus::Completed),
1466                                        },
1467                                    ),
1468                                    ClaudeToolKind::Custom | ClaudeToolKind::ApplyPatch => {
1469                                        actual_kind = ClaudeToolKind::Custom;
1470                                        ResponseInputItem::CustomToolCall(ot::ResponseCustomToolCall {
1471                                            call_id: call_id.clone(),
1472                                            input: input_json,
1473                                            name: tool_name,
1474                                            type_: ot::ResponseCustomToolCallType::CustomToolCall,
1475                                            id: Some(call_id.clone()),
1476                                        })
1477                                    }
1478                                    ClaudeToolKind::Computer => {
1479                                        if let Some(action) = computer_action_from_input(&block.input) {
1480                                            ResponseInputItem::ComputerToolCall(
1481                                                ot::ResponseComputerToolCall {
1482                                                    id: call_id.clone(),
1483                                                    action,
1484                                                    call_id: call_id.clone(),
1485                                                    pending_safety_checks: Vec::new(),
1486                                                    status: ot::ResponseItemStatus::Completed,
1487                                                    type_: ot::ResponseComputerToolCallType::ComputerCall,
1488                                                },
1489                                            )
1490                                        } else {
1491                                            actual_kind = ClaudeToolKind::Custom;
1492                                            ResponseInputItem::CustomToolCall(ot::ResponseCustomToolCall {
1493                                                call_id: call_id.clone(),
1494                                                input: input_json,
1495                                                name: tool_name,
1496                                                type_: ot::ResponseCustomToolCallType::CustomToolCall,
1497                                                id: Some(call_id.clone()),
1498                                            })
1499                                        }
1500                                    }
1501                                    ClaudeToolKind::CodeInterpreter => ResponseInputItem::CodeInterpreterToolCall(
1502                                        ot::ResponseCodeInterpreterToolCall {
1503                                            id: call_id.clone(),
1504                                            code: str_field(&block.input, "code")
1505                                                .unwrap_or_default()
1506                                                .to_string(),
1507                                            container_id: str_field(&block.input, "container_id")
1508                                                .unwrap_or_default()
1509                                                .to_string(),
1510                                            outputs: None,
1511                                            status: ot::ResponseCodeInterpreterToolCallStatus::Completed,
1512                                            type_: ot::ResponseCodeInterpreterToolCallType::CodeInterpreterCall,
1513                                        },
1514                                    ),
1515                                    ClaudeToolKind::Shell => ResponseInputItem::ShellCall(
1516                                        ot::ResponseShellCall {
1517                                            action: ot::ResponseShellCallAction {
1518                                                commands: shell_commands(&block.input),
1519                                                max_output_length: None,
1520                                                timeout_ms: block
1521                                                    .input
1522                                                    .get("timeout_ms")
1523                                                    .and_then(|value| value.as_u64()),
1524                                            },
1525                                            call_id: call_id.clone(),
1526                                            type_: ot::ResponseShellCallType::ShellCall,
1527                                            id: Some(call_id.clone()),
1528                                            environment: None,
1529                                            status: Some(ot::ResponseItemStatus::Completed),
1530                                        },
1531                                    ),
1532                                    ClaudeToolKind::FileSearch => ResponseInputItem::FileSearchToolCall(
1533                                        ot::ResponseFileSearchToolCall {
1534                                            id: call_id.clone(),
1535                                            queries: file_search_queries(&block.input),
1536                                            status: ot::ResponseFileSearchToolCallStatus::Completed,
1537                                            type_: ot::ResponseFileSearchToolCallType::FileSearchCall,
1538                                            results: None,
1539                                        },
1540                                    ),
1541                                    ClaudeToolKind::WebSearch => ResponseInputItem::FunctionWebSearch(
1542                                        ot::ResponseFunctionWebSearch {
1543                                            id: Some(call_id.clone()),
1544                                            action: ot::ResponseFunctionWebSearchAction::Search {
1545                                                query: str_field(&block.input, "query")
1546                                                    .map(ToString::to_string),
1547                                                queries: {
1548                                                    let queries = string_list(block.input.get("queries"));
1549                                                    (queries.len() > 1).then_some(queries)
1550                                                },
1551                                                sources: None,
1552                                            },
1553                                            status: ot::ResponseFunctionWebSearchStatus::Completed,
1554                                            type_: ot::ResponseFunctionWebSearchType::WebSearchCall,
1555                                        },
1556                                    ),
1557                                    ClaudeToolKind::WebFetch => ResponseInputItem::FunctionWebSearch(
1558                                        ot::ResponseFunctionWebSearch {
1559                                            id: Some(call_id.clone()),
1560                                            action: ot::ResponseFunctionWebSearchAction::OpenPage {
1561                                                url: str_field(&block.input, "url")
1562                                                    .map(ToString::to_string),
1563                                            },
1564                                            status: ot::ResponseFunctionWebSearchStatus::Completed,
1565                                            type_: ot::ResponseFunctionWebSearchType::WebSearchCall,
1566                                        },
1567                                    ),
1568                                    ClaudeToolKind::Mcp => ResponseInputItem::FunctionToolCall(
1569                                        ot::ResponseFunctionToolCall {
1570                                            arguments: input_json,
1571                                            call_id: call_id.clone(),
1572                                            name: tool_name,
1573                                            type_: ot::ResponseFunctionToolCallType::FunctionCall,
1574                                            id: Some(call_id.clone()),
1575                                            status: Some(ot::ResponseItemStatus::Completed),
1576                                        },
1577                                    ),
1578                                };
1579                                let item_index = input_items.len();
1580                                input_items.push(item);
1581                                recorded_calls.insert(
1582                                    call_id,
1583                                    RecordedToolCall {
1584                                        item_index,
1585                                        kind: actual_kind,
1586                                    },
1587                                );
1588                            }
1589                            ct::BetaContentBlockParam::ServerToolUse(block) => {
1590                                let call_id = block.id.clone();
1591                                let item = match block.name {
1592                                    ct::BetaServerToolUseName::CodeExecution => {
1593                                        ResponseInputItem::CodeInterpreterToolCall(
1594                                            ot::ResponseCodeInterpreterToolCall {
1595                                                id: call_id.clone(),
1596                                                code: str_field(&block.input, "code")
1597                                                    .unwrap_or_default()
1598                                                    .to_string(),
1599                                                container_id: str_field(&block.input, "container_id")
1600                                                    .unwrap_or_default()
1601                                                    .to_string(),
1602                                                outputs: None,
1603                                                status: ot::ResponseCodeInterpreterToolCallStatus::Completed,
1604                                                type_: ot::ResponseCodeInterpreterToolCallType::CodeInterpreterCall,
1605                                            },
1606                                        )
1607                                    }
1608                                    ct::BetaServerToolUseName::WebSearch => {
1609                                        ResponseInputItem::FunctionWebSearch(ot::ResponseFunctionWebSearch {
1610                                            id: Some(call_id.clone()),
1611                                            action: ot::ResponseFunctionWebSearchAction::Search {
1612                                                query: str_field(&block.input, "query")
1613                                                    .map(ToString::to_string),
1614                                                queries: {
1615                                                    let queries = string_list(block.input.get("queries"));
1616                                                    (queries.len() > 1).then_some(queries)
1617                                                },
1618                                                sources: None,
1619                                            },
1620                                            status: ot::ResponseFunctionWebSearchStatus::Completed,
1621                                            type_: ot::ResponseFunctionWebSearchType::WebSearchCall,
1622                                        })
1623                                    }
1624                                    ct::BetaServerToolUseName::WebFetch => {
1625                                        ResponseInputItem::FunctionWebSearch(ot::ResponseFunctionWebSearch {
1626                                            id: Some(call_id.clone()),
1627                                            action: ot::ResponseFunctionWebSearchAction::OpenPage {
1628                                                url: str_field(&block.input, "url")
1629                                                    .map(ToString::to_string),
1630                                            },
1631                                            status: ot::ResponseFunctionWebSearchStatus::Completed,
1632                                            type_: ot::ResponseFunctionWebSearchType::WebSearchCall,
1633                                        })
1634                                    }
1635                                    ct::BetaServerToolUseName::BashCodeExecution => {
1636                                        ResponseInputItem::ShellCall(ot::ResponseShellCall {
1637                                            action: ot::ResponseShellCallAction {
1638                                                commands: shell_commands(&block.input),
1639                                                max_output_length: None,
1640                                                timeout_ms: block
1641                                                    .input
1642                                                    .get("timeout_ms")
1643                                                    .and_then(|value| value.as_u64()),
1644                                            },
1645                                            call_id: call_id.clone(),
1646                                            type_: ot::ResponseShellCallType::ShellCall,
1647                                            id: Some(call_id.clone()),
1648                                            environment: None,
1649                                            status: Some(ot::ResponseItemStatus::Completed),
1650                                        })
1651                                    }
1652                                    ct::BetaServerToolUseName::TextEditorCodeExecution => {
1653                                        ResponseInputItem::CustomToolCall(ot::ResponseCustomToolCall {
1654                                            call_id: call_id.clone(),
1655                                            input: json_string(&block.input),
1656                                            name: "text_editor_code_execution".to_string(),
1657                                            type_: ot::ResponseCustomToolCallType::CustomToolCall,
1658                                            id: Some(call_id.clone()),
1659                                        })
1660                                    }
1661                                    ct::BetaServerToolUseName::ToolSearchToolRegex => {
1662                                        ResponseInputItem::FileSearchToolCall(ot::ResponseFileSearchToolCall {
1663                                            id: call_id.clone(),
1664                                            queries: file_search_queries(&block.input),
1665                                            status: ot::ResponseFileSearchToolCallStatus::Completed,
1666                                            type_: ot::ResponseFileSearchToolCallType::FileSearchCall,
1667                                            results: None,
1668                                        })
1669                                    }
1670                                    ct::BetaServerToolUseName::ToolSearchToolBm25 => {
1671                                        ResponseInputItem::FileSearchToolCall(ot::ResponseFileSearchToolCall {
1672                                            id: call_id.clone(),
1673                                            queries: file_search_queries(&block.input),
1674                                            status: ot::ResponseFileSearchToolCallStatus::Completed,
1675                                            type_: ot::ResponseFileSearchToolCallType::FileSearchCall,
1676                                            results: None,
1677                                        })
1678                                    }
1679                                };
1680                                let kind = match block.name {
1681                                    ct::BetaServerToolUseName::CodeExecution => {
1682                                        ClaudeToolKind::CodeInterpreter
1683                                    }
1684                                    ct::BetaServerToolUseName::WebSearch => {
1685                                        ClaudeToolKind::WebSearch
1686                                    }
1687                                    ct::BetaServerToolUseName::WebFetch => ClaudeToolKind::WebFetch,
1688                                    ct::BetaServerToolUseName::BashCodeExecution => {
1689                                        ClaudeToolKind::Shell
1690                                    }
1691                                    ct::BetaServerToolUseName::TextEditorCodeExecution => {
1692                                        ClaudeToolKind::Custom
1693                                    }
1694                                    ct::BetaServerToolUseName::ToolSearchToolRegex
1695                                    | ct::BetaServerToolUseName::ToolSearchToolBm25 => {
1696                                        ClaudeToolKind::FileSearch
1697                                    }
1698                                };
1699                                let item_index = input_items.len();
1700                                input_items.push(item);
1701                                recorded_calls
1702                                    .insert(call_id, RecordedToolCall { item_index, kind });
1703                            }
1704                            ct::BetaContentBlockParam::McpToolUse(block) => {
1705                                let item_index = input_items.len();
1706                                input_items.push(ResponseInputItem::McpCall(ot::ResponseMcpCall {
1707                                    id: block.id.clone(),
1708                                    arguments: json_string(&block.input),
1709                                    name: block.name,
1710                                    server_label: block.server_name,
1711                                    type_: ot::ResponseMcpCallType::McpCall,
1712                                    approval_request_id: None,
1713                                    error: None,
1714                                    output: None,
1715                                    status: Some(ot::ResponseToolCallStatus::Completed),
1716                                }));
1717                                recorded_calls.insert(
1718                                    block.id,
1719                                    RecordedToolCall {
1720                                        item_index,
1721                                        kind: ClaudeToolKind::Mcp,
1722                                    },
1723                                );
1724                            }
1725                            ct::BetaContentBlockParam::Compaction(block) => {
1726                                input_items.push(ResponseInputItem::CompactionItem(
1727                                    ot::ResponseCompactionItemParam {
1728                                        encrypted_content: block.content.unwrap_or_default(),
1729                                        type_: ot::ResponseCompactionItemType::Compaction,
1730                                        id: None,
1731                                        created_by: None,
1732                                    },
1733                                ));
1734                            }
1735                            ct::BetaContentBlockParam::ContainerUpload(block) => {
1736                                input_items.push(output_message_item(
1737                                    format!("msg_{assistant_message_index}"),
1738                                    format!("container_upload:{}", block.file_id),
1739                                ));
1740                                assistant_message_index += 1;
1741                            }
1742                            other => {
1743                                input_items.push(output_message_item(
1744                                    format!("msg_{assistant_message_index}"),
1745                                    json_string(&other),
1746                                ));
1747                                assistant_message_index += 1;
1748                            }
1749                        }
1750                    }
1751                }
1752            }
1753        }
1754
1755        let mut converted_tools = Vec::new();
1756        if let Some(tools) = body.tools {
1757            for tool in tools {
1758                match tool {
1759                    BetaToolUnion::Custom(tool) => {
1760                        if matches!(tool.type_, Some(ct::BetaCustomToolType::Custom)) {
1761                            converted_tools.push(ResponseTool::Custom(ot::ResponseCustomTool {
1762                                name: tool.name,
1763                                type_: ot::ResponseCustomToolType::Custom,
1764                                defer_loading: None,
1765                                description: tool.description,
1766                                format: Some(ot::ResponseCustomToolInputFormat::Text(
1767                                    ot::ResponseCustomToolTextFormat {
1768                                        type_: ot::ResponseCustomToolTextFormatType::Text,
1769                                    },
1770                                )),
1771                            }));
1772                        } else {
1773                            converted_tools.push(ResponseTool::Function(ResponseFunctionTool {
1774                                name: tool.name,
1775                                parameters: tool_input_schema_to_json_object(tool.input_schema),
1776                                strict: tool.common.strict,
1777                                type_: ResponseFunctionToolType::Function,
1778                                defer_loading: None,
1779                                description: tool.description,
1780                            }));
1781                        }
1782                    }
1783                    BetaToolUnion::CodeExecution20250522(_)
1784                    | BetaToolUnion::CodeExecution20250825(_) => {
1785                        converted_tools.push(ResponseTool::CodeInterpreter(
1786                            ResponseCodeInterpreterTool {
1787                                container: ResponseCodeInterpreterContainer::Auto(
1788                                    ResponseCodeInterpreterToolAuto {
1789                                        type_: ResponseCodeInterpreterToolAutoType::Auto,
1790                                        file_ids: None,
1791                                        memory_limit: None,
1792                                        network_policy: None,
1793                                    },
1794                                ),
1795                                type_: ResponseCodeInterpreterToolType::CodeInterpreter,
1796                            },
1797                        ));
1798                    }
1799                    BetaToolUnion::ComputerUse20241022(tool) => {
1800                        converted_tools.push(ResponseTool::Computer(ResponseComputerTool {
1801                            display_height: Some(tool.display_height_px),
1802                            display_width: Some(tool.display_width_px),
1803                            environment: Some(ResponseComputerEnvironment::Browser),
1804                            type_: ResponseComputerToolType::ComputerUsePreview,
1805                        }));
1806                    }
1807                    BetaToolUnion::ComputerUse20250124(tool) => {
1808                        converted_tools.push(ResponseTool::Computer(ResponseComputerTool {
1809                            display_height: Some(tool.display_height_px),
1810                            display_width: Some(tool.display_width_px),
1811                            environment: Some(ResponseComputerEnvironment::Browser),
1812                            type_: ResponseComputerToolType::ComputerUsePreview,
1813                        }));
1814                    }
1815                    BetaToolUnion::ComputerUse20251124(tool) => {
1816                        converted_tools.push(ResponseTool::Computer(ResponseComputerTool {
1817                            display_height: Some(tool.display_height_px),
1818                            display_width: Some(tool.display_width_px),
1819                            environment: Some(ResponseComputerEnvironment::Browser),
1820                            type_: ResponseComputerToolType::ComputerUsePreview,
1821                        }));
1822                    }
1823                    BetaToolUnion::WebSearch20250305(tool) => {
1824                        converted_tools.push(ResponseTool::WebSearch(ResponseWebSearchTool {
1825                            type_: ResponseWebSearchToolType::WebSearch,
1826                            filters: tool.allowed_domains.map(|allowed_domains| {
1827                                ResponseWebSearchFilters {
1828                                    allowed_domains: Some(allowed_domains),
1829                                }
1830                            }),
1831                            search_context_size: None,
1832                            user_location: tool.user_location.map(|location| {
1833                                ResponseApproximateLocation {
1834                                    city: location.city,
1835                                    country: location.country,
1836                                    region: location.region,
1837                                    timezone: location.timezone,
1838                                    type_: Some(ResponseApproximateLocationType::Approximate),
1839                                }
1840                            }),
1841                        }));
1842                    }
1843                    BetaToolUnion::WebFetch20250910(tool) => {
1844                        converted_tools.push(ResponseTool::WebSearch(ResponseWebSearchTool {
1845                            type_: ResponseWebSearchToolType::WebSearch,
1846                            filters: tool.allowed_domains.map(|allowed_domains| {
1847                                ResponseWebSearchFilters {
1848                                    allowed_domains: Some(allowed_domains),
1849                                }
1850                            }),
1851                            search_context_size: None,
1852                            user_location: None,
1853                        }));
1854                    }
1855                    BetaToolUnion::Bash20241022(_) | BetaToolUnion::Bash20250124(_) => {
1856                        converted_tools.push(ResponseTool::Shell(ResponseFunctionShellTool {
1857                            type_: ResponseFunctionShellToolType::Shell,
1858                            environment: None,
1859                        }));
1860                    }
1861                    BetaToolUnion::ToolSearchBm25_20251119(_)
1862                    | BetaToolUnion::ToolSearchRegex20251119(_) => {
1863                        converted_tools.push(ResponseTool::FileSearch(
1864                            ot::ResponseFileSearchTool {
1865                                type_: ot::ResponseFileSearchToolType::FileSearch,
1866                                vector_store_ids: Vec::new(),
1867                                filters: None,
1868                                max_num_results: None,
1869                                ranking_options: None,
1870                            },
1871                        ));
1872                    }
1873                    BetaToolUnion::TextEditor20241022(_)
1874                    | BetaToolUnion::TextEditor20250124(_)
1875                    | BetaToolUnion::TextEditor20250429(_)
1876                    | BetaToolUnion::TextEditor20250728(_) => {
1877                        converted_tools.push(ResponseTool::ApplyPatch(ResponseApplyPatchTool {
1878                            type_: ResponseApplyPatchToolType::ApplyPatch,
1879                        }));
1880                    }
1881                    BetaToolUnion::McpToolset(tool) => {
1882                        let allowed_tools = tool.configs.and_then(|configs| {
1883                            let names = configs
1884                                .into_iter()
1885                                .filter_map(|(name, config)| {
1886                                    if config.enabled.unwrap_or(true) {
1887                                        Some(name)
1888                                    } else {
1889                                        None
1890                                    }
1891                                })
1892                                .collect::<Vec<_>>();
1893                            if names.is_empty() {
1894                                None
1895                            } else {
1896                                Some(ResponseMcpAllowedTools::ToolNames(names))
1897                            }
1898                        });
1899                        converted_tools.push(ResponseTool::Mcp(ResponseMcpTool {
1900                            server_label: tool.mcp_server_name,
1901                            type_: ResponseMcpToolType::Mcp,
1902                            allowed_tools,
1903                            authorization: None,
1904                            connector_id: None,
1905                            defer_loading: None,
1906                            headers: None,
1907                            require_approval: None,
1908                            server_description: None,
1909                            server_url: None,
1910                        }));
1911                    }
1912                    BetaToolUnion::Memory20250818(_) => {}
1913                }
1914            }
1915        }
1916        if let Some(servers) = body.mcp_servers {
1917            for server in servers {
1918                converted_tools.push(ResponseTool::Mcp(ResponseMcpTool {
1919                    server_label: server.name,
1920                    type_: ResponseMcpToolType::Mcp,
1921                    allowed_tools: server
1922                        .tool_configuration
1923                        .as_ref()
1924                        .and_then(|config| config.allowed_tools.clone())
1925                        .map(ResponseMcpAllowedTools::ToolNames),
1926                    authorization: server.authorization_token,
1927                    connector_id: None,
1928                    defer_loading: None,
1929                    headers: None,
1930                    require_approval: None,
1931                    server_description: None,
1932                    server_url: Some(server.url),
1933                }));
1934            }
1935        }
1936        let tools = if converted_tools.is_empty() {
1937            None
1938        } else {
1939            Some(converted_tools)
1940        };
1941
1942        let metadata = if let Some(user_id) = body
1943            .metadata
1944            .as_ref()
1945            .and_then(|value| value.user_id.clone())
1946        {
1947            let mut map = Metadata::new();
1948            map.insert("user_id".to_string(), user_id);
1949            Some(map)
1950        } else {
1951            None
1952        };
1953        let service_tier = match body.service_tier {
1954            Some(BetaServiceTierParam::Auto) => Some(ResponseServiceTier::Auto),
1955            Some(BetaServiceTierParam::StandardOnly) => Some(ResponseServiceTier::Default),
1956            None => match body.speed {
1957                Some(BetaSpeed::Fast) => Some(ResponseServiceTier::Priority),
1958                Some(BetaSpeed::Standard) | None => None,
1959            },
1960        };
1961
1962        Ok(Self {
1963            method: HttpMethod::Post,
1964            path: PathParameters::default(),
1965            query: QueryParameters::default(),
1966            headers: RequestHeaders::default(),
1967            body: RequestBody {
1968                context_management,
1969                input: if input_items.is_empty() {
1970                    None
1971                } else {
1972                    Some(ResponseInput::Items(input_items))
1973                },
1974                instructions,
1975                max_output_tokens: Some(body.max_tokens),
1976                metadata,
1977                model: Some(model),
1978                parallel_tool_calls,
1979                reasoning,
1980                service_tier,
1981                stream: body.stream,
1982                temperature: body.temperature,
1983                text,
1984                tool_choice,
1985                tools,
1986                top_p: body.top_p,
1987                truncation,
1988                ..RequestBody::default()
1989            },
1990        })
1991    }
1992}