diff --git a/crates/busbar-plane-llm/src/codec/anthropic/tests/ir_mapping_q57_tests.rs b/crates/busbar-plane-llm/src/codec/anthropic/tests/ir_mapping_q57_tests.rs index 3dda799761..2f18a364d7 100644 --- a/crates/busbar-plane-llm/src/codec/anthropic/tests/ir_mapping_q57_tests.rs +++ b/crates/busbar-plane-llm/src/codec/anthropic/tests/ir_mapping_q57_tests.rs @@ -734,3 +734,46 @@ fn ant18_search_result_citation_source_maps_both_ways() { "title": "KB", "search_result_index": 0, "start_block_index": 0, "end_block_index": 1}), ); } + +/// F25 REHOME row 110, from `crates/busbar-llm/src/engine/tests/egress_dropped_controls_audit_tests.rs:245` +/// (`native_structured_output_lane_drops_schema_less_json_with_audit`): on a lane declaring native +/// structured outputs a `json_schema` directive rides `output_config.format.type: json_schema` with no +/// `tool_choice`; a schema-less `json_object` writes no `output_config` and no `tools`, and is reported +/// as the dropped control `response_format` (the caller's `response_format on anthropic` audit row). +#[test] +fn native_structured_output_lane_drops_schema_less_json_and_reports_it() { + let native = busbar_contract::ir::egress_prep::LaneCaps { + native_structured_output: true, + ..Default::default() + }; + let schema = json!({"type": "object", "properties": {"a": {"type": "string"}}}); + let with_schema = xreq_lane( + "openai", + "anthropic", + &json!({"model": "gpt-4o", "messages": [{"role": "user", "content": "hi"}], + "response_format": {"type": "json_schema", "json_schema": {"name": "o", "schema": schema}}}), + &native, + ); + assert_eq!( + with_schema.pointer("/output_config/format/type"), + Some(&json!("json_schema")), + "{with_schema}" + ); + assert!(with_schema.get("tool_choice").is_none(), "{with_schema}"); + + let object_body = json!({"model": "gpt-4o", "messages": [{"role": "user", "content": "hi"}], + "response_format": {"type": "json_object"}}); + let object_mode = xreq_lane("openai", "anthropic", &object_body, &native); + assert!(object_mode.get("output_config").is_none(), "{object_mode}"); + assert!(object_mode.get("tools").is_none(), "{object_mode}"); + let ir = protocol_for("openai") + .unwrap() + .reader() + .read_request(&object_body) + .unwrap(); + assert!(protocol_for("anthropic") + .unwrap() + .writer() + .dropped_egress_controls_for_lane(&ir, &native) + .contains(&"response_format")); +} diff --git a/crates/busbar-plane-llm/src/codec/bedrock/tests/tests.rs b/crates/busbar-plane-llm/src/codec/bedrock/tests/tests.rs index d108b63a45..33dbd53cff 100644 --- a/crates/busbar-plane-llm/src/codec/bedrock/tests/tests.rs +++ b/crates/busbar-plane-llm/src/codec/bedrock/tests/tests.rs @@ -6771,3 +6771,67 @@ fn the_tails_sigv4_parameters_restate_the_signing_scheme() { .as_str() .is_some_and(|w| w.contains("defaulting SigV4 scope to us-east-1"))); } + +// ── F25 REHOME (row 108) ───────────────────────────────────────────────────────────────────────── + +/// The production request translate steps (reader → egress seam → writer → shim strip), with the +/// prepared IR beside the written body. +fn rehome_xreq( + ingress: &'static str, + egress: &str, + body: &serde_json::Value, +) -> (crate::codec::ir::IrRequest, serde_json::Value) { + let ingress_p = crate::codec::proto_codec::protocol_for(ingress).expect("ingress"); + let egress_p = crate::codec::proto_codec::protocol_for(egress).expect("egress"); + let mut req = ingress_p.reader().read_request(body).expect("read_request"); + crate::codec::chat_handle::chat_prepare_for_egress( + &mut req, + &busbar_contract::ir::egress_prep::EgressPrep { + thought_signature_fill: false, + ingress_protocol: ingress, + egress_requires_max_tokens: egress_p.decl().is_some_and(|d| d.requires_max_tokens), + lane_default_max_tokens: None, + global_default_max_tokens: 4096, + reasoning_allowed: true, + reasoning_budgets: crate::codec::ir::REASONING_BUDGET_DEFAULTS, + prompt_caching_allowed: true, + cache_control_cap: None, + lane_caps: Default::default(), + }, + ); + let mut out = egress_p.writer().write_request(&req); + crate::codec::wire_shim::strip_router_shim_keys(&mut out, egress); + (req, out) +} + +/// F25 REHOME row 108, from `crates/busbar-llm/src/engine/tests/egress_dropped_controls_audit_tests.rs:89` +/// (`openai_to_bedrock_tool_choice_none_forwards_and_audits_degraded`): an OpenAI caller's tools plus +/// `tool_choice: "none"` onto Bedrock still forwards (a non-empty body, the tool kept, `toolChoice` +/// omitted: Converse has no "do not call a tool"), and the drop is reported as the control +/// `tool_choice=none`, the name the caller's `egress.control_unrepresentable` audit row carries +/// (`tool_choice=none on bedrock`). +#[test] +fn openai_tool_choice_none_onto_bedrock_forwards_and_reports_the_drop() { + let body = serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}], + "tools": [{ + "type": "function", + "function": {"name": "get_weather", "parameters": {"type": "object"}} + }], + "tool_choice": "none" + }); + let (req, out) = rehome_xreq("openai", "bedrock", &body); + assert!(out.as_object().is_some_and(|o| !o.is_empty()), "{out}"); + assert_eq!( + out.pointer("/toolConfig/tools/0/toolSpec/name"), + Some(&serde_json::json!("get_weather")), + "{out}" + ); + assert!(out.pointer("/toolConfig/toolChoice").is_none(), "{out}"); + let dropped = crate::codec::proto_codec::protocol_for("bedrock") + .expect("bedrock") + .writer() + .dropped_egress_controls(&req); + assert!(dropped.contains(&"tool_choice=none"), "{dropped:?}"); +} diff --git a/crates/busbar-plane-llm/src/codec/tests/proto/response_format_matrix_tests.rs b/crates/busbar-plane-llm/src/codec/tests/proto/response_format_matrix_tests.rs index 4006c7d9fe..b1c82ec355 100644 --- a/crates/busbar-plane-llm/src/codec/tests/proto/response_format_matrix_tests.rs +++ b/crates/busbar-plane-llm/src/codec/tests/proto/response_format_matrix_tests.rs @@ -391,3 +391,71 @@ fn parallel_tool_calls_discarded_on_gemini_cohere_bedrock_warns() { cap2.messages() ); } + +// ── F25 REHOME (rows 613, 614): the Cohere stop-sequence cap ───────────────────────────────────── + +/// The production request translate steps onto `egress` (reader → egress seam → writer). +fn rehome_xreq(ingress: &'static str, egress: &str, body: &serde_json::Value) -> serde_json::Value { + let ingress_p = crate::codec::proto_codec::protocol_for(ingress).expect("ingress"); + let egress_p = crate::codec::proto_codec::protocol_for(egress).expect("egress"); + let mut req = ingress_p.reader().read_request(body).expect("read_request"); + crate::codec::chat_handle::chat_prepare_for_egress( + &mut req, + &busbar_contract::ir::egress_prep::EgressPrep { + thought_signature_fill: false, + ingress_protocol: ingress, + egress_requires_max_tokens: egress_p.decl().is_some_and(|d| d.requires_max_tokens), + lane_default_max_tokens: None, + global_default_max_tokens: 4096, + reasoning_allowed: true, + reasoning_budgets: crate::codec::ir::REASONING_BUDGET_DEFAULTS, + prompt_caching_allowed: true, + cache_control_cap: None, + lane_caps: Default::default(), + }, + ); + egress_p.writer().write_request(&req) +} + +/// F25 REHOME row 613, from `crates/busbar-llm/src/engine/tests/stop_sequence_cap_degrade_tests.rs:23` +/// (`openai_to_cohere_over_cap_stop_sequences_is_clamped_not_rejected`): an OpenAI caller's six stop +/// sequences onto a Cohere lane are CLAMPED to Cohere's cap of 5 and forwarded, not rejected +/// (v1.5.4's truncate-and-forward). +#[test] +fn openai_to_cohere_over_cap_stop_sequences_are_clamped_not_rejected() { + let out = rehome_xreq( + "openai", + "cohere", + &serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}], + "stop": ["a", "b", "c", "d", "e", "f"] + }), + ); + assert_eq!( + out.get("stop_sequences"), + Some(&serde_json::json!(["a", "b", "c", "d", "e"])), + "an over-cap stop list is clamped to Cohere's cap of 5: {out}" + ); +} + +/// F25 REHOME row 614, from `crates/busbar-llm/src/engine/tests/stop_sequence_cap_degrade_tests.rs:63` +/// (`openai_to_cohere_exactly_cap_stop_sequences_is_allowed`): exactly five stop sequences is within +/// Cohere's cap and is forwarded whole. +#[test] +fn openai_to_cohere_exactly_cap_stop_sequences_are_forwarded_whole() { + let out = rehome_xreq( + "openai", + "cohere", + &serde_json::json!({ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "hi"}], + "stop": ["a", "b", "c", "d", "e"] + }), + ); + assert_eq!( + out.get("stop_sequences"), + Some(&serde_json::json!(["a", "b", "c", "d", "e"])), + "an at-cap stop list is forwarded whole: {out}" + ); +} diff --git a/crates/busbar-plane-llm/src/codec/tests/proto/stream_translate_tests.rs b/crates/busbar-plane-llm/src/codec/tests/proto/stream_translate_tests.rs index 54bd198ef1..b51434b9f7 100644 --- a/crates/busbar-plane-llm/src/codec/tests/proto/stream_translate_tests.rs +++ b/crates/busbar-plane-llm/src/codec/tests/proto/stream_translate_tests.rs @@ -5081,3 +5081,53 @@ fn responses_failed_stream_bills_its_reported_usage() { assert!(none.usage().is_none()); assert!(none.terminal_error().is_some()); } + +/// F25 REHOME row 350, from `crates/busbar-llm/src/engine/tests/ingress_indistinguishability_tests.rs:2234` +/// (`test_forward_once_cross_protocol_remaps_tool_call_id`): an Anthropic answer whose `tool_use` id +/// is `toolu_origRAW123`, delivered to an OpenAI caller through the production ingress seam +/// (`chat_prepare_for_ingress`, which every attempt path runs), carries an OpenAI-native `call_…` +/// id, never the raw one, and the raw id appears nowhere in the client body. The client's echo of +/// that id decodes back to the far end's original. +#[test] +fn an_anthropic_tool_call_id_reaches_an_openai_client_remapped_and_unleaked() { + let ir_resp = AnthropicReader + .read_response(&serde_json::json!({ + "id": "msg_x", + "type": "message", + "role": "assistant", + "content": [{ + "type": "tool_use", + "id": "toolu_origRAW123", + "name": "get_weather", + "input": {"city": "SF"} + }], + "model": "claude-3", + "stop_reason": "tool_use", + "usage": {"input_tokens": 3, "output_tokens": 2} + })) + .expect("anthropic read"); + let mut ir_resp = ir_resp; + crate::codec::chat_handle::chat_prepare_for_ingress(&mut ir_resp, "openai", 1_752_000_000); + let body = crate::codec::proto_codec::protocol_for("openai") + .expect("openai") + .writer() + .write_response(&ir_resp); + let tool_id = body + .pointer("/choices/0/message/tool_calls/0/id") + .and_then(|s| s.as_str()) + .unwrap_or_else(|| panic!("expected an OpenAI tool_calls id in: {body}")) + .to_string(); + assert!(tool_id.starts_with("call_"), "{tool_id}"); + assert_ne!(tool_id, "toolu_origRAW123"); + let bytes = serde_json::to_vec(&body).expect("serializes"); + assert!( + !bytes + .windows("toolu_origRAW123".len()) + .any(|w| w == b"toolu_origRAW123"), + "the raw Anthropic tool id must not appear anywhere in the client body: {body}" + ); + assert_eq!( + decode_native_tool_id("openai", &tool_id).as_deref(), + Some("toolu_origRAW123") + ); +} diff --git a/crates/busbar-plane-llm/src/codec/tests/wire_shim_tests.rs b/crates/busbar-plane-llm/src/codec/tests/wire_shim_tests.rs index c4557c2832..f68c4f28b4 100644 --- a/crates/busbar-plane-llm/src/codec/tests/wire_shim_tests.rs +++ b/crates/busbar-plane-llm/src/codec/tests/wire_shim_tests.rs @@ -297,3 +297,92 @@ fn a_transcription_duration_ledgers_one_audio_ms_line() { fn a_flat_answer_ledgers_no_open_class() { assert!(non_token_open(busbar_contract::billing::Billing::Flat).is_empty()); } + +// ── F25 REHOME (rows 504, 505): the router shim keys ───────────────────────────────────────────── + +const SHIM: &str = crate::codec::gemini::GEMINI_JSON_ARRAY_SHIM_KEY; + +/// F25 REHOME row 504, from `crates/busbar-llm/src/engine/tests/mid_stream_error_tests.rs:294` (`test_strip_router_shim_keys`): +/// `strip_router_shim_keys` removes the never-native shim keys per egress — the gemini array key for +/// every egress, `stream` for a path-model egress (bedrock, gemini) — and never `model`; a +/// body-model egress (openai) keeps its genuine `model` and `stream`. +#[test] +fn strip_router_shim_keys_removes_the_never_native_keys_per_egress() { + use super::strip_router_shim_keys; + use serde_json::json; + let mut v = json!({"model": "p", "stream": true, (SHIM): true, "messages": []}); + assert!(strip_router_shim_keys(&mut v, "bedrock")); + assert_eq!( + v["model"], "p", + "model NOT stripped here (rewrite_model owns it)" + ); + assert!(v.get("stream").is_none(), "bedrock: stream shim stripped"); + assert!( + v.get(SHIM).is_none(), + "gemini array shim key stripped on every protocol" + ); + assert!(v.get("messages").is_some(), "real fields retained"); + + let mut v = json!({"stream": true, (SHIM): true}); + assert!(strip_router_shim_keys(&mut v, "gemini")); + assert!(v.get("stream").is_none() && v.get(SHIM).is_none()); + + let mut v = json!({"model": "gpt-4o", "stream": true, (SHIM): true}); + assert!(strip_router_shim_keys(&mut v, "openai")); + assert_eq!( + v["model"], "gpt-4o", + "openai model is genuine, not stripped" + ); + assert_eq!(v["stream"], true, "openai stream is genuine, not stripped"); + assert!( + v.get(SHIM).is_none(), + "gemini array key stripped even for body-model ingress" + ); +} + +/// F25 REHOME row 505, from `crates/busbar-llm/src/engine/tests/mid_stream_error_tests.rs:337` +/// (`test_shim_strip_ordering_cross_protocol_keeps_model`): a path-model ingress (gemini) crossing to +/// a body-model egress (openai) reaches the far end WITH the authoritative model: strip, then +/// `rewrite_model_if_needed`, leaves `model: gpt-4o` and the writer-authored `stream: true`, with +/// the array key gone; within one dialect (gemini→gemini) the relay sends the caller's own bytes, +/// with the spliced `model` and `stream` removed. +#[test] +fn shim_strip_then_rewrite_model_keeps_the_model_cross_protocol() { + use super::strip_router_shim_keys; + use serde_json::json; + let mut v = json!({"model": "router-placeholder", "stream": true, (SHIM): true}); + strip_router_shim_keys(&mut v, "openai"); + crate::codec::proto_codec::protocol_for("openai") + .expect("openai") + .writer() + .rewrite_model_if_needed(&mut v, "gpt-4o"); + assert_eq!( + v["model"], "gpt-4o", + "cross-protocol egress body MUST carry the authoritative model" + ); + assert_eq!( + v["stream"], true, + "writer-authored `stream` survives for a body-model egress" + ); + assert!( + v.get(SHIM).is_none(), + "gemini array key stripped cross-protocol" + ); + + let caller = br#"{ "contents": [ ], "z" : 1.0 }"#; + let carried = br#"{"model":"router-placeholder","stream":true, "contents": [ ], "z" : 1.0 }"#; + let far = crate::exchange::shaping::FarShape { + dialect: "gemini", + wire_model: "gemini-1.5-pro", + default_max_tokens: None, + prompt_caching: false, + caps: Default::default(), + path_base: None, + }; + let relayed = crate::exchange::attempt::relay_request(far, "application/json", carried); + assert_eq!( + relayed.bytes.as_ref(), + caller.as_slice(), + "same-protocol gemini relay sends the caller's bytes, no body model or stream" + ); +}