gproxy-protocol 4.1.2

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

pub(super) fn to_responses(tools: Vec<c::ChatTool>) -> Result<Vec<r::Tool>, TransformError> {
    tools
        .into_iter()
        .map(|tool| {
            Ok(match tool {
                c::ChatTool::Function(tool) => {
                    let mut out = r::FunctionTool::builder(
                        tool.function.name,
                        tool.function.parameters,
                        tool.function.strict.flatten(),
                    )
                    .build();
                    out.description = Some(tool.function.description);
                    out.allowed_callers = None;
                    r::Tool::Function(out)
                }
                c::ChatTool::Custom(tool) => {
                    let mut out = r::CustomTool::builder(tool.custom.name).build();
                    out.description = tool.custom.description;
                    out.format = tool.custom.format.map(|format| match format {
                        c::CustomToolFormat::Text(_) => r::CustomToolFormat::Text(
                            r::CustomTextFormat::builder(r::TextFormatType::Text).build(),
                        ),
                        c::CustomToolFormat::Grammar(format) => r::CustomToolFormat::Grammar(
                            r::GrammarFormat::builder(
                                format.grammar.definition,
                                match format.grammar.syntax {
                                    c::GrammarSyntax::Lark => r::GrammarSyntax::Lark,
                                    c::GrammarSyntax::Regex => r::GrammarSyntax::Regex,
                                },
                                r::GrammarFormatType::Grammar,
                            )
                            .build(),
                        ),
                    });
                    r::Tool::Custom(out)
                }
            })
        })
        .filter_map(|value| crate::transform::optional(value).transpose())
        .collect()
}

pub(super) fn to_chat(tools: Vec<r::Tool>) -> Result<Vec<c::ChatTool>, TransformError> {
    tools
        .into_iter()
        .map(|tool| {
            Ok(match tool {
                r::Tool::Function(tool) => {
                    let mut function = c::FunctionDefinition::builder(tool.name).build();
                    function.parameters = tool.parameters;
                    function.description = tool.description.flatten();
                    function.strict = Some(tool.strict);
                    c::ChatTool::Function(
                        c::FunctionTool::builder(c::FunctionToolType::Function, function).build(),
                    )
                }
                r::Tool::Custom(tool) => {
                    let mut out = c::CustomTool::builder(tool.name).build();
                    out.description = tool.description;
                    out.format = tool.format.map(|format| match format {
                        r::CustomToolFormat::Text(_) => c::CustomToolFormat::Text(
                            c::TextFormat::builder(c::TextFormatType::Text).build(),
                        ),
                        r::CustomToolFormat::Grammar(format) => c::CustomToolFormat::Grammar(
                            c::GrammarFormat::builder(
                                c::GrammarFormatType::Grammar,
                                c::Grammar::builder(
                                    format.definition,
                                    match format.syntax {
                                        r::GrammarSyntax::Lark => c::GrammarSyntax::Lark,
                                        r::GrammarSyntax::Regex => c::GrammarSyntax::Regex,
                                    },
                                )
                                .build(),
                            )
                            .build(),
                        ),
                    });
                    c::ChatTool::Custom(
                        c::CustomToolDefinition::builder(c::CustomToolDefinitionType::Custom, out)
                            .build(),
                    )
                }
                r::Tool::FileSearch(_)
                | r::Tool::Computer(_)
                | r::Tool::ComputerHosted(_)
                | r::Tool::WebSearch(_)
                | r::Tool::WebSearchPreview20250311(_)
                | r::Tool::WebSearchLegacy(_)
                | r::Tool::WebSearch2025(_)
                | r::Tool::CodeInterpreter(_)
                | r::Tool::Namespace(_)
                | r::Tool::LocalShell(_)
                | r::Tool::Shell(_)
                | r::Tool::ImageGeneration(_)
                | r::Tool::Mcp(_)
                | r::Tool::Programmatic(_)
                | r::Tool::ApplyPatch(_)
                | r::Tool::ToolSearch(_) => {
                    return Err(TransformError::unsupported(
                        "tools",
                        "Responses hosted/namespaced tools require an explicit Chat host adapter",
                    ));
                }
            })
        })
        .filter_map(|value| crate::transform::optional(value).transpose())
        .collect()
}

pub(super) fn choice_to_responses(choice: c::ToolChoice) -> Result<i::ToolChoice, TransformError> {
    Ok(match choice {
        c::ToolChoice::Mode(mode) => i::ToolChoice::Mode(match mode {
            c::ToolChoiceMode::None => i::ToolChoiceMode::None,
            c::ToolChoiceMode::Auto => i::ToolChoiceMode::Auto,
            c::ToolChoiceMode::Required => i::ToolChoiceMode::Required,
        }),
        c::ToolChoice::Function(choice) => i::ToolChoice::Function(
            i::ToolChoiceFunction::builder(
                i::ToolChoiceFunctionType::ToolChoiceFunction,
                choice.function.name,
            )
            .build(),
        ),
        c::ToolChoice::Custom(choice) => i::ToolChoice::Custom(
            i::ToolChoiceCustom::builder(
                i::ToolChoiceCustomType::ToolChoiceCustom,
                choice.custom.name,
            )
            .build(),
        ),
        c::ToolChoice::Allowed(choice) => i::ToolChoice::Allowed(
            i::ToolChoiceAllowed::builder(
                i::ToolChoiceAllowedType::ToolChoiceAllowed,
                match choice.allowed_tools.mode {
                    c::AllowedToolsMode::Auto => i::AllowedToolChoiceMode::Auto,
                    c::AllowedToolsMode::Required => i::AllowedToolChoiceMode::Required,
                },
                selectors(choice.allowed_tools.tools, true)?,
            )
            .build(),
        ),
    })
}

pub(super) fn choice_to_chat(choice: i::ToolChoice) -> Result<c::ToolChoice, TransformError> {
    Ok(match choice {
        i::ToolChoice::Mode(mode) => c::ToolChoice::Mode(match mode {
            i::ToolChoiceMode::None => c::ToolChoiceMode::None,
            i::ToolChoiceMode::Auto => c::ToolChoiceMode::Auto,
            i::ToolChoiceMode::Required => c::ToolChoiceMode::Required,
        }),
        i::ToolChoice::Function(choice) => c::ToolChoice::Function(
            c::NamedFunctionChoice::builder(
                c::NamedFunctionChoiceType::Function,
                c::NamedFunction::builder(choice.name).build(),
            )
            .build(),
        ),
        i::ToolChoice::Custom(choice) => c::ToolChoice::Custom(
            c::NamedCustomChoice::builder(
                c::NamedCustomChoiceType::Custom,
                c::NamedCustom::builder(choice.name).build(),
            )
            .build(),
        ),
        i::ToolChoice::Allowed(choice) => c::ToolChoice::Allowed(
            c::AllowedToolChoice::builder(
                c::AllowedToolChoiceType::AllowedTools,
                c::AllowedTools::builder(
                    match choice.mode {
                        i::AllowedToolChoiceMode::Auto => c::AllowedToolsMode::Auto,
                        i::AllowedToolChoiceMode::Required => c::AllowedToolsMode::Required,
                    },
                    selectors(choice.tools, false)?,
                )
                .build(),
            )
            .build(),
        ),
        i::ToolChoice::Hosted(_)
        | i::ToolChoice::Mcp(_)
        | i::ToolChoice::Programmatic(_)
        | i::ToolChoice::ApplyPatch(_)
        | i::ToolChoice::Shell(_) => {
            return Err(TransformError::unsupported(
                "tool_choice",
                "hosted tool choice requires Chat host adapter",
            ));
        }
    })
}

fn selectors(
    input: Vec<crate::Rest>,
    to_responses: bool,
) -> Result<Vec<crate::Rest>, TransformError> {
    input
        .into_iter()
        .map(|value| {
            let kind = value
                .get("type")
                .and_then(serde_json::Value::as_str)
                .ok_or_else(|| {
                    TransformError::shape("tool_choice.allowed", "missing selector type")
                })?;
            if kind != "function" && kind != "custom" {
                return Err(TransformError::unsupported(
                    "tool_choice.allowed",
                    "selector names a hosted tool unavailable in Chat",
                ));
            }
            let name = if to_responses {
                value.get(kind).and_then(|v| v.get("name"))
            } else {
                value.get("name")
            }
            .and_then(serde_json::Value::as_str)
            .ok_or_else(|| TransformError::shape("tool_choice.allowed", "missing selector name"))?;
            let mut output = crate::Rest::new();
            output.insert("type".into(), kind.into());
            if to_responses {
                output.insert("name".into(), name.into());
            } else {
                output.insert(kind.into(), serde_json::json!({"name":name}));
            }
            Ok(output)
        })
        .filter_map(|value| crate::transform::optional(value).transpose())
        .collect()
}

pub(super) fn legacy(functions: Vec<c::LegacyFunction>) -> Result<Vec<r::Tool>, TransformError> {
    Ok(functions
        .into_iter()
        .map(|function| {
            let mut target =
                r::FunctionTool::builder(function.name, function.parameters, None).build();
            target.description = Some(function.description);
            r::Tool::Function(target)
        })
        .collect())
}

pub(super) fn legacy_choice(choice: c::FunctionCallChoice) -> i::ToolChoice {
    match choice {
        c::FunctionCallChoice::Mode(c::FunctionCallMode::Auto) => {
            i::ToolChoice::Mode(i::ToolChoiceMode::Auto)
        }
        c::FunctionCallChoice::Mode(c::FunctionCallMode::None) => {
            i::ToolChoice::Mode(i::ToolChoiceMode::None)
        }
        c::FunctionCallChoice::Name(choice) => i::ToolChoice::Function(
            i::ToolChoiceFunction::builder(
                i::ToolChoiceFunctionType::ToolChoiceFunction,
                choice.name,
            )
            .build(),
        ),
    }
}