use crate::body::extract_body;
use crate::forward::forward_request;
use crate::ProxyState;
use axum::body::Bytes;
use axum::extract::State;
use axum::http::{HeaderMap, Method, StatusCode, Uri};
use axum::response::{IntoResponse, Response};
pub async fn passthrough_handler(
State(state): State<ProxyState>,
method: Method,
uri: Uri,
headers: HeaderMap,
body: Bytes,
) -> Response {
let has_body = !matches!(method, Method::GET | Method::HEAD);
let body = if has_body {
match extract_body(&headers, body) {
Ok(s) => s,
Err(e) => {
return (
StatusCode::BAD_REQUEST,
axum::Json(serde_json::json!({"error": e})),
)
.into_response();
}
}
} else {
String::new()
};
let path = uri.path();
let provider = detect_provider_from_route(path);
let base_url = match provider {
Some("anthropic") => &state.config.providers.anthropic,
Some("openai") => &state.config.providers.openai,
Some("google") => &state.config.providers.google,
_ => {
return (
StatusCode::NOT_FOUND,
axum::Json(serde_json::json!({
"error": "Unknown route — cannot determine upstream provider"
})),
)
.into_response();
}
};
let query = uri.query().map(|q| format!("?{}", q)).unwrap_or_default();
let target_url = format!("{}{}{}", base_url, path, query);
if state.config.verbose {
tracing::info!("Passthrough: {} {} → {}{}", method, path, base_url, path);
}
let body = if has_body { Some(body) } else { None };
forward_request(
&state.http_client,
method.as_str(),
&target_url,
&headers,
body,
)
.await
}
fn detect_provider_from_route(path: &str) -> Option<&'static str> {
if path.starts_with("/v1/messages") || path == "/messages" {
Some("anthropic")
} else if path.starts_with("/v1/chat/")
|| path.starts_with("/v1/embeddings")
|| path.starts_with("/v1/responses")
|| path == "/responses"
{
Some("openai")
} else if path.starts_with("/v1beta/") || path.starts_with("/v1/models/gemini") {
Some("google")
} else if path.starts_with("/v1/") {
Some("openai")
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detect_anthropic() {
assert_eq!(
detect_provider_from_route("/v1/messages"),
Some("anthropic")
);
assert_eq!(
detect_provider_from_route("/v1/messages/batches"),
Some("anthropic")
);
assert_eq!(detect_provider_from_route("/messages"), Some("anthropic"));
}
#[test]
fn detect_openai() {
assert_eq!(
detect_provider_from_route("/v1/chat/completions"),
Some("openai")
);
assert_eq!(detect_provider_from_route("/v1/embeddings"), Some("openai"));
assert_eq!(detect_provider_from_route("/v1/responses"), Some("openai"));
assert_eq!(detect_provider_from_route("/responses"), Some("openai"));
}
#[test]
fn detect_google() {
assert_eq!(
detect_provider_from_route("/v1beta/models/gemini-2.5-pro:generateContent"),
Some("google")
);
}
#[test]
fn detect_unknown() {
assert_eq!(detect_provider_from_route("/unknown"), None);
}
}