feat(gateway): harden provider request execution

Preserve exact request payloads and model client surface and API operation explicitly.

Add Anthropic compatibility profiles, bounded stream commitment, and scoped OAuth retry behavior across provider transports.
This commit is contained in:
elky
2026-07-27 09:36:31 +08:00
parent 79b70f7b5c
commit 531cf11025
152 changed files with 13984 additions and 2075 deletions
@@ -71,6 +71,20 @@ pub(super) fn extract_request_credentials(
}
}
pub(in crate::control) fn resolve_gateway_credential_carrier(
headers: &http::HeaderMap,
uri: &Uri,
auth_endpoint_signature: &str,
) -> Option<GatewayCredentialCarrier> {
extract_request_credentials(headers, uri, auth_endpoint_signature)
.primary
.map(|credential| match credential {
GatewayPrimaryCredential::ProviderApiKey { carrier, .. }
| GatewayPrimaryCredential::BearerToken { carrier, .. }
| GatewayPrimaryCredential::CookieHeader { carrier, .. } => carrier,
})
}
fn has_trusted_gateway_marker(headers: &http::HeaderMap) -> bool {
header_value_str(headers, crate::constants::GATEWAY_HEADER)
.unwrap_or_default()
@@ -5,6 +5,7 @@ mod resolution;
mod types;
pub(crate) use credentials::extract_requested_model;
pub(super) use credentials::resolve_gateway_credential_carrier;
pub(crate) use gate::{
execution_plan_balance_capacity_rejection, request_model_local_rejection,
should_buffer_request_for_local_auth, trusted_auth_local_rejection, GatewayLocalAuthRejection,
@@ -14,3 +15,4 @@ pub(crate) use resolution::{
GatewayAdminPrincipalContext, GatewayControlAuthContext,
};
pub(super) use resolution::{resolve_control_decision_auth, ControlDecisionAuthResolution};
pub(crate) use types::GatewayCredentialCarrier;
+21 -1
View File
@@ -1,5 +1,5 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum GatewayCredentialCarrier {
pub(crate) enum GatewayCredentialCarrier {
AuthorizationBearer,
XApiKey,
ApiKey,
@@ -8,6 +8,26 @@ pub(super) enum GatewayCredentialCarrier {
CookieHeader,
}
impl GatewayCredentialCarrier {
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::AuthorizationBearer => "authorization_bearer",
Self::XApiKey => "x_api_key",
Self::ApiKey => "api_key",
Self::XGoogApiKey => "x_goog_api_key",
Self::QueryKey => "query_key",
Self::CookieHeader => "cookie_header",
}
}
pub(crate) const fn request_auth_channel(self) -> &'static str {
match self {
Self::AuthorizationBearer | Self::CookieHeader => "bearer_like",
Self::XApiKey | Self::ApiKey | Self::XGoogApiKey | Self::QueryKey => "api_key",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(super) struct GatewayTrustedAuthHeaders {
pub(super) user_id: String,
+1 -1
View File
@@ -12,7 +12,7 @@ pub(crate) use auth::{
refresh_execution_runtime_auth_context, request_model_local_rejection,
resolve_execution_runtime_auth_context, should_buffer_request_for_local_auth,
trusted_auth_local_rejection, GatewayAdminPrincipalContext, GatewayControlAuthContext,
GatewayLocalAuthRejection,
GatewayCredentialCarrier, GatewayLocalAuthRejection,
};
pub(crate) use execute::{allows_control_execute_emergency, maybe_execute_via_control};
pub(crate) use management_token_permissions::{
+43 -22
View File
@@ -1,7 +1,8 @@
use super::{
classified, classified_with_request_auth_channel, is_claude_cli_request, is_gemini_cli_request,
is_gemini_models_route, is_gemini_operation_route, ClassifiedRoute,
classified, classified_with_request_auth_channel, detect_claude_client_surface,
is_gemini_cli_request, is_gemini_models_route, is_gemini_operation_route, ClassifiedRoute,
};
use crate::ai_serving::ApiOperation;
pub(super) fn classify_ai_public_route(
method: &http::Method,
@@ -76,27 +77,33 @@ pub(super) fn classify_ai_public_route(
true,
))
} else if method == http::Method::POST && normalized_path == "/v1/messages/count_tokens" {
Some(classified(
"ai_public",
"claude",
"count_tokens",
"claude:messages",
false,
))
let request_auth_channel = claude_request_auth_channel(headers);
Some(
classified_with_request_auth_channel(
"ai_public",
"claude",
"count_tokens",
request_auth_channel,
"claude:messages",
true,
)
.with_client_surface(detect_claude_client_surface(headers))
.with_api_operation(ApiOperation::ClaudeCountTokens),
)
} else if method == http::Method::POST && normalized_path == "/v1/messages" {
let request_auth_channel = if is_claude_cli_request(headers) {
"bearer_like"
} else {
"api_key"
};
Some(classified_with_request_auth_channel(
"ai_public",
"claude",
"messages",
request_auth_channel,
"claude:messages",
true,
))
let request_auth_channel = claude_request_auth_channel(headers);
Some(
classified_with_request_auth_channel(
"ai_public",
"claude",
"messages",
request_auth_channel,
"claude:messages",
true,
)
.with_client_surface(detect_claude_client_surface(headers))
.with_api_operation(ApiOperation::ClaudeMessagesCreate),
)
} else if normalized_path.starts_with("/v1/videos") {
Some(classified(
"ai_public",
@@ -178,6 +185,20 @@ pub(super) fn classify_ai_public_route(
}
}
fn claude_request_auth_channel(headers: &http::HeaderMap) -> &'static str {
if crate::headers::header_value_str(headers, "x-api-key").is_some()
|| crate::headers::header_value_str(headers, "api-key").is_some()
{
"api_key"
} else if crate::headers::header_value_str(headers, http::header::AUTHORIZATION.as_str())
.is_some_and(|value| value.trim().to_ascii_lowercase().starts_with("bearer "))
{
"bearer_like"
} else {
"api_key"
}
}
fn is_gemini_operation_method(method: &http::Method, normalized_path: &str) -> bool {
method == http::Method::GET
|| (method == http::Method::POST && normalized_path.ends_with(":cancel"))
+61 -5
View File
@@ -1,5 +1,6 @@
use axum::http::Uri;
use crate::ai_serving::{ApiOperation, ClientSurface};
use crate::headers::header_value_str;
use crate::{AppState, GatewayError};
@@ -9,7 +10,10 @@ mod internal;
mod oauth;
mod public_support;
use super::auth::{resolve_control_decision_auth, ControlDecisionAuthResolution};
use super::auth::{
resolve_control_decision_auth, resolve_gateway_credential_carrier,
ControlDecisionAuthResolution, GatewayCredentialCarrier,
};
use super::{GatewayAdminPrincipalContext, GatewayControlAuthContext, GatewayLocalAuthRejection};
#[derive(Debug, Clone)]
@@ -19,6 +23,9 @@ pub(crate) struct GatewayControlDecision {
pub(crate) route_class: Option<String>,
pub(crate) route_family: Option<String>,
pub(crate) route_kind: Option<String>,
pub(crate) client_surface: Option<ClientSurface>,
pub(crate) api_operation: Option<ApiOperation>,
pub(crate) gateway_credential_carrier: Option<GatewayCredentialCarrier>,
pub(crate) request_auth_channel: Option<String>,
pub(crate) auth_endpoint_signature: Option<String>,
pub(crate) execution_runtime_candidate: bool,
@@ -42,6 +49,9 @@ impl GatewayControlDecision {
route_class,
route_family,
route_kind,
client_surface: None,
api_operation: None,
gateway_credential_carrier: None,
request_auth_channel: None,
auth_endpoint_signature,
execution_runtime_candidate: false,
@@ -80,6 +90,8 @@ pub(super) struct ClassifiedRoute {
route_family: &'static str,
route_kind: &'static str,
request_auth_channel: Option<&'static str>,
client_surface: Option<ClientSurface>,
api_operation: Option<ApiOperation>,
auth_endpoint_signature: String,
execution_runtime_candidate: bool,
}
@@ -96,6 +108,8 @@ pub(super) fn classified(
route_family,
route_kind,
request_auth_channel: None,
client_surface: None,
api_operation: None,
auth_endpoint_signature: auth_endpoint_signature.into(),
execution_runtime_candidate,
}
@@ -114,11 +128,25 @@ pub(super) fn classified_with_request_auth_channel(
route_family,
route_kind,
request_auth_channel: Some(request_auth_channel),
client_surface: None,
api_operation: None,
auth_endpoint_signature: auth_endpoint_signature.into(),
execution_runtime_candidate,
}
}
impl ClassifiedRoute {
pub(super) fn with_client_surface(mut self, client_surface: ClientSurface) -> Self {
self.client_surface = Some(client_surface);
self
}
pub(super) fn with_api_operation(mut self, api_operation: ApiOperation) -> Self {
self.api_operation = Some(api_operation);
self
}
}
impl ClassifiedRoute {
fn into_decision(self, public_path: String) -> GatewayControlDecision {
GatewayControlDecision {
@@ -127,6 +155,9 @@ impl ClassifiedRoute {
route_class: Some(self.route_class.to_string()),
route_family: Some(self.route_family.to_string()),
route_kind: Some(self.route_kind.to_string()),
client_surface: self.client_surface,
api_operation: self.api_operation,
gateway_credential_carrier: None,
request_auth_channel: self.request_auth_channel.map(str::to_string),
auth_endpoint_signature: Some(self.auth_endpoint_signature),
execution_runtime_candidate: self.execution_runtime_candidate,
@@ -183,7 +214,17 @@ pub(crate) fn classify_control_route(
.or_else(|| internal::classify_internal_route(method, &normalized_path))
.or_else(|| ai::classify_ai_public_route(method, &normalized_path, headers))?;
Some(classified.into_decision(normalized_path))
let mut decision = classified.into_decision(normalized_path);
if let Some(signature) = decision.auth_endpoint_signature.as_deref() {
decision.gateway_credential_carrier =
resolve_gateway_credential_carrier(headers, uri, signature);
}
if decision.route_family.as_deref() == Some("claude") {
if let Some(carrier) = decision.gateway_credential_carrier {
decision.request_auth_channel = Some(carrier.request_auth_channel().to_string());
}
}
Some(decision)
}
pub(super) fn detect_public_models_auth_signature(uri: &Uri, headers: &http::HeaderMap) -> String {
@@ -220,11 +261,26 @@ pub(super) fn detect_public_models_auth_signature(uri: &Uri, headers: &http::Hea
"openai:chat".to_string()
}
pub(super) fn is_claude_cli_request(headers: &http::HeaderMap) -> bool {
let auth_header = header_value_str(headers, http::header::AUTHORIZATION.as_str())
pub(super) fn detect_claude_client_surface(headers: &http::HeaderMap) -> ClientSurface {
let user_agent = header_value_str(headers, http::header::USER_AGENT.as_str())
.unwrap_or_default()
.to_ascii_lowercase();
auth_header.starts_with("bearer ")
let x_app_is_cli = header_value_str(headers, "x-app")
.is_some_and(|value| value.trim().eq_ignore_ascii_case("cli"));
if user_agent.contains("claude-code")
|| user_agent.contains("claude-cli")
|| user_agent.contains("claude code")
|| x_app_is_cli
|| header_value_str(headers, "x-claude-code-session-id").is_some()
{
ClientSurface::ClaudeCode
} else if user_agent.contains("anthropic/")
|| header_value_str(headers, "x-stainless-lang").is_some()
{
ClientSurface::AnthropicSdk
} else {
ClientSurface::GenericCompatible
}
}
pub(super) fn is_gemini_cli_request(headers: &http::HeaderMap) -> bool {
+74 -6
View File
@@ -1,9 +1,11 @@
use aether_ai_formats::{ApiOperation, ClientSurface};
use http::Uri;
use super::super::auth::GatewayCredentialCarrier;
use super::{classify_control_route, headers};
#[test]
fn classifies_claude_count_tokens_as_non_execution_runtime_public_route() {
fn classifies_claude_count_tokens_as_execution_runtime_operation() {
let headers = headers(&[("x-api-key", "sk-test")]);
let uri: Uri = "/v1/messages/count_tokens"
.parse()
@@ -17,7 +19,11 @@ fn classifies_claude_count_tokens_as_non_execution_runtime_public_route() {
decision.auth_endpoint_signature.as_deref(),
Some("claude:messages")
);
assert!(!decision.is_execution_runtime_candidate());
assert!(decision.is_execution_runtime_candidate());
assert_eq!(
decision.api_operation,
Some(ApiOperation::ClaudeCountTokens)
);
}
#[test]
@@ -141,7 +147,7 @@ fn classifies_models_list_as_claude_when_headers_match() {
}
#[test]
fn classifies_claude_messages_cli_when_bearer_without_api_key() {
fn bearer_auth_does_not_imply_claude_code_client_surface() {
let headers = headers(&[("authorization", "Bearer token-123")]);
let uri: Uri = "/v1/messages".parse().expect("uri should parse");
let decision =
@@ -149,6 +155,10 @@ fn classifies_claude_messages_cli_when_bearer_without_api_key() {
assert_eq!(decision.route_family.as_deref(), Some("claude"));
assert_eq!(decision.route_kind.as_deref(), Some("messages"));
assert_eq!(
decision.client_surface,
Some(ClientSurface::GenericCompatible)
);
assert_eq!(
decision.request_auth_channel.as_deref(),
Some("bearer_like")
@@ -161,7 +171,7 @@ fn classifies_claude_messages_cli_when_bearer_without_api_key() {
}
#[test]
fn classifies_claude_messages_cli_when_bearer_is_present_even_with_api_key() {
fn claude_api_key_carrier_keeps_precedence_over_bearer() {
let headers = headers(&[
("authorization", "Bearer token-123"),
("x-api-key", "sk-client"),
@@ -172,9 +182,10 @@ fn classifies_claude_messages_cli_when_bearer_is_present_even_with_api_key() {
assert_eq!(decision.route_family.as_deref(), Some("claude"));
assert_eq!(decision.route_kind.as_deref(), Some("messages"));
assert_eq!(decision.request_auth_channel.as_deref(), Some("api_key"));
assert_eq!(
decision.request_auth_channel.as_deref(),
Some("bearer_like")
decision.gateway_credential_carrier,
Some(GatewayCredentialCarrier::XApiKey)
);
assert_eq!(
decision.auth_endpoint_signature.as_deref(),
@@ -183,6 +194,63 @@ fn classifies_claude_messages_cli_when_bearer_is_present_even_with_api_key() {
assert!(decision.is_execution_runtime_candidate());
}
#[test]
fn detects_claude_code_independently_from_bearer_auth() {
let headers = headers(&[
("authorization", "Bearer token-123"),
("user-agent", "Claude-Code/2.1.0"),
]);
let uri: Uri = "/v1/messages".parse().expect("uri should parse");
let decision =
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
assert_eq!(decision.client_surface, Some(ClientSurface::ClaudeCode));
assert_eq!(
decision.gateway_credential_carrier,
Some(GatewayCredentialCarrier::AuthorizationBearer)
);
assert_eq!(
decision.api_operation,
Some(ApiOperation::ClaudeMessagesCreate)
);
}
#[test]
fn detects_current_claude_cli_user_agent() {
let headers = headers(&[
("x-api-key", "sk-client"),
("user-agent", "claude-cli/2.1.161 (external, cli)"),
]);
let uri: Uri = "/v1/messages".parse().expect("uri should parse");
let decision =
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
assert_eq!(decision.client_surface, Some(ClientSurface::ClaudeCode));
assert_eq!(decision.request_auth_channel.as_deref(), Some("api_key"));
assert_eq!(
decision.gateway_credential_carrier,
Some(GatewayCredentialCarrier::XApiKey)
);
}
#[test]
fn detects_claude_code_from_explicit_x_app_signal() {
let headers = headers(&[
("x-api-key", "sk-client"),
("user-agent", "rewritten-by-proxy"),
("x-app", "cli"),
]);
let uri: Uri = "/v1/messages".parse().expect("uri should parse");
let decision =
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
assert_eq!(decision.client_surface, Some(ClientSurface::ClaudeCode));
assert_eq!(
decision.api_operation,
Some(ApiOperation::ClaudeMessagesCreate)
);
}
#[test]
fn classifies_claude_messages_when_api_key_without_bearer() {
let headers = headers(&[("x-api-key", "sk-client")]);