gproxy-protocol 4.0.4

GPROXY v4 connection modelling, operation declarations, vendor wire types and pairwise conversion
Documentation
use crate::wire::claude::count_tokens as cc;

use crate::{
    Rest,
    transform::{Report, TransformError},
    wire::{claude::tools as ct, openai::chat},
};

pub(crate) fn claude_tools_to_openai(
    tools: Option<&Vec<ct::ToolUnion>>,
    report: &mut Report,
) -> Result<Option<Vec<chat::ChatTool>>, TransformError> {
    let Some(tools) = tools else {
        return Ok(None);
    };
    let mut output = Vec::new();
    for tool in tools {
        let ct::ToolUnion::Custom(tool) = tool else {
            continue;
        };
        if tool.allowed_callers.as_ref().is_some_and(|callers| {
            callers
                .iter()
                .any(|caller| !matches!(caller, ct::AllowedCaller::Direct))
        }) || tool.defer_loading == Some(true)
        {
            report.omitted(
                "tools.allowed_callers/defer_loading",
                "controls have no target representation",
            );
        }
        for (present, field) in [
            (tool.cache_control.is_some(), "cache_control"),
            (
                tool.eager_input_streaming.is_some(),
                "eager_input_streaming",
            ),
            (tool.input_examples.is_some(), "input_examples"),
        ] {
            if present {
                report.omitted(
                    format!("tools.{field}"),
                    "Chat function definitions have no equivalent advisory field",
                );
            }
        }
        let parameters = super::schema::to_chat(&tool.input_schema)?;
        output.push(chat::ChatTool::Function(chat::FunctionTool {
            type_: chat::FunctionToolType::Function,
            function: chat::FunctionDefinition {
                name: tool.name.clone(),
                description: tool.description.clone(),
                parameters: Some(parameters),
                strict: tool.strict.map(Some),
                rest: Rest::new(),
            },
            rest: Rest::new(),
        }));
    }
    if output.is_empty() {
        report.omitted("tools", "empty tool declaration omitted");
    }
    Ok(Some(output))
}

pub(crate) fn openai_tools_to_claude(
    tools: Option<&Vec<chat::ChatTool>>,
) -> Result<Option<Vec<ct::ToolUnion>>, TransformError> {
    let Some(tools) = tools else {
        return Ok(None);
    };
    let mut output = Vec::new();
    for tool in tools {
        let chat::ChatTool::Function(tool) = tool else {
            continue;
        };
        let input_schema = match tool.function.parameters.as_ref() {
            Some(parameters) => {
                let Some(schema) =
                    crate::transform::optional(super::schema::from_chat(parameters))?
                else {
                    continue;
                };
                schema
            }
            None => {
                let mut schema = ct::JsonSchema::builder(ct::JsonSchemaType::Object).build();
                schema.properties = Some(serde_json::json!({}));
                schema.additional_properties = Some(serde_json::Value::Bool(false));
                schema
            }
        };
        output.push(ct::ToolUnion::Custom(ct::Tool {
            input_schema,
            name: tool.function.name.clone(),
            allowed_callers: None,
            cache_control: None,
            defer_loading: None,
            description: tool.function.description.clone(),
            eager_input_streaming: None,
            input_examples: None,
            strict: tool.function.strict.flatten(),
            type_: None,
            rest: Rest::new(),
        }));
    }
    Ok(Some(output))
}

pub(crate) fn claude_choice_to_openai(
    choice: Option<&cc::ToolChoice>,
) -> Result<Option<chat::ToolChoice>, TransformError> {
    let Some(choice) = choice else {
        return Ok(None);
    };
    Ok(Some(match choice {
        cc::ToolChoice::Auto(_) => chat::ToolChoice::Mode(chat::ToolChoiceMode::Auto),
        cc::ToolChoice::Any(_) => chat::ToolChoice::Mode(chat::ToolChoiceMode::Required),
        cc::ToolChoice::None(_) => chat::ToolChoice::Mode(chat::ToolChoiceMode::None),
        cc::ToolChoice::Tool(tool) => chat::ToolChoice::Function(chat::NamedFunctionChoice {
            type_: chat::NamedFunctionChoiceType::Function,
            function: chat::NamedFunction {
                name: tool.name.clone(),
                rest: Rest::new(),
            },
            rest: Rest::new(),
        }),
    }))
}

pub(crate) fn openai_choice_to_claude(
    choice: Option<&chat::ToolChoice>,
) -> Result<Option<cc::ToolChoice>, TransformError> {
    let Some(choice) = choice else {
        return Ok(None);
    };
    Ok(Some(match choice {
        chat::ToolChoice::Mode(chat::ToolChoiceMode::Auto) => {
            cc::ToolChoice::Auto(cc::ToolChoiceAuto {
                disable_parallel_tool_use: None,
                rest: Rest::new(),
            })
        }
        chat::ToolChoice::Mode(chat::ToolChoiceMode::Required) => {
            cc::ToolChoice::Any(cc::ToolChoiceAny {
                disable_parallel_tool_use: None,
                rest: Rest::new(),
            })
        }
        chat::ToolChoice::Mode(chat::ToolChoiceMode::None) => {
            cc::ToolChoice::None(cc::ToolChoiceNone { rest: Rest::new() })
        }
        chat::ToolChoice::Function(choice) => cc::ToolChoice::Tool(cc::ToolChoiceTool {
            name: choice.function.name.clone(),
            disable_parallel_tool_use: None,
            rest: Rest::new(),
        }),
        chat::ToolChoice::Custom(_) | chat::ToolChoice::Allowed(_) => {
            return Ok(None);
        }
    }))
}

pub(super) fn legacy_choice(choice: &chat::FunctionCallChoice) -> cc::ToolChoice {
    match choice {
        chat::FunctionCallChoice::Mode(chat::FunctionCallMode::Auto) => {
            cc::ToolChoice::Auto(cc::ToolChoiceAuto::builder().build())
        }
        chat::FunctionCallChoice::Mode(chat::FunctionCallMode::None) => {
            cc::ToolChoice::None(cc::ToolChoiceNone::builder().build())
        }
        chat::FunctionCallChoice::Name(choice) => {
            cc::ToolChoice::Tool(cc::ToolChoiceTool::builder(choice.name.clone()).build())
        }
    }
}

pub(super) fn legacy_tools(
    functions: &[chat::LegacyFunction],
) -> Result<Vec<ct::ToolUnion>, TransformError> {
    functions
        .iter()
        .map(|function| {
            let schema = match &function.parameters {
                Some(parameters) => super::schema::from_chat(parameters)?,
                None => {
                    let mut schema = ct::JsonSchema::builder(ct::JsonSchemaType::Object).build();
                    schema.additional_properties = Some(serde_json::Value::Bool(false));
                    schema
                }
            };
            let mut tool = ct::Tool::builder(schema, function.name.clone()).build();
            tool.description = function.description.clone();
            Ok(ct::ToolUnion::Custom(tool))
        })
        .filter_map(|value| crate::transform::optional(value).transpose())
        .collect()
}