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}