1use crate::claude::count_tokens::request::ClaudeCountTokensRequest;
2use crate::claude::count_tokens::types::{
3 BetaMessageRole, BetaOutputEffort, BetaThinkingConfigParam, BetaToolChoice,
4 BetaToolInputSchema, BetaToolInputSchemaType, BetaToolUnion,
5};
6use crate::openai::count_tokens::request::{
7 OpenAiCountTokensRequest, PathParameters, QueryParameters, RequestBody, RequestHeaders,
8};
9use crate::openai::count_tokens::types::{
10 HttpMethod, ResponseApplyPatchTool, ResponseApplyPatchToolType, ResponseApproximateLocation,
11 ResponseApproximateLocationType, ResponseCodeInterpreterContainer, ResponseCodeInterpreterTool,
12 ResponseCodeInterpreterToolAuto, ResponseCodeInterpreterToolAutoType,
13 ResponseCodeInterpreterToolType, ResponseComputerEnvironment, ResponseComputerTool,
14 ResponseComputerToolType, ResponseFormatTextJsonSchemaConfig,
15 ResponseFormatTextJsonSchemaConfigType, ResponseFunctionShellTool,
16 ResponseFunctionShellToolType, ResponseFunctionTool, ResponseFunctionToolType, ResponseInput,
17 ResponseInputItem, ResponseInputMessage, ResponseInputMessageContent, ResponseInputMessageRole,
18 ResponseInputMessageType, ResponseMcpAllowedTools, ResponseMcpTool, ResponseMcpToolType,
19 ResponseReasoning, ResponseReasoningEffort, ResponseTextConfig, ResponseTextFormatConfig,
20 ResponseTool, ResponseToolChoice, ResponseToolChoiceFunction, ResponseToolChoiceFunctionType,
21 ResponseToolChoiceOptions, ResponseTruncation, ResponseWebSearchFilters, ResponseWebSearchTool,
22 ResponseWebSearchToolType,
23};
24use crate::transform::claude::count_tokens::utils::{
25 beta_message_content_to_text, beta_system_prompt_to_text, claude_model_to_string,
26};
27use crate::transform::utils::TransformError;
28use serde_json::{Map, Value};
29
30fn tool_input_schema_to_json_object(
31 input_schema: BetaToolInputSchema,
32) -> std::collections::BTreeMap<String, Value> {
33 let mut parameters = std::collections::BTreeMap::new();
34 let schema_type = match input_schema.type_ {
35 BetaToolInputSchemaType::Object => "object",
36 };
37 parameters.insert("type".to_string(), Value::String(schema_type.to_string()));
38 if let Some(properties) = input_schema.properties {
39 let properties_object = properties.into_iter().collect::<Map<String, Value>>();
40 parameters.insert("properties".to_string(), Value::Object(properties_object));
41 }
42 if let Some(required) = input_schema.required {
43 parameters.insert(
44 "required".to_string(),
45 Value::Array(required.into_iter().map(Value::String).collect()),
46 );
47 }
48 parameters
49}
50
51impl TryFrom<ClaudeCountTokensRequest> for OpenAiCountTokensRequest {
52 type Error = TransformError;
53
54 fn try_from(value: ClaudeCountTokensRequest) -> Result<Self, TransformError> {
55 let model = claude_model_to_string(&value.body.model);
56 let input_items = value
57 .body
58 .messages
59 .into_iter()
60 .map(|message| {
61 ResponseInputItem::Message(ResponseInputMessage {
62 content: ResponseInputMessageContent::Text(beta_message_content_to_text(
63 &message.content,
64 )),
65 role: match message.role {
66 BetaMessageRole::User => ResponseInputMessageRole::User,
67 BetaMessageRole::Assistant => ResponseInputMessageRole::Assistant,
68 },
69 phase: None,
70 status: None,
71 type_: Some(ResponseInputMessageType::Message),
72 })
73 })
74 .collect::<Vec<_>>();
75 let instructions = beta_system_prompt_to_text(value.body.system);
76 let parallel_tool_calls = match value.body.tool_choice.as_ref() {
77 Some(BetaToolChoice::Auto(choice)) => choice.disable_parallel_tool_use.map(|v| !v),
78 Some(BetaToolChoice::Any(choice)) => choice.disable_parallel_tool_use.map(|v| !v),
79 Some(BetaToolChoice::Tool(choice)) => choice.disable_parallel_tool_use.map(|v| !v),
80 Some(BetaToolChoice::None(_)) | None => None,
81 };
82 let tool_choice = match value.body.tool_choice {
83 Some(BetaToolChoice::Auto(_)) => {
84 Some(ResponseToolChoice::Options(ResponseToolChoiceOptions::Auto))
85 }
86 Some(BetaToolChoice::Any(_)) => Some(ResponseToolChoice::Options(
87 ResponseToolChoiceOptions::Required,
88 )),
89 Some(BetaToolChoice::Tool(choice)) => {
90 Some(ResponseToolChoice::Function(ResponseToolChoiceFunction {
91 name: choice.name,
92 type_: ResponseToolChoiceFunctionType::Function,
93 }))
94 }
95 Some(BetaToolChoice::None(_)) => {
96 Some(ResponseToolChoice::Options(ResponseToolChoiceOptions::None))
97 }
98 None => None,
99 };
100 let reasoning_effort_from_thinking = match value.body.thinking {
101 Some(BetaThinkingConfigParam::Enabled(config)) => Some(if config.budget_tokens == 0 {
102 ResponseReasoningEffort::None
103 } else if config.budget_tokens <= 4096 {
104 ResponseReasoningEffort::Minimal
105 } else if config.budget_tokens <= 8192 {
106 ResponseReasoningEffort::Low
107 } else if config.budget_tokens <= 16384 {
108 ResponseReasoningEffort::Medium
109 } else if config.budget_tokens <= 32768 {
110 ResponseReasoningEffort::High
111 } else {
112 ResponseReasoningEffort::XHigh
113 }),
114 Some(BetaThinkingConfigParam::Disabled(_)) => Some(ResponseReasoningEffort::None),
115 Some(BetaThinkingConfigParam::Adaptive(_)) => Some(ResponseReasoningEffort::Medium),
116 None => None,
117 };
118 let reasoning_effort_from_output = value
119 .body
120 .output_config
121 .as_ref()
122 .and_then(|config| config.effort.as_ref())
123 .map(|effort| match effort {
124 BetaOutputEffort::Low => ResponseReasoningEffort::Low,
125 BetaOutputEffort::Medium => ResponseReasoningEffort::Medium,
126 BetaOutputEffort::High => ResponseReasoningEffort::High,
127 BetaOutputEffort::XHigh => ResponseReasoningEffort::XHigh,
128 BetaOutputEffort::Max => ResponseReasoningEffort::XHigh,
129 });
130 let reasoning = reasoning_effort_from_thinking
131 .or(reasoning_effort_from_output)
132 .map(|effort| ResponseReasoning {
133 effort: Some(effort),
134 generate_summary: None,
135 summary: None,
136 });
137 let output_schema = value
138 .body
139 .output_config
140 .as_ref()
141 .and_then(|config| config.format.as_ref());
142 let text = output_schema.map(|schema| ResponseTextConfig {
143 format: Some(ResponseTextFormatConfig::JsonSchema(
144 ResponseFormatTextJsonSchemaConfig {
145 name: "output".to_string(),
146 schema: schema.schema.clone(),
147 type_: ResponseFormatTextJsonSchemaConfigType::JsonSchema,
148 description: None,
149 strict: None,
150 },
151 )),
152 verbosity: None,
153 });
154 let truncation = value
155 .body
156 .context_management
157 .as_ref()
158 .map(|_| ResponseTruncation::Auto);
159
160 let mut converted_tools = Vec::new();
161 if let Some(tools) = value.body.tools {
162 for tool in tools {
163 match tool {
164 BetaToolUnion::Custom(tool) => {
165 converted_tools.push(ResponseTool::Function(ResponseFunctionTool {
166 name: tool.name,
167 parameters: tool_input_schema_to_json_object(tool.input_schema),
168 strict: tool.common.strict,
169 type_: ResponseFunctionToolType::Function,
170 defer_loading: None,
171 description: tool.description,
172 }));
173 }
174 BetaToolUnion::CodeExecution20250522(_)
175 | BetaToolUnion::CodeExecution20250825(_) => {
176 converted_tools.push(ResponseTool::CodeInterpreter(
177 ResponseCodeInterpreterTool {
178 container: ResponseCodeInterpreterContainer::Auto(
179 ResponseCodeInterpreterToolAuto {
180 type_: ResponseCodeInterpreterToolAutoType::Auto,
181 file_ids: None,
182 memory_limit: None,
183 network_policy: None,
184 },
185 ),
186 type_: ResponseCodeInterpreterToolType::CodeInterpreter,
187 },
188 ));
189 }
190 BetaToolUnion::ComputerUse20241022(tool) => {
191 converted_tools.push(ResponseTool::Computer(ResponseComputerTool {
192 display_height: Some(tool.display_height_px),
193 display_width: Some(tool.display_width_px),
194 environment: Some(ResponseComputerEnvironment::Browser),
195 type_: ResponseComputerToolType::ComputerUsePreview,
196 }));
197 }
198 BetaToolUnion::ComputerUse20250124(tool) => {
199 converted_tools.push(ResponseTool::Computer(ResponseComputerTool {
200 display_height: Some(tool.display_height_px),
201 display_width: Some(tool.display_width_px),
202 environment: Some(ResponseComputerEnvironment::Browser),
203 type_: ResponseComputerToolType::ComputerUsePreview,
204 }));
205 }
206 BetaToolUnion::ComputerUse20251124(tool) => {
207 converted_tools.push(ResponseTool::Computer(ResponseComputerTool {
208 display_height: Some(tool.display_height_px),
209 display_width: Some(tool.display_width_px),
210 environment: Some(ResponseComputerEnvironment::Browser),
211 type_: ResponseComputerToolType::ComputerUsePreview,
212 }));
213 }
214 BetaToolUnion::WebSearch20250305(tool) => {
215 converted_tools.push(ResponseTool::WebSearch(ResponseWebSearchTool {
216 type_: ResponseWebSearchToolType::WebSearch,
217 filters: tool.allowed_domains.map(|allowed_domains| {
218 ResponseWebSearchFilters {
219 allowed_domains: Some(allowed_domains),
220 }
221 }),
222 search_context_size: None,
223 user_location: tool.user_location.map(|location| {
224 ResponseApproximateLocation {
225 city: location.city,
226 country: location.country,
227 region: location.region,
228 timezone: location.timezone,
229 type_: Some(ResponseApproximateLocationType::Approximate),
230 }
231 }),
232 }));
233 }
234 BetaToolUnion::WebFetch20250910(tool) => {
235 converted_tools.push(ResponseTool::WebSearch(ResponseWebSearchTool {
236 type_: ResponseWebSearchToolType::WebSearch,
237 filters: tool.allowed_domains.map(|allowed_domains| {
238 ResponseWebSearchFilters {
239 allowed_domains: Some(allowed_domains),
240 }
241 }),
242 search_context_size: None,
243 user_location: None,
244 }));
245 }
246 BetaToolUnion::Bash20241022(_)
247 | BetaToolUnion::Bash20250124(_)
248 | BetaToolUnion::ToolSearchBm25_20251119(_)
249 | BetaToolUnion::ToolSearchRegex20251119(_) => {
250 converted_tools.push(ResponseTool::Shell(ResponseFunctionShellTool {
251 type_: ResponseFunctionShellToolType::Shell,
252 environment: None,
253 }));
254 }
255 BetaToolUnion::TextEditor20241022(_)
256 | BetaToolUnion::TextEditor20250124(_)
257 | BetaToolUnion::TextEditor20250429(_)
258 | BetaToolUnion::TextEditor20250728(_) => {
259 converted_tools.push(ResponseTool::ApplyPatch(ResponseApplyPatchTool {
260 type_: ResponseApplyPatchToolType::ApplyPatch,
261 }));
262 }
263 BetaToolUnion::McpToolset(tool) => {
264 let allowed_tools = tool.configs.and_then(|configs| {
265 let names = configs
266 .into_iter()
267 .filter_map(|(name, config)| {
268 if config.enabled.unwrap_or(true) {
269 Some(name)
270 } else {
271 None
272 }
273 })
274 .collect::<Vec<_>>();
275 if names.is_empty() {
276 None
277 } else {
278 Some(ResponseMcpAllowedTools::ToolNames(names))
279 }
280 });
281 converted_tools.push(ResponseTool::Mcp(ResponseMcpTool {
282 server_label: tool.mcp_server_name,
283 type_: ResponseMcpToolType::Mcp,
284 allowed_tools,
285 authorization: None,
286 connector_id: None,
287 defer_loading: None,
288 headers: None,
289 require_approval: None,
290 server_description: None,
291 server_url: None,
292 }));
293 }
294 BetaToolUnion::Memory20250818(_) => {}
295 }
296 }
297 }
298 if let Some(servers) = value.body.mcp_servers {
299 for server in servers {
300 converted_tools.push(ResponseTool::Mcp(ResponseMcpTool {
301 server_label: server.name,
302 type_: ResponseMcpToolType::Mcp,
303 allowed_tools: server
304 .tool_configuration
305 .as_ref()
306 .and_then(|config| config.allowed_tools.clone())
307 .map(ResponseMcpAllowedTools::ToolNames),
308 authorization: server.authorization_token,
309 connector_id: None,
310 defer_loading: None,
311 headers: None,
312 require_approval: None,
313 server_description: None,
314 server_url: Some(server.url),
315 }));
316 }
317 }
318 let tools = if converted_tools.is_empty() {
319 None
320 } else {
321 Some(converted_tools)
322 };
323
324 Ok(Self {
325 method: HttpMethod::Post,
326 path: PathParameters::default(),
327 query: QueryParameters::default(),
328 headers: RequestHeaders::default(),
329 body: RequestBody {
330 input: if input_items.is_empty() {
331 None
332 } else {
333 Some(ResponseInput::Items(input_items))
334 },
335 instructions,
336 model: Some(model),
337 parallel_tool_calls,
338 reasoning,
339 text,
340 tool_choice,
341 tools,
342 truncation,
343 ..RequestBody::default()
344 },
345 })
346 }
347}