mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-04 08:27:46 +08:00
fix(claude-code): 让 openai:responses/chat 转 claude 的路径也应用请求体伪装
openai:responses 与 openai:chat 的决策构建器各自构建上游 body,不经过 apply_transport_request_body_semantics,导致跨格式请求转发到 claude_code provider 时请求体伪装没有生效,上游仍返回 429。 - responses/decision/request.rs:finalize 之后应用伪装 - chat/decision/request.rs:finalize 成功后应用伪装 - 同格式路径改为经 ai_serving::transport 门面引用,满足架构守卫 (ai_serving 不得直接依赖 crate::provider_transport) - 更新 chat -> claude_code 用例断言为新的请求体形态
This commit is contained in:
@@ -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
|
// Same-format requests skip `apply_transport_request_body_semantics`, so the opt-in
|
||||||
// Claude Code body mimicry has to be applied here as well.
|
// 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,
|
&mut base_provider_request_body,
|
||||||
&transport,
|
&transport,
|
||||||
prepared.provider_api_format.as_str(),
|
prepared.provider_api_format.as_str(),
|
||||||
|
|||||||
+12
-2
@@ -140,7 +140,7 @@ fn finalize_openai_chat_provider_request_body(
|
|||||||
mapped_model,
|
mapped_model,
|
||||||
source_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,
|
provider_request_body,
|
||||||
crate::ai_serving::OpenAiProviderRequestFinalization {
|
crate::ai_serving::OpenAiProviderRequestFinalization {
|
||||||
source_api_format: "openai:chat",
|
source_api_format: "openai:chat",
|
||||||
@@ -170,7 +170,17 @@ fn finalize_openai_chat_provider_request_body(
|
|||||||
provider_api_format,
|
provider_api_format,
|
||||||
"openai_chat_request_finalization",
|
"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)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
|||||||
+7
@@ -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);
|
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;
|
let provider_request_body = base_provider_request_body;
|
||||||
|
|
||||||
if let Some(kiro_auth) = kiro_auth.as_ref() {
|
if let Some(kiro_auth) = kiro_auth.as_ref() {
|
||||||
|
|||||||
@@ -2028,14 +2028,30 @@ async fn gateway_returns_openai_chat_error_for_local_cross_format_claude_cli_syn
|
|||||||
"claude-code-upstream"
|
"claude-code-upstream"
|
||||||
);
|
);
|
||||||
assert_eq!(seen_execution_runtime_request.body["max_tokens"], 64);
|
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!(
|
assert_eq!(
|
||||||
seen_execution_runtime_request.body["system"],
|
system[1]["text"],
|
||||||
"You are terse."
|
"You are Claude Code, Anthropic's official CLI for Claude."
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
seen_execution_runtime_request.body["messages"],
|
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!(
|
assert_eq!(
|
||||||
seen_execution_runtime_request.client_api_format,
|
seen_execution_runtime_request.client_api_format,
|
||||||
"openai:chat"
|
"openai:chat"
|
||||||
|
|||||||
Reference in New Issue
Block a user