fix(gateway): support claude_code cross-format Claude messages

This commit is contained in:
zhefox
2026-08-05 13:57:20 +08:00
parent 1aab31a148
commit 4d67569873
13 changed files with 349 additions and 46 deletions
@@ -6,6 +6,7 @@ use crate::auth::{
build_claude_passthrough_headers, build_complete_passthrough_headers_with_auth,
build_openai_passthrough_headers, build_passthrough_headers, ensure_upstream_auth_header,
};
use crate::claude_code::build_claude_code_passthrough_headers;
use crate::headers::force_identity_accept_encoding;
use crate::rules::{
apply_local_body_rules, apply_local_body_rules_with_request_headers,
@@ -224,7 +225,23 @@ pub fn build_standard_provider_request_headers(
) -> Option<StandardProviderRequestHeaders> {
let uses_vertex_query_auth =
uses_vertex_api_key_query_auth(input.transport, input.provider_api_format);
let mut headers = if input.same_format {
let is_claude_code_messages = input
.transport
.provider
.provider_type
.trim()
.eq_ignore_ascii_case("claude_code")
&& aether_ai_formats::normalize_api_format_alias(input.provider_api_format)
== "claude:messages";
let mut headers = if is_claude_code_messages {
build_claude_code_passthrough_headers(
input.headers,
input.auth_header,
input.auth_value,
input.extra_headers,
input.upstream_is_stream,
)
} else if input.same_format {
build_complete_passthrough_headers_with_auth(
input.headers,
input.auth_header,
@@ -473,6 +490,41 @@ mod tests {
);
}
#[test]
fn builds_claude_code_identity_headers_for_cross_format_messages() {
let mut transport = sample_transport("claude:messages");
transport.provider.provider_type = "claude_code".to_string();
let resolved =
build_standard_provider_request_headers(StandardProviderRequestHeadersInput {
transport: &transport,
provider_api_format: "claude:messages",
same_format: false,
headers: &HeaderMap::new(),
auth_header: "authorization",
auth_value: "Bearer oauth-token",
extra_headers: &BTreeMap::new(),
header_rules: None,
provider_request_body: &json!({"model":"claude-opus-4-6"}),
original_request_body: &json!({"model":"gpt-source"}),
upstream_is_stream: true,
})
.expect("Claude Code headers should build");
assert_eq!(resolved.headers.get("x-app"), Some(&"cli".to_string()));
assert!(resolved
.headers
.get("user-agent")
.is_some_and(|value| value.starts_with("claude-cli/")));
assert!(resolved
.headers
.get("anthropic-beta")
.is_some_and(|value| value.contains("claude-code-20250219")));
assert_eq!(
resolved.headers.get("authorization"),
Some(&"Bearer oauth-token".to_string())
);
}
#[test]
fn endpoint_header_rules_do_not_override_protected_authorization() {
let mut transport = sample_transport("openai:responses");