gproxy-protocol 4.1.10

GPROXY v4 connection modelling, operation declarations, vendor wire types and pairwise conversion
Documentation
use crate::{
    transform::{Report, TransformError},
    wire::{
        claude::{count_tokens as cc, tools as c},
        openai::responses::{input as i, tools as r},
    },
};

pub(crate) fn to_responses(
    input: Vec<c::ToolUnion>,
    report: &mut Report,
) -> Result<Vec<r::Tool>, TransformError> {
    input
        .into_iter()
        .map(|tool| {
            if matches!(tool, c::ToolUnion::WebSearch20250305(_) | c::ToolUnion::WebSearch20260209(_) | c::ToolUnion::WebSearch20260318(_)) {
                report.changed("tools.web_search", "executed by Responses web search; provider-specific search controls are omitted");
                return Ok(r::Tool::WebSearchLegacy(r::WebSearchTool::builder().build()));
            }
            let c::ToolUnion::Custom(tool) = tool else {
                return Err(TransformError::unsupported(
                    "tools",
                    "Claude hosted tool requires Responses host binding",
                ));
            };
            if tool
                .allowed_callers
                .as_ref()
                .is_some_and(|v| v.iter().any(|v| !matches!(v, c::AllowedCaller::Direct)))
            {
                report.omitted(
                    "tools.allowed_callers",
                    "caller restriction has no target representation",
                );
            }
            let schema = super::schema::to_chat(&tool.input_schema)?;
            let mut target = r::FunctionTool::builder(tool.name, Some(schema), tool.strict).build();
            target.description = Some(tool.description);
            target.defer_loading = tool.defer_loading;
            if tool.cache_control.is_some()
                || tool.eager_input_streaming.is_some()
                || tool.input_examples.is_some()
            {
                report.omitted(
                    "tools.advisory",
                    "target lacks per-tool cache/input examples/eager streaming controls",
                );
            }
            Ok(r::Tool::Function(target))
        })
        .filter_map(|value| crate::transform::optional(value).transpose())
        .collect()
}

pub(crate) fn to_claude(input: Vec<r::Tool>) -> Result<Vec<c::ToolUnion>, TransformError> {
    let mut tools: Vec<c::ToolUnion> = input
        .into_iter()
        .map(|tool| {
            if matches!(
                tool,
                r::Tool::WebSearch(_)
                    | r::Tool::WebSearchPreview20250311(_)
                    | r::Tool::WebSearchLegacy(_)
                    | r::Tool::WebSearch2025(_)
            ) {
                return Ok(c::ToolUnion::WebSearch20250305(
                    c::WebSearchTool20250305::builder(
                        c::WebSearchTool20250305Name::Name,
                        c::WebSearchTool20250305Type::Tag,
                    )
                    .build(),
                ));
            }
            let r::Tool::Function(tool) = tool else {
                return Err(TransformError::unsupported(
                    "tools",
                    "Responses hosted/custom tools need explicit Claude binding",
                ));
            };

            let schema = match tool.parameters {
                Some(schema) => super::schema::from_chat(&schema)?,
                None => c::JsonSchema::builder(c::JsonSchemaType::Object).build(),
            };
            let mut target = c::Tool::builder(schema, tool.name).build();
            target.description = tool.description.flatten();
            target.strict = tool.strict;
            target.defer_loading = tool.defer_loading;
            Ok(c::ToolUnion::Custom(target))
        })
        .filter_map(|value| crate::transform::optional(value).transpose())
        .collect::<Result<_, _>>()?;
    // Responses web search can open and find within pages. Claude exposes
    // page retrieval separately, so retain that capability alongside search.
    if tools
        .iter()
        .any(|tool| matches!(tool, c::ToolUnion::WebSearch20250305(_)))
    {
        tools.push(c::ToolUnion::WebFetch20250910(
            c::WebFetchTool20250910::builder(
                c::WebFetchTool20250910Name::Name,
                c::WebFetchTool20250910Type::Tag,
            )
            .build(),
        ));
    }
    Ok(tools)
}

pub(crate) fn choice_to_responses(choice: cc::ToolChoice) -> (i::ToolChoice, Option<bool>) {
    match choice {
        cc::ToolChoice::Auto(v) => (
            i::ToolChoice::Mode(i::ToolChoiceMode::Auto),
            v.disable_parallel_tool_use.map(|v| !v),
        ),
        cc::ToolChoice::Any(v) => (
            i::ToolChoice::Mode(i::ToolChoiceMode::Required),
            v.disable_parallel_tool_use.map(|v| !v),
        ),
        cc::ToolChoice::None(_) => (i::ToolChoice::Mode(i::ToolChoiceMode::None), None),
        cc::ToolChoice::Tool(v) => (
            i::ToolChoice::Function(
                i::ToolChoiceFunction::builder(
                    i::ToolChoiceFunctionType::ToolChoiceFunction,
                    v.name,
                )
                .build(),
            ),
            v.disable_parallel_tool_use.map(|v| !v),
        ),
    }
}

pub(crate) fn choice_to_claude(
    choice: i::ToolChoice,
    parallel: Option<bool>,
) -> Result<cc::ToolChoice, TransformError> {
    let disable = parallel.map(|v| !v);
    Ok(match choice {
        i::ToolChoice::Mode(i::ToolChoiceMode::Auto) => {
            let mut v = cc::ToolChoiceAuto::builder().build();
            v.disable_parallel_tool_use = disable;
            cc::ToolChoice::Auto(v)
        }
        i::ToolChoice::Mode(i::ToolChoiceMode::Required) => {
            let mut v = cc::ToolChoiceAny::builder().build();
            v.disable_parallel_tool_use = disable;
            cc::ToolChoice::Any(v)
        }
        i::ToolChoice::Mode(i::ToolChoiceMode::None) => {
            cc::ToolChoice::None(cc::ToolChoiceNone::builder().build())
        }
        i::ToolChoice::Function(v) => {
            let mut out = cc::ToolChoiceTool::builder(v.name).build();
            out.disable_parallel_tool_use = disable;
            cc::ToolChoice::Tool(out)
        }
        i::ToolChoice::Allowed(_)
        | i::ToolChoice::Hosted(_)
        | i::ToolChoice::Mcp(_)
        | i::ToolChoice::Custom(_)
        | i::ToolChoice::Programmatic(_)
        | i::ToolChoice::ApplyPatch(_)
        | i::ToolChoice::Shell(_) => {
            return Err(TransformError::unsupported(
                "tool_choice",
                "Responses subset/hosted/custom choice requires tool adaptation",
            ));
        }
    })
}