gproxy-transform 3.0.0-alpha.0

GPROXY v3 pairwise protocol transforms
Documentation
use gproxy_protocol::{claude, openai};

use crate::TransformError;
use crate::common::tools::callers_to_claude;

pub(super) fn convert(tool: openai::ResponseTool) -> Result<claude::Tool, TransformError> {
    Ok(match tool {
        openai::ResponseTool::Function {
            name,
            parameters,
            strict,
            defer_loading,
            description,
            output_schema: _,
            allowed_callers,
            ..
        } => claude::Tool::Custom(crate::wire!(claude::CustomTool {
            input_schema: schema(parameters)?,
            name,
            type_: Some(claude::CustomToolType::Custom),
            description,
            eager_input_streaming: None,
            common: claude::ToolCommon {
                allowed_callers: callers_to_claude(allowed_callers),
                defer_loading,
                strict: match strict {
                    openai::ResponseFunctionStrict::Value(strict) => Some(strict),
                    openai::ResponseFunctionStrict::Null
                    | openai::ResponseFunctionStrict::Absent => None,
                    #[cfg(not(feature = "exhaustive"))]
                    _ =>
                        return Err(crate::TransformError::unsupported(
                            "protocol enum",
                            "unrecognized external variant"
                        )),
                },
                ..Default::default()
            },
            rest: Default::default(),
        })),
        ref custom @ openai::ResponseTool::Custom {
            ref name,
            ref description,
            defer_loading,
            ref allowed_callers,
            ..
        } => fallback(
            custom,
            name.clone(),
            description.clone(),
            defer_loading,
            allowed_callers.clone(),
        )?,
        ref namespace @ openai::ResponseTool::Namespace {
            ref description,
            ref name,
            ..
        } => fallback(
            namespace,
            name.clone(),
            Some(description.clone()),
            None,
            None,
        )?,
        openai::ResponseTool::LocalShell { .. } => bash(None),
        openai::ResponseTool::Shell {
            allowed_callers, ..
        } => bash(allowed_callers),
        openai::ResponseTool::ApplyPatch {
            allowed_callers,
            max_characters,
            ..
        } => text_editor(allowed_callers, max_characters),
        openai::ResponseTool::CodeExecution { .. } => code_execution(None),
        openai::ResponseTool::CodeInterpreter {
            allowed_callers, ..
        } => code_execution(allowed_callers),
        openai::ResponseTool::ComputerUsePreview {
            display_height,
            display_width,
            ..
        } => computer(display_width, display_height),
        openai::ResponseTool::WebSearch {
            filters,
            max_uses,
            user_location,
            ..
        }
        | openai::ResponseTool::WebSearch20250826 {
            filters,
            max_uses,
            user_location,
            ..
        } => web_search(filters, max_uses, user_location),
        openai::ResponseTool::WebSearchPreview { user_location, .. }
        | openai::ResponseTool::WebSearchPreview20250311 { user_location, .. } => {
            preview_search(user_location)
        }
        openai::ResponseTool::WebFetch {
            allowed_domains,
            blocked_domains,
            max_content_tokens,
            max_uses,
            ..
        } => web_fetch(
            allowed_domains,
            blocked_domains,
            max_content_tokens,
            max_uses,
        ),
        openai::ResponseTool::Memory { .. } => memory(),
        openai::ResponseTool::ToolSearch { execution, .. } => tool_search(execution),
        openai::ResponseTool::Mcp {
            server_label,
            server_url: None,
            ..
        } => claude::Tool::McpToolset(crate::wire!(claude::McpToolset {
            mcp_server_name: server_label,
            type_: claude::McpToolsetType::McpToolset,
            cache_control: None,
            configs: Default::default(),
            default_config: None,
            rest: Default::default(),
        })),
        unsupported @ (openai::ResponseTool::FileSearch { .. }
        | openai::ResponseTool::Computer { .. }
        | openai::ResponseTool::XSearch { .. }
        | openai::ResponseTool::CollectionsSearch { .. }
        | openai::ResponseTool::Mcp {
            server_url: Some(_),
            ..
        }
        | openai::ResponseTool::ImageGeneration { .. }
        | openai::ResponseTool::ProgrammaticToolCalling { .. }) => {
            return Err(TransformError::unsupported(
                "OpenAI Responses tool",
                serde_json::to_string(&unsupported)?,
            ));
        }
        #[cfg(not(feature = "exhaustive"))]
        _ => {
            return Err(crate::TransformError::unsupported(
                "protocol enum",
                "unrecognized external variant",
            ));
        }
    })
}

fn bash(callers: Option<Vec<openai::ToolCaller>>) -> claude::Tool {
    claude::Tool::Command(claude::CommandTool::Bash20250124(crate::wire!(
        claude::BashTool20250124 {
            name: claude::BashToolName::Bash,
            type_: claude::BashTool20250124Type::Bash20250124,
            common: claude::ToolCommon {
                allowed_callers: callers_to_claude(callers),
                ..Default::default()
            },
            rest: Default::default(),
        }
    )))
}

fn text_editor(
    callers: Option<Vec<openai::ToolCaller>>,
    max_characters: Option<u64>,
) -> claude::Tool {
    claude::Tool::TextEditor(claude::TextEditorTool::TextEditor20250728(crate::wire!(
        claude::TextEditorTool20250728 {
            name: claude::StrReplaceBasedEditToolName::StrReplaceBasedEditTool,
            type_: claude::TextEditorTool20250728Type::TextEditor20250728,
            max_characters,
            common: claude::ToolCommon {
                allowed_callers: callers_to_claude(callers),
                ..Default::default()
            },
            rest: Default::default(),
        }
    )))
}

fn code_execution(callers: Option<Vec<openai::ToolCaller>>) -> claude::Tool {
    claude::Tool::Command(claude::CommandTool::CodeExecution20260120(crate::wire!(
        claude::CodeExecutionTool20260120 {
            name: claude::CodeExecutionToolName::CodeExecution,
            type_: claude::CodeExecutionTool20260120Type::CodeExecution20260120,
            common: claude::ToolCommonWithoutInputExamples {
                allowed_callers: callers_to_claude(callers),
                ..Default::default()
            },
            rest: Default::default(),
        }
    )))
}

fn computer(width: u32, height: u32) -> claude::Tool {
    claude::Tool::Computer(claude::ComputerTool::Computer20250124(crate::wire!(
        claude::ComputerTool20250124 {
            display_height_px: u64::from(height),
            display_width_px: u64::from(width),
            name: claude::ComputerToolName::Computer,
            type_: claude::ComputerTool20250124Type::Computer20250124,
            display_number: None,
            common: Default::default(),
            rest: Default::default(),
        }
    )))
}

fn web_search(
    filters: Option<openai::WebSearchFilters>,
    max_uses: Option<u64>,
    user_location: Option<openai::WebSearchUserLocation>,
) -> claude::Tool {
    let (allowed_domains, blocked_domains) = filters
        .map(|filters| (filters.allowed_domains, filters.blocked_domains))
        .unwrap_or_default();
    claude_search(
        allowed_domains,
        blocked_domains,
        max_uses,
        user_location.map(location),
    )
}

fn preview_search(user_location: Option<openai::WebSearchPreviewUserLocation>) -> claude::Tool {
    claude_search(None, None, None, user_location.map(preview_location))
}

fn claude_search(
    allowed_domains: Option<Vec<String>>,
    blocked_domains: Option<Vec<String>>,
    max_uses: Option<u64>,
    user_location: Option<claude::UserLocation>,
) -> claude::Tool {
    claude::Tool::WebSearch(claude::WebSearchTool::WebSearch20260209(crate::wire!(
        claude::WebSearchTool20260209 {
            name: claude::WebSearchToolName::WebSearch,
            type_: claude::WebSearchTool20260209Type::WebSearch20260209,
            params: claude::WebSearchToolParams {
                allowed_domains,
                blocked_domains,
                max_uses,
                user_location,
                rest: Default::default(),
            },
            common: Default::default(),
            rest: Default::default(),
        }
    )))
}

fn location(location: openai::WebSearchUserLocation) -> claude::UserLocation {
    crate::wire!(claude::UserLocation {
        type_: claude::UserLocationType::Approximate,
        city: location.city,
        country: location.country,
        region: location.region,
        timezone: location.timezone,
        rest: Default::default(),
    })
}

fn preview_location(location: openai::WebSearchPreviewUserLocation) -> claude::UserLocation {
    crate::wire!(claude::UserLocation {
        type_: claude::UserLocationType::Approximate,
        city: location.city,
        country: location.country,
        region: location.region,
        timezone: location.timezone,
        rest: Default::default(),
    })
}

fn web_fetch(
    allowed_domains: Option<Vec<String>>,
    blocked_domains: Option<Vec<String>>,
    max_content_tokens: Option<u64>,
    max_uses: Option<u64>,
) -> claude::Tool {
    claude::Tool::WebFetch(claude::WebFetchTool::WebFetch20260209(crate::wire!(
        claude::WebFetchTool20260209 {
            name: claude::WebFetchToolName::WebFetch,
            type_: claude::WebFetchTool20260209Type::WebFetch20260209,
            params: claude::WebFetchToolParams {
                allowed_domains,
                blocked_domains,
                citations: None,
                max_content_tokens,
                max_uses,
                rest: Default::default(),
            },
            common: Default::default(),
            rest: Default::default(),
        }
    )))
}

fn memory() -> claude::Tool {
    claude::Tool::Command(claude::CommandTool::Memory20250818(crate::wire!(
        claude::MemoryTool20250818 {
            name: claude::MemoryToolName::Memory,
            type_: claude::MemoryTool20250818Type::Memory20250818,
            common: Default::default(),
            rest: Default::default(),
        }
    )))
}

fn tool_search(execution: Option<openai::ToolSearchExecution>) -> claude::Tool {
    if matches!(execution, Some(openai::ToolSearchExecution::Client)) {
        claude::Tool::Command(claude::CommandTool::ToolSearchRegex(crate::wire!(
            claude::ToolSearchRegexTool {
                name: claude::ToolSearchRegexToolName::ToolSearchRegex,
                type_: claude::ToolSearchRegexToolType::ToolSearchRegex,
                common: Default::default(),
                rest: Default::default(),
            }
        )))
    } else {
        claude::Tool::Command(claude::CommandTool::ToolSearchBm25(crate::wire!(
            claude::ToolSearchBm25Tool {
                name: claude::ToolSearchBm25ToolName::ToolSearchBm25,
                type_: claude::ToolSearchBm25ToolType::ToolSearchBm25,
                common: Default::default(),
                rest: Default::default(),
            }
        )))
    }
}

fn fallback(
    _tool: &openai::ResponseTool,
    name: String,
    description: Option<String>,
    defer_loading: Option<bool>,
    allowed_callers: Option<Vec<openai::ToolCaller>>,
) -> Result<claude::Tool, TransformError> {
    Ok(claude::Tool::Custom(crate::wire!(claude::CustomTool {
        input_schema: claude::JsonSchema {
            type_: claude::JsonSchemaObjectType::Known(claude::JsonSchemaObjectTypeKnown::Object),
            properties: Default::default(),
            required: Vec::new(),
            rest: Default::default(),
        },
        name,
        type_: Some(claude::CustomToolType::Custom),
        description,
        eager_input_streaming: None,
        common: claude::ToolCommon {
            allowed_callers: callers_to_claude(allowed_callers),
            defer_loading,
            ..Default::default()
        },
        rest: Default::default(),
    })))
}

fn schema(
    parameters: openai::ResponseFunctionParameters,
) -> Result<claude::JsonSchema, TransformError> {
    let schema = match parameters {
        openai::ResponseFunctionParameters::Schema(schema) => schema,
        openai::ResponseFunctionParameters::Null => Default::default(),
        #[cfg(not(feature = "exhaustive"))]
        _ => {
            return Err(crate::TransformError::unsupported(
                "protocol enum",
                "unrecognized external variant",
            ));
        }
    };
    Ok(serde_json::from_value(serde_json::Value::Object(schema))?)
}