gproxy-protocol 4.1.14

GPROXY v4 connection modelling, operation declarations, vendor wire types and pairwise conversion
Documentation
use gproxy_protocol::{
    Dialect,
    transform::{
        generate::stream::chat::{ChatStreamCollector, ChatStreamLimits, synthesize_chat_stream},
        identity::{IdentityFlow, TargetIdPolicy},
    },
    wire::openai::chat as c,
};
use serde_json::json;

#[test]
fn deepseek_and_openrouter_reasoning_roundtrip_and_collect() {
    let mut collector = ChatStreamCollector::new(
        IdentityFlow::new(gproxy_protocol::transform::identity::IdNamespace::with_bytes([91; 16])),
        TargetIdPolicy::new(Dialect::OpenAiChat),
    );
    for (text, detail) in [("think ", "first "), ("more", "second")] {
        let value = json!({"id":"chat1","object":"chat.completion.chunk","created":1,"model":"m","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":text,"reasoning":text,"reasoning_details":[{"type":"reasoning.text","index":0,"format":"unknown","text":detail}]}}]});
        let chunk: c::stream::ChatCompletionChunk = serde_json::from_value(value.clone()).unwrap();
        assert_eq!(serde_json::to_value(&chunk).unwrap(), value);
        collector.push(chunk).unwrap();
    }
    collector.push(serde_json::from_value(json!({"id":"chat1","object":"chat.completion.chunk","created":1,"model":"m","choices":[{"index":0,"delta":{"content":"answer"},"finish_reason":"stop"}]})).unwrap()).unwrap();
    let usage = json!({"prompt_tokens":10,"completion_tokens":2,"total_tokens":12,
        "prompt_tokens_details":{"text_tokens":7,"image_tokens":3},
        "completion_tokens_details":{"text_tokens":2}});
    collector.push(serde_json::from_value(json!({"id":"chat1","object":"chat.completion.chunk","created":1,"model":"m","choices":[],"usage":usage})).unwrap()).unwrap();
    collector.push_done().unwrap();
    let response = collector.finish().unwrap().value;
    assert_eq!(serde_json::to_value(&response.usage).unwrap(), usage);
    let message = &response.choices[0].message;
    assert_eq!(message.reasoning_content, Some(Some("think more".into())));
    assert_eq!(message.reasoning, Some(Some("think more".into())));
    assert_eq!(
        message
            .reasoning_details
            .as_ref()
            .unwrap()
            .as_ref()
            .unwrap()[0]
            .text,
        Some(Some("first second".into()))
    );
    assert_eq!(
        c::visible_reasoning(
            &message.reasoning_content,
            &message.reasoning,
            &message.reasoning_details
        ),
        Some("think more".into())
    );
    let chunks = synthesize_chat_stream(response, ChatStreamLimits::default())
        .unwrap()
        .value;
    let usage_chunk = chunks
        .iter()
        .find_map(|chunk| chunk.usage.as_ref())
        .unwrap();
    assert_eq!(serde_json::to_value(usage_chunk).unwrap(), usage);
    assert_eq!(
        chunks[0].choices[0].delta.reasoning_content,
        Some(Some("think more".into()))
    );
}
#[test]
fn encrypted_details_are_not_visible_text_and_assistant_replay_keeps_them() {
    let value = json!({"role":"assistant","content":null,"reasoning_content":"","reasoning_details":[{"type":"reasoning.encrypted","data":"opaque","id":"r1","format":"openai-responses-v1"}]});
    let message: c::ChatMessage = serde_json::from_value(value.clone()).unwrap();
    assert_eq!(serde_json::to_value(&message).unwrap(), value);
    let c::ChatMessage::Assistant(message) = message else {
        panic!("assistant")
    };
    assert_eq!(
        c::visible_reasoning(
            &message.reasoning_content,
            &message.reasoning,
            &message.reasoning_details
        ),
        Some("".into())
    );
}

#[test]
fn compatible_reasoning_only_response_can_omit_openai_nullable_extras() {
    let body:c::GenerateContentResponseBody=serde_json::from_value(json!({"id":"r","object":"chat.completion","created":1,"model":"deepseek-reasoner","choices":[{"index":0,"finish_reason":"stop","message":{"role":"assistant","reasoning_content":"analysis"}}]})).unwrap();
    assert_eq!(
        body.choices[0].message.reasoning_content,
        Some(Some("analysis".into()))
    );
    assert!(body.choices[0].message.content.is_none());
    assert!(body.choices[0].message.refusal.is_none());
    assert!(body.choices[0].logprobs.is_none());
}