codex_convert_proxy/convert/request/
tools.rs1use 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
9pub 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
67fn 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
80fn 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
98pub 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 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}