gproxy-protocol 1.0.20

Wire-format types and cross-protocol transforms for Claude, OpenAI, and Gemini LLM APIs.
Documentation
use crate::claude::count_tokens::request::ClaudeCountTokensRequest;
use crate::claude::count_tokens::types::{
    BetaMessageRole, BetaOutputEffort, BetaThinkingConfigParam, BetaToolChoice,
    BetaToolInputSchema, BetaToolInputSchemaType, BetaToolUnion,
};
use crate::openai::count_tokens::request::{
    OpenAiCountTokensRequest, PathParameters, QueryParameters, RequestBody, RequestHeaders,
};
use crate::openai::count_tokens::types::{
    HttpMethod, ResponseApplyPatchTool, ResponseApplyPatchToolType, ResponseApproximateLocation,
    ResponseApproximateLocationType, ResponseCodeInterpreterContainer, ResponseCodeInterpreterTool,
    ResponseCodeInterpreterToolAuto, ResponseCodeInterpreterToolAutoType,
    ResponseCodeInterpreterToolType, ResponseComputerEnvironment, ResponseComputerTool,
    ResponseComputerToolType, ResponseFormatTextJsonSchemaConfig,
    ResponseFormatTextJsonSchemaConfigType, ResponseFunctionShellTool,
    ResponseFunctionShellToolType, ResponseFunctionTool, ResponseFunctionToolType, ResponseInput,
    ResponseInputItem, ResponseInputMessage, ResponseInputMessageContent, ResponseInputMessageRole,
    ResponseInputMessageType, ResponseMcpAllowedTools, ResponseMcpTool, ResponseMcpToolType,
    ResponseReasoning, ResponseReasoningEffort, ResponseTextConfig, ResponseTextFormatConfig,
    ResponseTool, ResponseToolChoice, ResponseToolChoiceFunction, ResponseToolChoiceFunctionType,
    ResponseToolChoiceOptions, ResponseTruncation, ResponseWebSearchFilters, ResponseWebSearchTool,
    ResponseWebSearchToolType,
};
use crate::transform::claude::count_tokens::utils::{
    beta_message_content_to_text, beta_system_prompt_to_text, claude_model_to_string,
};
use crate::transform::utils::TransformError;
use serde_json::{Map, Value};

fn tool_input_schema_to_json_object(
    input_schema: BetaToolInputSchema,
) -> std::collections::BTreeMap<String, Value> {
    let mut parameters = std::collections::BTreeMap::new();
    let schema_type = match input_schema.type_ {
        BetaToolInputSchemaType::Object => "object",
    };
    parameters.insert("type".to_string(), Value::String(schema_type.to_string()));
    if let Some(properties) = input_schema.properties {
        let properties_object = properties.into_iter().collect::<Map<String, Value>>();
        parameters.insert("properties".to_string(), Value::Object(properties_object));
    }
    if let Some(required) = input_schema.required {
        parameters.insert(
            "required".to_string(),
            Value::Array(required.into_iter().map(Value::String).collect()),
        );
    }
    parameters
}

impl TryFrom<ClaudeCountTokensRequest> for OpenAiCountTokensRequest {
    type Error = TransformError;

    fn try_from(value: ClaudeCountTokensRequest) -> Result<Self, TransformError> {
        let model = claude_model_to_string(&value.body.model);
        let input_items = value
            .body
            .messages
            .into_iter()
            .map(|message| {
                ResponseInputItem::Message(ResponseInputMessage {
                    content: ResponseInputMessageContent::Text(beta_message_content_to_text(
                        &message.content,
                    )),
                    role: match message.role {
                        BetaMessageRole::User => ResponseInputMessageRole::User,
                        BetaMessageRole::Assistant => ResponseInputMessageRole::Assistant,
                    },
                    phase: None,
                    status: None,
                    type_: Some(ResponseInputMessageType::Message),
                })
            })
            .collect::<Vec<_>>();
        let instructions = beta_system_prompt_to_text(value.body.system);
        let parallel_tool_calls = match value.body.tool_choice.as_ref() {
            Some(BetaToolChoice::Auto(choice)) => choice.disable_parallel_tool_use.map(|v| !v),
            Some(BetaToolChoice::Any(choice)) => choice.disable_parallel_tool_use.map(|v| !v),
            Some(BetaToolChoice::Tool(choice)) => choice.disable_parallel_tool_use.map(|v| !v),
            Some(BetaToolChoice::None(_)) | None => None,
        };
        let tool_choice = match value.body.tool_choice {
            Some(BetaToolChoice::Auto(_)) => {
                Some(ResponseToolChoice::Options(ResponseToolChoiceOptions::Auto))
            }
            Some(BetaToolChoice::Any(_)) => Some(ResponseToolChoice::Options(
                ResponseToolChoiceOptions::Required,
            )),
            Some(BetaToolChoice::Tool(choice)) => {
                Some(ResponseToolChoice::Function(ResponseToolChoiceFunction {
                    name: choice.name,
                    type_: ResponseToolChoiceFunctionType::Function,
                }))
            }
            Some(BetaToolChoice::None(_)) => {
                Some(ResponseToolChoice::Options(ResponseToolChoiceOptions::None))
            }
            None => None,
        };
        let reasoning_effort_from_thinking = match value.body.thinking {
            Some(BetaThinkingConfigParam::Enabled(config)) => Some(if config.budget_tokens == 0 {
                ResponseReasoningEffort::None
            } else if config.budget_tokens <= 4096 {
                ResponseReasoningEffort::Minimal
            } else if config.budget_tokens <= 8192 {
                ResponseReasoningEffort::Low
            } else if config.budget_tokens <= 16384 {
                ResponseReasoningEffort::Medium
            } else if config.budget_tokens <= 32768 {
                ResponseReasoningEffort::High
            } else {
                ResponseReasoningEffort::XHigh
            }),
            Some(BetaThinkingConfigParam::Disabled(_)) => Some(ResponseReasoningEffort::None),
            Some(BetaThinkingConfigParam::Adaptive(_)) => Some(ResponseReasoningEffort::Medium),
            None => None,
        };
        let reasoning_effort_from_output = value
            .body
            .output_config
            .as_ref()
            .and_then(|config| config.effort.as_ref())
            .map(|effort| match effort {
                BetaOutputEffort::Low => ResponseReasoningEffort::Low,
                BetaOutputEffort::Medium => ResponseReasoningEffort::Medium,
                BetaOutputEffort::High => ResponseReasoningEffort::High,
                BetaOutputEffort::XHigh => ResponseReasoningEffort::XHigh,
                BetaOutputEffort::Max => ResponseReasoningEffort::XHigh,
            });
        let reasoning = reasoning_effort_from_thinking
            .or(reasoning_effort_from_output)
            .map(|effort| ResponseReasoning {
                effort: Some(effort),
                generate_summary: None,
                summary: None,
            });
        let output_schema = value
            .body
            .output_config
            .as_ref()
            .and_then(|config| config.format.as_ref());
        let text = output_schema.map(|schema| ResponseTextConfig {
            format: Some(ResponseTextFormatConfig::JsonSchema(
                ResponseFormatTextJsonSchemaConfig {
                    name: "output".to_string(),
                    schema: schema.schema.clone(),
                    type_: ResponseFormatTextJsonSchemaConfigType::JsonSchema,
                    description: None,
                    strict: None,
                },
            )),
            verbosity: None,
        });
        let truncation = value
            .body
            .context_management
            .as_ref()
            .map(|_| ResponseTruncation::Auto);

        let mut converted_tools = Vec::new();
        if let Some(tools) = value.body.tools {
            for tool in tools {
                match tool {
                    BetaToolUnion::Custom(tool) => {
                        converted_tools.push(ResponseTool::Function(ResponseFunctionTool {
                            name: tool.name,
                            parameters: tool_input_schema_to_json_object(tool.input_schema),
                            strict: tool.common.strict,
                            type_: ResponseFunctionToolType::Function,
                            defer_loading: None,
                            description: tool.description,
                        }));
                    }
                    BetaToolUnion::CodeExecution20250522(_)
                    | BetaToolUnion::CodeExecution20250825(_) => {
                        converted_tools.push(ResponseTool::CodeInterpreter(
                            ResponseCodeInterpreterTool {
                                container: ResponseCodeInterpreterContainer::Auto(
                                    ResponseCodeInterpreterToolAuto {
                                        type_: ResponseCodeInterpreterToolAutoType::Auto,
                                        file_ids: None,
                                        memory_limit: None,
                                        network_policy: None,
                                    },
                                ),
                                type_: ResponseCodeInterpreterToolType::CodeInterpreter,
                            },
                        ));
                    }
                    BetaToolUnion::ComputerUse20241022(tool) => {
                        converted_tools.push(ResponseTool::Computer(ResponseComputerTool {
                            display_height: Some(tool.display_height_px),
                            display_width: Some(tool.display_width_px),
                            environment: Some(ResponseComputerEnvironment::Browser),
                            type_: ResponseComputerToolType::ComputerUsePreview,
                        }));
                    }
                    BetaToolUnion::ComputerUse20250124(tool) => {
                        converted_tools.push(ResponseTool::Computer(ResponseComputerTool {
                            display_height: Some(tool.display_height_px),
                            display_width: Some(tool.display_width_px),
                            environment: Some(ResponseComputerEnvironment::Browser),
                            type_: ResponseComputerToolType::ComputerUsePreview,
                        }));
                    }
                    BetaToolUnion::ComputerUse20251124(tool) => {
                        converted_tools.push(ResponseTool::Computer(ResponseComputerTool {
                            display_height: Some(tool.display_height_px),
                            display_width: Some(tool.display_width_px),
                            environment: Some(ResponseComputerEnvironment::Browser),
                            type_: ResponseComputerToolType::ComputerUsePreview,
                        }));
                    }
                    BetaToolUnion::WebSearch20250305(tool) => {
                        converted_tools.push(ResponseTool::WebSearch(ResponseWebSearchTool {
                            type_: ResponseWebSearchToolType::WebSearch,
                            filters: tool.allowed_domains.map(|allowed_domains| {
                                ResponseWebSearchFilters {
                                    allowed_domains: Some(allowed_domains),
                                }
                            }),
                            search_context_size: None,
                            user_location: tool.user_location.map(|location| {
                                ResponseApproximateLocation {
                                    city: location.city,
                                    country: location.country,
                                    region: location.region,
                                    timezone: location.timezone,
                                    type_: Some(ResponseApproximateLocationType::Approximate),
                                }
                            }),
                        }));
                    }
                    BetaToolUnion::WebFetch20250910(tool) => {
                        converted_tools.push(ResponseTool::WebSearch(ResponseWebSearchTool {
                            type_: ResponseWebSearchToolType::WebSearch,
                            filters: tool.allowed_domains.map(|allowed_domains| {
                                ResponseWebSearchFilters {
                                    allowed_domains: Some(allowed_domains),
                                }
                            }),
                            search_context_size: None,
                            user_location: None,
                        }));
                    }
                    BetaToolUnion::Bash20241022(_)
                    | BetaToolUnion::Bash20250124(_)
                    | BetaToolUnion::ToolSearchBm25_20251119(_)
                    | BetaToolUnion::ToolSearchRegex20251119(_) => {
                        converted_tools.push(ResponseTool::Shell(ResponseFunctionShellTool {
                            type_: ResponseFunctionShellToolType::Shell,
                            environment: None,
                        }));
                    }
                    BetaToolUnion::TextEditor20241022(_)
                    | BetaToolUnion::TextEditor20250124(_)
                    | BetaToolUnion::TextEditor20250429(_)
                    | BetaToolUnion::TextEditor20250728(_) => {
                        converted_tools.push(ResponseTool::ApplyPatch(ResponseApplyPatchTool {
                            type_: ResponseApplyPatchToolType::ApplyPatch,
                        }));
                    }
                    BetaToolUnion::McpToolset(tool) => {
                        let allowed_tools = tool.configs.and_then(|configs| {
                            let names = configs
                                .into_iter()
                                .filter_map(|(name, config)| {
                                    if config.enabled.unwrap_or(true) {
                                        Some(name)
                                    } else {
                                        None
                                    }
                                })
                                .collect::<Vec<_>>();
                            if names.is_empty() {
                                None
                            } else {
                                Some(ResponseMcpAllowedTools::ToolNames(names))
                            }
                        });
                        converted_tools.push(ResponseTool::Mcp(ResponseMcpTool {
                            server_label: tool.mcp_server_name,
                            type_: ResponseMcpToolType::Mcp,
                            allowed_tools,
                            authorization: None,
                            connector_id: None,
                            defer_loading: None,
                            headers: None,
                            require_approval: None,
                            server_description: None,
                            server_url: None,
                        }));
                    }
                    BetaToolUnion::Memory20250818(_) => {}
                }
            }
        }
        if let Some(servers) = value.body.mcp_servers {
            for server in servers {
                converted_tools.push(ResponseTool::Mcp(ResponseMcpTool {
                    server_label: server.name,
                    type_: ResponseMcpToolType::Mcp,
                    allowed_tools: server
                        .tool_configuration
                        .as_ref()
                        .and_then(|config| config.allowed_tools.clone())
                        .map(ResponseMcpAllowedTools::ToolNames),
                    authorization: server.authorization_token,
                    connector_id: None,
                    defer_loading: None,
                    headers: None,
                    require_approval: None,
                    server_description: None,
                    server_url: Some(server.url),
                }));
            }
        }
        let tools = if converted_tools.is_empty() {
            None
        } else {
            Some(converted_tools)
        };

        Ok(Self {
            method: HttpMethod::Post,
            path: PathParameters::default(),
            query: QueryParameters::default(),
            headers: RequestHeaders::default(),
            body: RequestBody {
                input: if input_items.is_empty() {
                    None
                } else {
                    Some(ResponseInput::Items(input_items))
                },
                instructions,
                model: Some(model),
                parallel_tool_calls,
                reasoning,
                text,
                tool_choice,
                tools,
                truncation,
                ..RequestBody::default()
            },
        })
    }
}