refactor: separate request auth channel from route kind

Extract request_auth_channel as a distinct routing dimension to distinguish
between api_key and bearer_like authentication methods. This improves routing
clarity by decoupling authentication mechanism from route classification.
This commit is contained in:
fawney19
2026-05-02 21:23:33 +08:00
parent 47ee8b9c13
commit 3a770306cc
16 changed files with 337 additions and 58 deletions

View File

@@ -1,6 +1,6 @@
use super::{
classified, is_claude_cli_request, is_gemini_cli_request, is_gemini_models_route,
is_gemini_operation_route, ClassifiedRoute,
classified, classified_with_request_auth_channel, is_claude_cli_request, is_gemini_cli_request,
is_gemini_models_route, is_gemini_operation_route, ClassifiedRoute,
};
pub(super) fn classify_ai_public_route(
@@ -58,15 +58,16 @@ pub(super) fn classify_ai_public_route(
false,
))
} else if method == http::Method::POST && normalized_path == "/v1/messages" {
let route_kind = if is_claude_cli_request(headers) {
"cli"
let request_auth_channel = if is_claude_cli_request(headers) {
"bearer_like"
} else {
"messages"
"api_key"
};
Some(classified(
Some(classified_with_request_auth_channel(
"ai_public",
"claude",
route_kind,
"messages",
request_auth_channel,
"claude:messages",
true,
))
@@ -88,18 +89,20 @@ pub(super) fn classify_ai_public_route(
true,
))
} else if is_gemini_cli_request(headers) {
Some(classified(
Some(classified_with_request_auth_channel(
"ai_public",
"gemini",
"cli",
"generate_content",
"bearer_like",
"gemini:generate_content",
true,
))
} else {
Some(classified(
Some(classified_with_request_auth_channel(
"ai_public",
"gemini",
"generate_content",
"api_key",
"gemini:generate_content",
true,
))

View File

@@ -19,6 +19,7 @@ pub(crate) struct GatewayControlDecision {
pub(crate) route_class: Option<String>,
pub(crate) route_family: Option<String>,
pub(crate) route_kind: Option<String>,
pub(crate) request_auth_channel: Option<String>,
pub(crate) auth_endpoint_signature: Option<String>,
pub(crate) execution_runtime_candidate: bool,
pub(crate) auth_context: Option<GatewayControlAuthContext>,
@@ -40,6 +41,7 @@ impl GatewayControlDecision {
route_class,
route_family,
route_kind,
request_auth_channel: None,
auth_endpoint_signature,
execution_runtime_candidate: false,
auth_context: None,
@@ -75,6 +77,7 @@ pub(super) struct ClassifiedRoute {
route_class: &'static str,
route_family: &'static str,
route_kind: &'static str,
request_auth_channel: Option<&'static str>,
auth_endpoint_signature: String,
execution_runtime_candidate: bool,
}
@@ -90,6 +93,25 @@ pub(super) fn classified(
route_class,
route_family,
route_kind,
request_auth_channel: None,
auth_endpoint_signature: auth_endpoint_signature.into(),
execution_runtime_candidate,
}
}
pub(super) fn classified_with_request_auth_channel(
route_class: &'static str,
route_family: &'static str,
route_kind: &'static str,
request_auth_channel: &'static str,
auth_endpoint_signature: impl Into<String>,
execution_runtime_candidate: bool,
) -> ClassifiedRoute {
ClassifiedRoute {
route_class,
route_family,
route_kind,
request_auth_channel: Some(request_auth_channel),
auth_endpoint_signature: auth_endpoint_signature.into(),
execution_runtime_candidate,
}
@@ -103,6 +125,7 @@ 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()),
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,
auth_context: None,

View File

@@ -44,7 +44,11 @@ fn classifies_claude_messages_cli_when_bearer_without_api_key() {
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_family.as_deref(), Some("claude"));
assert_eq!(decision.route_kind.as_deref(), Some("cli"));
assert_eq!(decision.route_kind.as_deref(), Some("messages"));
assert_eq!(
decision.request_auth_channel.as_deref(),
Some("bearer_like")
);
assert_eq!(
decision.auth_endpoint_signature.as_deref(),
Some("claude:messages")
@@ -63,7 +67,11 @@ fn classifies_claude_messages_cli_when_bearer_is_present_even_with_api_key() {
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_family.as_deref(), Some("claude"));
assert_eq!(decision.route_kind.as_deref(), Some("cli"));
assert_eq!(decision.route_kind.as_deref(), Some("messages"));
assert_eq!(
decision.request_auth_channel.as_deref(),
Some("bearer_like")
);
assert_eq!(
decision.auth_endpoint_signature.as_deref(),
Some("claude:messages")
@@ -80,6 +88,7 @@ fn classifies_claude_messages_when_api_key_without_bearer() {
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.auth_endpoint_signature.as_deref(),
Some("claude:messages")
@@ -97,7 +106,30 @@ fn classifies_gemini_cli_generate_content_when_x_app_contains_cli() {
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_family.as_deref(), Some("gemini"));
assert_eq!(decision.route_kind.as_deref(), Some("cli"));
assert_eq!(decision.route_kind.as_deref(), Some("generate_content"));
assert_eq!(
decision.request_auth_channel.as_deref(),
Some("bearer_like")
);
assert_eq!(
decision.auth_endpoint_signature.as_deref(),
Some("gemini:generate_content")
);
assert!(decision.is_execution_runtime_candidate());
}
#[test]
fn classifies_gemini_generate_content_api_key_without_cli_marker() {
let headers = headers(&[("x-goog-api-key", "gemini-key")]);
let uri: Uri = "/v1beta/models/gemini-2.5-pro:generateContent"
.parse()
.expect("uri should parse");
let decision =
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_family.as_deref(), Some("gemini"));
assert_eq!(decision.route_kind.as_deref(), Some("generate_content"));
assert_eq!(decision.request_auth_channel.as_deref(), Some("api_key"));
assert_eq!(
decision.auth_endpoint_signature.as_deref(),
Some("gemini:generate_content")

View File

@@ -61,7 +61,14 @@ fn builds_public_request_context_from_request_parts() {
.control_decision
.as_ref()
.and_then(|value| value.route_kind.as_deref()),
Some("cli")
Some("generate_content")
);
assert_eq!(
context
.control_decision
.as_ref()
.and_then(|value| value.request_auth_channel.as_deref()),
Some("bearer_like")
);
}