use axum::body::Body;
use axum::extract::{OriginalUri, Path, State};
use axum::http::{HeaderMap, StatusCode};
use axum::response::{IntoResponse, Response};
use futures_util::StreamExt as _;
use serde_json::{Value, json};
use super::{AppState, code_assist_envelope, route_gemini_token};
use crate::metrics::Surface;
use crate::proxy::{error_response, retry_after_duration};
fn native_model_document(model: &str, raw: Option<&serde_json::Map<String, Value>>) -> Value {
let mut projected =
serde_json::Map::from_iter([("name".into(), Value::String(model.to_string()))]);
if let Some(raw) = raw {
for key in [
"baseModelId",
"version",
"displayName",
"description",
"inputTokenLimit",
"outputTokenLimit",
"supportedGenerationMethods",
"temperature",
"maxTemperature",
"topP",
"topK",
] {
if let Some(value) = raw.get(key) {
projected.insert(key.into(), value.clone());
}
}
}
Value::Object(projected)
}
async fn advertised_models(
state: &AppState,
principal: Option<&str>,
) -> Vec<(
crate::subscription::SubscriptionProvider,
crate::model_catalog::CatalogRecord,
)> {
let snapshot = crate::model_routing::configured_catalog_snapshot(state).await;
let healthy = snapshot.healthy_providers();
let providers = match state.upstream_provider {
crate::config::UpstreamProvider::Auto => healthy,
provider => provider
.subscription_provider()
.filter(|provider| healthy.contains(provider))
.into_iter()
.collect(),
};
providers
.into_iter()
.flat_map(|provider| {
principal
.map_or_else(
|| snapshot.records(provider),
|principal| {
if state.subscription_cache.evidence_for(provider, principal)
== Some(crate::refresh::CredentialEvidence::Rejected)
{
Vec::new()
} else {
state
.model_catalogs
.records_for_accounts(provider, &[principal.to_string()])
}
},
)
.into_iter()
.map(move |record| (provider, record))
})
.collect()
}
pub async fn native_models(
State(state): State<AppState>,
OriginalUri(uri): OriginalUri,
headers: HeaderMap,
) -> Response {
let claims = match crate::proxy::authenticate_client(&state, &headers) {
Ok(claims) => claims,
Err(response) => return *response,
};
let mut advertised = advertised_models(&state, claims.principal_id.as_deref()).await;
advertised.retain(|(provider, _)| {
crate::client_policy::enforce_subscription_for_claims(
&state,
&claims,
&headers,
*provider,
crate::client_policy::ClientProtocol::Catalog,
uri.path(),
)
.is_ok()
});
if state.upstream_provider != crate::config::UpstreamProvider::Auto
&& advertised.is_empty()
&& let Some(provider) = state.upstream_provider.subscription_provider()
&& let Err(response) = crate::client_policy::enforce_subscription_for_claims(
&state,
&claims,
&headers,
provider,
crate::client_policy::ClientProtocol::Catalog,
uri.path(),
)
{
return response;
}
let mut seen = std::collections::HashMap::new();
for (provider, record) in &advertised {
if let Some(previous) = seen.insert(record.canonical_id.clone(), *provider)
&& previous != *provider
{
return native_error(
StatusCode::CONFLICT,
&format!(
"exact native model id '{}' is advertised by more than one healthy provider",
record.canonical_id
),
);
}
}
let mut emitted = std::collections::HashSet::new();
let mut models = advertised
.into_iter()
.filter(|(_, record)| emitted.insert(record.canonical_id.clone()))
.map(|(_, record)| native_model_document(&record.canonical_id, Some(&record.raw)))
.collect::<Vec<_>>();
if let Ok(Some(provider)) = crate::zai_coding_plan::resolve(&state)
&& let Ok((client, _)) =
crate::zai_coding_plan::authorize_catalog(&provider, &claims, &headers, uri.path())
&& client == crate::clients::ClientKind::GeminiCli
&& let Ok(live) = crate::zai_coding_plan::live_catalog(&state, &provider).await
&& let Ok(registry) = crate::zai_coding_plan::live_registry_for_client(client, &live)
{
for entry in registry {
if models.iter().any(|model| {
model.get("name").and_then(Value::as_str) == Some(entry.exposed_id.as_str())
}) {
return native_error(
StatusCode::CONFLICT,
&format!(
"exact model id '{}' is advertised by more than one healthy provider",
entry.exposed_id
),
);
}
models.push(native_model_document(&entry.exposed_id, None));
}
}
(StatusCode::OK, axum::Json(json!({"models": models}))).into_response()
}
pub async fn native_model(
State(state): State<AppState>,
OriginalUri(uri): OriginalUri,
headers: HeaderMap,
Path(model): Path<String>,
) -> Response {
let claims = match crate::proxy::authenticate_client(&state, &headers) {
Ok(claims) => claims,
Err(response) => return *response,
};
let requested_id = model.trim_start_matches("models/");
let mut owners = advertised_models(&state, claims.principal_id.as_deref())
.await
.into_iter()
.filter_map(|(owner, candidate)| {
(candidate.canonical_id.trim_start_matches("models/") == requested_id
&& crate::client_policy::enforce_subscription_for_claims(
&state,
&claims,
&headers,
owner,
crate::client_policy::ClientProtocol::Catalog,
uri.path(),
)
.is_ok())
.then_some((owner, candidate))
})
.collect::<Vec<_>>();
owners.sort_by(|left, right| left.0.as_str().cmp(right.0.as_str()));
owners.dedup();
if owners.len() > 1 {
return native_error(
StatusCode::CONFLICT,
&format!(
"exact native model id '{}' is advertised by more than one healthy provider",
owners[0].1.canonical_id
),
);
}
if owners.is_empty()
&& let Ok(Some(provider)) = crate::zai_coding_plan::resolve(&state)
&& let Ok((client, _)) =
crate::zai_coding_plan::authorize_catalog(&provider, &claims, &headers, uri.path())
&& client == crate::clients::ClientKind::GeminiCli
&& let Ok(live) = crate::zai_coding_plan::live_catalog(&state, &provider).await
&& live
.iter()
.any(|entry| entry.id.trim_start_matches("models/") == requested_id)
{
return (
StatusCode::OK,
axum::Json(native_model_document(requested_id, None)),
)
.into_response();
}
owners
.pop()
.map_or_else(
|| {
(
StatusCode::NOT_FOUND,
axum::Json(crate::gemini_bridge::openai_error_to_gemini(
404,
&json!({"error": {"message": format!("model '{requested_id}' is not available")}}),
)),
)
},
|(_, candidate)| {
(
StatusCode::OK,
axum::Json(native_model_document(
&candidate.canonical_id,
Some(&candidate.raw),
)),
)
},
)
.into_response()
}
pub(super) fn parse_native_target(path: &str) -> Option<(String, bool)> {
let (resource, action) = path.rsplit_once(':')?;
let streaming = match action {
"generateContent" => false,
"streamGenerateContent" => true,
_ => return None,
};
let model = resource
.rsplit_once("/models/")
.map_or(resource, |(_, model)| model)
.trim_start_matches("models/")
.trim_matches('/');
(!model.is_empty()).then(|| (model.to_string(), streaming))
}
pub async fn forward_native_gemini(
State(state): State<AppState>,
Path(path): Path<String>,
headers: HeaderMap,
body: Result<axum::Json<Value>, axum::extract::rejection::JsonRejection>,
) -> Response {
let body = match body {
Ok(axum::Json(body)) => body,
Err(error) => {
return crate::api_error::malformed_json_response_for_dialect(
crate::api_error::ApiDialect::Gemini,
&error.to_string(),
);
}
};
Box::pin(forward_native(&state, &headers, &path, body)).await
}
#[cfg(test)]
#[allow(clippy::redundant_pub_crate)]
pub(crate) async fn forward_native_gemini_authorized(
state: &AppState,
path: &str,
headers: &HeaderMap,
body: Value,
) -> Response {
Box::pin(forward_native_authorized(state, headers, path, body)).await
}
pub async fn forward_native_vertex(
State(state): State<AppState>,
Path(path): Path<String>,
headers: HeaderMap,
body: Result<axum::Json<Value>, axum::extract::rejection::JsonRejection>,
) -> Response {
if path
.split('/')
.zip(path.split('/').skip(1))
.any(|segments| segments == ("publishers", "anthropic"))
{
return native_error(StatusCode::NOT_FOUND, "route not found");
}
let body = match body {
Ok(axum::Json(body)) => body,
Err(error) => {
return crate::api_error::malformed_json_response_for_dialect(
crate::api_error::ApiDialect::Gemini,
&error.to_string(),
);
}
};
Box::pin(forward_native(&state, &headers, &path, body)).await
}
fn native_error(status: StatusCode, message: &str) -> Response {
let body = crate::gemini_bridge::openai_error_to_gemini(
status.as_u16(),
&json!({"error": {"message": message}}),
);
(status, axum::Json(body)).into_response()
}
async fn native_owner(
state: &AppState,
headers: &HeaderMap,
path: &str,
model: &str,
) -> Result<crate::model_routing::RoutedState, Response> {
let routed = if state.upstream_provider == crate::config::UpstreamProvider::Auto {
let claims =
crate::proxy::authenticate_client(state, headers).map_err(|response| *response)?;
let client = crate::client_policy::bound_client(&claims)
.map(|(client, _)| client)
.map_err(|error| native_error(StatusCode::FORBIDDEN, &error))?;
let entitled = crate::subscription::SubscriptionProvider::ALL
.into_iter()
.filter(|provider| {
crate::client_policy::enforce_subscription_for_claims(
state,
&claims,
headers,
*provider,
crate::client_policy::ClientProtocol::GeminiNative,
path,
)
.is_ok()
})
.collect::<Vec<_>>();
let requested_id = model.trim_start_matches("models/");
let exact_candidates = advertised_models(state, claims.principal_id.as_deref())
.await
.into_iter()
.filter(|(provider, candidate)| {
entitled.contains(provider)
&& candidate.canonical_id.trim_start_matches("models/") == requested_id
})
.fold(
std::collections::HashMap::new(),
|mut candidates, (provider, record)| {
candidates.entry(provider).or_insert(record.canonical_id);
candidates
},
);
if exact_candidates.len() > 1 {
return Err(native_error(
StatusCode::CONFLICT,
&format!(
"exact native model id '{requested_id}' is advertised by more than one healthy provider"
),
));
}
let routing_model = exact_candidates
.values()
.next()
.map_or(model, String::as_str);
crate::model_routing::route_state_with_subscription_for_client(
state,
&json!({"model": routing_model}),
&entitled,
Some(client),
crate::zai_coding_plan::authorize_automatic_discovery(
state,
&claims,
headers,
crate::client_policy::ClientProtocol::GeminiNative,
path,
),
)
.await
} else if state.upstream_provider == crate::config::UpstreamProvider::ZaiCodingPlan {
Ok(crate::model_routing::RoutedState {
state: state.clone(),
subscription: None,
})
} else {
let provider = state
.upstream_provider
.subscription_provider()
.ok_or_else(|| {
native_error(
StatusCode::BAD_REQUEST,
&format!(
"UPSTREAM_PROVIDER={} does not back a subscription catalog",
state.upstream_provider.as_str()
),
)
})?;
crate::model_routing::route_pinned_subscription(state, provider).await
};
routed.map_err(|error| {
let status = match error {
crate::model_routing::ModelRouteError::NotFound(_) => StatusCode::NOT_FOUND,
crate::model_routing::ModelRouteError::Conflict(_) => StatusCode::CONFLICT,
crate::model_routing::ModelRouteError::ModelRequired => StatusCode::BAD_REQUEST,
};
native_error(status, &error.to_string())
})
}
async fn forward_native(
state: &AppState,
headers: &HeaderMap,
path: &str,
body: Value,
) -> Response {
let Some((model, streaming)) = parse_native_target(path) else {
return native_error(
StatusCode::NOT_FOUND,
"expected a model :generateContent or :streamGenerateContent action",
);
};
let full_path = format!("/api/services/gemini/{path}");
let routed = match native_owner(state, headers, &full_path, &model).await {
Ok(routed) => routed,
Err(response) => return response,
};
let entitlement = if let Some(owner) = routed.state.upstream_provider.subscription_provider() {
match crate::client_policy::enforce_subscription(
&routed.state,
headers,
owner,
crate::client_policy::ClientProtocol::GeminiNative,
&full_path,
) {
Ok(entitlement) => Some(entitlement),
Err(response) => return response,
}
} else if routed.state.upstream_provider != crate::config::UpstreamProvider::ZaiCodingPlan {
return native_error(
StatusCode::BAD_REQUEST,
"selected provider has no Gemini adapter",
);
} else {
None
};
Box::pin(forward_native_authorized_after_route(
routed,
headers,
path,
model,
streaming,
body,
entitlement,
))
.await
}
#[cfg(test)]
async fn forward_native_authorized(
state: &AppState,
headers: &HeaderMap,
path: &str,
body: Value,
) -> Response {
let Some((model, streaming)) = parse_native_target(path) else {
return native_error(
StatusCode::NOT_FOUND,
"expected a model :generateContent or :streamGenerateContent action",
);
};
let routed = match native_owner(state, headers, path, &model).await {
Ok(routed) => routed,
Err(response) => return response,
};
Box::pin(forward_native_authorized_after_route(
routed, headers, path, model, streaming, body, None,
))
.await
}
async fn forward_native_authorized_after_route(
routed: crate::model_routing::RoutedState,
headers: &HeaderMap,
path: &str,
model: String,
streaming: bool,
body: Value,
entitlement: Option<crate::client_policy::EntitlementDecision>,
) -> Response {
if routed.state.upstream_provider == crate::config::UpstreamProvider::ZaiCodingPlan {
return forward_native_via_zai(routed.state, headers, path, &model, streaming, &body).await;
}
let owner = routed
.state
.upstream_provider
.subscription_provider()
.expect("native routing always selects a subscription provider");
if owner != crate::subscription::SubscriptionProvider::Gemini {
return forward_native_via_chat(
routed,
headers,
&model,
streaming,
&body,
entitlement.expect("subscription ingress always records an entitlement"),
)
.await;
}
let state = &routed.state;
let mut routed = match route_gemini_token(
state,
headers,
&body,
&body,
Surface::OpenAIChat,
path,
routed.subscription.as_ref(),
)
.await
{
Ok(routed) => routed,
Err(response) => return response,
};
let envelope = code_assist_envelope(&model, &body);
let serialized = match serde_json::to_vec(&envelope) {
Ok(serialized) => serialized,
Err(error) => {
return error_response(
StatusCode::INTERNAL_SERVER_ERROR,
"api_error",
&format!("failed to serialize Gemini request: {error}"),
);
}
};
let base = routed
.token
.base_url(crate::subscription::SubscriptionProvider::Gemini);
let upstream_url = if streaming {
format!(
"{}/v1internal:streamGenerateContent?alt=sse",
base.trim_end_matches('/')
)
} else {
format!("{}/v1internal:generateContent", base.trim_end_matches('/'))
};
let mut upstream_request = state
.client
.post(upstream_url)
.header("content-type", "application/json")
.header(
"authorization",
format!("Bearer {}", routed.token.access_token),
)
.body(serialized.clone());
if streaming {
upstream_request = upstream_request.header("accept", "text/event-stream");
}
if let Some(request_id) = crate::proxy::translated_request_id(headers) {
upstream_request = upstream_request.header("x-request-id", request_id);
}
let correlation_id = crate::request_log::correlation_id(headers);
let upstream = match state
.request_log
.send_upstream(&correlation_id, &state.client, upstream_request)
.await
{
Ok(response) => response,
Err(error) => {
state
.metrics
.record_request(Surface::OpenAIChat, 502, Some(&routed.account));
return error_response(
StatusCode::BAD_GATEWAY,
"api_error",
&format!("Gemini subscription upstream request failed: {error}"),
);
}
};
let status = StatusCode::from_u16(upstream.status().as_u16())
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
let retry_after = retry_after_duration(upstream.headers());
let response_headers = crate::proxy::relay_response_headers(upstream.headers());
state
.subscription_cache
.record_status_for_credential(
crate::subscription::SubscriptionProvider::Gemini,
&routed.account,
&routed.token,
status.as_u16(),
)
.await;
state
.metrics
.record_request(Surface::OpenAIChat, status.as_u16(), Some(&routed.account));
if status == StatusCode::TOO_MANY_REQUESTS
&& let Some(router) = state.account_router.as_ref()
{
router.report_failure_with_retry_after(
&routed.account,
"Gemini subscription upstream returned 429",
retry_after,
);
}
if status.is_success() && streaming {
state.metrics.record_bytes(serialized.len() as u64, 0);
let response_log = std::sync::Arc::clone(&state.request_log);
let metrics = std::sync::Arc::clone(&state.metrics);
let mut usage = routed.reservation.take().into_tracker();
let mut translator = super::stream::NativeStreamTranslator::default();
let stream = upstream.bytes_stream().map(move |chunk| {
chunk.map_or_else(
|error| Err(std::io::Error::other(error)),
|bytes| {
response_log.record_upstream_body(&correlation_id, &bytes);
metrics.record_bytes(0, bytes.len() as u64);
usage.feed(&bytes);
translator.push(&bytes)
},
)
});
let mut response = Response::new(Body::from_stream(stream));
*response.status_mut() = status;
*response.headers_mut() = response_headers;
response.headers_mut().insert(
"content-type",
axum::http::HeaderValue::from_static("text/event-stream"),
);
return response;
}
let response_body = match upstream.bytes().await {
Ok(bytes) => bytes,
Err(error) => {
return error_response(
StatusCode::BAD_GATEWAY,
"api_error",
&format!("Gemini subscription upstream body read failed: {error}"),
);
}
};
state
.request_log
.record_upstream_body(&correlation_id, &response_body);
state
.metrics
.record_bytes(serialized.len() as u64, response_body.len() as u64);
if !status.is_success() {
let mut response = Response::new(Body::from(response_body));
*response.status_mut() = status;
*response.headers_mut() = response_headers;
response.headers_mut().insert(
"content-type",
axum::http::HeaderValue::from_static("application/json"),
);
return response;
}
let mut usage = routed.reservation.take().into_tracker();
usage.feed(&response_body);
let parsed: Value = match serde_json::from_slice(&response_body) {
Ok(parsed) => parsed,
Err(error) => {
return error_response(
StatusCode::BAD_GATEWAY,
"api_error",
&format!("failed to parse Gemini response: {error}"),
);
}
};
let native = parsed.get("response").cloned().unwrap_or(parsed);
let mut response = (StatusCode::OK, axum::Json(native)).into_response();
*response.headers_mut() = response_headers;
response.headers_mut().insert(
"content-type",
axum::http::HeaderValue::from_static("application/json"),
);
response
}
async fn forward_native_via_zai(
state: AppState,
headers: &HeaderMap,
path: &str,
model: &str,
streaming: bool,
body: &Value,
) -> Response {
let chat_request = match crate::gemini_bridge::gemini_request_to_chat_checked(model, body) {
Ok(request) => request,
Err(reason) => return native_error(StatusCode::BAD_REQUEST, &reason),
};
let full_path = format!("/api/services/gemini/{path}");
let response = crate::zai_coding_plan::forward(
&state,
headers,
chat_request,
&full_path,
crate::client_policy::ClientProtocol::GeminiNative,
Surface::OpenAIChat,
)
.await;
translated_chat_response(response, &state, model, streaming).await
}
async fn forward_native_via_chat(
routed: crate::model_routing::RoutedState,
headers: &HeaderMap,
model: &str,
streaming: bool,
body: &Value,
entitlement: crate::client_policy::EntitlementDecision,
) -> Response {
let chat_request = match crate::gemini_bridge::gemini_request_to_chat_checked(model, body) {
Ok(request) => request,
Err(reason) => return native_error(StatusCode::BAD_REQUEST, &reason),
};
let state = routed.state;
let response = crate::proxy::openai_chat_completions_routed(
state.clone(),
headers.clone(),
chat_request,
routed.subscription,
entitlement,
)
.await;
translated_chat_response(response, &state, model, streaming).await
}
async fn translated_chat_response(
response: Response,
state: &AppState,
model: &str,
streaming: bool,
) -> Response {
let (parts, body) = response.into_parts();
let status = parts.status;
let response_headers = crate::proxy::relay_response_headers(&parts.headers);
let bytes = match axum::body::to_bytes(body, state.max_proxy_request_bytes).await {
Ok(bytes) => bytes,
Err(error) => {
return native_error(
StatusCode::BAD_GATEWAY,
&format!("failed to read the translated upstream response: {error}"),
);
}
};
let parsed = serde_json::from_slice::<Value>(&bytes).unwrap_or_else(|error| {
json!({"error": {"message": format!("failed to parse the translated response: {error}")}})
});
if !status.is_success() {
let mut response = (
status,
axum::Json(crate::gemini_bridge::openai_error_to_gemini(
status.as_u16(),
&parsed,
)),
)
.into_response();
*response.headers_mut() = response_headers;
response.headers_mut().insert(
"content-type",
axum::http::HeaderValue::from_static("application/json"),
);
return response;
}
let native = crate::gemini_bridge::chat_to_gemini_response(&parsed, model);
if streaming {
let mut response = Response::new(Body::from(format!("data: {native}\n\n")));
*response.status_mut() = StatusCode::OK;
*response.headers_mut() = response_headers;
response.headers_mut().insert(
"content-type",
axum::http::HeaderValue::from_static("text/event-stream"),
);
return response;
}
let mut response = (StatusCode::OK, axum::Json(native)).into_response();
*response.headers_mut() = response_headers;
response.headers_mut().insert(
"content-type",
axum::http::HeaderValue::from_static("application/json"),
);
response
}
#[cfg(test)]
mod catalog_projection_tests {
use super::*;
#[test]
fn gemini_model_projection_preserves_exact_id_and_only_native_fields() {
let raw = json!({
"name": "models/upstream-other",
"displayName": "Synthetic live model",
"description": "Provider description",
"inputTokenLimit": 123,
"outputTokenLimit": 45,
"supportedGenerationMethods": ["generateContent"],
"provider": "private-provider",
"canonical_id": "different-id",
"router_fetched_at": 1,
"private": "must-not-survive"
});
let projected = native_model_document("exact/live-id", raw.as_object());
assert_eq!(projected["name"], "exact/live-id");
assert_eq!(projected["displayName"], "Synthetic live model");
assert_eq!(projected["inputTokenLimit"], 123);
assert_eq!(projected["outputTokenLimit"], 45);
assert_eq!(
projected["supportedGenerationMethods"],
json!(["generateContent"])
);
let rendered = projected.to_string();
for forbidden in [
"models/upstream-other",
"private-provider",
"canonical_id",
"router_fetched_at",
"must-not-survive",
] {
assert!(!rendered.contains(forbidden), "{rendered}");
}
}
}