Skip to main content

codex_convert_proxy/convert/request/
tools.rs

1//! Tool conversion utilities for Responses API → Chat API.
2
3use crate::types::chat_api::{ChatTool, ChatToolChoice, ChatToolChoiceMode, FunctionChoice, FunctionDefinition, NamedToolChoice};
4use crate::types::response_api::{
5    Tool as ResponseTool, ToolChoice as ResponseToolChoice,
6    ToolType as ResponseToolType,
7};
8
9/// Convert Responses API tools to Chat API tools.
10pub fn convert_tools(tools: Vec<ResponseTool>) -> Vec<ChatTool> {
11    tools
12        .into_iter()
13        .filter_map(|t| match t.tool_type {
14            ResponseToolType::Function | ResponseToolType::Custom | ResponseToolType::Other => {
15                Some(passthrough_tool(t))
16            }
17            ResponseToolType::WebSearchPreview => Some(builtin_tool(
18                t,
19                "web_search_preview",
20                "Web search tool",
21                serde_json::json!({
22                    "type": "object",
23                    "properties": {
24                        "query": {
25                            "type": "string",
26                            "description": "The search query"
27                        }
28                    },
29                    "required": ["query"]
30                }),
31            )),
32            ResponseToolType::CodeInterpreter => Some(builtin_tool(
33                t,
34                "code_interpreter",
35                "Code interpreter tool",
36                serde_json::json!({
37                    "type": "object",
38                    "properties": {
39                        "code": {
40                            "type": "string",
41                            "description": "The code to execute"
42                        }
43                    },
44                    "required": ["code"]
45                }),
46            )),
47            ResponseToolType::FileSearch => Some(builtin_tool(
48                t,
49                "file_search",
50                "File search tool",
51                serde_json::json!({
52                    "type": "object",
53                    "properties": {
54                        "query": {
55                            "type": "string",
56                            "description": "The search query"
57                        }
58                    },
59                    "required": ["query"]
60                }),
61            )),
62            ResponseToolType::Namespace => None,
63        })
64        .collect()
65}
66
67/// Pass through a tool as-is as a function tool.
68fn passthrough_tool(t: ResponseTool) -> ChatTool {
69    ChatTool {
70        tool_type: "function".to_string(),
71        function: FunctionDefinition {
72            name: t.name.unwrap_or_default(),
73            description: t.description,
74            parameters: t.parameters,
75            strict: t.strict,
76        },
77    }
78}
79
80/// Convert a built-in tool to a function tool with default parameters.
81fn builtin_tool(
82    t: ResponseTool,
83    default_name: &str,
84    default_desc: &str,
85    param_schema: serde_json::Value,
86) -> ChatTool {
87    ChatTool {
88        tool_type: "function".to_string(),
89        function: FunctionDefinition {
90            name: t.name.unwrap_or_else(|| default_name.to_string()),
91            description: t.description.or_else(|| Some(default_desc.to_string())),
92            parameters: Some(param_schema),
93            strict: t.strict,
94        },
95    }
96}
97
98/// Convert Responses API tool choice to Chat API tool choice.
99pub fn convert_tool_choice(choice: ResponseToolChoice) -> ChatToolChoice {
100    match choice {
101        ResponseToolChoice::Auto => ChatToolChoice::Mode(ChatToolChoiceMode::Auto),
102        ResponseToolChoice::None => ChatToolChoice::Mode(ChatToolChoiceMode::None),
103        ResponseToolChoice::Required => ChatToolChoice::Mode(ChatToolChoiceMode::Required),
104        ResponseToolChoice::Function(f) => ChatToolChoice::Named(NamedToolChoice {
105            tool_type: "function".to_string(),
106            function: FunctionChoice { name: f.name },
107        }),
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use crate::types::response_api::FunctionToolChoice;
115
116    #[test]
117    fn test_named_tool_choice_serializes_to_openai_shape() {
118        // OpenAI `ChatCompletionNamedToolChoice` requires
119        // `{type:"function", function:{name:"…"}}`.
120        let choice = convert_tool_choice(ResponseToolChoice::Function(FunctionToolChoice {
121            name: "get_weather".to_string(),
122        }));
123        let json = serde_json::to_value(&choice).unwrap();
124        assert_eq!(json["type"], "function");
125        assert_eq!(json["function"]["name"], "get_weather");
126    }
127
128    #[test]
129    fn test_tool_choice_mode_serializes_as_string() {
130        let json = serde_json::to_value(convert_tool_choice(ResponseToolChoice::None)).unwrap();
131        assert_eq!(json, serde_json::json!("none"));
132        let json = serde_json::to_value(convert_tool_choice(ResponseToolChoice::Required)).unwrap();
133        assert_eq!(json, serde_json::json!("required"));
134    }
135
136    #[test]
137    fn test_function_definition_carries_strict() {
138        let tool = ResponseTool {
139            tool_type: ResponseToolType::Function,
140            name: Some("f".to_string()),
141            description: None,
142            parameters: None,
143            strict: Some(true),
144            extra: Default::default(),
145        };
146        let chat_tools = convert_tools(vec![tool]);
147        assert_eq!(chat_tools[0].function.strict, Some(true));
148    }
149}