link-assistant-router 1.4.4

Link.Assistant.Router — Claude MAX OAuth proxy and token gateway for Anthropic APIs
Documentation
use super::*;

#[test]
fn codex_response_stream_converts_to_chat_chunks() {
    let mut translator = ResponsesChatStreamTranslator::new("gpt-5.6-sol");
    let first = translator.push(
        br#"event: response.created
data: {"type":"response.created","response":{"id":"resp_1","created_at":1786448400,"model":"gpt-5.6-sol","status":"in_progress"}}

event: response.output_text.delta
data: {"type":"response.output_text.delta","item_id":"msg_1","output_index":0,"content_index":0,"delta":"1"}

"#,
    );
    let second = translator.push(
        br#"event: response.output_text.delta
data: {"type":"response.output_text.delta","item_id":"msg_1","output_index":0,"content_index":0,"delta":"3"}

event: response.completed
data: {"type":"response.completed","response":{"id":"resp_1","created_at":1786448400,"model":"gpt-5.6-sol","status":"completed","output":[]}}

"#,
    );
    let joined = first.into_iter().chain(second).collect::<String>();

    assert!(joined.contains("\"object\":\"chat.completion.chunk\""));
    assert!(joined.contains("\"role\":\"assistant\""));
    assert!(joined.contains("\"content\":\"1\""));
    assert!(joined.contains("\"content\":\"3\""));
    assert!(joined.contains("\"finish_reason\":\"stop\""));
    assert!(joined.ends_with("data: [DONE]\n\n"));
    assert!(!joined.contains("response.output_text.delta"));
}

#[test]
fn codex_chat_stream_emits_requested_usage_chunk() {
    let mut translator = ResponsesChatStreamTranslator::new("gpt-5.6-sol").with_include_usage(true);
    let frames = translator.push(
        br#"event: response.completed
data: {"type":"response.completed","response":{"id":"resp_1","model":"gpt-5.6-sol","usage":{"input_tokens":9,"output_tokens":2,"total_tokens":11}}}

"#,
    );
    let usage = frames
        .iter()
        .filter_map(|frame| frame.strip_prefix("data: "))
        .filter_map(|data| serde_json::from_str::<Value>(data.trim()).ok())
        .find(|chunk| chunk["choices"].as_array().is_some_and(Vec::is_empty))
        .expect("usage chunk");
    assert_eq!(usage["usage"]["prompt_tokens"], 9);
    assert_eq!(usage["usage"]["completion_tokens"], 2);
    assert_eq!(usage["usage"]["total_tokens"], 11);
}

#[test]
fn codex_chat_stream_holds_a_cross_chunk_stop_sequence() {
    let mut translator =
        ResponsesChatStreamTranslator::new("gpt-5.6-sol").with_stop_sequences(vec!["<END>".into()]);
    let first = translator
        .push(b"data:{\"type\":\"response.output_text.delta\",\"delta\":\"visible<E\"}\n\n");
    let second = translator
        .push(b"data:{\"type\":\"response.output_text.delta\",\"delta\":\"ND>hidden\"}\n\n");
    let joined = first.into_iter().chain(second).collect::<String>();

    assert!(joined.contains("visible"));
    assert!(!joined.contains("<END>"));
    assert!(!joined.contains("hidden"));
    assert!(joined.contains("\"finish_reason\":\"stop\""));
    assert!(joined.ends_with("data: [DONE]\n\n"));
}

#[test]
fn codex_chat_stream_translates_incomplete_as_length() {
    let mut translator = ResponsesChatStreamTranslator::new("gpt-5.6-sol");
    let output = translator
        .push(b"data: {\"type\":\"response.incomplete\",\"response\":{}}\n\n")
        .join("");

    assert!(output.contains("\"finish_reason\":\"length\""), "{output}");
    assert!(output.ends_with("data: [DONE]\n\n"), "{output}");
}

#[test]
fn codex_chat_stream_failed_after_deltas_emits_only_an_error_terminal() {
    let mut translator = ResponsesChatStreamTranslator::new("gpt-5.6-sol");
    let mut output = translator
        .push(b"data: {\"type\":\"response.output_text.delta\",\"delta\":\"partial\"}\n\ndata: {\"type\":\"response.output_item.added\",\"output_index\":0,\"item\":{\"type\":\"function_call\",\"call_id\":\"call_1\",\"name\":\"lookup\"}}\n\ndata: {\"type\":\"response.function_call_arguments.delta\",\"output_index\":0,\"delta\":\"{}\"}\n\n")
        .join("");
    output.push_str(&translator.push(b"data: {\"type\":\"response.failed\",\"response\":{\"id\":\"resp_1\",\"error\":{\"message\":\"boom\",\"type\":\"server_error\",\"code\":\"upstream_failed\",\"parameter\":\"input\",\"private_account\":\"secret\"}}}\n\ndata: [DONE]\n\n").join(""));

    assert!(output.contains("partial"), "{output}");
    assert!(output.contains("\"message\":\"boom\""), "{output}");
    assert!(output.contains("\"type\":\"server_error\""), "{output}");
    assert!(output.contains("\"code\":\"upstream_failed\""), "{output}");
    assert!(output.contains("\"param\":\"input\""), "{output}");
    assert!(!output.contains("private_account"), "{output}");
    assert!(!output.contains("\"finish_reason\":\""), "{output}");
    assert!(!output.contains("data: [DONE]"), "{output}");
}

#[test]
fn codex_chat_stream_standalone_error_after_deltas_is_terminal() {
    let mut translator = ResponsesChatStreamTranslator::new("gpt-5.6-sol");
    let mut output = translator
        .push(b"data: {\"type\":\"response.output_text.delta\",\"delta\":\"partial\"}\n\ndata: {\"type\":\"response.output_item.added\",\"output_index\":0,\"item\":{\"type\":\"function_call\",\"call_id\":\"call_1\",\"name\":\"lookup\"}}\n\ndata: {\"type\":\"response.function_call_arguments.delta\",\"output_index\":0,\"delta\":\"{}\"}\n\n")
        .join("");
    output.push_str(&translator.push(b"data: {\"type\":\"error\",\"message\":\"standalone boom\",\"code\":\"server_error\",\"param\":\"input\",\"private_account\":\"secret\"}\n\ndata: {\"type\":\"response.completed\",\"response\":{}}\n\ndata: [DONE]\n\n").join(""));

    assert!(output.contains("partial"), "{output}");
    assert!(
        output.contains("\"message\":\"standalone boom\""),
        "{output}"
    );
    assert!(output.contains("\"type\":\"api_error\""), "{output}");
    assert!(output.contains("\"code\":\"server_error\""), "{output}");
    assert!(output.contains("\"param\":\"input\""), "{output}");
    assert!(!output.contains("private_account"), "{output}");
    assert!(!output.contains("\"finish_reason\":\""), "{output}");
    assert!(!output.contains("data: [DONE]"), "{output}");
}

#[test]
fn codex_chat_stream_preserves_refusal_deltas_and_content_order() {
    let mut translator = ResponsesChatStreamTranslator::new("gpt-5.6-sol");
    let output = translator.push(b"data: {\"type\":\"response.output_text.delta\",\"output_index\":0,\"content_index\":0,\"delta\":\"before \"}\n\ndata: {\"type\":\"response.refusal.delta\",\"output_index\":0,\"content_index\":1,\"delta\":\"cannot comply\"}\n\ndata: {\"type\":\"response.refusal.done\",\"output_index\":0,\"content_index\":1,\"refusal\":\"cannot comply\"}\n\ndata: {\"type\":\"response.output_text.delta\",\"output_index\":1,\"content_index\":0,\"delta\":\"after\"}\n\ndata: {\"type\":\"response.completed\",\"response\":{}}\n\n").join("");

    let before = output.find("\"content\":\"before \"").unwrap();
    let refusal = output.find("\"refusal\":\"cannot comply\"").unwrap();
    let after = output.find("\"content\":\"after\"").unwrap();
    assert!(before < refusal && refusal < after, "{output}");
    assert_eq!(output.matches("\"refusal\":\"cannot comply\"").count(), 1);
    assert!(output.contains("\"finish_reason\":\"stop\""), "{output}");
}