gproxy-protocol 3.0.0-alpha.0

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

use super::compact::CompactResponseRequestBody;
use super::generate_content::chat::{ChatCompletionChunk, ChatCompletionRequest};
use super::generate_content::responses::{
    ResponseCreateRequest, ResponseItem, ResponseNamespaceTool, ResponseObject,
    ResponseStreamEvent, ResponseTool, ResponseWebSocketRequest,
};
use super::images::{CreateImageRequest, EditImageRequest};
use super::memories::{MemorySummarizeRequest, MemorySummarizeResponse};
use super::models::{ListModelsRequest, RetrieveModelRequest};

mod item_actions;
mod item_content;
mod multi_agent;
mod response_stream;
mod retained_unions;
mod tool_runtime;
mod typed_items;

fn round_trip<T>(value: Value) -> T
where
    T: serde::de::DeserializeOwned + serde::Serialize,
{
    let parsed = serde_json::from_value::<T>(value.clone()).expect("decode OpenAI wire value");
    assert_eq!(
        serde_json::to_value(&parsed).expect("encode OpenAI wire value"),
        value
    );
    parsed
}

#[test]
fn requests_preserve_rest_and_unknown_union_members() {
    let create_image = json!({
        "prompt":"landscape","quality":"hd","size":"1792x1024"
    });
    let create_image = round_trip::<CreateImageRequest>(create_image);
    assert_eq!(format!("{:?}", create_image.quality), "Some(Hd)");
    let edit_image = serde_json::from_value::<EditImageRequest>(json!({
        "images":[{"image_url":"data:image/png;base64,AAAA"}],
        "prompt":"edit","quality":"hd","size":"1792x1024"
    }))
    .unwrap();
    assert_eq!(format!("{:?}", edit_image.quality), "Some(Unknown(\"hd\"))");

    let chat = json!({
        "model": "gpt-future",
        "messages": [
            {
                "role": "user",
                "content": [{"type":"text", "text":"hi", "part_future":7}],
                "message_future": {"enabled":true}
            },
            {"role":"future_role", "payload":{"nested":[1,2,3]}}
        ],
        "request_future": "kept"
    });
    let parsed = round_trip::<ChatCompletionRequest>(chat);
    assert_eq!(parsed.rest["request_future"], "kept");
    assert!(matches!(
        parsed.messages[1],
        super::generate_content::chat::ChatCompletionMessageParam::Unknown(_)
    ));

    let responses = json!({
        "model":"gpt-future",
        "input":[{"type":"future_item", "payload":{"x":1}}],
        "tools":[
            {
                "type":"namespace",
                "description":"CRM tools",
                "name":"crm",
                "tools":[{
                    "type":"function",
                    "name":"lookup",
                    "parameters":{"type":"object"},
                    "namespace_future":true
                }],
                "tool_future":["a","b"]
            },
            {
                "type":"function",
                "name":"ping",
                "parameters":null,
                "strict":null,
                "output_schema":{"type":"string"}
            }
        ],
        "response_request_future":42
    });
    let parsed = round_trip::<ResponseCreateRequest>(responses);
    assert_eq!(parsed.rest["response_request_future"], 42);
    let input = parsed.input.expect("response input");
    let super::generate_content::responses::ResponseInput::Items(items) = input else {
        panic!("expected item input");
    };
    assert!(matches!(items[0], ResponseItem::Unknown(_)));
    let ResponseTool::Namespace { tools, rest, .. } = &parsed.tools.as_ref().expect("tools")[0]
    else {
        panic!("expected namespace tool");
    };
    assert_eq!(rest["tool_future"], json!(["a", "b"]));
    let ResponseNamespaceTool::Function { rest, .. } = &tools[0] else {
        panic!("expected namespace function");
    };
    assert_eq!(rest["namespace_future"], true);
    assert!(matches!(
        parsed.tools.as_ref().expect("tools")[1],
        ResponseTool::Function {
            parameters: super::generate_content::responses::ResponseFunctionParameters::Null,
            strict: super::generate_content::responses::ResponseFunctionStrict::Null,
            ..
        }
    ));

    round_trip::<CompactResponseRequestBody>(json!({
        "model":"gpt-future",
        "compact_future":{"mode":"new"}
    }));
    round_trip::<ListModelsRequest>(json!({"future_query":"kept"}));
    round_trip::<RetrieveModelRequest>(json!({
        "model":"gpt-future",
        "future_path_metadata":true
    }));

    let memory = round_trip::<MemorySummarizeRequest>(json!({
        "model":"gpt-future",
        "traces":[{
            "id":"trace-1",
            "metadata":{"source_path":"/tmp/trace.jsonl", "metadata_future":1},
            "items":[{"type":"future_item", "payload":{"x":1}}],
            "trace_future":true
        }],
        "request_future":{"mode":"new"}
    }));
    assert!(memory.reasoning.is_none());
}

#[test]
fn responses_preserve_unknown_fields_and_items() {
    let response = json!({
        "id":"resp_1",
        "created_at":1,
        "object":"response",
        "output":[{"type":"future_output", "raw":{"deep":true}}],
        "response_future":{"region":"moon"}
    });
    let parsed = round_trip::<ResponseObject>(response);
    assert_eq!(parsed.rest["response_future"]["region"], "moon");
    assert!(matches!(parsed.output[0], ResponseItem::Unknown(_)));

    round_trip::<MemorySummarizeResponse>(json!({
        "output":[{
            "trace_summary":"raw summary",
            "memory_summary":"memory summary",
            "summary_future":true
        }],
        "response_future":{"region":"moon"}
    }));
}

#[test]
fn chat_and_responses_stream_events_round_trip_future_data() {
    round_trip::<ChatCompletionChunk>(json!({
        "id":"chatcmpl_1",
        "choices":[{
            "index":0,
            "delta":{"content":"hi", "delta_future":true},
            "finish_reason":null,
            "choice_future":9
        }],
        "created":1,
        "model":"gpt-future",
        "object":"chat.completion.chunk",
        "chunk_future":{"trace":"x"}
    }));

    let known = json!({
        "type":"response.output_text.delta",
        "content_index":0,
        "delta":"hello",
        "item_id":"msg_1",
        "output_index":0,
        "event_future":{"trace_id":"t1"}
    });
    let parsed = round_trip::<ResponseStreamEvent>(known);
    assert!(matches!(parsed, ResponseStreamEvent::Known(_)));

    let unknown = json!({
        "type":"response.future_event",
        "sequence_number":7,
        "raw":{"nested":[{"x":1}]}
    });
    let parsed = round_trip::<ResponseStreamEvent>(unknown.clone());
    let ResponseStreamEvent::Unknown(raw) = parsed else {
        panic!("future event must remain raw");
    };
    assert_eq!(serde_json::to_value(raw).unwrap(), unknown);

    round_trip::<ResponseWebSocketRequest>(json!({
        "type":"response.create",
        "model":"gpt-future",
        "input":"hello"
    }));
    round_trip::<ResponseWebSocketRequest>(json!({
        "type":"response.inject",
        "response_id":"resp_1",
        "input":[{"type":"function_call_output","call_id":"call_1","output":"ok"}],
        "futureInject":true
    }));
    let created = round_trip::<ResponseStreamEvent>(json!({
        "type":"response.inject.created",
        "sequence_number":9,
        "response_id":"resp_1",
        "futureAck":true
    }));
    assert!(matches!(created, ResponseStreamEvent::Known(_)));
    let future = round_trip::<ResponseWebSocketRequest>(json!({
        "type":"response.future",
        "payload":{"x":1}
    }));
    assert!(matches!(future, ResponseWebSocketRequest::Unknown(_)));
}