everruns-contracts 0.44.0

Shared runtime, provider, capability, and model profile contracts for Everruns
Documentation
use super::*;
use serde_json::json;

#[test]
fn input_constructors_emit_complete_literal_payloads() {
    for (item, expected) in [
        (
            InputItem::Message(MessageItem::user("Hello")),
            json!({"type":"message","role":"user","content":"Hello"}),
        ),
        (
            InputItem::Message(MessageItem::developer("Rules")),
            json!({"type":"message","role":"developer","content":"Rules"}),
        ),
        (
            InputItem::Message(MessageItem::assistant("Answer")),
            json!({"type":"message","role":"assistant","content":"Answer"}),
        ),
        (
            InputItem::FunctionCall(FunctionCallItem::new(
                "call_123",
                "get_weather",
                r#"{"location":"NYC"}"#,
            )),
            json!({"type":"function_call","call_id":"call_123","name":"get_weather","arguments":"{\"location\":\"NYC\"}"}),
        ),
        (
            InputItem::FunctionCallOutput(FunctionCallOutputItem::new(
                "call_123",
                r#"{"temp": 72}"#,
            )),
            json!({"type":"function_call_output","call_id":"call_123","output":"{\"temp\": 72}"}),
        ),
    ] {
        assert_eq!(serde_json::to_value(item).unwrap(), expected);
    }
}

#[test]
fn request_items_use_discriminators_and_preserve_complete_payloads() {
    let items = json!([
        {"type":"message","id":"msg","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Answer"}]},
        {"type":"function_call","id":"fc","status":"in_progress","call_id":"call","name":"weather","arguments":"{\"city\":\"NYC\"}"},
        {"type":"function_call_output","id":"out","status":"completed","call_id":"call","output":[{"type":"input_text","text":"72"}]},
        {"type":"reasoning","id":"rs","summary":[{"type":"summary_text","text":"Summary"}],"content":[{"type":"reasoning_text","text":"Reasoning"}],"encrypted_content":"opaque"},
        {"type":"item_reference","id":"msg_123"}
    ]);
    let request = json!({"model":"model","input":items});
    let parsed: CreateResponseBody = serde_json::from_value(request.clone()).unwrap();
    let Some(Input::Items(items)) = &parsed.input else {
        panic!("expected item array")
    };
    assert!(matches!(
        items.as_slice(),
        [
            InputItem::Message(_),
            InputItem::FunctionCall(_),
            InputItem::FunctionCallOutput(_),
            InputItem::Reasoning(_),
            InputItem::Reference(_)
        ]
    ));
    assert_eq!(serde_json::to_value(parsed).unwrap(), request);
    for invalid in [
        json!({"type":"function_call","id":"fc"}),
        json!({"type":"unknown","id":"id"}),
        json!({"id":"id"}),
        json!({"type":"message","id":"msg","content":"missing role"}),
    ] {
        assert!(
            serde_json::from_value::<InputItem>(invalid.clone()).is_err(),
            "{invalid}"
        );
    }
    let simple = json!({"input":"Hello"});
    assert_eq!(
        serde_json::to_value(serde_json::from_value::<CreateResponseBody>(simple.clone()).unwrap())
            .unwrap(),
        simple
    );
}

#[test]
fn lifecycle_statuses_decode_from_response_snapshots_and_emit_protocol_values() {
    for (wire, expected) in [
        ("in_progress", ResponseStatus::InProgress),
        ("completed", ResponseStatus::Completed),
        ("failed", ResponseStatus::Failed),
        ("cancelled", ResponseStatus::Cancelled),
        ("queued", ResponseStatus::Queued),
        ("incomplete", ResponseStatus::Incomplete),
        ("provider_extension", ResponseStatus::Unknown),
    ] {
        let value = json!({"type":"response.in_progress","sequence_number":7,"response":{"id":"resp","object":"response","created_at":1,"model":"model","status":wire,"output":[],"tools":[]}});
        let event: StreamingEvent = serde_json::from_value(value.clone()).unwrap();
        let StreamingEvent::ResponseInProgress {
            sequence_number,
            response,
        } = &event
        else {
            panic!("wrong event")
        };
        assert_eq!(*sequence_number, 7);
        assert_eq!(response.status, expected);
        let mut encoded = value;
        if expected == ResponseStatus::Unknown {
            encoded["response"]["status"] = json!("unknown");
        }
        assert_eq!(serde_json::to_value(event).unwrap(), encoded);
    }
}

#[test]
fn streaming_deltas_preserve_routing_indexes_text_and_optional_fields() {
    let logprobs = json!([{"token":"hi","logprob":-0.25,"bytes":[104,105],"top_logprobs":[{"token":"hey","logprob":-0.5,"bytes":[104,101,121]}]}]);
    for wire in [
        json!({"type":"response.output_text.delta","sequence_number":5,"item_id":"msg_123","output_index":2,"content_index":3,"delta":"Hello","logprobs":logprobs,"obfuscation":"opaque"}),
        json!({"type":"response.reasoning_text.delta","sequence_number":3,"item_id":"rs_tmp","output_index":4,"content_index":2,"delta":"User asks"}),
    ] {
        let parsed: StreamingEvent = serde_json::from_value(wire.clone()).unwrap();
        assert_eq!(serde_json::to_value(parsed).unwrap(), wire);
    }
    let minimal: StreamingEvent =
        serde_json::from_value(json!({"type":"response.reasoning_text.delta","delta":"text"}))
            .unwrap();
    assert_eq!(
        serde_json::to_value(minimal).unwrap(),
        json!({"type":"response.reasoning_text.delta","sequence_number":0,"item_id":"","output_index":0,"content_index":0,"delta":"text"})
    );
    let no_logprobs: StreamingEvent = serde_json::from_value(json!({"type":"response.output_text.delta","sequence_number":5,"item_id":"msg","output_index":0,"content_index":0,"delta":"text"})).unwrap();
    assert_eq!(
        serde_json::to_value(no_logprobs).unwrap(),
        json!({"type":"response.output_text.delta","sequence_number":5,"item_id":"msg","output_index":0,"content_index":0,"delta":"text","logprobs":[]})
    );
}

#[test]
fn errors_preserve_full_payload_and_omit_absent_options() {
    let error = ErrorPayload {
        type_: "invalid_request_error".into(),
        code: Some("model_not_found".into()),
        message: "Model not found".into(),
        param: Some("model".into()),
        headers: Some(HashMap::from([("retry-after".into(), "5".into())])),
    };
    let expected = json!({"type":"invalid_request_error","code":"model_not_found","message":"Model not found","param":"model","headers":{"retry-after":"5"}});
    assert_eq!(serde_json::to_value(error).unwrap(), expected);
    let wire = json!({"type":"error","sequence_number":8,"error":expected});
    assert_eq!(
        serde_json::to_value(serde_json::from_value::<StreamingEvent>(wire.clone()).unwrap())
            .unwrap(),
        wire
    );
    let minimal = json!({"type":"server_error","message":"Failed"});
    assert_eq!(
        serde_json::to_value(serde_json::from_value::<ErrorPayload>(minimal.clone()).unwrap())
            .unwrap(),
        minimal
    );
    assert_eq!(
        Error {
            code: "failed".into(),
            message: "Problem".into()
        }
        .to_string(),
        "failed: Problem"
    );
}

#[test]
fn function_names_enforce_literal_length_and_ascii_boundaries() {
    for name in [
        "a".into(),
        "a".repeat(64),
        "get_weather".into(),
        "get-weather".into(),
        "getWeather123".into(),
        "_-09AZaz".into(),
    ] {
        assert!(validate_function_name(&name), "{name}");
    }
    for name in [
        "".into(),
        "a".repeat(65),
        "get weather".into(),
        "get.weather".into(),
        "é".into(),
        "a\n".into(),
        "a/b".into(),
    ] {
        assert!(!validate_function_name(&name), "{name:?}");
    }
}

#[test]
fn metadata_enforces_pair_and_unicode_character_boundaries() {
    for count in [0, 1, 16, 17] {
        let values = (0..count)
            .map(|i| (format!("key{i}"), "value".into()))
            .collect();
        assert_eq!(validate_metadata(&values), count <= 16, "{count}");
    }
    for unit in ["a", "é", "🦀"] {
        for (key_len, value_len, valid) in [
            (0, 0, true),
            (64, 512, true),
            (65, 512, false),
            (64, 513, false),
        ] {
            assert_eq!(
                validate_metadata(&HashMap::from([(
                    unit.repeat(key_len),
                    unit.repeat(value_len)
                )])),
                valid,
                "{unit}: {key_len}/{value_len}"
            );
        }
    }
}

#[test]
fn content_variants_preserve_full_multimodal_payloads() {
    for wire in [
        json!({"type":"input_text","text":"Hello"}),
        json!({"type":"input_image","image_url":"data:image/png;base64,aGk=","detail":"high"}),
        json!({"type":"input_image"}),
        json!({"type":"input_audio","input_audio":{"data":"aGk=","format":"wav"}}),
        json!({"type":"input_file","filename":"note.txt","file_data":"aGk=","file_url":"https://example.com/file"}),
        json!({"type":"input_video","video_url":"https://example.com/video"}),
        json!({"type":"output_text","text":"answer","annotations":[{"type":"url_citation","url":"https://example.com","title":"Source","start_index":0,"end_index":6}],"logprobs":[{"token":"answer","logprob":-0.5,"bytes":[97],"top_logprobs":[]}]}),
        json!({"type":"text","text":"plain"}),
        json!({"type":"reasoning_text","text":"thought"}),
        json!({"type":"summary_text","text":"summary"}),
        json!({"type":"refusal","refusal":"Cannot"}),
    ] {
        assert_eq!(
            serde_json::to_value(serde_json::from_value::<ContentPart>(wire.clone()).unwrap())
                .unwrap(),
            wire
        );
    }
    assert_eq!(
        serde_json::to_value(InputTextContent::new("Hello")).unwrap(),
        json!({"type":"input_text","text":"Hello"})
    );
    assert_eq!(
        serde_json::to_value(InputImageContent::new("https://example.com/image")).unwrap(),
        json!({"type":"input_image","image_url":"https://example.com/image"})
    );
}

#[test]
fn function_tools_and_choices_preserve_schema_strictness_and_omission() {
    let schema = json!({"type":"object","properties":{"city":{"type":"string"}},"required":["city"],"additionalProperties":false});
    let tool = Tool::Function {
        name: "get_weather".into(),
        description: Some("Get weather".into()),
        parameters: Some(schema.clone()),
        strict: Some(true),
    };
    let wire = json!({"type":"function","name":"get_weather","description":"Get weather","parameters":schema,"strict":true});
    assert_eq!(serde_json::to_value(tool).unwrap(), wire);
    assert_eq!(
        serde_json::to_value(serde_json::from_value::<Tool>(wire.clone()).unwrap()).unwrap(),
        wire
    );
    let built = FunctionTool::new("get_weather")
        .with_description("Get weather")
        .with_parameters(schema);
    let mut expected = wire;
    expected.as_object_mut().unwrap().remove("strict");
    assert_eq!(serde_json::to_value(built).unwrap(), expected);
    assert_eq!(
        serde_json::to_value(FunctionTool::new("minimal")).unwrap(),
        json!({"type":"function","name":"minimal"})
    );
    for wire in [
        json!("auto"),
        json!("none"),
        json!("required"),
        json!({"type":"function","name":"get_weather"}),
        json!({"type":"allowed_tools","mode":"required","tools":[{"type":"function","name":"get_weather"}]}),
    ] {
        assert_eq!(
            serde_json::to_value(serde_json::from_value::<ToolChoice>(wire.clone()).unwrap())
                .unwrap(),
            wire
        );
    }
}

#[test]
fn usage_preserves_complete_counts_details_and_optional_gateway_cost() {
    for wire in [
        json!({"input_tokens":194,"output_tokens":2,"total_tokens":196,"cost":0.00095,"input_tokens_details":{"cached_tokens":180},"output_tokens_details":{"reasoning_tokens":1}}),
        json!({"input_tokens":10,"output_tokens":5,"total_tokens":15}),
        json!({"input_tokens":0,"output_tokens":0,"total_tokens":0,"cost":0.0}),
    ] {
        let usage: Usage = serde_json::from_value(wire.clone()).unwrap();
        assert_eq!(usage.cost, wire.get("cost").and_then(Value::as_f64));
        assert_eq!(serde_json::to_value(usage).unwrap(), wire);
    }
}
#[test]
fn hosted_tool_search_preserves_complete_namespace_and_call_payloads() {
    let json = r#"{
            "id": "resp_123",
            "object": "response",
            "created_at": 1780000000,
            "status": "completed",
            "model": "gpt-5.5",
            "output": [
                {
                    "type": "tool_search_call",
                    "execution": "server",
                    "call_id": null,
                    "status": "completed",
                    "arguments": { "paths": ["Math"] }
                },
                {
                    "type": "tool_search_output",
                    "execution": "server",
                    "call_id": null,
                    "status": "completed",
                    "tools": [
                        {
                            "type": "namespace",
                            "name": "Math",
                            "description": "Tools for Math",
                            "tools": [
                                {
                                    "type": "function",
                                    "name": "add",
                                    "description": "Add numbers.",
                                    "defer_loading": true,
                                    "parameters": {
                                        "type": "object",
                                        "properties": {
                                            "a": { "type": "number" },
                                            "b": { "type": "number" }
                                        },
                                        "required": ["a", "b"],
                                        "additionalProperties": false
                                    }
                                }
                            ]
                        }
                    ]
                },
                {
                    "type": "function_call",
                    "id": "fc_123",
                    "call_id": "call_123",
                    "name": "add",
                    "namespace": "Math",
                    "arguments": "{\"a\":7,\"b\":3}",
                    "status": "completed"
                }
            ],
            "usage": {
                "input_tokens": 10,
                "output_tokens": 5,
                "total_tokens": 15
            }
        }"#;

    let response: ResponseResource = serde_json::from_str(json).unwrap();
    let mut expected: Value = serde_json::from_str(json).unwrap();
    expected["output"][0]
        .as_object_mut()
        .unwrap()
        .remove("call_id");
    expected["output"][1]
        .as_object_mut()
        .unwrap()
        .remove("call_id");
    expected["tools"] = json!([]);
    assert_eq!(serde_json::to_value(response).unwrap(), expected);
}