gproxy-protocol 3.0.0-alpha.0

GPROXY v3 protocol model: operation taxonomy, wire kinds, and the OperationSpec registry
Documentation
use serde_json::json;

use crate::openai::generate_content::responses::{
    ApplyPatchOperation, CodeInterpreterOutput, ComputerAction, ComputerScreenshot,
    ComputerScreenshotType, LocalShellAction, LocalShellActionType, McpToolDescription,
    ResponseAnnotation, ResponseReasoningSummaryPart, ResponseReasoningSummaryType,
    ResponseReasoningTextPart, ResponseReasoningTextType, ShellCallOutcome, ShellEnvironment,
    WebSearchAction, WebSearchSourceType,
};

use super::round_trip;

type ItemActionFixture = (
    Vec<ResponseAnnotation>,
    Vec<ComputerAction>,
    ComputerScreenshot,
    Vec<WebSearchAction>,
    ResponseReasoningSummaryPart,
    ResponseReasoningTextPart,
    Vec<CodeInterpreterOutput>,
    LocalShellAction,
    Vec<ShellEnvironment>,
    Vec<ShellCallOutcome>,
    Vec<ApplyPatchOperation>,
    McpToolDescription,
);

#[test]
fn item_action_unions_round_trip() {
    let fixture = round_trip::<ItemActionFixture>(json!([
        [
            {"type":"file_citation","file_id":"file_1","filename":"a.txt","index":0,"future":1},
            {"type":"url_citation","end_index":9,"start_index":1,"title":"Source","url":"https://example.com","future":2},
            {"type":"container_file_citation","container_id":"ctr_1","end_index":9,"file_id":"file_2","filename":"b.txt","start_index":1,"future":3},
            {"type":"file_path","file_id":"file_3","index":2,"future":4}
        ],
        [
            {"type":"click","button":"left","x":1.0,"y":2.0,"future":1},
            {"type":"double_click","keys":[],"x":2.0,"y":3.0,"future":2},
            {"type":"drag","path":[{"x":1.0,"y":2.0,"point_future":true}],"future":3},
            {"type":"keypress","keys":["ENTER"],"future":4},
            {"type":"move","x":4.0,"y":5.0,"future":5},
            {"type":"screenshot","future":6},
            {"type":"scroll","scroll_x":0.0,"scroll_y":10.0,"x":4.0,"y":5.0,"future":7},
            {"type":"type","text":"hello","future":8},
            {"type":"wait","future":9}
        ],
        {"type":"computer_screenshot","file_id":"file_4","screenshot_future":true},
        [
            {"type":"search","query":"rust","sources":[{"type":"url","url":"https://example.com","source_future":true}],"future":1},
            {"type":"open_page","future":2},
            {"type":"find_in_page","pattern":"serde","url":"https://example.com","future":3}
        ],
        {"type":"summary_text","text":"summary","future":1},
        {"type":"reasoning_text","text":"reasoning","future":2},
        [
            {"type":"logs","logs":"ok","future":1},
            {"type":"image","url":"https://example.com/image.png","future":2}
        ],
        {"type":"exec","command":["pwd"],"env":{},"timeout_ms":1000,"future":1},
        [
            {"type":"local","skills":[{"skill_id":"skill_1","version":"latest","skill_future":true}],"future":1},
            {"type":"container_reference","container_id":"ctr_1","future":2}
        ],
        [
            {"type":"timeout","future":1},
            {"type":"exit","exit_code":0,"future":2}
        ],
        [
            {"type":"create_file","diff":"+new","path":"a.txt","future":1},
            {"type":"delete_file","path":"b.txt","future":2},
            {"type":"update_file","diff":"-old\n+new","path":"c.txt","future":3}
        ],
        {"input_schema":{"type":"object"},"name":"lookup","annotations":{"readOnlyHint":true},"mcp_future":true}
    ]));

    let (
        annotations,
        computer,
        screenshot,
        web,
        summary,
        reasoning,
        code,
        local,
        shell,
        outcomes,
        patch,
        mcp,
    ) = fixture;
    assert!(matches!(
        annotations.as_slice(),
        [
            ResponseAnnotation::FileCitation { .. },
            ResponseAnnotation::UrlCitation { .. },
            ResponseAnnotation::ContainerFileCitation { .. },
            ResponseAnnotation::FilePath { .. }
        ]
    ));
    assert!(matches!(
        computer.as_slice(),
        [
            ComputerAction::Click { .. },
            ComputerAction::DoubleClick { .. },
            ComputerAction::Drag { .. },
            ComputerAction::Keypress { .. },
            ComputerAction::Move { .. },
            ComputerAction::Screenshot { .. },
            ComputerAction::Scroll { .. },
            ComputerAction::Type { .. },
            ComputerAction::Wait { .. }
        ]
    ));
    assert!(matches!(
        screenshot.type_,
        ComputerScreenshotType::ComputerScreenshot
    ));
    assert!(matches!(
        web.as_slice(),
        [
            WebSearchAction::Search { .. },
            WebSearchAction::OpenPage { .. },
            WebSearchAction::FindInPage { .. }
        ]
    ));
    let WebSearchAction::Search {
        sources: Some(sources),
        ..
    } = &web[0]
    else {
        panic!("expected search sources")
    };
    assert!(matches!(sources[0].type_, WebSearchSourceType::Url));
    assert!(matches!(
        summary.type_,
        ResponseReasoningSummaryType::SummaryText
    ));
    assert!(matches!(
        reasoning.type_,
        ResponseReasoningTextType::ReasoningText
    ));
    assert!(matches!(
        code.as_slice(),
        [
            CodeInterpreterOutput::Logs { .. },
            CodeInterpreterOutput::Image { .. }
        ]
    ));
    assert!(matches!(local.type_, LocalShellActionType::Exec));
    assert!(matches!(
        shell.as_slice(),
        [
            ShellEnvironment::Local { .. },
            ShellEnvironment::ContainerReference { .. }
        ]
    ));
    assert!(matches!(
        outcomes.as_slice(),
        [
            ShellCallOutcome::Timeout { .. },
            ShellCallOutcome::Exit { .. }
        ]
    ));
    assert!(matches!(
        patch.as_slice(),
        [
            ApplyPatchOperation::CreateFile { .. },
            ApplyPatchOperation::DeleteFile { .. },
            ApplyPatchOperation::UpdateFile { .. }
        ]
    ));
    assert!(mcp.annotations.is_some());
}