Skip to main content

gproxy_protocol/transform/claude/count_tokens/openai/
request.rs

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}