use super::*;
#[test]
fn responses_api_translation() {
let req = OpenAIResponseRequest {
model: "gpt-4o".into(),
input: Value::String("write a haiku".into()),
instructions: Some("be poetic".into()),
max_output_tokens: Some(128),
temperature: Some(0.9),
stream: None,
tools: None,
tool_choice: None,
reasoning: None,
};
let body = response_to_anthropic(&req);
assert_eq!(body["model"], "gpt-4o");
assert_eq!(body["system"], "be poetic");
assert_eq!(body["max_tokens"], 128);
assert_eq!(body["messages"][0]["content"], "write a haiku");
let resp = json!({
"id": "msg_1",
"model": "claude-sonnet-4-5-20250929",
"content": [{"type":"text","text":"line1"}]
});
let out = anthropic_to_response(&resp, "gpt-4o");
assert_eq!(out["object"], "response");
assert_eq!(out["model"], "claude-sonnet-4-5-20250929");
assert_eq!(out["output"][0]["content"][0]["text"], "line1");
}
#[test]
fn anthropic_server_tool_results_keep_their_kind_payload_and_usage() {
let response = anthropic_to_response(
&json!({
"id": "msg_tools",
"model": "claude-sonnet-4-5",
"content": [
{"type":"server_tool_use","id":"search_1","name":"web_search","input":{"query":"Rust"}},
{"type":"web_search_tool_result","tool_use_id":"search_1","content":[{"type":"web_search_result","url":"https://www.rust-lang.org"}]},
{"type":"server_tool_use","id":"fetch_1","name":"web_fetch","input":{"url":"https://www.rust-lang.org"}},
{"type":"web_fetch_tool_result","tool_use_id":"fetch_1","content":{"type":"web_fetch_result","url":"https://www.rust-lang.org"}}
],
"usage": {
"input_tokens": 3,
"output_tokens": 2,
"server_tool_use": {"web_search_requests":1,"web_fetch_requests":1}
}
}),
"claude-sonnet-4-5",
);
assert_eq!(response["output"][0]["type"], "web_search_call");
assert_eq!(response["output"][0]["status"], "completed");
assert_eq!(
response["output"][0]["result"][0]["type"],
"web_search_result"
);
assert_eq!(response["output"][1]["type"], "web_fetch_call");
assert_eq!(response["output"][1]["status"], "completed");
assert_eq!(response["output"][1]["result"]["type"], "web_fetch_result");
assert_eq!(
response["usage"]["server_tool_use"]["web_search_requests"],
1
);
assert_eq!(
response["usage"]["server_tool_use"]["web_fetch_requests"],
1
);
}
#[test]
fn buffered_chat_stop_is_enforced_locally() {
let mut response = json!({
"choices": [{
"message": {"role": "assistant", "content": "visible<END>hidden"},
"finish_reason": "length"
}]
});
enforce_chat_stop(&mut response, &["<END>".into()]);
assert_eq!(response["choices"][0]["message"]["content"], "visible");
assert_eq!(response["choices"][0]["finish_reason"], "stop");
}
#[test]
fn responses_structured_input_translates_to_anthropic() {
let req = OpenAIResponseRequest {
model: "gpt-5".into(),
input: json!([
{
"role": "developer",
"content": [{"type": "input_text", "text": "be terse"}]
},
{
"role": "system",
"content": [{"type": "input_text", "text": "answer plainly"}]
},
{
"role": "user",
"content": [
{"type": "input_text", "text": "describe this"},
{"type": "input_image", "image_url": "https://example.com/image.png"}
]
},
{
"role": "assistant",
"content": [{"type": "output_text", "text": "a prior answer"}]
}
]),
instructions: Some("follow policy".into()),
max_output_tokens: None,
temperature: None,
stream: None,
tools: None,
tool_choice: None,
reasoning: None,
};
let body = response_to_anthropic(&req);
assert_eq!(
body["system"],
"follow policy\n\nbe terse\n\nanswer plainly"
);
assert_eq!(body["messages"].as_array().map(Vec::len), Some(2));
assert_eq!(body["messages"][0]["role"], "user");
assert_eq!(
body["messages"][0]["content"],
json!([
{"type": "text", "text": "describe this"},
{
"type": "image",
"source": {"type": "url", "url": "https://example.com/image.png"}
}
])
);
assert_eq!(body["messages"][1]["role"], "assistant");
assert_eq!(
body["messages"][1]["content"],
json!([{"type": "text", "text": "a prior answer"}])
);
}
#[test]
fn chat_completion_projects_to_responses_input() {
let body = json!({
"model": "gpt-5-codex",
"messages": [
{"role": "system", "content": "be terse"},
{"role": "user", "content": "hello"},
{"role": "assistant", "content": "hi"}
],
"max_tokens": 256,
});
let out = chat_completion_to_responses(&body);
assert_eq!(out["model"], "gpt-5-codex");
assert_eq!(out["instructions"], "be terse");
assert_eq!(out["max_output_tokens"], 256);
assert_eq!(out["input"][0]["role"], "user");
assert_eq!(out["input"][0]["content"][0]["type"], "input_text");
assert_eq!(out["input"][1]["content"][0]["type"], "output_text");
}
#[test]
fn chat_completion_projects_tools_and_results_to_responses() {
let body = json!({
"model": "gpt-5.6-sol",
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}}
},
"strict": true
}
}],
"tool_choice": {"type": "function", "function": {"name": "get_weather"}},
"messages": [
{"role": "user", "content": "weather in Moscow?"},
{
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_weather_1",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Moscow\"}"
}
}]
},
{
"role": "tool",
"tool_call_id": "call_weather_1",
"content": "cold"
}
]
});
let out = chat_completion_to_responses(&body);
assert_eq!(
out["tools"][0],
json!({
"type": "function",
"name": "get_weather",
"description": "Get the weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}}
},
"strict": true
})
);
assert_eq!(
out["tool_choice"],
json!({"type": "function", "name": "get_weather"})
);
assert_eq!(
out["input"][1],
json!({
"type": "function_call",
"call_id": "call_weather_1",
"name": "get_weather",
"arguments": "{\"city\":\"Moscow\"}"
})
);
assert_eq!(
out["input"][2],
json!({
"type": "function_call_output",
"call_id": "call_weather_1",
"output": "cold"
})
);
}
#[test]
fn chat_completion_preserves_stream_request_for_codex() {
let body = json!({
"model": "gpt-5.6-sol",
"messages": [{"role": "user", "content": "hello"}],
"stream": true,
});
let out = chat_completion_to_responses(&body);
assert_eq!(out["stream"], true);
}
#[test]
fn codex_response_converts_to_chat_completion() {
let response = json!({
"id": "resp_1",
"object": "response",
"created_at": 1_786_448_400,
"model": "gpt-5.6-sol",
"status": "completed",
"output": [{
"id": "msg_1",
"type": "message",
"role": "assistant",
"content": [{"type": "output_text", "text": "13"}]
}],
"usage": {"input_tokens": 9, "output_tokens": 2, "total_tokens": 11}
});
let out = response_to_chat_completion(&response, "gpt-5.6-sol");
assert_eq!(out["object"], "chat.completion");
assert_eq!(out["choices"][0]["message"]["role"], "assistant");
assert_eq!(out["choices"][0]["message"]["content"], "13");
assert_eq!(out["choices"][0]["finish_reason"], "stop");
assert_eq!(out["usage"]["prompt_tokens"], 9);
assert_eq!(out["usage"]["completion_tokens"], 2);
assert_eq!(out["usage"]["total_tokens"], 11);
assert!(out.get("output").is_none());
assert!(out.get("instructions").is_none());
}
#[test]
fn codex_function_calls_convert_to_chat_tool_calls() {
let response = json!({
"id": "resp_tools",
"model": "gpt-5.6-sol",
"status": "completed",
"output": [{
"id": "fc_1",
"call_id": "call_1",
"type": "function_call",
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}"
}]
});
let out = response_to_chat_completion(&response, "gpt-5.6-sol");
assert!(out["choices"][0]["message"]["content"].is_null());
assert_eq!(out["choices"][0]["finish_reason"], "tool_calls");
assert_eq!(
out["choices"][0]["message"]["tool_calls"][0]["id"],
"call_1"
);
assert_eq!(
out["choices"][0]["message"]["tool_calls"][0]["function"]["name"],
"get_weather"
);
}
#[test]
fn normalizes_string_input_and_preserves_typed_input() {
let typed = json!([{
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": "скажи ок"}],
}]);
assert_eq!(
normalize_input_items(&Value::String("скажи ок".into())),
typed
);
assert_eq!(normalize_input_items(&typed), typed);
}
#[test]
fn drops_temperature_for_claude_5_models() {
let req = OpenAIResponseRequest {
model: "claude-opus-5".into(),
input: Value::String("hello".into()),
instructions: None,
max_output_tokens: None,
temperature: Some(0.7),
stream: None,
tools: None,
tool_choice: None,
reasoning: None,
};
let body = response_to_anthropic(&req);
assert!(body.get("temperature").is_none());
}
#[test]
fn responses_reasoning_is_preserved_as_claude_thinking() {
let req: OpenAIResponseRequest = serde_json::from_value(json!({
"model":"claude-opus-5",
"input":"hello",
"max_output_tokens":40_000,
"reasoning":{"effort":"xhigh"}
}))
.unwrap();
let body = response_to_anthropic(&req);
assert_eq!(body["thinking"]["type"], "adaptive");
assert_eq!(body["output_config"]["effort"], "max");
assert_eq!(body["max_tokens"], 40_000);
assert!(body.get("reasoning").is_none());
}
#[test]
fn prior_function_items_survive_responses_to_anthropic_translation() {
let req: OpenAIResponseRequest = serde_json::from_value(json!({
"model": "gpt-5.6-sol",
"input": [
{"type": "function_call", "call_id": "call_1", "name": "lookup", "arguments": "{\"q\":1}"},
{"type": "function_call_output", "call_id": "call_1", "output": "found"}
]
}))
.unwrap();
let body = response_to_anthropic(&req);
assert_eq!(body["messages"][0]["content"][0]["type"], "tool_use");
assert_eq!(body["messages"][0]["content"][0]["id"], "call_1");
assert_eq!(body["messages"][1]["content"][0]["type"], "tool_result");
assert_eq!(body["messages"][1]["content"][0]["tool_use_id"], "call_1");
}