use super::bodies::{
build_chat_completion_tools_json, chat_completion_tools_json, codex_responses_body,
openai_compatible_chat_completions_body, openai_compatible_chat_completions_body_with_protocol,
openai_compatible_responses_body, openai_compatible_responses_body_with_protocol,
};
use super::catalog::{
MODEL_CATALOG_SUCCESS_BODY_MAX_BYTES, fetch_model_catalog_response_text_cancellable,
};
use super::headers::{
codex_model_catalog_headers, codex_sse_headers, model_catalog_headers, sse_json_headers,
};
use super::*;
use crate::agent::cancellation::{AgentCancellationHandle, is_run_canceled};
use crate::config::TextVerbosity;
use crate::providers::openai_stream::{
PROVIDER_STREAM_NO_SEMANTIC_PROGRESS_TIMEOUT, sleep_cancellable, stream_with_transport,
};
use crate::providers::{
ChatMessage, HttpRequest, HttpTransport, ProviderConversationItem, ProviderToolResult,
};
use crate::tools::mvp_tool_definitions_json;
use serde_json::{Value, json};
use std::collections::BTreeMap;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
struct NoopTransport;
fn compressed_tool_request() -> ProviderRequest {
ProviderRequest::from_conversation(
"model-a",
vec![ProviderConversationItem::ToolResult(ProviderToolResult {
call_id: "call_1".to_string(),
tool_name: "bash".to_string(),
success: true,
output:
"[tool_output_compression]\ntool: bash\nrule: bash.git_status\nRAW_ABSENT_SUMMARY"
.to_string(),
})],
)
}
#[test]
fn responses_body_uses_curated_provider_tool_result_output() {
let body = codex_responses_body("model-a", &compressed_tool_request());
let item = body["input"].as_array().unwrap().first().unwrap();
assert_eq!(item["type"], "function_call_output");
assert_eq!(
item["output"],
"[tool_output_compression]\ntool: bash\nrule: bash.git_status\nRAW_ABSENT_SUMMARY"
);
assert!(body.to_string().contains("[tool_output_compression]"));
assert!(!body.to_string().contains("RAW_TOOL_OUTPUT_POISON"));
}
#[test]
fn chat_body_uses_curated_provider_tool_result_output() {
let body = openai_compatible_chat_completions_body("model-a", &compressed_tool_request());
let message = body["messages"].as_array().unwrap().first().unwrap();
assert_eq!(message["role"], "tool");
assert_eq!(
message["content"],
"[tool_output_compression]\ntool: bash\nrule: bash.git_status\nRAW_ABSENT_SUMMARY"
);
assert!(body.to_string().contains("[tool_output_compression]"));
assert!(!body.to_string().contains("RAW_TOOL_OUTPUT_POISON"));
}
#[test]
fn chat_completion_tools_cache_matches_uncached_transform_and_preserves_order() {
let cached = chat_completion_tools_json(true);
let rebuilt = build_chat_completion_tools_json(true);
assert_eq!(cached, rebuilt);
assert_eq!(chat_completion_tools_json(true), cached);
let chat_names = cached
.as_array()
.unwrap()
.iter()
.map(|tool| tool["function"]["name"].as_str().unwrap())
.collect::<Vec<_>>();
let response_tools = mvp_tool_definitions_json();
let response_names = response_tools
.as_array()
.unwrap()
.iter()
.map(|tool| tool["name"].as_str().unwrap())
.collect::<Vec<_>>();
assert_eq!(chat_names, response_names);
}
#[test]
fn hash_edit_tool_schema_is_present() {
let definitions = mvp_tool_definitions_json();
let definitions = definitions.as_array().unwrap();
let hash_edit = definitions
.iter()
.find(|definition| definition["name"] == "hash_edit")
.expect("hash_edit tool definition");
let parameters = &hash_edit["parameters"];
assert_eq!(parameters["required"], json!(["input"]));
assert_eq!(parameters["additionalProperties"], false);
assert_eq!(parameters["properties"]["input"]["type"], "string");
let chat_tools = chat_completion_tools_json(true);
let chat_hash_edit = chat_tools
.as_array()
.unwrap()
.iter()
.find(|tool| tool["function"]["name"] == "hash_edit")
.expect("chat hash_edit tool definition");
assert_eq!(chat_hash_edit["function"]["parameters"], *parameters);
}
#[test]
fn provider_headers_use_package_user_agent_for_openai_compatible_requests() {
let expected = format!("magi-code/{}", env!("CARGO_PKG_VERSION"));
assert_eq!(sse_json_headers(None)["user-agent"], expected);
assert_eq!(model_catalog_headers(None)["user-agent"], expected);
}
#[test]
fn codex_headers_use_codex_cli_rs_user_agent() {
let expected = "codex_cli_rs/0.144.0";
assert_eq!(codex_sse_headers("token", "acct")["user-agent"], expected);
assert_eq!(
codex_model_catalog_headers("token", "acct")["user-agent"],
expected
);
}
#[derive(Debug, Clone)]
struct ChunkTransport {
chunks: Vec<&'static str>,
delivered: std::sync::Arc<std::sync::atomic::AtomicUsize>,
}
impl HttpTransport for ChunkTransport {
fn stream_json(
&self,
request: HttpRequest,
on_chunk: &mut dyn FnMut(&str) -> anyhow::Result<()>,
) -> anyhow::Result<()> {
self.stream_json_cancellable(request, &AgentCancellation::default(), on_chunk)
}
fn stream_json_cancellable(
&self,
_request: HttpRequest,
cancellation: &AgentCancellation,
on_chunk: &mut dyn FnMut(&str) -> anyhow::Result<()>,
) -> anyhow::Result<()> {
for chunk in &self.chunks {
cancellation.check()?;
self.delivered
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
on_chunk(chunk)?;
}
Ok(())
}
fn stream_json_cancellable_with_semantic_deadline(
&self,
request: HttpRequest,
cancellation: &AgentCancellation,
_semantic_deadline: &std::sync::atomic::AtomicU64,
on_chunk: &mut dyn FnMut(&str) -> anyhow::Result<()>,
) -> anyhow::Result<()> {
self.stream_json_cancellable(request, cancellation, on_chunk)
}
}
#[test]
fn stream_with_transport_returns_stream_parser_errors() {
let delivered = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let transport = ChunkTransport {
chunks: vec!["data: {bad}\n\n", "data: {\"delta\":\"late\"}\n\n"],
delivered: std::sync::Arc::clone(&delivered),
};
let request = HttpRequest {
method: "POST".to_string(),
url: "https://provider.test/v1/chat/completions".to_string(),
headers: BTreeMap::new(),
body: Value::Null,
};
let error = stream_with_transport(
&transport,
request,
&AgentCancellation::default(),
PROVIDER_STREAM_NO_SEMANTIC_PROGRESS_TIMEOUT,
&mut |_| Ok(()),
)
.unwrap_err()
.to_string();
assert!(
error.contains("malformed provider SSE data JSON"),
"{error}"
);
assert_eq!(delivered.load(std::sync::atomic::Ordering::SeqCst), 1);
}
#[test]
fn retry_backoff_sleep_observes_cancellation_promptly() {
let (cancellation, handle): (AgentCancellation, AgentCancellationHandle) =
AgentCancellation::default().child_token();
let started = Instant::now();
let canceler = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(50));
handle.cancel();
});
let error = sleep_cancellable(Duration::from_secs(5), &cancellation).unwrap_err();
canceler.join().unwrap();
assert!(is_run_canceled(&error));
assert!(started.elapsed() < Duration::from_secs(1));
}
#[test]
fn custom_no_auth_requests_omit_authorization_headers() {
let provider = OpenAiCompatibleProvider::custom(
"local-ai",
"model-a",
None,
"http://localhost:8080/v1",
false,
NoopTransport,
);
let request = ProviderRequest::new("model-a", vec![ChatMessage::user("hello")]);
let http = provider.build_http_request(&request);
assert_eq!(http.url, "http://localhost:8080/v1/chat/completions");
assert!(!http.headers.contains_key("authorization"));
assert_eq!(
provider.build_model_catalog_request().url,
"http://localhost:8080/v1/models"
);
assert!(
!provider
.build_model_catalog_request()
.headers
.contains_key("authorization")
);
}
#[test]
fn custom_env_backed_requests_include_bearer_header() {
let provider = OpenAiCompatibleProvider::custom(
"local-ai",
"model-a",
Some("runtime-key".to_string()),
"http://localhost:8080/v1",
false,
NoopTransport,
);
let request = ProviderRequest::new("model-a", vec![ChatMessage::user("hello")]);
assert_eq!(
provider
.build_http_request(&request)
.headers
.get("authorization"),
Some(&"Bearer runtime-key".to_string())
);
assert_eq!(
provider
.build_model_catalog_request()
.headers
.get("authorization"),
Some(&"Bearer runtime-key".to_string())
);
}
#[test]
fn custom_provider_debug_redacts_api_key() {
let provider = OpenAiCompatibleProvider::custom(
"local-ai",
"model-a",
Some("<API_KEY>".to_string()),
"http://localhost:8080/v1",
false,
NoopTransport,
);
let debug = format!("{provider:?}");
assert!(debug.contains("local-ai"));
assert!(debug.contains("model-a"));
assert!(debug.contains("http://localhost:8080/v1/chat/completions"));
assert!(debug.contains("<redacted>"));
assert!(!debug.contains("<API_KEY>"));
}
#[test]
fn request_construction_selects_custom_chat_or_responses_endpoint_mode() {
let messages = vec![ChatMessage::system("system"), ChatMessage::user("hello")];
let request = ProviderRequest::new("ignored", messages);
let chat_provider = OpenAiCompatibleProvider::custom(
"local-ai",
"model-a",
Some("runtime-key".to_string()),
"http://localhost:8080/v1",
false,
NoopTransport,
);
let chat = chat_provider.build_http_request(&request);
assert_eq!(chat.url, "http://localhost:8080/v1/chat/completions");
assert!(chat.body.get("messages").is_some());
assert!(chat.body.get("input").is_none());
assert!(chat.body.get("include").is_none());
let responses_provider = OpenAiCompatibleProvider::custom(
"local-ai",
"model-a",
Some("runtime-key".to_string()),
"http://localhost:8080/v1",
true,
NoopTransport,
);
let responses = responses_provider.build_http_request(&request);
assert_eq!(responses.url, "http://localhost:8080/v1/responses");
let expected_bearer = ["Bearer", "runtime-key"].join(" ");
assert_eq!(responses.headers["authorization"], expected_bearer);
assert_eq!(responses.body["model"], "model-a");
assert_eq!(responses.body["store"], false);
assert_eq!(responses.body["stream"], true);
assert_eq!(responses.body["instructions"], "system");
assert_eq!(responses.body["input"][0]["role"], "user");
assert_eq!(responses.body["tool_choice"], "auto");
assert!(responses.body.get("tools").is_some());
assert!(responses.body.get("messages").is_none());
assert!(responses.body.get("reasoning_effort").is_none());
assert_eq!(
responses.body["include"],
json!(["reasoning.encrypted_content"])
);
assert!(responses.body.get("text").is_none());
assert!(responses.body.get("parallel_tool_calls").is_none());
}
#[test]
fn capable_custom_responses_provider_serializes_text_verbosity() {
let request = ProviderRequest::new("model-a", vec![ChatMessage::user("hello")])
.with_text_verbosity(Some(TextVerbosity::Medium));
let capable = OpenAiCompatibleProvider::custom(
"local-ai",
"model-a",
None,
"http://localhost:8080/v1",
true,
NoopTransport,
)
.with_text_verbosity_support(true)
.build_http_request(&request);
assert_eq!(capable.body["text"], json!({"verbosity": "medium"}));
let unsupported = OpenAiCompatibleProvider::custom(
"local-ai",
"model-a",
None,
"http://localhost:8080/v1",
true,
NoopTransport,
)
.build_http_request(&request);
assert!(unsupported.body.get("text").is_none());
}
#[test]
fn prompt_cache_responses_and_codex_bodies_include_key_when_present() {
let base_request = ProviderRequest::new("model-a", vec![ChatMessage::user("hello")]);
let request = base_request
.clone()
.with_prompt_cache_key("magi-code-session-0123456789abcdef0123456789abcdef");
let custom_with_key = openai_compatible_responses_body("model-a", &request);
let custom_without_key = openai_compatible_responses_body("model-a", &base_request);
let codex_with_key = codex_responses_body("gpt-5", &request);
assert_eq!(
custom_with_key["prompt_cache_key"],
"magi-code-session-0123456789abcdef0123456789abcdef"
);
assert!(custom_without_key.get("prompt_cache_key").is_none());
assert_eq!(
codex_with_key["prompt_cache_key"],
"magi-code-session-0123456789abcdef0123456789abcdef"
);
}
#[test]
fn prompt_cache_chat_body_requests_stream_usage_without_cache_key() {
let mut streaming = ProviderRequest::new("model-a", vec![ChatMessage::user("hello")])
.with_prompt_cache_key("magi-code-session-0123456789abcdef0123456789abcdef");
streaming.stream = true;
let mut non_streaming = streaming.clone();
non_streaming.stream = false;
let streaming_body = openai_compatible_chat_completions_body("model-a", &streaming);
let non_streaming_body = openai_compatible_chat_completions_body("model-a", &non_streaming);
assert_eq!(streaming_body["stream_options"]["include_usage"], true);
assert!(streaming_body.get("prompt_cache_key").is_none());
assert!(non_streaming_body.get("stream_options").is_none());
}
#[test]
fn custom_responses_headers_stay_custom_provider_only() {
let request = ProviderRequest::new("model-a", vec![ChatMessage::user("hello")]);
let no_key = OpenAiCompatibleProvider::custom(
"local-ai",
"model-a",
None,
"http://localhost:8080/v1",
true,
NoopTransport,
)
.build_http_request(&request);
assert_eq!(
no_key.headers.keys().cloned().collect::<Vec<_>>(),
vec!["accept", "content-type", "user-agent"]
);
assert!(!no_key.headers.contains_key("authorization"));
let with_key = OpenAiCompatibleProvider::custom(
"local-ai",
"model-a",
Some("runtime-key".to_string()),
"http://localhost:8080/v1",
true,
NoopTransport,
)
.build_http_request(&request);
assert_eq!(
with_key.headers.keys().cloned().collect::<Vec<_>>(),
vec!["accept", "authorization", "content-type", "user-agent"]
);
let expected_bearer = ["Bearer", "runtime-key"].join(" ");
assert_eq!(with_key.headers["authorization"], expected_bearer);
assert!(!with_key.headers.contains_key("chatgpt-account-id"));
assert!(!with_key.headers.contains_key("originator"));
assert!(!with_key.headers.contains_key("openai-beta"));
}
#[test]
fn custom_responses_catalog_request_still_targets_models() {
let provider = OpenAiCompatibleProvider::custom(
"local-ai",
"model-a",
Some("runtime-key".to_string()),
"http://localhost:8080/v1",
true,
NoopTransport,
);
let http = provider.build_model_catalog_request();
assert_eq!(http.method, "GET");
assert_eq!(http.url, "http://localhost:8080/v1/models");
let expected_bearer = ["Bearer", "runtime-key"].join(" ");
assert_eq!(http.headers["authorization"], expected_bearer);
assert_eq!(http.body, Value::Null);
}
#[test]
fn custom_responses_body_scrubs_unstored_response_item_ids() {
let request = ProviderRequest::from_conversation(
"model-a",
vec![
ProviderConversationItem::Message(ChatMessage::user("hello")),
ProviderConversationItem::ResponseItem(json!({
"id": "rs_issue36",
"type": "reasoning",
"summary": [],
"encrypted_content": "enc_issue36",
"status": null
})),
ProviderConversationItem::ResponseItem(json!({
"id": "fc_issue36",
"type": "function_call",
"call_id": "call_issue36",
"name": "read",
"arguments": "{\"path\":\"a.txt\"}",
"status": "completed"
})),
ProviderConversationItem::ToolResult(ProviderToolResult {
call_id: "call_issue36".to_string(),
tool_name: "read".to_string(),
success: true,
output: "file contents".to_string(),
}),
],
);
let body = openai_compatible_responses_body("model-a", &request);
let input = body["input"].as_array().unwrap();
let reasoning = &input[1];
let function_call = &input[2];
let tool_output = &input[3];
assert_eq!(body["store"], false);
assert_eq!(body["include"], json!(["reasoning.encrypted_content"]));
assert_eq!(reasoning["type"], "reasoning");
assert_eq!(reasoning["summary"], json!([]));
assert_eq!(reasoning["encrypted_content"], "enc_issue36");
assert!(reasoning.get("id").is_none());
assert!(reasoning.get("status").is_none());
assert_eq!(function_call["type"], "function_call");
assert_eq!(function_call["call_id"], "call_issue36");
assert_eq!(function_call["name"], "read");
assert_eq!(function_call["arguments"], "{\"path\":\"a.txt\"}");
assert!(function_call.get("id").is_none());
assert!(function_call.get("status").is_none());
assert_eq!(tool_output["type"], "function_call_output");
assert_eq!(tool_output["call_id"], "call_issue36");
assert_eq!(tool_output["output"], "file contents");
assert_eq!(input.len(), 4);
}
#[test]
fn custom_responses_body_preserves_tool_continuation_and_reasoning_shape() {
let request = ProviderRequest::from_conversation(
"model-a",
vec![
ProviderConversationItem::Message(ChatMessage::system("sys")),
ProviderConversationItem::Message(ChatMessage::user("old")),
ProviderConversationItem::ResponseItem(json!({
"type": "function_call",
"call_id": "call_1",
"name": "read",
"arguments": "{\"path\":\"a.txt\"}",
"status": "completed"
})),
ProviderConversationItem::ToolResult(ProviderToolResult {
call_id: "call_1".to_string(),
tool_name: "read".to_string(),
success: true,
output: "file contents".to_string(),
}),
],
)
.with_thinking_level(crate::thinking::ThinkingLevel::High)
.with_default_reasoning_summary(true);
let body = openai_compatible_responses_body("model-a", &request);
let input = body["input"].as_array().unwrap();
assert_eq!(body["instructions"], "sys");
assert_eq!(body["reasoning"]["effort"], "high");
assert_eq!(input[0]["role"], "user");
assert_eq!(input[1]["type"], "function_call");
assert_eq!(input[1]["call_id"], "call_1");
assert_eq!(input[2]["type"], "function_call_output");
assert_eq!(input[2]["call_id"], "call_1");
assert_eq!(input.len(), 3);
let unsupported = ProviderRequest::new("model-a", vec![ChatMessage::user("think")])
.with_thinking_level(crate::thinking::ThinkingLevel::High);
assert!(
openai_compatible_responses_body("model-a", &unsupported)
.get("reasoning")
.is_none()
);
}
#[test]
fn codex_responses_body_strips_unstored_response_item_ids() {
let request = ProviderRequest::from_conversation(
"model-a",
vec![
ProviderConversationItem::Message(ChatMessage::user("hello")),
ProviderConversationItem::ResponseItem(json!({
"id": "rs_codex",
"type": "reasoning",
"summary": [],
"status": null
})),
ProviderConversationItem::ResponseItem(json!({
"id": "fc_codex",
"type": "function_call",
"call_id": "call_codex",
"name": "read",
"arguments": "{\"path\":\"a.txt\"}",
"status": "completed"
})),
],
);
let body = codex_responses_body("model-a", &request);
let input = body["input"].as_array().unwrap();
assert_eq!(body["include"], json!(["reasoning.encrypted_content"]));
assert!(input[1].get("id").is_none());
assert!(input[1].get("status").is_none());
assert!(input[2].get("id").is_none());
assert!(input[2].get("status").is_none());
assert_eq!(input[1]["type"], "reasoning");
assert_eq!(input[1]["summary"], json!([]));
assert_eq!(input[2]["type"], "function_call");
assert_eq!(input[2]["call_id"], "call_codex");
assert_eq!(input[2]["name"], "read");
assert_eq!(input[2]["arguments"], "{\"path\":\"a.txt\"}");
}
#[test]
fn codex_responses_body_strips_chat_tool_calls_from_replayed_response_items() {
let request = ProviderRequest::from_conversation(
"gpt-5",
vec![
ProviderConversationItem::Message(ChatMessage::user("inspect")),
ProviderConversationItem::ResponseItem(json!({
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_chat_prior",
"type": "function",
"function": {
"name": "read",
"arguments": "{\"path\":\"src/lib.rs\"}"
}
}]
})),
ProviderConversationItem::ResponseItem(json!({
"type": "function_call",
"call_id": "call_codex",
"name": "read",
"arguments": "{\"path\":\"a.txt\"}",
"status": "completed"
})),
ProviderConversationItem::ResponseItem(json!({
"type": "function_call_output",
"call_id": "call_codex",
"output": "file contents"
})),
],
);
let body = codex_responses_body("gpt-5", &request);
let input = body["input"].as_array().unwrap();
assert_eq!(input[1]["role"], "assistant");
assert_eq!(input[1]["content"], " ");
assert!(input[1].get("tool_calls").is_none());
assert_eq!(input[2]["type"], "function_call");
assert_eq!(input[2]["call_id"], "call_codex");
assert_eq!(input[2]["arguments"], "{\"path\":\"a.txt\"}");
assert_eq!(input[3]["type"], "function_call_output");
assert_eq!(input[3]["call_id"], "call_codex");
assert!(
input
.iter()
.all(|item| item.get("content") != Some(&Value::Null))
);
assert!(
input.iter().all(|item| item.get("tool_calls").is_none()),
"codex responses input must not contain chat-completions tool_calls: {input:?}"
);
}
#[test]
fn custom_responses_failure_diagnostic_redacts_secrets() {
let api_key = "<RUNTIME_HEADER_SECRET>".to_string();
let body_secret = "<BODY_SECRET>".to_string();
let jwt = "<JWT>".to_string();
let bearer = "<BEARER>".to_string();
let account_id = "<ACCOUNT_ID>".to_string();
let mut body_json = serde_json::Map::new();
body_json.insert("error".to_string(), Value::String("invalid".to_string()));
body_json.insert("api_key".to_string(), Value::String(body_secret.clone()));
body_json.insert("authorization".to_string(), Value::String(bearer.clone()));
body_json.insert("token".to_string(), Value::String(jwt.to_string()));
body_json.insert(
"accountId".to_string(),
Value::String(account_id.to_string()),
);
let response_body = Value::Object(body_json).to_string();
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let base_url = format!(
"http://127.0.0.1:{}/v1",
listener.local_addr().unwrap().port()
);
let handle = std::thread::spawn(move || {
let (mut stream, _) = listener.accept().unwrap();
let mut request_bytes = [0_u8; 2048];
let _ = std::io::Read::read(&mut stream, &mut request_bytes);
let headers = format!(
"HTTP/1.1 401 Unauthorized\r\ncontent-length: {}\r\nconnection: close\r\n\r\n",
response_body.len()
);
let _ = std::io::Write::write_all(&mut stream, headers.as_bytes());
let _ = std::io::Write::write_all(&mut stream, response_body.as_bytes());
});
let provider = OpenAiCompatibleProvider::custom(
"local-ai",
"model-a",
Some(api_key.to_string()),
base_url,
true,
crate::providers::ReqwestHttpTransport,
);
let error = provider
.stream(
ProviderRequest::new_without_tools("model-a", vec![ChatMessage::user("hello")]),
&mut |_| Ok(()),
)
.unwrap_err()
.to_string();
handle.join().unwrap();
assert!(error.contains("/v1/responses"), "{error}");
assert!(error.contains("401 Unauthorized"), "{error}");
assert!(error.contains("invalid"), "{error}");
assert!(!error.contains(&api_key), "{error}");
assert!(!error.contains(&body_secret), "{error}");
assert!(!error.contains(&bearer), "{error}");
assert!(!error.contains(&jwt), "{error}");
assert!(!error.contains(&account_id), "{error}");
}
#[test]
fn model_catalog_failure_body_is_bounded() {
let secret = "<API_KEY>".to_string();
let body = format!(
r#"{{"error":"invalid","api_key":"{secret}","detail":"{}"}}"#,
"x".repeat(crate::providers::transport::PROVIDER_ERROR_BODY_MAX_BYTES + 512)
);
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let url = format!(
"http://127.0.0.1:{}/v1/models?api_key=fixture#fragment",
listener.local_addr().unwrap().port()
);
let response_body = body.clone();
let handle = std::thread::spawn(move || {
let (mut stream, _) = listener.accept().unwrap();
let mut request_bytes = [0_u8; 1024];
let _ = std::io::Read::read(&mut stream, &mut request_bytes);
let headers = format!(
"HTTP/1.1 500 Internal Server Error\r\ncontent-length: {}\r\nconnection: close\r\n\r\n",
response_body.len()
);
let _ = std::io::Write::write_all(&mut stream, headers.as_bytes());
let _ = std::io::Write::write_all(&mut stream, response_body.as_bytes());
});
let request = HttpRequest {
method: "GET".to_string(),
url,
headers: BTreeMap::new(),
body: Value::Null,
};
let error = fetch_model_catalog_response_text_cancellable(
request,
"local-ai",
&AgentCancellation::default(),
)
.unwrap_err()
.to_string();
handle.join().unwrap();
assert!(error.contains("local-ai model discovery failed"), "{error}");
assert!(error.contains("500 Internal Server Error"), "{error}");
assert!(error.contains("http://127.0.0.1:"), "{error}");
assert!(!error.contains("fixture"), "{error}");
assert!(!error.contains("fragment"), "{error}");
assert!(!error.contains(&secret), "{error}");
assert!(error.contains("<truncated provider error body>"), "{error}");
assert!(
error.len() < crate::providers::transport::PROVIDER_ERROR_BODY_MAX_BYTES + 1024,
"{}",
error.len()
);
}
#[test]
fn model_catalog_send_error_sanitizes_credential_url() {
let secret = "<API_KEY>".to_string();
let request = HttpRequest {
method: "GET".to_string(),
url: format!("http://user:password@127.0.0.1:9/v1/models?api_key={secret}#frag"),
headers: BTreeMap::new(),
body: Value::Null,
};
let error = fetch_model_catalog_response_text_cancellable(
request,
"local-ai",
&AgentCancellation::default(),
)
.unwrap_err()
.to_string();
assert!(
error.contains("local-ai model discovery request failed"),
"{error}"
);
assert!(error.contains("http://127.0.0.1:9/v1/models"), "{error}");
assert!(!error.contains(&secret), "{error}");
assert!(!error.contains("user"), "{error}");
assert!(!error.contains("password"), "{error}");
assert!(!error.contains("api_key"), "{error}");
assert!(!error.contains("frag"), "{error}");
}
#[test]
fn model_catalog_success_body_is_bounded() {
let body = format!(
r#"{{"data":[],"padding":"{}"}}"#,
"x".repeat(MODEL_CATALOG_SUCCESS_BODY_MAX_BYTES as usize + 1)
);
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let url = format!(
"http://127.0.0.1:{}/v1/models",
listener.local_addr().unwrap().port()
);
let handle = std::thread::spawn(move || {
let (mut stream, _) = listener.accept().unwrap();
let mut request_bytes = [0_u8; 1024];
let _ = std::io::Read::read(&mut stream, &mut request_bytes);
let headers = format!(
"HTTP/1.1 200 OK\r\ncontent-length: {}\r\nconnection: close\r\n\r\n",
body.len()
);
let _ = std::io::Write::write_all(&mut stream, headers.as_bytes());
let _ = std::io::Write::write_all(&mut stream, body.as_bytes());
});
let request = HttpRequest {
method: "GET".to_string(),
url,
headers: BTreeMap::new(),
body: Value::Null,
};
let error = fetch_model_catalog_response_text_cancellable(
request,
"local-ai",
&AgentCancellation::default(),
)
.unwrap_err()
.to_string();
handle.join().unwrap();
assert!(
error.contains("model discovery response exceeded"),
"{error}"
);
}
#[test]
fn model_catalog_fetch_observes_cancellation_during_header_wait() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let url = format!(
"http://127.0.0.1:{}/v1/models",
listener.local_addr().unwrap().port()
);
let release_server = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let server_release = std::sync::Arc::clone(&release_server);
let server = std::thread::spawn(move || {
let (mut stream, _) = listener.accept().unwrap();
stream
.set_read_timeout(Some(Duration::from_secs(1)))
.unwrap();
let mut request_bytes = [0_u8; 1024];
let _ = std::io::Read::read(&mut stream, &mut request_bytes);
while !server_release.load(std::sync::atomic::Ordering::SeqCst) {
std::thread::sleep(Duration::from_millis(10));
}
});
let request = HttpRequest {
method: "GET".to_string(),
url,
headers: BTreeMap::new(),
body: Value::Null,
};
let (cancellation, handle): (AgentCancellation, AgentCancellationHandle) =
AgentCancellation::default().child_token();
let canceler = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(50));
handle.cancel();
});
let started = Instant::now();
let error = fetch_model_catalog_response_text_cancellable(request, "local-ai", &cancellation)
.unwrap_err();
release_server.store(true, std::sync::atomic::Ordering::SeqCst);
canceler.join().unwrap();
server.join().unwrap();
assert!(is_run_canceled(&error));
assert!(started.elapsed() < Duration::from_secs(1));
}
#[test]
fn no_tools_requests_omit_tool_fields_from_provider_bodies() {
let request = ProviderRequest::new_without_tools("model-a", vec![ChatMessage::user("title")]);
let chat_provider = OpenAiCompatibleProvider::custom(
"local-ai",
"model-a",
None,
"http://localhost:8080/v1",
false,
NoopTransport,
);
let chat_body = chat_provider.build_http_request(&request).body;
assert!(chat_body.get("tools").is_none());
assert!(chat_body.get("tool_choice").is_none());
assert!(chat_body.get("parallel_tool_calls").is_none());
let responses_provider = OpenAiCompatibleProvider::custom(
"local-ai",
"model-a",
None,
"http://localhost:8080/v1",
true,
NoopTransport,
);
let responses_body = responses_provider.build_http_request(&request).body;
assert!(responses_body.get("tools").is_none());
assert!(responses_body.get("tool_choice").is_none());
assert!(responses_body.get("parallel_tool_calls").is_none());
let codex_provider =
OpenAiCodexProvider::new("gpt-5", "<TOKEN>", Some("acct".to_string()), NoopTransport);
let codex_body = codex_provider.build_http_request(&request).unwrap().body;
assert!(codex_body.get("tools").is_none());
assert!(codex_body.get("tool_choice").is_none());
assert!(codex_body.get("parallel_tool_calls").is_none());
}
#[test]
fn provider_bodies_hide_subagents_when_request_disables_it() {
let request = ProviderRequest::new("model-a", vec![ChatMessage::user("inspect")])
.with_subagents_tool_enabled(false);
let chat_body = openai_compatible_chat_completions_body("model-a", &request);
let chat_names = chat_body["tools"]
.as_array()
.unwrap()
.iter()
.map(|tool| tool["function"]["name"].as_str().unwrap())
.collect::<Vec<_>>();
assert!(!chat_names.contains(&"subagents"));
assert!(chat_names.contains(&"read"));
let custom_body = openai_compatible_responses_body("model-a", &request);
let custom_names = custom_body["tools"]
.as_array()
.unwrap()
.iter()
.map(|tool| tool["name"].as_str().unwrap())
.collect::<Vec<_>>();
assert!(!custom_names.contains(&"subagents"));
assert!(custom_names.contains(&"read"));
let codex_body = codex_responses_body("gpt-5", &request);
let codex_names = codex_body["tools"]
.as_array()
.unwrap()
.iter()
.map(|tool| tool["name"].as_str().unwrap())
.collect::<Vec<_>>();
assert!(!codex_names.contains(&"subagents"));
assert!(codex_names.contains(&"read"));
}
#[test]
fn default_requests_include_tool_fields_in_provider_bodies() {
let request = ProviderRequest::new("model-a", vec![ChatMessage::user("inspect")]);
let chat_body = openai_compatible_chat_completions_body("model-a", &request);
assert!(chat_body.get("tools").is_some());
assert_eq!(chat_body["tool_choice"], "auto");
let codex_body = codex_responses_body("gpt-5", &request);
assert!(codex_body.get("tools").is_some());
assert_eq!(codex_body["tool_choice"], "auto");
assert_eq!(codex_body["parallel_tool_calls"], true);
}
#[test]
fn openai_codex_text_verbosity_defaults_to_low_in_body() {
let request = ProviderRequest::new("gpt-5", vec![ChatMessage::user("hello")]);
let body = codex_responses_body("gpt-5", &request);
assert_eq!(body["text"]["verbosity"], "low");
}
#[test]
fn openai_codex_text_verbosity_uses_configured_medium_in_body() {
let request = ProviderRequest::new("gpt-5", vec![ChatMessage::user("hello")])
.with_text_verbosity(Some(TextVerbosity::Medium));
let body = codex_responses_body("gpt-5", &request);
assert_eq!(body["text"]["verbosity"], "medium");
}
#[test]
fn openai_codex_text_verbosity_uses_configured_high_in_body() {
let request = ProviderRequest::new("gpt-5", vec![ChatMessage::user("hello")])
.with_text_verbosity(Some(TextVerbosity::High));
let body = codex_responses_body("gpt-5", &request);
assert_eq!(body["text"]["verbosity"], "high");
}
#[test]
fn codex_reasoning_effort_serializes_only_for_supported_explicit_levels() {
for (level, expected) in [
(crate::thinking::ThinkingLevel::Low, "low"),
(crate::thinking::ThinkingLevel::Medium, "medium"),
(crate::thinking::ThinkingLevel::High, "high"),
] {
let request = ProviderRequest::new("gpt-5", vec![ChatMessage::user("think")])
.with_thinking_level(level)
.with_default_reasoning_summary(true);
let body = codex_responses_body("gpt-5", &request);
assert_eq!(body["reasoning"]["effort"], expected);
assert_eq!(body["reasoning"]["summary"], "auto");
}
let default = ProviderRequest::new("gpt-5", vec![ChatMessage::user("think")])
.with_thinking_level(crate::thinking::ThinkingLevel::Default)
.with_default_reasoning_summary(true);
let default_body = codex_responses_body("gpt-5", &default);
assert_eq!(default_body["reasoning"]["summary"], "auto");
assert!(default_body["reasoning"].get("effort").is_none());
let unsupported = ProviderRequest::new("gpt-4.1", vec![ChatMessage::user("think")])
.with_thinking_level(crate::thinking::ThinkingLevel::High);
assert!(
codex_responses_body("gpt-4.1", &unsupported)
.get("reasoning")
.is_none()
);
}
#[test]
fn custom_provider_bodies_serialize_reasoning_only_when_supported() {
let request = ProviderRequest::new("gpt-5", vec![ChatMessage::user("think")])
.with_thinking_level(crate::thinking::ThinkingLevel::High)
.with_default_reasoning_summary(true);
let body = openai_compatible_chat_completions_body("gpt-5", &request);
assert_eq!(body["reasoning_effort"], "high");
assert!(body.get("reasoning").is_none());
assert!(body.get("summary").is_none());
let default = ProviderRequest::new("gpt-5", vec![ChatMessage::user("think")])
.with_thinking_level(crate::thinking::ThinkingLevel::Default)
.with_default_reasoning_summary(true);
let default_body = openai_compatible_chat_completions_body("gpt-5", &default);
assert!(default_body.get("reasoning_effort").is_none());
assert!(default_body.get("reasoning").is_none());
assert!(default_body.get("summary").is_none());
let responses_body = openai_compatible_responses_body("gpt-5", &request);
assert_eq!(responses_body["reasoning"]["effort"], "high");
assert_eq!(responses_body["reasoning"]["summary"], "auto");
let default_responses_body = openai_compatible_responses_body("gpt-5", &default);
assert_eq!(default_responses_body["reasoning"]["summary"], "auto");
assert!(default_responses_body["reasoning"].get("effort").is_none());
}
#[test]
fn custom_responses_body_requests_reasoning_summary_when_supported() {
let request = ProviderRequest::new("gpt-5", vec![ChatMessage::user("think")])
.with_thinking_level(crate::thinking::ThinkingLevel::High)
.with_default_reasoning_summary(true);
let body = openai_compatible_responses_body("gpt-5", &request);
assert_eq!(body["reasoning"]["effort"], "high");
assert_eq!(body["reasoning"]["summary"], "auto");
assert_eq!(body["include"], json!(["reasoning.encrypted_content"]));
}
#[test]
fn custom_provider_bodies_omit_reasoning_without_capability() {
let request = ProviderRequest::new("gpt-5", vec![ChatMessage::user("think")])
.with_thinking_level(crate::thinking::ThinkingLevel::High);
let body = openai_compatible_chat_completions_body("gpt-5", &request);
assert!(body.get("reasoning_effort").is_none());
assert!(body.get("reasoning").is_none());
}
#[test]
fn chat_ordered_conversation_replay_preserves_tool_boundaries() {
let request = ProviderRequest::from_conversation(
"model-a",
vec![
ProviderConversationItem::Message(ChatMessage::system("sys")),
ProviderConversationItem::Message(ChatMessage::user("old")),
ProviderConversationItem::ResponseItem(json!({
"type": "function_call",
"call_id": "call_1",
"name": "read",
"arguments": {"path":"a.txt"},
"status": "completed"
})),
ProviderConversationItem::ToolResult(ProviderToolResult {
call_id: "call_1".to_string(),
tool_name: "read".to_string(),
success: true,
output: "file contents".to_string(),
}),
ProviderConversationItem::Message(ChatMessage::assistant("done")),
ProviderConversationItem::Message(ChatMessage::user("current")),
],
);
let body = openai_compatible_chat_completions_body("model-a", &request);
let messages = body["messages"].as_array().unwrap();
assert_eq!(messages[0]["role"], "system");
assert_eq!(messages[1]["role"], "user");
assert_eq!(messages[2]["role"], "assistant");
assert_eq!(messages[2]["tool_calls"][0]["id"], "call_1");
assert_eq!(messages[3]["role"], "tool");
assert_eq!(messages[3]["tool_call_id"], "call_1");
assert_eq!(messages[4]["role"], "assistant");
assert_eq!(messages[5]["content"], "current");
}
#[test]
fn codex_ordered_conversation_replay_preserves_tool_boundaries() {
let request = ProviderRequest::from_conversation(
"gpt-5",
vec![
ProviderConversationItem::Message(ChatMessage::system("sys")),
ProviderConversationItem::Message(ChatMessage::user("old")),
ProviderConversationItem::ResponseItem(json!({
"type": "function_call",
"call_id": "call_1",
"name": "read",
"arguments": "{\"path\":\"a.txt\"}",
"status": "completed"
})),
ProviderConversationItem::ToolResult(ProviderToolResult {
call_id: "call_1".to_string(),
tool_name: "read".to_string(),
success: true,
output: "file contents".to_string(),
}),
ProviderConversationItem::Message(ChatMessage::assistant("done")),
ProviderConversationItem::Message(ChatMessage::user("current")),
],
);
let body = codex_responses_body("gpt-5", &request);
assert_eq!(body["instructions"], "sys");
let input = body["input"].as_array().unwrap();
assert_eq!(input[0]["role"], "user");
assert_eq!(input[1]["type"], "function_call");
assert_eq!(input[1]["call_id"], "call_1");
assert_eq!(input[2]["type"], "function_call_output");
assert_eq!(input[2]["call_id"], "call_1");
assert_eq!(input[3]["role"], "assistant");
assert_eq!(input[4]["content"], "current");
}
#[test]
fn openai_compatible_chat_body_converts_response_tool_items_to_chat_messages() {
let request = ProviderRequest::new("model-a", vec![ChatMessage::user("inspect")])
.with_response_items(vec![
json!({
"type": "function_call",
"call_id": "call_1",
"name": "read",
"arguments": {"path": "src/lib.rs"},
"status": "completed"
}),
json!({
"type": "function_call_output",
"call_id": "call_1",
"output": "file contents"
}),
])
.with_tool_results(vec![ProviderToolResult {
call_id: "call_1".to_string(),
tool_name: "read".to_string(),
success: true,
output: "file contents".to_string(),
}]);
let body = openai_compatible_chat_completions_body("model-a", &request);
let messages = body["messages"].as_array().unwrap();
assert_eq!(messages[1]["role"], "assistant");
assert_eq!(messages[1]["content"], " ");
assert_eq!(messages[1]["tool_calls"][0]["id"], "call_1");
assert_eq!(messages[1]["tool_calls"][0]["type"], "function");
assert_eq!(messages[1]["tool_calls"][0]["function"]["name"], "read");
assert_eq!(
messages[1]["tool_calls"][0]["function"]["arguments"],
r#"{"path":"src/lib.rs"}"#
);
assert_eq!(messages[2]["role"], "tool");
assert_eq!(messages[2]["tool_call_id"], "call_1");
assert_eq!(messages[2]["content"], "file contents");
assert_eq!(
messages
.iter()
.filter(|message| message["role"] == "tool")
.count(),
1
);
for message in messages {
assert!(message.get("role").and_then(Value::as_str).is_some());
assert!(!message.as_object().unwrap().contains_key("type"));
assert!(!message.as_object().unwrap().contains_key("call_id"));
assert!(!message.as_object().unwrap().contains_key("arguments"));
assert!(!message.as_object().unwrap().contains_key("status"));
assert!(!message.as_object().unwrap().contains_key("output"));
}
}
#[test]
fn openai_compatible_chat_body_deduplicates_chat_and_response_tool_calls() {
let request = ProviderRequest::new("model-a", vec![ChatMessage::user("inspect")])
.with_response_items(vec![
json!({
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_1",
"type": "function",
"function": {"name": "read", "arguments": "{\"path\":\"src/lib.rs\"}"}
}]
}),
json!({
"type": "function_call",
"call_id": "call_1",
"name": "read",
"arguments": {"path": "src/lib.rs"},
"status": "completed"
}),
]);
let body = openai_compatible_chat_completions_body("model-a", &request);
let messages = body["messages"].as_array().unwrap();
assert_eq!(
messages
.iter()
.filter(|message| message.get("tool_calls").is_some())
.count(),
1
);
assert_eq!(messages[1]["content"], " ");
}
#[test]
fn openai_compatible_chat_body_deduplicates_long_interleaved_tool_history() {
let tool_pair_count = 96;
let mut items = vec![
ProviderConversationItem::Message(ChatMessage::user("inspect")),
ProviderConversationItem::ResponseItem(json!({
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_chat_prior",
"type": "function",
"function": {"name": "read", "arguments": "{\"path\":\"prior.txt\"}"}
}]
})),
ProviderConversationItem::ResponseItem(json!({
"type": "function_call",
"call_id": "call_chat_prior",
"name": "duplicate_read",
"arguments": {"path": "duplicate.txt"},
"status": "completed"
})),
ProviderConversationItem::ResponseItem(json!({
"role": "tool",
"tool_call_id": "call_tool_prior",
"content": "prior output"
})),
ProviderConversationItem::ToolResult(ProviderToolResult {
call_id: "call_tool_prior".to_string(),
tool_name: "read".to_string(),
success: true,
output: "duplicate prior output".to_string(),
}),
];
for index in 0..tool_pair_count {
let call_id = format!("call_{index}");
items.push(ProviderConversationItem::ResponseItem(json!({
"type": "function_call",
"call_id": call_id,
"name": "read",
"arguments": {"path": format!("fixtures/{index}.txt")},
"status": "completed"
})));
if index % 12 == 0 {
items.push(ProviderConversationItem::ResponseItem(json!({
"type": "function_call",
"call_id": format!("call_{index}"),
"name": "duplicate_read",
"arguments": {"path": "duplicate.txt"},
"status": "completed"
})));
}
items.push(ProviderConversationItem::ToolResult(ProviderToolResult {
call_id: format!("call_{index}"),
tool_name: "read".to_string(),
success: true,
output: format!("file contents {index}"),
}));
if index % 12 == 0 {
items.push(ProviderConversationItem::ResponseItem(json!({
"type": "function_call_output",
"call_id": format!("call_{index}"),
"output": "duplicate output"
})));
items.push(ProviderConversationItem::ToolResult(ProviderToolResult {
call_id: format!("call_{index}"),
tool_name: "read".to_string(),
success: true,
output: "duplicate output".to_string(),
}));
}
}
let request = ProviderRequest::from_conversation("model-a", items);
let body = openai_compatible_chat_completions_body("model-a", &request);
let messages = body["messages"].as_array().unwrap();
let assistant_tool_calls = messages
.iter()
.filter(|message| message.get("tool_calls").is_some())
.count();
let tool_results = messages
.iter()
.filter(|message| message.get("role").and_then(Value::as_str) == Some("tool"))
.count();
assert_eq!(assistant_tool_calls, tool_pair_count + 1);
assert_eq!(tool_results, tool_pair_count + 1);
assert_eq!(messages.len(), 1 + 2 + tool_pair_count * 2);
assert!(messages.iter().all(|message| {
message
.pointer("/tool_calls/0/function/name")
.and_then(Value::as_str)
!= Some("duplicate_read")
}));
assert!(
messages
.iter()
.all(|message| message.get("content").and_then(Value::as_str)
!= Some("duplicate output"))
);
assert!(messages.iter().any(|message| {
message["role"] == "assistant"
&& message["tool_calls"][0]["id"] == format!("call_{}", tool_pair_count - 1)
}));
assert!(messages.iter().any(|message| {
message["role"] == "tool"
&& message["tool_call_id"] == format!("call_{}", tool_pair_count - 1)
}));
}
#[test]
fn optimization_harness_chat_body_keeps_large_synthetic_tool_pairs() {
let tool_pair_count = 384;
let mut items = vec![
ProviderConversationItem::Message(ChatMessage::system("system")),
ProviderConversationItem::Message(ChatMessage::user("inspect all")),
];
for index in 0..tool_pair_count {
let call_id = format!("call_{index}");
items.push(ProviderConversationItem::ResponseItem(json!({
"type": "function_call",
"call_id": call_id,
"name": "read",
"arguments": {"path": format!("fixtures/{index}.txt")},
"status": "completed"
})));
items.push(ProviderConversationItem::ToolResult(ProviderToolResult {
call_id: format!("call_{index}"),
tool_name: "read".to_string(),
success: true,
output: format!("file contents {index}"),
}));
}
let request = ProviderRequest::from_conversation("model-a", items);
let body = openai_compatible_chat_completions_body("model-a", &request);
let messages = body["messages"].as_array().unwrap();
let assistant_tool_calls = messages
.iter()
.filter(|message| message.get("tool_calls").is_some())
.count();
let tool_results = messages
.iter()
.filter(|message| message.get("role").and_then(Value::as_str) == Some("tool"))
.count();
assert_eq!(assistant_tool_calls, tool_pair_count);
assert_eq!(tool_results, tool_pair_count);
assert_eq!(messages.len(), 2 + tool_pair_count * 2);
assert!(messages.iter().any(|message| {
message["role"] == "assistant"
&& message["tool_calls"][0]["id"] == format!("call_{}", tool_pair_count - 1)
}));
assert!(messages.iter().any(|message| {
message["role"] == "tool"
&& message["tool_call_id"] == format!("call_{}", tool_pair_count - 1)
}));
}
#[test]
#[ignore = "diagnostic-only optimization measurement; run explicitly with --ignored --nocapture"]
fn optimization_harness_measures_large_chat_body_construction() {
let tool_pair_count = 2_000;
let mut items = vec![ProviderConversationItem::Message(ChatMessage::user(
"measure body construction",
))];
for index in 0..tool_pair_count {
items.push(ProviderConversationItem::ResponseItem(json!({
"type": "function_call",
"call_id": format!("call_{index}"),
"name": "read",
"arguments": {"path": format!("fixtures/{index}.txt")},
"status": "completed"
})));
items.push(ProviderConversationItem::ToolResult(ProviderToolResult {
call_id: format!("call_{index}"),
tool_name: "read".to_string(),
success: true,
output: format!("file contents {index}"),
}));
}
let request = ProviderRequest::from_conversation("model-a", items);
let started = std::time::Instant::now();
let body = openai_compatible_chat_completions_body("model-a", &request);
let elapsed = started.elapsed();
let messages = body["messages"].as_array().unwrap();
println!(
"optimization_harness chat_body tool_pairs={} messages={} elapsed={elapsed:?}",
tool_pair_count,
messages.len()
);
assert_eq!(messages.len(), 1 + tool_pair_count * 2);
}
#[test]
fn parse_openai_compatible_model_catalog_response_preserves_provider_native_slash_ids() {
let entries = parse_openai_compatible_model_catalog_response(
"foundry",
r#"{"data":[{"id":"gpt-5-nano"},{"id":"minimaxai/minimax-m2.7"},{"id":"moonshotai/kimi-k2.5"},{"id":"qwen3.5:9b"},{"id":"Qwen/Qwen3.6-35B-A3B-FP8"}]}"#,
)
.unwrap();
assert_eq!(
entries
.iter()
.map(|entry| (
entry.provider.as_str(),
entry.model.as_str(),
entry.id.as_str()
))
.collect::<Vec<_>>(),
vec![
("foundry", "gpt-5-nano", "foundry/gpt-5-nano"),
(
"foundry",
"minimaxai/minimax-m2.7",
"foundry/minimaxai/minimax-m2.7"
),
(
"foundry",
"moonshotai/kimi-k2.5",
"foundry/moonshotai/kimi-k2.5"
),
("foundry", "qwen3.5:9b", "foundry/qwen3.5:9b"),
(
"foundry",
"Qwen/Qwen3.6-35B-A3B-FP8",
"foundry/Qwen/Qwen3.6-35B-A3B-FP8"
),
]
);
}
#[test]
fn anthropic_like_custom_bodies_use_thinking_fields_only() {
let request = ProviderRequest::new("model-a", vec![ChatMessage::user("think")])
.with_thinking_level(crate::thinking::ThinkingLevel::High)
.with_default_reasoning_summary(true);
let protocol = crate::config::CustomReasoningProtocol::AnthropicLike;
let chat = openai_compatible_chat_completions_body_with_protocol(
"model-a",
&request,
protocol,
Some(20_000),
);
let responses =
openai_compatible_responses_body_with_protocol("model-a", &request, protocol, Some(20_000));
for body in [chat, responses] {
assert_eq!(body["thinking"]["type"], "enabled");
assert_eq!(body["thinking"]["budget_tokens"], 16_384);
assert_eq!(body["max_tokens"], 20_000);
assert!(body.get("reasoning_effort").is_none());
assert!(body.get("reasoning").is_none());
}
}
#[test]
fn anthropic_like_custom_bodies_require_reasoning_capability() {
let request = ProviderRequest::new("model-a", vec![ChatMessage::user("think")])
.with_thinking_level(crate::thinking::ThinkingLevel::Max);
let protocol = crate::config::CustomReasoningProtocol::AnthropicLike;
for body in [
openai_compatible_chat_completions_body_with_protocol(
"model-a",
&request,
protocol,
Some(40_000),
),
openai_compatible_responses_body_with_protocol("model-a", &request, protocol, Some(40_000)),
] {
assert!(body.get("thinking").is_none());
assert!(body.get("max_tokens").is_none());
assert!(body.get("reasoning_effort").is_none());
assert!(body.get("reasoning").is_none());
}
}
#[test]
fn custom_reasoning_body_matrix_preserves_gpt_and_limits_anthropic_fields() {
let mut request = ProviderRequest::new(
"model-a",
vec![ChatMessage::system("system"), ChatMessage::user("hello")],
)
.with_default_reasoning_summary(true);
request.stream = true;
for endpoint in ["chat", "responses"] {
let build = |protocol, request: &ProviderRequest, ceiling| {
if endpoint == "chat" {
openai_compatible_chat_completions_body_with_protocol(
"model-a", request, protocol, ceiling,
)
} else {
openai_compatible_responses_body_with_protocol(
"model-a", request, protocol, ceiling,
)
}
};
for level in [
crate::thinking::ThinkingLevel::Default,
crate::thinking::ThinkingLevel::Low,
crate::thinking::ThinkingLevel::Medium,
crate::thinking::ThinkingLevel::XHigh,
] {
let request = request.clone().with_thinking_level(level);
let body = build(
crate::config::CustomReasoningProtocol::AnthropicLike,
&request,
Some(40_000),
);
assert!(body.get("thinking").is_none(), "{endpoint} {level}");
assert!(body.get("max_tokens").is_none(), "{endpoint} {level}");
assert!(body.get("reasoning_effort").is_none(), "{endpoint} {level}");
assert!(body.get("reasoning").is_none(), "{endpoint} {level}");
assert_eq!(body["model"], "model-a");
assert_eq!(body["stream"], true);
}
for (level, budget) in [
(crate::thinking::ThinkingLevel::High, 16_384_u64),
(crate::thinking::ThinkingLevel::Max, 32_768_u64),
] {
let request = request.clone().with_thinking_level(level);
let body = build(
crate::config::CustomReasoningProtocol::AnthropicLike,
&request,
Some(40_000),
);
assert_eq!(body["thinking"]["budget_tokens"], budget);
assert_eq!(body["max_tokens"], 40_000);
}
let fallback_request = request
.clone()
.with_thinking_level(crate::thinking::ThinkingLevel::Max);
let fallback = build(
crate::config::CustomReasoningProtocol::AnthropicLike,
&fallback_request,
None,
);
assert_eq!(fallback["thinking"]["budget_tokens"], 4095);
assert_eq!(fallback["max_tokens"], 4096);
let request = request
.clone()
.with_thinking_level(crate::thinking::ThinkingLevel::Max);
let clamped = build(
crate::config::CustomReasoningProtocol::AnthropicLike,
&request,
Some(16_385),
);
assert_eq!(clamped["thinking"]["budget_tokens"], 16_384);
assert_eq!(clamped["max_tokens"], 16_385);
for ceiling in [Some(0), Some(1)] {
let body = build(
crate::config::CustomReasoningProtocol::AnthropicLike,
&request,
ceiling,
);
assert!(body.get("thinking").is_none());
assert!(body.get("max_tokens").is_none());
}
let gpt_request = request
.clone()
.with_thinking_level(crate::thinking::ThinkingLevel::High);
let expected = if endpoint == "chat" {
openai_compatible_chat_completions_body("model-a", &gpt_request)
} else {
openai_compatible_responses_body("model-a", &gpt_request)
};
assert_eq!(
build(
crate::config::CustomReasoningProtocol::GptLike,
&gpt_request,
Some(1),
),
expected
);
}
}