use serde::Serialize;
use serde_json::Value;
use codewhale_config::provider::WireFormat;
use crate::config::ApiProvider;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum WireDialect {
ChatCompletions,
AnthropicMessages,
OpenAiResponses,
GoogleCloudCode,
}
impl WireDialect {
pub(crate) fn from_wire_format(format: WireFormat) -> Self {
match format {
WireFormat::ChatCompletions => Self::ChatCompletions,
WireFormat::AnthropicMessages => Self::AnthropicMessages,
WireFormat::Responses => Self::OpenAiResponses,
}
}
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::ChatCompletions => "chat-completions",
Self::AnthropicMessages => "anthropic-messages",
Self::OpenAiResponses => "openai-responses",
Self::GoogleCloudCode => "google-cloud-code",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum RouteShape {
Standard,
DeepseekBetaStrictTools,
KimiCodeK3,
DirectMoonshotK3,
CodexResponses,
OpencodeZen,
CustomCompatible,
CloudCode,
}
impl RouteShape {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Standard => "standard",
Self::DeepseekBetaStrictTools => "deepseek-beta-strict-tools",
Self::KimiCodeK3 => "kimi-code-k3",
Self::DirectMoonshotK3 => "direct-moonshot-k3",
Self::CodexResponses => "codex-responses",
Self::OpencodeZen => "opencode-zen",
Self::CustomCompatible => "custom-compatible",
Self::CloudCode => "cloud-code",
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct EndpointIdentity {
pub(crate) provider_id: String,
pub(crate) provider_display: String,
pub(crate) route_id: Option<String>,
pub(crate) url: String,
pub(crate) shape: RouteShape,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ReasoningReceipt {
pub(crate) requested_effort: Option<String>,
pub(crate) wire_controls: Vec<(String, Value)>,
}
const REASONING_DISCLOSURE_ONLY_KEYS: &[&str] = &["include"];
impl ReasoningReceipt {
fn control_keys(dialect: WireDialect) -> &'static [&'static str] {
match dialect {
WireDialect::ChatCompletions => &[
"reasoning_effort",
"thinking",
"think",
"reasoning",
"reasoning_split",
"chat_template_kwargs",
],
WireDialect::AnthropicMessages => &["thinking", "output_config"],
WireDialect::OpenAiResponses => &["reasoning", "include"],
WireDialect::GoogleCloudCode => &[],
}
}
fn from_body(dialect: WireDialect, body: &Value, requested_effort: Option<String>) -> Self {
let mut wire_controls = Vec::new();
for key in Self::control_keys(dialect) {
if let Some(value) = body.get(*key) {
wire_controls.push(((*key).to_string(), value.clone()));
}
}
Self {
requested_effort,
wire_controls,
}
}
pub(crate) fn wire_effort_string(&self) -> Option<&str> {
self.wire_controls
.iter()
.find(|(key, _)| key == "reasoning_effort")
.and_then(|(_, value)| value.as_str())
}
pub(crate) fn wire_effort(&self) -> Option<(&'static str, &str)> {
if let Some(effort) = self.wire_effort_string() {
return Some(("reasoning_effort", effort));
}
for (key, value) in &self.wire_controls {
let Some(effort) = value.get("effort").and_then(Value::as_str) else {
continue;
};
let path = match key.as_str() {
"thinking" => "thinking.effort",
"reasoning" => "reasoning.effort",
"output_config" => "output_config.effort",
"think" => "think.effort",
"reasoning_split" => "reasoning_split.effort",
"chat_template_kwargs" => "chat_template_kwargs.effort",
_ => continue,
};
return Some((path, effort));
}
None
}
pub(crate) fn controls_reasoning(&self) -> bool {
self.wire_controls
.iter()
.any(|(key, _)| !REASONING_DISCLOSURE_ONLY_KEYS.contains(&key.as_str()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum CallerStreamMode {
Streaming,
Blocking,
}
impl CallerStreamMode {
pub(crate) fn from_stream_flag(stream: bool) -> Self {
if stream {
Self::Streaming
} else {
Self::Blocking
}
}
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Streaming => "streaming",
Self::Blocking => "blocking",
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct PreparedOutboundRequest {
pub(crate) dialect: WireDialect,
pub(crate) endpoint: EndpointIdentity,
pub(crate) wire_model: String,
pub(crate) body: Value,
pub(crate) reasoning: ReasoningReceipt,
pub(crate) replay_input_tokens: Option<u32>,
pub(crate) entrypoint: CallerStreamMode,
}
impl PreparedOutboundRequest {
pub(crate) fn new(
dialect: WireDialect,
endpoint: EndpointIdentity,
wire_model: String,
body: Value,
requested_effort: Option<String>,
replay_input_tokens: Option<u32>,
entrypoint: CallerStreamMode,
) -> Self {
let reasoning = ReasoningReceipt::from_body(dialect, &body, requested_effort);
Self {
dialect,
endpoint,
wire_model,
body,
reasoning,
replay_input_tokens,
entrypoint,
}
}
pub(crate) fn wire_stream_field(&self) -> Option<bool> {
self.body.get("stream").and_then(Value::as_bool)
}
pub(crate) fn canonical_body(&self) -> String {
canonical_json(&self.body)
}
pub(crate) fn body_sha256(&self) -> String {
crate::hashing::sha256_hex(self.canonical_body().as_bytes())
}
pub(crate) fn wire_view(&self) -> WireBodyView<'_> {
WireBodyView::extract(self.dialect, &self.body)
}
#[must_use]
pub(crate) fn with_route_id(mut self, route_id: Option<String>) -> Self {
self.endpoint.route_id = route_id;
self
}
pub(crate) fn endpoint_fingerprint(&self) -> String {
crate::hashing::sha256_hex(self.endpoint.url.as_bytes())
}
pub(crate) fn safe_endpoint_host_class(&self) -> String {
let Ok(url) = reqwest::Url::parse(&self.endpoint.url) else {
let digest = crate::hashing::sha256_hex(self.endpoint.url.as_bytes());
return format!("unparseable sha256:{}", &digest[..12]);
};
let scheme = match url.scheme() {
"http" => "http",
"https" => "https",
_ => "other",
};
let host = url.host_str().unwrap_or_default();
let loopback = host.eq_ignore_ascii_case("localhost")
|| host
.parse::<std::net::IpAddr>()
.is_ok_and(|address| address.is_loopback());
if loopback {
return format!("{scheme} loopback");
}
let authority = url.port().map_or_else(
|| host.to_ascii_lowercase(),
|port| format!("{}:{port}", host.to_ascii_lowercase()),
);
let digest = crate::hashing::sha256_hex(authority.as_bytes());
format!("{scheme} remote sha256:{}", &digest[..12])
}
pub(crate) fn wire_output_cap_tokens(&self) -> Option<u64> {
["max_tokens", "max_completion_tokens", "max_output_tokens"]
.into_iter()
.find_map(|key| self.body.get(key).and_then(Value::as_u64))
}
}
pub(crate) fn canonical_json(value: &Value) -> String {
let mut out = String::new();
write_canonical(value, &mut out);
out
}
fn write_canonical(value: &Value, out: &mut String) {
match value {
Value::Object(map) => {
let mut keys: Vec<&String> = map.keys().collect();
keys.sort_unstable();
out.push('{');
for (index, key) in keys.iter().enumerate() {
if index > 0 {
out.push(',');
}
push_json_string(key, out);
out.push(':');
write_canonical(&map[*key], out);
}
out.push('}');
}
Value::Array(items) => {
out.push('[');
for (index, item) in items.iter().enumerate() {
if index > 0 {
out.push(',');
}
write_canonical(item, out);
}
out.push(']');
}
other => out.push_str(&other.to_string()),
}
}
fn push_json_string(value: &str, out: &mut String) {
out.push_str(&Value::String(value.to_string()).to_string());
}
#[derive(Debug, Default)]
pub(crate) struct WireBodyView<'a> {
pub(crate) body_bytes: usize,
pub(crate) system_bytes: usize,
pub(crate) system_sha256: String,
pub(crate) tool_schema_bytes: usize,
pub(crate) tool_schema_sha256: String,
pub(crate) tool_count: usize,
pub(crate) items: Vec<&'a Value>,
pub(crate) item_bytes: usize,
pub(crate) tool_result_bytes: usize,
pub(crate) attachment_count: usize,
pub(crate) attachment_bytes: usize,
pub(crate) framing_bytes: usize,
}
impl<'a> WireBodyView<'a> {
fn extract(dialect: WireDialect, body: &'a Value) -> Self {
let body_bytes = canonical_json(body).len();
let mut view = Self {
body_bytes,
..Self::default()
};
let Some(object) = body.as_object() else {
view.framing_bytes = view.body_bytes;
return view;
};
let (system_key, items_key) = match dialect {
WireDialect::ChatCompletions => (None, "messages"),
WireDialect::AnthropicMessages => (Some("system"), "messages"),
WireDialect::OpenAiResponses => (Some("instructions"), "input"),
WireDialect::GoogleCloudCode => (None, "request"),
};
let mut system_region = String::new();
if let Some(key) = system_key
&& let Some(system) = object.get(key)
{
system_region.push_str(&canonical_json(system));
}
if let Some(tools) = object.get("tools") {
let canonical_tools = canonical_json(tools);
view.tool_schema_bytes = canonical_tools.len();
view.tool_schema_sha256 = crate::hashing::sha256_hex(canonical_tools.as_bytes());
view.tool_count = tools.as_array().map(Vec::len).unwrap_or(0);
}
if let Some(items_value) = object.get(items_key) {
let mut item_region_bytes = canonical_json(items_value).len();
if let Some(items) = items_value.as_array() {
for item in items {
let bytes = canonical_json(item).len();
if dialect == WireDialect::ChatCompletions
&& item.get("role").and_then(Value::as_str) == Some("system")
{
system_region.push_str(&canonical_json(item));
item_region_bytes = item_region_bytes.saturating_sub(bytes);
continue;
}
if is_tool_result_item(dialect, item) {
view.tool_result_bytes = view.tool_result_bytes.saturating_add(bytes);
}
let (count, attachment_bytes) = count_attachments(dialect, item);
view.attachment_count = view.attachment_count.saturating_add(count);
view.attachment_bytes = view.attachment_bytes.saturating_add(attachment_bytes);
view.items.push(item);
}
}
view.item_bytes = item_region_bytes;
}
view.system_bytes = system_region.len();
if !system_region.is_empty() {
view.system_sha256 = crate::hashing::sha256_hex(system_region.as_bytes());
}
view.framing_bytes = view
.body_bytes
.saturating_sub(view.system_bytes)
.saturating_sub(view.tool_schema_bytes)
.saturating_sub(view.item_bytes);
view
}
pub(crate) fn partition_is_exact(&self) -> bool {
self.system_bytes
.saturating_add(self.tool_schema_bytes)
.saturating_add(self.item_bytes)
.saturating_add(self.framing_bytes)
== self.body_bytes
}
}
fn is_tool_result_item(dialect: WireDialect, item: &Value) -> bool {
match dialect {
WireDialect::ChatCompletions => item.get("role").and_then(Value::as_str) == Some("tool"),
WireDialect::AnthropicMessages => item
.get("content")
.and_then(Value::as_array)
.is_some_and(|blocks| {
blocks
.iter()
.any(|block| block.get("type").and_then(Value::as_str) == Some("tool_result"))
}),
WireDialect::OpenAiResponses => {
item.get("type").and_then(Value::as_str) == Some("function_call_output")
}
WireDialect::GoogleCloudCode => false,
}
}
fn count_attachments(dialect: WireDialect, item: &Value) -> (usize, usize) {
let Some(parts) = item.get("content").and_then(Value::as_array) else {
return (0, 0);
};
let mut count = 0usize;
let mut bytes = 0usize;
for part in parts {
let part_type = part.get("type").and_then(Value::as_str);
let is_attachment = match dialect {
WireDialect::ChatCompletions => {
part_type == Some("image_url") || part.get("image_url").is_some()
}
WireDialect::AnthropicMessages => {
matches!(part_type, Some("image" | "document"))
}
WireDialect::OpenAiResponses => {
matches!(part_type, Some("input_image" | "input_file"))
}
WireDialect::GoogleCloudCode => false,
};
if !is_attachment {
continue;
}
count += 1;
bytes = bytes.saturating_add(canonical_json(part).len());
}
(count, bytes)
}
pub(crate) fn chat_route_shape(
provider: ApiProvider,
base_url: &str,
wire_model: &str,
url: &str,
) -> RouteShape {
if provider == ApiProvider::OpencodeZen {
return RouteShape::OpencodeZen;
}
if url.contains("/beta/chat/completions") {
return RouteShape::DeepseekBetaStrictTools;
}
if crate::config::is_exact_kimi_code_k3_route(provider, base_url, wire_model) {
return RouteShape::KimiCodeK3;
}
if crate::config::is_exact_direct_moonshot_k3_route(provider, base_url, wire_model) {
return RouteShape::DirectMoonshotK3;
}
if provider == ApiProvider::Custom {
return RouteShape::CustomCompatible;
}
RouteShape::Standard
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::{Map, json};
fn endpoint() -> EndpointIdentity {
EndpointIdentity {
provider_id: "deepseek".to_string(),
provider_display: "DeepSeek".to_string(),
route_id: None,
url: "https://api.deepseek.com/chat/completions".to_string(),
shape: RouteShape::Standard,
}
}
fn prepared(body: Value) -> PreparedOutboundRequest {
PreparedOutboundRequest::new(
WireDialect::ChatCompletions,
endpoint(),
"deepseek-chat".to_string(),
body,
Some("high".to_string()),
None,
CallerStreamMode::Streaming,
)
}
#[test]
fn canonical_json_is_key_order_independent() {
let a = json!({"b": 1, "a": {"z": 2, "y": [3, {"q": 4, "p": 5}]}});
let mut b = Map::new();
b.insert("a".to_string(), json!({"y": [3, {"p": 5, "q": 4}], "z": 2}));
b.insert("b".to_string(), json!(1));
assert_eq!(canonical_json(&a), canonical_json(&Value::Object(b)));
assert_eq!(
canonical_json(&a),
r#"{"a":{"y":[3,{"p":5,"q":4}],"z":2},"b":1}"#
);
}
#[test]
fn body_hash_covers_every_wire_field() {
let base = prepared(json!({
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "hi"}],
"max_tokens": 4096,
"tools": [{"type": "function", "function": {"name": "read_file"}}],
"tool_choice": {"type": "auto"},
"reasoning_effort": "high",
"stream": true,
}));
let baseline = base.body_sha256();
let mutations: Vec<(&str, Value)> = vec![
("max_tokens", json!(2048)),
("tool_choice", json!("required")),
("reasoning_effort", json!("low")),
("stream", json!(false)),
("temperature", json!(0.2)),
];
for (key, value) in mutations {
let mut body = base.body.clone();
body[key] = value;
assert_ne!(
baseline,
prepared(body).body_sha256(),
"mutating `{key}` must change the whole-body hash"
);
}
let mut nested = base.body.clone();
nested["tools"][0]["function"]["parameters"] = json!({"type": "object"});
assert_ne!(baseline, prepared(nested).body_sha256());
let mut thinking = base.body.clone();
thinking["thinking"] = json!({"type": "enabled", "effort": "max"});
assert_ne!(baseline, prepared(thinking).body_sha256());
}
#[test]
fn endpoint_host_class_never_prints_remote_authority_or_path() {
let hostile = |url: &str| {
let mut endpoint = endpoint();
endpoint.url = url.to_string();
PreparedOutboundRequest::new(
WireDialect::ChatCompletions,
endpoint,
"model".to_string(),
json!({"model": "model", "messages": []}),
None,
None,
CallerStreamMode::Streaming,
)
};
let token_host =
hostile("https://sk-live-abcdef0123456789.tenant.example/v1/deployments/secret/chat");
let same_host_other_path =
hostile("https://sk-live-abcdef0123456789.tenant.example/other/private/path");
let idn = hostile("https://秘密.example/private/path?api_key=secret");
let class = token_host.safe_endpoint_host_class();
assert_eq!(class, same_host_other_path.safe_endpoint_host_class());
assert_ne!(
token_host.endpoint_fingerprint(),
same_host_other_path.endpoint_fingerprint(),
"the separate full-endpoint fingerprint must still detect path drift"
);
for forbidden in ["sk-live", "tenant", "example", "deployment", "secret"] {
assert!(!class.contains(forbidden), "{forbidden} leaked in {class}");
}
let idn_class = idn.safe_endpoint_host_class();
for forbidden in ["秘密", "xn--", "example", "private", "api_key", "secret"] {
assert!(
!idn_class.contains(forbidden),
"{forbidden} leaked in {idn_class}"
);
}
assert!(class.starts_with("https remote sha256:"), "{class}");
assert!(class.len() <= 40, "{class}");
let loopback = hostile("http://127.0.0.1:8080/private/token-shaped-path");
assert_eq!(loopback.safe_endpoint_host_class(), "http loopback");
}
#[test]
fn wire_output_cap_is_read_only_from_the_finished_body() {
assert_eq!(
prepared(json!({"max_tokens": 1024})).wire_output_cap_tokens(),
Some(1024)
);
assert_eq!(
prepared(json!({"max_completion_tokens": 2048})).wire_output_cap_tokens(),
Some(2048)
);
assert_eq!(
prepared(json!({"model": "m"})).wire_output_cap_tokens(),
None
);
}
#[test]
fn reasoning_receipt_reads_the_finished_body_not_the_intent() {
let kimi = prepared(json!({
"model": "kimi-k3",
"messages": [],
"thinking": {"type": "enabled", "effort": "max"},
}));
assert_eq!(kimi.reasoning.requested_effort.as_deref(), Some("high"));
assert_eq!(kimi.reasoning.wire_effort_string(), None);
assert_eq!(
kimi.reasoning.wire_controls,
vec![(
"thinking".to_string(),
json!({"type": "enabled", "effort": "max"})
)]
);
}
#[test]
fn receipt_never_captures_message_or_prompt_fields() {
let leaky = prepared(json!({
"model": "m",
"messages": [{"role": "user", "content": "SECRET PROMPT"}],
"instructions": "SECRET INSTRUCTIONS",
"reasoning_effort": "high",
}));
let rendered = format!("{:?}", leaky.reasoning);
assert!(!rendered.contains("SECRET PROMPT"), "{rendered}");
assert!(!rendered.contains("SECRET INSTRUCTIONS"), "{rendered}");
}
#[test]
fn chat_view_folds_the_system_message_into_the_system_region() {
let request = prepared(json!({
"model": "m",
"messages": [
{"role": "system", "content": "SYS"},
{"role": "user", "content": "hi"},
{"role": "tool", "tool_call_id": "c1", "content": "OUT"},
],
"tools": [{"type": "function", "function": {"name": "a"}}],
"max_tokens": 100,
}));
let view = request.wire_view();
assert!(view.system_bytes > 0);
assert_eq!(view.items.len(), 2, "system message is not a turn item");
assert!(view.tool_result_bytes > 0);
assert_eq!(view.tool_count, 1);
assert!(view.framing_bytes > 0);
}
#[test]
fn anthropic_and_responses_views_use_their_own_shapes() {
let anthropic_request = PreparedOutboundRequest::new(
WireDialect::AnthropicMessages,
endpoint(),
"claude".to_string(),
json!({
"model": "claude",
"system": "SYS",
"messages": [
{"role": "user", "content": [{"type": "tool_result", "content": "OUT"}]},
{"role": "user", "content": [{"type": "image", "source": {"data": "AAA"}}]},
],
"tools": [{"name": "a"}, {"name": "b"}],
}),
None,
None,
CallerStreamMode::Streaming,
);
let anthropic = anthropic_request.wire_view();
assert!(anthropic.system_bytes > 0);
assert_eq!(anthropic.items.len(), 2);
assert!(anthropic.tool_result_bytes > 0);
assert_eq!(anthropic.attachment_count, 1);
assert_eq!(anthropic.tool_count, 2);
let responses_request = PreparedOutboundRequest::new(
WireDialect::OpenAiResponses,
endpoint(),
"gpt".to_string(),
json!({
"model": "gpt",
"instructions": "SYS",
"input": [
{"type": "message", "role": "user", "content": [{"type": "input_text"}]},
{"type": "function_call_output", "output": "OUT"},
],
"tools": [{"name": "a"}],
}),
None,
None,
CallerStreamMode::Streaming,
);
let responses = responses_request.wire_view();
assert!(responses.system_bytes > 0);
assert_eq!(responses.items.len(), 2);
assert!(responses.tool_result_bytes > 0);
assert_eq!(responses.tool_count, 1);
}
#[test]
fn nested_reasoning_efforts_are_read_from_every_dialect() {
let kimi = prepared(json!({
"model": "kimi-k3",
"messages": [],
"thinking": {"type": "enabled", "effort": "max"},
}));
assert_eq!(
kimi.reasoning.wire_effort(),
Some(("thinking.effort", "max"))
);
assert!(kimi.reasoning.controls_reasoning());
let responses = PreparedOutboundRequest::new(
WireDialect::OpenAiResponses,
endpoint(),
"gpt".to_string(),
json!({
"model": "gpt",
"input": [],
"reasoning": {"effort": "high", "summary": "auto"},
"include": ["reasoning.encrypted_content"],
}),
None,
None,
CallerStreamMode::Streaming,
);
assert_eq!(
responses.reasoning.wire_effort(),
Some(("reasoning.effort", "high"))
);
assert!(responses.reasoning.controls_reasoning());
let anthropic = PreparedOutboundRequest::new(
WireDialect::AnthropicMessages,
endpoint(),
"claude".to_string(),
json!({
"model": "claude",
"messages": [],
"output_config": {"effort": "low"},
}),
None,
None,
CallerStreamMode::Streaming,
);
assert_eq!(
anthropic.reasoning.wire_effort(),
Some(("output_config.effort", "low"))
);
let chat = prepared(json!({
"model": "m",
"messages": [],
"reasoning_effort": "medium",
}));
assert_eq!(
chat.reasoning.wire_effort(),
Some(("reasoning_effort", "medium"))
);
}
#[test]
fn responses_include_alone_is_not_a_reasoning_control() {
let disclosure_only = PreparedOutboundRequest::new(
WireDialect::OpenAiResponses,
endpoint(),
"gpt".to_string(),
json!({
"model": "gpt",
"input": [],
"include": ["reasoning.encrypted_content"],
}),
None,
None,
CallerStreamMode::Streaming,
);
assert!(
!disclosure_only.reasoning.wire_controls.is_empty(),
"`include` is still disclosed on the receipt"
);
assert!(
!disclosure_only.reasoning.controls_reasoning(),
"`include` alone must not read as a reasoning request"
);
assert_eq!(disclosure_only.reasoning.wire_effort(), None);
}
fn assert_partition_exact(request: &PreparedOutboundRequest, what: &str) {
let view = request.wire_view();
assert_eq!(
view.body_bytes,
request.canonical_body().len(),
"{what}: the view must measure the bytes that would be POSTed"
);
assert!(
view.partition_is_exact(),
"{what}: {} + {} + {} + {} != {}",
view.system_bytes,
view.tool_schema_bytes,
view.item_bytes,
view.framing_bytes,
view.body_bytes
);
assert!(view.tool_result_bytes <= view.item_bytes, "{what}");
assert!(view.attachment_bytes <= view.item_bytes, "{what}");
}
#[test]
fn byte_classes_sum_to_the_whole_wire_body_in_every_dialect() {
assert_partition_exact(
&prepared(json!({
"model": "m",
"messages": [
{"role": "system", "content": "SYS"},
{"role": "user", "content": "hi"},
{"role": "tool", "tool_call_id": "c1", "content": "OUT"},
],
"tools": [{"type": "function", "function": {"name": "a"}}],
"tool_choice": {"type": "auto"},
"max_tokens": 100,
"stream": true,
})),
"chat streaming",
);
assert_partition_exact(
&prepared(json!({
"model": "m",
"messages": [{"role": "user", "content": "hi"}],
"max_tokens": 100,
})),
"chat blocking (no tools, no system, no stream field)",
);
assert_partition_exact(
&prepared(json!({"model": "m", "messages": []})),
"chat minimal",
);
assert_partition_exact(
&PreparedOutboundRequest::new(
WireDialect::AnthropicMessages,
endpoint(),
"claude".to_string(),
json!({
"model": "claude",
"system": [{"type": "text", "text": "SYS"}],
"messages": [
{"role": "user", "content": [{"type": "tool_result", "content": "OUT"}]},
{"role": "user", "content": [{"type": "image", "source": {"data": "AAA"}}]},
],
"tools": [{"name": "a"}],
"stream": true,
}),
None,
None,
CallerStreamMode::Streaming,
),
"anthropic streaming",
);
assert_partition_exact(
&PreparedOutboundRequest::new(
WireDialect::OpenAiResponses,
endpoint(),
"gpt".to_string(),
json!({
"model": "gpt",
"instructions": "SYS",
"input": [
{"type": "message", "role": "user", "content": [{"type": "input_text"}]},
{"type": "function_call_output", "output": "OUT"},
],
"tools": [{"name": "a"}],
"reasoning": {"effort": "high"},
"stream": true,
}),
None,
None,
CallerStreamMode::Blocking,
),
"responses blocking entry point (wire still streams)",
);
}
#[test]
fn byte_classes_track_the_region_that_changed() {
let base = json!({
"model": "m",
"messages": [
{"role": "system", "content": "SYS"},
{"role": "user", "content": "hi"},
],
"tools": [{"type": "function", "function": {"name": "a"}}],
"max_tokens": 100,
});
let baseline = prepared(base.clone());
let baseline_view = baseline.wire_view();
let mut bigger_system = base.clone();
bigger_system["messages"][0]["content"] = json!("SYSTEM PROMPT, MUCH LONGER");
let request = prepared(bigger_system);
let view = request.wire_view();
assert_partition_exact(&request, "grown system");
assert!(view.system_bytes > baseline_view.system_bytes);
assert_eq!(view.item_bytes, baseline_view.item_bytes);
let mut bigger_tools = base.clone();
bigger_tools["tools"][0]["function"]["parameters"] = json!({"type": "object"});
let request = prepared(bigger_tools);
let view = request.wire_view();
assert_partition_exact(&request, "grown tool schema");
assert!(view.tool_schema_bytes > baseline_view.tool_schema_bytes);
assert_ne!(view.tool_schema_sha256, baseline_view.tool_schema_sha256);
let mut extra_message = base.clone();
extra_message["messages"]
.as_array_mut()
.expect("messages array")
.push(json!({"role": "user", "content": "the hypothetical next prompt"}));
let request = prepared(extra_message);
let view = request.wire_view();
assert_partition_exact(&request, "appended message");
assert!(view.item_bytes > baseline_view.item_bytes);
assert_eq!(view.system_bytes, baseline_view.system_bytes);
let mut extra_framing = base;
extra_framing["stream_options"] = json!({"include_usage": true});
let request = prepared(extra_framing);
let view = request.wire_view();
assert_partition_exact(&request, "added framing field");
assert!(view.framing_bytes > baseline_view.framing_bytes);
assert_eq!(view.item_bytes, baseline_view.item_bytes);
}
#[test]
fn wire_tool_hash_tracks_dialect_schema_shaping() {
let logical = prepared(json!({
"model": "m",
"messages": [],
"tools": [{"type": "function", "function": {"name": "a", "parameters": {"type": "object"}}}],
}));
let shaped = prepared(json!({
"model": "m",
"messages": [],
"tools": [{"type": "function", "function": {
"name": "a",
"parameters": {"type": "object", "additionalProperties": false},
"strict": true,
}}],
}));
assert_ne!(
logical.wire_view().tool_schema_sha256,
shaped.wire_view().tool_schema_sha256,
"strict-mode schema sanitizing must move the wire tool hash"
);
let toolless = prepared(json!({"model": "m", "messages": []}));
assert!(toolless.wire_view().tool_schema_sha256.is_empty());
}
#[test]
fn dialect_labels_are_stable() {
assert_eq!(
WireDialect::from_wire_format(WireFormat::ChatCompletions).as_str(),
"chat-completions"
);
assert_eq!(
WireDialect::from_wire_format(WireFormat::AnthropicMessages).as_str(),
"anthropic-messages"
);
assert_eq!(
WireDialect::from_wire_format(WireFormat::Responses).as_str(),
"openai-responses"
);
}
}
#[cfg(test)]
mod dialect_seam_tests {
use super::*;
use crate::config::{Config, ProviderConfig, ProvidersConfig};
use crate::models::{ContentBlock, Message, MessageRequest, SystemPrompt, Tool};
use serde_json::json;
use super::super::DeepSeekClient;
fn tool(name: &str) -> Tool {
Tool {
tool_type: None,
name: name.to_string(),
description: format!("{name} description"),
input_schema: json!({"type": "object", "properties": {}}),
allowed_callers: None,
defer_loading: None,
input_examples: None,
strict: None,
cache_control: None,
}
}
fn request(model: &str) -> MessageRequest {
MessageRequest {
model: model.to_string(),
messages: vec![Message {
role: "user".to_string(),
content: vec![ContentBlock::Text {
text: "hello".to_string(),
cache_control: None,
}],
}],
max_tokens: 4096,
system: Some(SystemPrompt::Text("BASE PROMPT".to_string())),
tools: Some(vec![tool("read_file"), tool("Bash")]),
tool_choice: Some(json!({"type": "auto"})),
metadata: None,
thinking: None,
reasoning_effort: Some("high".to_string()),
stream: Some(true),
temperature: None,
top_p: None,
}
}
fn client(provider: &str, configure: impl FnOnce(&mut ProvidersConfig)) -> DeepSeekClient {
let mut providers = ProvidersConfig::default();
configure(&mut providers);
DeepSeekClient::new(&Config {
provider: Some(provider.to_string()),
providers: Some(providers),
..Config::default()
})
.expect("client resolves for this route")
}
fn configured(api_key: &str, base_url: Option<&str>, model: &str) -> ProviderConfig {
ProviderConfig {
api_key: Some(api_key.to_string()),
base_url: base_url.map(str::to_string),
model: Some(model.to_string()),
..ProviderConfig::default()
}
}
fn sha256(value: &str) -> String {
crate::hashing::sha256_hex(value.as_bytes())
}
fn preprocessed(client: &DeepSeekClient, request: MessageRequest) -> MessageRequest {
client
.bind_request_to_protocol(client.prepare_model_bound_request(request))
.expect("protocol binding succeeds")
}
#[test]
fn chat_completions_preview_matches_the_production_chat_builder() {
let client = client("deepseek", |providers| {
providers.deepseek = configured("sk-test-deepseek", None, "deepseek-chat");
});
let prepared = client
.prepare_outbound_request(request("deepseek-chat"), true)
.expect("chat request prepares");
assert_eq!(prepared.dialect, WireDialect::ChatCompletions);
let reference = super::super::chat::build_chat_wire_body(
&preprocessed(&client, request("deepseek-chat")),
client.api_provider(),
client.base_url(),
true,
)
.expect("reference body builds");
assert_eq!(
prepared.body_sha256(),
sha256(&canonical_json(&reference.body))
);
assert_eq!(prepared.wire_model, reference.model);
assert!(
prepared.body.get("tool_choice").is_none(),
"DeepSeek thinking requests omit tool_choice on the final wire body"
);
}
#[test]
fn kimi_code_keeps_its_own_shape_and_is_not_projected_through_plain_chat() {
let client = client("moonshot", |providers| {
providers.moonshot = configured(
"sk-test-kimi",
Some(crate::config::DEFAULT_KIMI_CODE_BASE_URL),
crate::config::KIMI_CODE_K3_MODEL,
);
});
let prepared = client
.prepare_outbound_request(request(crate::config::KIMI_CODE_K3_MODEL), true)
.expect("kimi code request prepares");
assert_eq!(prepared.dialect, WireDialect::ChatCompletions);
assert_eq!(prepared.endpoint.shape, RouteShape::KimiCodeK3);
assert_eq!(prepared.reasoning.wire_effort_string(), None);
assert!(
prepared
.reasoning
.wire_controls
.iter()
.any(|(key, _)| key == "thinking"),
"{:?}",
prepared.reasoning
);
let reference = super::super::chat::build_chat_wire_body(
&preprocessed(&client, request(crate::config::KIMI_CODE_K3_MODEL)),
client.api_provider(),
client.base_url(),
true,
)
.expect("reference body builds");
assert_eq!(
prepared.body_sha256(),
sha256(&canonical_json(&reference.body))
);
}
#[test]
fn anthropic_messages_preview_matches_the_production_messages_builder() {
type ProviderCase = (
&'static str,
&'static str,
Box<dyn Fn(&mut ProvidersConfig)>,
);
let cases: Vec<ProviderCase> = vec![
(
"anthropic",
"claude-sonnet-4-5",
Box::new(|providers: &mut ProvidersConfig| {
providers.anthropic = configured("sk-ant-test", None, "claude-sonnet-4-5");
}),
),
(
"deepseek-anthropic",
"deepseek-v4",
Box::new(|providers: &mut ProvidersConfig| {
providers.deepseek_anthropic =
configured("sk-test-deepseek-anthropic", None, "deepseek-v4");
}),
),
(
"minimax-anthropic",
"MiniMax-M3",
Box::new(|providers: &mut ProvidersConfig| {
providers.minimax_anthropic =
configured("sk-test-minimax-anthropic", None, "MiniMax-M3");
}),
),
];
for (provider, model, configure) in cases {
let client = client(provider, |providers| configure(providers));
let prepared = client
.prepare_outbound_request(request(model), true)
.unwrap_or_else(|error| panic!("{provider} request prepares: {error}"));
assert_eq!(
prepared.dialect,
WireDialect::AnthropicMessages,
"{provider} must keep the Messages dialect, not be projected through Chat"
);
let reference =
client.build_anthropic_body(&preprocessed(&client, request(model)), true);
assert_eq!(
prepared.body_sha256(),
sha256(&canonical_json(&reference)),
"{provider} preview body must hash identically to the production builder"
);
assert_eq!(
prepared
.body
.get("tool_choice")
.and_then(|value| value.get("type"))
.and_then(serde_json::Value::as_str),
Some("auto"),
"{provider} tool_choice must come from the final Messages body"
);
assert_eq!(prepared.reasoning.wire_effort_string(), None, "{provider}");
assert_eq!(
prepared.reasoning.requested_effort.as_deref(),
Some("high"),
"{provider}"
);
}
}
fn codex_client() -> DeepSeekClient {
let _env_lock = crate::test_support::lock_test_env();
let _codex_token =
crate::test_support::EnvVarGuard::set("OPENAI_CODEX_ACCESS_TOKEN", "test-token");
let _legacy_codex_token = crate::test_support::EnvVarGuard::remove("CODEX_ACCESS_TOKEN");
client("openai-codex", |providers| {
providers.openai_codex = configured("", None, "gpt-5-codex");
})
}
#[test]
fn responses_preview_matches_the_production_responses_builder() {
let client = codex_client();
let prepared = client
.prepare_outbound_request(request("gpt-5-codex"), true)
.expect("responses request prepares");
assert_eq!(prepared.dialect, WireDialect::OpenAiResponses);
assert_eq!(prepared.endpoint.shape, RouteShape::CodexResponses);
let reference = super::super::responses::build_responses_body(&preprocessed(
&client,
request("gpt-5-codex"),
));
assert_eq!(prepared.body_sha256(), sha256(&canonical_json(&reference)));
assert_eq!(prepared.body.get("tool_choice"), Some(&json!("auto")));
}
#[test]
fn every_dialect_reports_a_distinct_body_hash_for_the_same_logical_request() {
let chat = client("deepseek", |providers| {
providers.deepseek = configured("sk-test-deepseek", None, "deepseek-chat");
})
.prepare_outbound_request(request("deepseek-chat"), true)
.expect("chat prepares");
let codex = codex_client();
let responses = codex
.prepare_outbound_request(request("gpt-5-codex"), true)
.expect("responses prepares");
assert_ne!(chat.dialect, responses.dialect);
assert_ne!(chat.body_sha256(), responses.body_sha256());
}
#[test]
fn streaming_and_blocking_bodies_are_distinguished_not_conflated() {
let client = client("deepseek", |providers| {
providers.deepseek = configured("sk-test-deepseek", None, "deepseek-chat");
});
let streaming = client
.prepare_outbound_request(request("deepseek-chat"), true)
.expect("streaming prepares");
let blocking = client
.prepare_outbound_request(request("deepseek-chat"), false)
.expect("blocking prepares");
assert_eq!(streaming.entrypoint, CallerStreamMode::Streaming);
assert_eq!(blocking.entrypoint, CallerStreamMode::Blocking);
assert_eq!(streaming.wire_stream_field(), Some(true));
assert_eq!(blocking.wire_stream_field(), None);
assert_ne!(streaming.body_sha256(), blocking.body_sha256());
}
#[test]
fn responses_wire_streaming_is_read_from_the_body_not_the_caller_mode() {
let client = codex_client();
let blocking = client
.prepare_outbound_request(request("gpt-5-codex"), false)
.expect("blocking responses prepares");
assert_eq!(blocking.entrypoint, CallerStreamMode::Blocking);
assert_eq!(
blocking.wire_stream_field(),
Some(true),
"the Responses blocking path genuinely sends an SSE body"
);
let streaming = client
.prepare_outbound_request(request("gpt-5-codex"), true)
.expect("streaming responses prepares");
assert_eq!(streaming.wire_stream_field(), Some(true));
assert_eq!(
streaming.body_sha256(),
blocking.body_sha256(),
"the two Responses entry points send the same bytes; only the \
caller mode differs"
);
}
#[test]
fn preparation_is_deterministic_across_repeated_calls() {
let client = client("deepseek", |providers| {
providers.deepseek = configured("sk-test-deepseek", None, "deepseek-chat");
});
let first = client
.prepare_outbound_request(request("deepseek-chat"), true)
.expect("first prepares");
let second = client
.prepare_outbound_request(request("deepseek-chat"), true)
.expect("second prepares");
assert_eq!(first.body_sha256(), second.body_sha256());
assert_eq!(first.endpoint_fingerprint(), second.endpoint_fingerprint());
}
}