gproxy-protocol 4.0.0

GPROXY v4 connection modelling, operation declarations, vendor wire types and pairwise conversion
Documentation
use gproxy_protocol::openai::{compact::*, conversation::*, memory::*, web_search::*};

#[test]
fn compact_and_conversation_payloads_round_trip_nullable_fields() {
    let compact = serde_json::json!({"model":null,"input":"hello","instructions":null,"previous_response_id":null,"prompt_cache_key":"k","service_tier":"fast","future":true});
    let body: CompactRequestBody = serde_json::from_value(compact.clone()).unwrap();
    assert_eq!(serde_json::to_value(body).unwrap(), compact);
    let conversation = serde_json::json!({"items":null,"metadata":{"k":"v"},"future":1});
    let body: CreateConversationRequestBody = serde_json::from_value(conversation.clone()).unwrap();
    assert_eq!(serde_json::to_value(body).unwrap(), conversation);
}

#[test]
fn memory_and_web_search_known_fields_and_unknown_rest_round_trip() {
    let memory = serde_json::json!({"model":"gpt","traces":[{"id":"t","metadata":{"source_path":"/tmp/a"},"items":[]}],"future":1});
    let body: MemorySummarizeRequestBody = serde_json::from_value(memory.clone()).unwrap();
    assert_eq!(serde_json::to_value(body).unwrap(), memory);
    let search = serde_json::json!({"id":"s","model":"gpt","input":"find","commands":{"search_query":[{"q":"openai","recency":7,"domains":["openai.com"]}]},"settings":{"search_context_size":"high","user_location":{"type":"approximate","country":"US"}},"future":true});
    let body: WebSearchRequestBody = serde_json::from_value(search.clone()).unwrap();
    assert_eq!(serde_json::to_value(body).unwrap(), search);
}

fn exact<T: serde::Serialize + serde::de::DeserializeOwned + std::fmt::Debug>(
    value: serde_json::Value,
) {
    let parsed: T = serde_json::from_value(value.clone()).unwrap();
    assert!(
        format!("{parsed:?}")
            .split("rest: ")
            .skip(1)
            .all(|rest| rest.starts_with("{}")),
        "known fields in rest: {parsed:?}"
    );
    assert_eq!(serde_json::to_value(parsed).unwrap(), value);
}
#[test]
fn compact_conversation_and_memory_follow_their_actual_contracts() {
    use serde_json::json;
    exact::<CompactRequestBody>(json!({"model":null}));
    assert!(serde_json::from_value::<CompactRequestBody>(json!({})).is_err());
    let value = json!({"model":"m","input":"x","instructions":"i","previous_response_id":"r","prompt_cache_key":"p","prompt_cache_options":{"mode":"explicit","ttl":"30m"},"prompt_cache_retention":"24h","service_tier":"priority"});
    for key in [
        "input",
        "instructions",
        "previous_response_id",
        "prompt_cache_key",
        "prompt_cache_options",
        "prompt_cache_retention",
        "service_tier",
    ] {
        let mut null = value.clone();
        null[key] = json!(null);
        exact::<CompactRequestBody>(null);
        let mut missing = value.clone();
        missing.as_object_mut().unwrap().remove(key);
        exact::<CompactRequestBody>(missing);
    }
    exact::<CompactResponseBody>(
        json!({"id":"c","created_at":1,"object":"response.compaction","output":[{"type":"compaction","id":"i","encrypted_content":"e"}],"usage":{"input_tokens":1,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":1,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":2}}),
    );
    for metadata in [json!(null), json!([1]), json!({"k":"v"})] {
        exact::<ConversationResponseBody>(
            json!({"id":"c","created_at":1,"object":"conversation","metadata":metadata}),
        );
    }
    exact::<MemorySummarizeRequestBody>(
        json!({"model":"m","traces":[{"id":"i","metadata":{"source_path":"/p"},"items":[null,1,"opaque",{"new_type":true}]}],"reasoning":{"effort":"model_defined","summary":"none","context":"all_turns"}}),
    );
    let output: MemorySummarizeOutput =
        serde_json::from_value(json!({"raw_memory":"raw","memory_summary":"sum"})).unwrap();
    assert_eq!(
        serde_json::to_value(output).unwrap(),
        json!({"trace_summary":"raw","memory_summary":"sum"})
    );
    exact::<MemorySummarizeResponseBody>(
        json!({"output":[{"trace_summary":"t","memory_summary":"m"}]}),
    );
}
#[test]
fn native_search_all_commands_and_settings_are_typed() {
    use serde_json::json;
    exact::<WebSearchRequestBody>(
        json!({"id":"s","model":"m","reasoning":{"effort":"high","summary":"concise","context":"current_turn"},"input":"q","max_output_tokens":10,"commands":{"search_query":[{"q":"q","recency":1,"domains":["a"]}],"image_query":[{"q":"i"}],"open":[{"ref_id":"r","lineno":2}],"click":[{"ref_id":"r","id":1}],"find":[{"ref_id":"r","pattern":"x"}],"screenshot":[{"ref_id":"r","pageno":0}],"finance":[{"ticker":"SPX","type":"index","market":"USA"}],"weather":[{"location":"FR, Paris","start":"2026-01-01","duration":2}],"sports":[{"tool":"sports","fn":"schedule","league":"nba","team":"GSW","opponent":"BOS","date_from":"2026-01-01","date_to":"2026-01-02","num_games":1,"locale":"en"}],"time":[{"utc_offset":"+01:00"}],"response_length":"long"},"settings":{"user_location":{"type":"approximate","country":"US","region":"CA","city":"SF","timezone":"UTC"},"search_context_size":"high","filters":{"allowed_domains":["a"],"blocked_domains":["b"]},"image_settings":{"max_results":2,"caption":true},"allowed_callers":["direct","shell","code_interpreter"],"external_web_access":"indexed"}}),
    );
    exact::<WebSearchRequestBody>(
        json!({"id":"s","model":"m","settings":{"external_web_access":false},"commands":{"weather":[{"location":"US"}],"sports":[{"fn":"standings","league":"ipl"}],"open":[{"ref_id":"r"}]}}),
    );
    for value in [
        json!({"output":"text"}),
        json!({"output":"text","encrypted_output":null,"results":null}),
        json!({"output":"text","encrypted_output":"e","results":[1,null,{"future":"opaque"}]}),
    ] {
        exact::<WebSearchResponseBody>(value);
    }
    assert!(serde_json::from_value::<FinanceAssetType>(json!("stock")).is_err());
    assert!(serde_json::from_value::<SportsOperation>(json!({"fn":"schedule"})).is_err());
}

fn client_items() -> Vec<serde_json::Value> {
    use serde_json::json;
    vec![
        json!({"type":"additional_tools","role":"system","tools":[{"future":true}]}),
        json!({"type":"message","role":"assistant","content":[{"type":"input_text","text":"a"},{"type":"input_image","image_url":"u","detail":"original"},{"type":"input_audio","audio_url":"a"},{"type":"output_text","text":"b"}],"phase":"final_answer","internal_chat_message_metadata_passthrough":{"turn_id":"t","create_time":1.5,"content_item_kinds":["future"],"cell_id":"c","tool_calls_complete":true,"executed_tool_calls":[{"name":"f","arguments":{"x":1},"tool_result_sources":[{"type":"file","id":"i"}]}]}}),
        json!({"type":"agent_message","author":"a","recipient":"b","content":[{"type":"input_text","text":"a"},{"type":"encrypted_content","encrypted_content":"e"}]}),
        json!({"type":"reasoning","summary":[{"type":"summary_text","text":"s"}],"encrypted_content":null,"content":[{"type":"reasoning_text","text":"r"}]}),
        json!({"type":"local_shell_call","call_id":null,"status":"completed","action":{"type":"exec","command":["pwd"],"timeout_ms":null,"working_directory":null,"env":null,"user":null}}),
        json!({"type":"function_call","name":"f","arguments":"{}","call_id":"c"}),
        json!({"type":"tool_search_call","call_id":null,"execution":"server","arguments":null}),
        json!({"type":"function_call_output","output":"text"}),
        json!({"type":"custom_tool_call","call_id":"c","name":"c","input":"raw"}),
        json!({"type":"custom_tool_call_output","call_id":"c","output":[{"type":"encrypted_content","encrypted_content":"e"}]}),
        json!({"type":"tool_search_output","call_id":null,"status":"completed","execution":"server","tools":[null]}),
        json!({"type":"web_search_call","action":{"type":"search","queries":["q"]}}),
        json!({"type":"image_generation_call","status":"completed","result":"b64"}),
        json!({"type":"compaction","encrypted_content":"e"}),
        json!({"type":"configuration_update","reasoning":{"effort":"model_defined"}}),
        json!({"type":"compaction_trigger"}),
        json!({"type":"context_compaction"}),
        json!({"type":"other"}),
    ]
}
#[test]
fn guardian_native_client_items_and_root_extensions_do_not_use_public_ir() {
    use gproxy_protocol::openai::guardian::*;
    use serde_json::json;
    for item in client_items() {
        exact::<ClientResponseItem>(item);
    }
    let value = json!({"model":"m","instructions":"i","input":client_items(),"tools":[{"custom_native":true}],"tool_choice":"auto","parallel_tool_calls":true,"reasoning":null,"store":false,"stream":true,"stream_options":{"reasoning_summary_delivery":"sequential_cutoff"},"include":["reasoning.encrypted_content"],"service_tier":"future-tier","prompt_cache_key":"k","text":{"verbosity":"low","format":{"type":"json_schema","strict":true,"schema":true,"name":"n"}},"client_metadata":{"source":"guardian"},"access_programs":{"cyber":"daybreak_blue"}});
    exact::<GuardianRequestBody>(value.clone());
    let mut missing = value;
    missing.as_object_mut().unwrap().remove("reasoning");
    assert!(serde_json::from_value::<GuardianRequestBody>(missing).is_err());
    exact::<WebSearchRequestBody>(json!({"id":"s","model":"m","input":client_items()}));
    let mut headers = http::HeaderMap::new();
    headers.insert("x-openai-subagent", "guardian".parse().unwrap());
    let body = GuardianRequestBody::builder(
        "m".into(),
        "".into(),
        vec![],
        "auto".into(),
        true,
        None,
        false,
        true,
        vec![],
    )
    .build();
    let request: GuardianClassifierRequest = gproxy_protocol::WireRequest {
        method: http::Method::POST,
        path: "/guardian-classifier".into(),
        query: None,
        headers: headers.clone(),
        body,
    };
    assert_eq!(request.method, http::Method::POST);
    assert_eq!(request.path, "/guardian-classifier");
    assert_eq!(request.headers, headers);
    assert!(request.query.is_none());
    assert_eq!(
        serde_json::to_value(request.body).unwrap(),
        json!({"model":"m","input":[],"tool_choice":"auto","parallel_tool_calls":true,"reasoning":null,"store":false,"stream":true,"include":[]})
    );
}

#[test]
fn context_http_envelopes_and_native_optional_payloads_are_separate() {
    use gproxy_protocol::{WireRequest, WireResponse};
    use serde_json::json;
    let request: CompactRequest = WireRequest {
        method: http::Method::POST,
        path: "/responses/compact".into(),
        query: Some("trace=1".into()),
        headers: http::HeaderMap::new(),
        body: CompactRequestBody::builder(None).build(),
    };
    assert_eq!(request.path, "/responses/compact");
    assert_eq!(request.query.as_deref(), Some("trace=1"));
    assert_eq!(
        serde_json::to_value(request.body).unwrap(),
        json!({"model":null})
    );
    let response: CreateConversationResponse = WireResponse {
        status: http::StatusCode::OK,
        headers: http::HeaderMap::new(),
        body: ConversationResponseBody::builder(
            "c".into(),
            1,
            json!(null),
            ConversationObject::Conversation,
        )
        .build(),
    };
    assert_eq!(response.status, http::StatusCode::OK);
    assert!(response.headers.is_empty());
    assert_eq!(
        serde_json::to_value(response.body).unwrap(),
        json!({"id":"c","created_at":1,"metadata":null,"object":"conversation"})
    );
    let body: gproxy_protocol::openai::guardian::GuardianRequestBody=serde_json::from_value(json!({"model":"m","input":[],"tools":null,"tool_choice":"auto","parallel_tool_calls":true,"reasoning":null,"store":false,"stream":true,"include":[]})).unwrap();
    assert_eq!(serde_json::to_value(body).unwrap()["tools"], json!(null));
    for asset in ["equity", "fund", "crypto", "index"] {
        exact::<FinanceAssetType>(json!(asset));
    }
    for league in [
        "nba", "wnba", "nfl", "nhl", "mlb", "epl", "ncaamb", "ncaawb", "ipl",
    ] {
        exact::<SportsLeague>(json!(league));
    }
    for mode in [
        json!(true),
        json!(false),
        json!("cached"),
        json!("indexed"),
        json!("live"),
    ] {
        exact::<ExternalWebAccess>(mode);
    }
}