diff --git a/apps/aether-gateway/src/ai_serving/planner/passthrough/provider/family/request.rs b/apps/aether-gateway/src/ai_serving/planner/passthrough/provider/family/request.rs index 28f701b58..4bd50da98 100644 --- a/apps/aether-gateway/src/ai_serving/planner/passthrough/provider/family/request.rs +++ b/apps/aether-gateway/src/ai_serving/planner/passthrough/provider/family/request.rs @@ -316,7 +316,7 @@ pub(crate) async fn resolve_local_same_format_provider_candidate_payload_parts( // Same-format requests skip `apply_transport_request_body_semantics`, so the opt-in // Claude Code body mimicry has to be applied here as well. - if crate::provider_transport::claude_code::apply_claude_code_body_mimicry_for_transport( + if crate::ai_serving::transport::claude_code::apply_claude_code_body_mimicry_for_transport( &mut base_provider_request_body, &transport, prepared.provider_api_format.as_str(), diff --git a/apps/aether-gateway/src/ai_serving/planner/standard/openai/chat/decision/request.rs b/apps/aether-gateway/src/ai_serving/planner/standard/openai/chat/decision/request.rs index ce719f126..87d17fd6d 100644 --- a/apps/aether-gateway/src/ai_serving/planner/standard/openai/chat/decision/request.rs +++ b/apps/aether-gateway/src/ai_serving/planner/standard/openai/chat/decision/request.rs @@ -140,7 +140,7 @@ fn finalize_openai_chat_provider_request_body( mapped_model, source_model, ); - crate::ai_serving::finalize_openai_provider_request_with_codex_model_capabilities_and_reasoning_replay_policy( + let finalization_failure = crate::ai_serving::finalize_openai_provider_request_with_codex_model_capabilities_and_reasoning_replay_policy( provider_request_body, crate::ai_serving::OpenAiProviderRequestFinalization { source_api_format: "openai:chat", @@ -170,7 +170,17 @@ fn finalize_openai_chat_provider_request_body( provider_api_format, "openai_chat_request_finalization", ) - }) + }); + if finalization_failure.is_none() { + // This builder does not go through `apply_transport_request_body_semantics`, so the + // Claude Code body mimicry must be applied here for Chat -> claude_code requests. + crate::ai_serving::transport::claude_code::apply_claude_code_body_mimicry_for_transport( + provider_request_body, + transport, + provider_api_format, + ); + } + finalization_failure } #[allow(clippy::too_many_arguments)] diff --git a/apps/aether-gateway/src/ai_serving/planner/standard/openai/responses/decision/request.rs b/apps/aether-gateway/src/ai_serving/planner/standard/openai/responses/decision/request.rs index a9170ce59..0a52493bc 100644 --- a/apps/aether-gateway/src/ai_serving/planner/standard/openai/responses/decision/request.rs +++ b/apps/aether-gateway/src/ai_serving/planner/standard/openai/responses/decision/request.rs @@ -635,6 +635,13 @@ pub(crate) async fn resolve_local_openai_responses_candidate_payload_parts_with_ { log_responses_to_chat_tool_conversion(trace_id, body_json, &base_provider_request_body); } + // This builder does not go through `apply_transport_request_body_semantics`, so the + // Claude Code body mimicry must be applied here for Responses -> claude_code requests. + crate::ai_serving::transport::claude_code::apply_claude_code_body_mimicry_for_transport( + &mut base_provider_request_body, + &transport, + provider_api_format, + ); let provider_request_body = base_provider_request_body; if let Some(kiro_auth) = kiro_auth.as_ref() { diff --git a/apps/aether-gateway/src/tests/ai_execute/sync/chat/local_decision.rs b/apps/aether-gateway/src/tests/ai_execute/sync/chat/local_decision.rs index 9d97ebd83..71465d9f3 100644 --- a/apps/aether-gateway/src/tests/ai_execute/sync/chat/local_decision.rs +++ b/apps/aether-gateway/src/tests/ai_execute/sync/chat/local_decision.rs @@ -2028,14 +2028,30 @@ async fn gateway_returns_openai_chat_error_for_local_cross_format_claude_cli_syn "claude-code-upstream" ); assert_eq!(seen_execution_runtime_request.body["max_tokens"], 64); + // claude_code providers get the Claude Code body shape: billing header + identity + + // generic prompt, with the client's own system moved into the message history. + let system = seen_execution_runtime_request.body["system"] + .as_array() + .expect("claude_code system should be rewritten into blocks"); + assert_eq!(system.len(), 3); + assert!(system[0]["text"] + .as_str() + .is_some_and(|text| text.starts_with("x-anthropic-billing-header: cc_version="))); assert_eq!( - seen_execution_runtime_request.body["system"], - "You are terse." + system[1]["text"], + "You are Claude Code, Anthropic's official CLI for Claude." ); assert_eq!( seen_execution_runtime_request.body["messages"], - json!([{"role":"user","content":"Say hello"}]) + json!([ + {"role":"user","content":[{"type":"text","text":"[System Instructions]\nYou are terse."}]}, + {"role":"assistant","content":[{"type":"text","text":"Understood. I will follow these instructions."}]}, + {"role":"user","content":"Say hello"} + ]) ); + assert!(seen_execution_runtime_request.body["metadata"]["user_id"] + .as_str() + .is_some_and(|user_id| user_id.contains("\"session_id\""))); assert_eq!( seen_execution_runtime_request.client_api_format, "openai:chat"