gproxy-transform 3.0.0-alpha.0

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

pub(crate) struct ClaudeResult {
    call_id: String,
    content: Option<claude::ToolResultContent>,
    is_error: Option<bool>,
}

pub(crate) fn openai_result(item: openai::TypedResponseItem) -> Option<ClaudeResult> {
    match item {
        openai::TypedResponseItem::ShellCallOutput {
            call_id, output, ..
        } => {
            let failed = output.iter().any(|part| match &part.outcome {
                openai::ShellCallOutcome::Exit { exit_code, .. } => *exit_code != 0,
                openai::ShellCallOutcome::Timeout { .. } => true,
                #[cfg(not(feature = "exhaustive"))]
                _ => true,
            });
            let text = output
                .into_iter()
                .flat_map(|part| [part.stdout, part.stderr])
                .filter(|part| !part.is_empty())
                .collect::<Vec<_>>()
                .join("\n");
            Some(ClaudeResult {
                call_id,
                content: (!text.is_empty()).then_some(claude::ToolResultContent::Text(text)),
                is_error: Some(failed),
            })
        }
        openai::TypedResponseItem::ApplyPatchCallOutput {
            call_id,
            status,
            id: _,
            output,
            ..
        } => Some(ClaudeResult {
            call_id,
            content: output.map(claude::ToolResultContent::Text),
            is_error: Some(matches!(
                status,
                openai::ResponseApplyPatchCallOutputStatus::Failed
            )),
        }),
        openai::TypedResponseItem::ProgramOutput {
            id: _,
            call_id,
            result,
            status,
            ..
        } => Some(ClaudeResult {
            call_id,
            content: Some(claude::ToolResultContent::Text(result)),
            is_error: Some(!matches!(
                status,
                openai::ResponseItemLifecycleStatus::Completed
            )),
        }),
        openai::TypedResponseItem::FileSearchCall { .. }
        | openai::TypedResponseItem::ComputerCall { .. }
        | openai::TypedResponseItem::ComputerCallOutput { .. }
        | openai::TypedResponseItem::WebSearchCall { .. }
        | openai::TypedResponseItem::FunctionCall { .. }
        | openai::TypedResponseItem::FunctionCallOutput { .. }
        | openai::TypedResponseItem::ToolSearchCall { .. }
        | openai::TypedResponseItem::ToolSearchOutput { .. }
        | openai::TypedResponseItem::AdditionalTools { .. }
        | openai::TypedResponseItem::Reasoning { .. }
        | openai::TypedResponseItem::Compaction { .. }
        | openai::TypedResponseItem::ImageGenerationCall { .. }
        | openai::TypedResponseItem::CodeInterpreterCall { .. }
        | openai::TypedResponseItem::LocalShellCall { .. }
        | openai::TypedResponseItem::LocalShellCallOutput { .. }
        | openai::TypedResponseItem::ShellCall { .. }
        | openai::TypedResponseItem::ApplyPatchCall { .. }
        | openai::TypedResponseItem::McpListTools { .. }
        | openai::TypedResponseItem::McpApprovalRequest { .. }
        | openai::TypedResponseItem::McpApprovalResponse { .. }
        | openai::TypedResponseItem::McpCall { .. }
        | openai::TypedResponseItem::CustomToolCall { .. }
        | openai::TypedResponseItem::CustomToolCallOutput { .. }
        | openai::TypedResponseItem::Program { .. }
        | openai::TypedResponseItem::MultiAgentCall { .. }
        | openai::TypedResponseItem::MultiAgentCallOutput { .. }
        | openai::TypedResponseItem::AgentMessage { .. }
        | openai::TypedResponseItem::ConfigurationUpdate { .. }
        | openai::TypedResponseItem::CompactionTrigger { .. }
        | openai::TypedResponseItem::ItemReference { .. } => None,
        #[cfg(not(feature = "exhaustive"))]
        _ => None,
    }
}

pub(crate) fn result_block(result: ClaudeResult) -> claude::ContentBlockParam {
    claude::ContentBlockParam::ToolResult(crate::wire!(claude::ToolResultBlock {
        tool_use_id: result.call_id,
        type_: claude::ToolResultBlockType::ToolResult,
        cache_control: None,
        content: result.content,
        is_error: result.is_error,
        rest: Default::default(),
    }))
}