use super::*;
#[test]
fn translates_anthropic_text_stream_to_openai_chat_chunks() {
let mut translator = OpenAIStreamTranslator::new(OpenAIStreamShape::ChatCompletion, "gpt-4o");
let frames = translator.push(
br#"event: message_start
data: {"type":"message_start","message":{"id":"msg_1","model":"claude-sonnet-4-5-20250929"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"hello"}}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"}}
event: message_stop
data: {"type":"message_stop"}
"#,
);
let joined = frames.join("");
assert!(joined.contains("\"object\":\"chat.completion.chunk\""));
assert!(joined.contains("\"content\":\"hello\""));
assert!(joined.contains("\"finish_reason\":\"stop\""));
assert!(joined.contains("\"model\":\"claude-sonnet-4-5-20250929\""));
assert!(joined.contains("data: [DONE]"));
}
#[test]
fn chat_stream_emits_usage_only_when_requested() {
let mut translator = OpenAIStreamTranslator::new(OpenAIStreamShape::ChatCompletion, "gpt-4o")
.with_include_usage(true);
let frames = translator.push(
br#"event: message_start
data: {"type":"message_start","message":{"usage":{"input_tokens":7,"output_tokens":0}}}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":3}}
event: message_stop
data: {"type":"message_stop"}
"#,
);
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"], 7);
assert_eq!(usage["usage"]["completion_tokens"], 3);
assert_eq!(usage["usage"]["total_tokens"], 10);
}
#[test]
fn translates_anthropic_text_stream_to_openai_response_events() {
let mut translator = OpenAIStreamTranslator::new(OpenAIStreamShape::Response, "gpt-4o");
let frames = translator.push(
br#"event: message_start
data: {"type":"message_start","message":{"id":"msg_1","model":"claude-sonnet-4-5-20250929"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"hello"}}
event: message_stop
data: {"type":"message_stop"}
"#,
);
let joined = frames.join("");
assert!(joined.contains("\"type\":\"response.created\""));
assert!(joined.contains("\"type\":\"response.output_text.delta\""));
assert!(joined.contains("\"type\":\"response.completed\""));
assert!(joined.contains("\"model\":\"claude-sonnet-4-5-20250929\""));
assert!(joined.contains("data: [DONE]"));
}
#[test]
fn translates_basic_chat_completion() {
let req = OpenAIChatCompletionRequest {
model: "gpt-4o".into(),
messages: vec![
ChatMessage {
role: "system".into(),
content: Value::String("You are helpful.".into()),
name: None,
tool_call_id: None,
tool_calls: None,
},
ChatMessage {
role: "user".into(),
content: Value::String("Hello".into()),
name: None,
tool_call_id: None,
tool_calls: None,
},
],
max_tokens: Some(100),
max_completion_tokens: None,
temperature: Some(0.5),
top_p: None,
stream: None,
stop: None,
tools: None,
tool_choice: None,
reasoning_effort: None,
reasoning: None,
};
let body = chat_completion_to_anthropic(&req);
assert_eq!(body["model"], "claude-sonnet-4-5-20250929");
assert_eq!(body["max_tokens"], 100);
assert_eq!(body["temperature"], 0.5);
assert_eq!(body["system"], "You are helpful.");
let msgs = body["messages"].as_array().unwrap();
assert_eq!(msgs.len(), 1);
assert_eq!(msgs[0]["role"], "user");
assert_eq!(msgs[0]["content"], "Hello");
}
#[test]
fn preserves_claude_native_model_id() {
let req = OpenAIChatCompletionRequest {
model: "claude-opus-4-7".into(),
messages: vec![ChatMessage {
role: "user".into(),
content: Value::String("hi".into()),
name: None,
tool_call_id: None,
tool_calls: None,
}],
max_tokens: None,
max_completion_tokens: None,
temperature: None,
top_p: None,
stream: None,
stop: None,
tools: None,
tool_choice: None,
reasoning_effort: None,
reasoning: None,
};
let body = chat_completion_to_anthropic(&req);
assert_eq!(body["model"], "claude-opus-4-7");
assert_eq!(body["max_tokens"], 4096);
}
#[test]
fn drops_temperature_for_claude_5_models() {
let req = OpenAIChatCompletionRequest {
model: "claude-sonnet-5".into(),
messages: vec![ChatMessage {
role: "user".into(),
content: Value::String("hi".into()),
name: None,
tool_call_id: None,
tool_calls: None,
}],
max_tokens: None,
max_completion_tokens: None,
temperature: Some(0.7),
top_p: None,
stream: None,
stop: None,
tools: None,
tool_choice: None,
reasoning_effort: None,
reasoning: None,
};
let body = chat_completion_to_anthropic(&req);
assert!(body.get("temperature").is_none());
}
#[test]
fn caller_reasoning_effort_uses_adaptive_thinking_and_preserves_explicit_limit() {
let req: OpenAIChatCompletionRequest = serde_json::from_value(json!({
"model":"claude-opus-5",
"messages":[{"role":"user","content":"hi"}],
"max_tokens":3000,
"reasoning_effort":"low"
}))
.unwrap();
let body = chat_completion_to_anthropic(&req);
assert_eq!(body["thinking"]["type"], "adaptive");
assert_eq!(body["output_config"]["effort"], "low");
assert_eq!(body["max_tokens"], 3000);
assert!(body.get("reasoning").is_none());
}
#[test]
fn omitted_limit_reserves_output_headroom_for_adaptive_thinking() {
let req: OpenAIChatCompletionRequest = serde_json::from_value(json!({
"model":"claude-opus-5",
"messages":[{"role":"user","content":"hi"}],
"reasoning_effort":"high"
}))
.unwrap();
let body = chat_completion_to_anthropic(&req);
assert_eq!(body["thinking"]["type"], "adaptive");
assert_eq!(body["output_config"]["effort"], "high");
assert_eq!(body["max_tokens"], 24_576);
}
#[test]
fn legacy_thinking_budget_keeps_visible_output_headroom() {
let req: OpenAIChatCompletionRequest = serde_json::from_value(json!({
"model":"claude-sonnet-4-5",
"messages":[{"role":"user","content":"hi"}],
"reasoning_effort":"high"
}))
.unwrap();
let body = chat_completion_to_anthropic(&req);
assert_eq!(body["thinking"]["type"], "enabled");
assert_eq!(body["thinking"]["budget_tokens"], 16_384);
assert_eq!(body["max_tokens"], 24_576);
}
#[test]
fn model_resolution_rejects_unknown_ids() {
assert_eq!(resolve_model("totally-made-up-model-xyz"), None);
}
#[test]
fn model_resolution_keeps_intentional_aliases_explicit() {
assert_eq!(
resolve_model("gpt-4o").as_deref(),
Some("claude-sonnet-4-5-20250929")
);
assert_eq!(resolve_model("gpt-5").as_deref(), Some("claude-opus-4-7"));
}
#[test]
fn translates_multipart_user_content() {
let req = OpenAIChatCompletionRequest {
model: "gpt-4o".into(),
messages: vec![ChatMessage {
role: "user".into(),
content: json!([
{"type": "text", "text": "describe"},
{"type": "image_url", "image_url": {"url": "https://example.com/x.png"}}
]),
name: None,
tool_call_id: None,
tool_calls: None,
}],
max_tokens: Some(50),
max_completion_tokens: None,
temperature: None,
top_p: None,
stream: None,
stop: None,
tools: None,
tool_choice: None,
reasoning_effort: None,
reasoning: None,
};
let body = chat_completion_to_anthropic(&req);
let parts = body["messages"][0]["content"].as_array().unwrap();
assert_eq!(parts[0]["type"], "text");
assert_eq!(parts[0]["text"], "describe");
assert_eq!(parts[1]["type"], "image");
assert_eq!(parts[1]["source"]["url"], "https://example.com/x.png");
}
#[test]
fn chat_tool_loop_preserves_call_and_result_ids() {
let req: OpenAIChatCompletionRequest = serde_json::from_value(json!({
"model": "gpt-4o",
"messages": [
{"role": "user", "content": "weather?"},
{
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "toolu_test123",
"type": "function",
"function": {"name": "weather", "arguments": "{\"city\":\"Paris\"}"}
}]
},
{"role": "tool", "tool_call_id": "toolu_test123", "content": "sunny"}
]
}))
.unwrap();
let body = chat_completion_to_anthropic(&req);
assert_eq!(body["messages"][1]["content"][0]["id"], "toolu_test123");
assert_eq!(body["messages"][1]["content"][0]["input"]["city"], "Paris");
assert_eq!(
body["messages"][2]["content"][0]["tool_use_id"],
"toolu_test123"
);
}
#[test]
fn responses_flat_tools_translate_without_silent_loss() {
let tools = json!([{
"type": "function",
"name": "get_weather",
"description": "Get weather",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}}
}]);
let translated = translate_tools(&tools);
assert_eq!(translated[0]["name"], "get_weather");
assert_eq!(
translated[0]["input_schema"]["properties"]["city"]["type"],
"string"
);
}
#[test]
fn responses_web_search_maps_to_anthropic_server_tool() {
let translated = translate_tools(&json!([{"type": "web_search", "max_uses": 2}]));
assert_eq!(translated[0]["type"], "web_search_20250305");
assert_eq!(translated[0]["name"], "web_search");
assert_eq!(translated[0]["max_uses"], 2);
}