gproxy-transform 3.0.11

GPROXY v3 pairwise protocol transforms
Documentation
use bytes::Bytes;
use gproxy_protocol::{ContentGenerationKind as Kind, Operation};
use serde_json::{Value, json};

use super::super::content;
use super::super::support::data_frames;
use crate::ResponseStream;

#[test]
fn chat_to_responses_closes_tools_and_preserves_trailing_usage() {
    let mut stream = ResponseStream::new(
        content(Operation::StreamGenerateContent, Kind::OpenAiResponses),
        content(Operation::StreamGenerateContent, Kind::OpenAiChat),
    )
    .unwrap();
    let deltas = [
        json!({"role":"assistant","content":""}),
        json!({"reasoning_content":"Run the test.","content":""}),
        json!({"tool_calls":[
            {"index":0,"id":"shell","type":"function","function":{"name":"exec_command","arguments":"{\"cmd\":"}},
            {"index":1,"id":"patch","type":"custom","custom":{"name":"apply_patch","input":"*** Begin Patch\n"}}
        ]}),
        json!({"tool_calls":[
            {"index":1,"custom":{"input":"*** End Patch"}},
            {"index":0,"function":{"arguments":"\"python3 test_calc.py\"}"}}
        ]}),
        json!({}),
    ];
    let mut events = Vec::new();
    for (index, delta) in deltas.into_iter().enumerate() {
        let chunk = json!({"id":"chat_tools","object":"chat.completion.chunk","created":1,
            "model":"test-model","choices":[{"index":0,"delta":delta,
                "finish_reason":(index == 4).then_some("tool_calls")}]});
        let output = stream
            .push(Bytes::from(format!("data: {chunk}\n\n")))
            .unwrap();
        events.extend(data_frames(&output.concat()));
    }
    assert!(
        events
            .iter()
            .all(|event| event["type"] != "response.completed")
    );
    let usage = json!({"id":"chat_tools","object":"chat.completion.chunk","created":1,
        "model":"test-model","choices":[],"usage":{"prompt_tokens":12,"completion_tokens":7,
            "total_tokens":19,"completion_tokens_details":{"reasoning_tokens":3}}});
    let output = stream
        .push(Bytes::from(format!("data: {usage}\n\ndata: [DONE]\n\n")))
        .unwrap();
    events.extend(data_frames(&output.concat()));
    assert!(stream.finish().unwrap().is_empty());
    assert_eq!(events[0]["type"], "response.created");
    let mut open = std::collections::BTreeSet::new();
    let mut completed = Vec::new();
    for (sequence, event) in events.iter().enumerate() {
        assert_eq!(event["sequence_number"], sequence);
        match event["type"].as_str().unwrap() {
            "response.output_item.added" => {
                assert!(open.insert(event["item"]["id"].as_str().unwrap().to_owned()));
            }
            "response.output_item.done" => {
                assert!(open.remove(event["item"]["id"].as_str().unwrap()));
                completed.push(event["item"].clone());
            }
            kind if kind.ends_with(".delta") => {
                assert!(open.contains(event["item_id"].as_str().unwrap()), "{event}");
            }
            _ => {}
        }
    }
    assert!(open.is_empty());
    assert_eq!(completed.len(), 3);
    assert_eq!(completed[0]["type"], "reasoning");
    assert_eq!(completed[0]["content"][0]["text"], "Run the test.");
    assert_eq!(
        completed[1]["arguments"],
        "{\"cmd\":\"python3 test_calc.py\"}"
    );
    assert_eq!(completed[2]["input"], "*** Begin Patch\n*** End Patch");
    let terminal = events.last().unwrap();
    assert_eq!(terminal["type"], "response.completed");
    assert_eq!(terminal["response"]["output"], Value::Array(completed));
    assert_eq!(terminal["response"]["usage"]["input_tokens"], 12);
    assert_eq!(terminal["response"]["usage"]["output_tokens"], 7);
    assert_eq!(
        terminal["response"]["usage"]["output_tokens_details"]["reasoning_tokens"],
        3
    );
}