use crate::providers::ProviderRegistry;
use crate::proxy::hot_path::StreamingHotPathService;
use crate::proxy::provider_router::ProviderRouter;
use crate::proxy::{
audit_path::AuditPathProcessor, middleware_stack::ProxyMiddlewareStack,
ring_buffer::RingBuffer, types::*, url_resolver::UrlResolver,
};
use axum::{
body::Body,
extract::{Request, State},
response::{IntoResponse, Response},
};
use std::sync::Arc;
use tokio::sync::mpsc;
const BEDROCK_ENDPOINT_OVERRIDE_ENV: &str = "BEDROCK_ENDPOINT_OVERRIDE";
pub struct ProxyService {
hot_path: StreamingHotPathService,
ring_buffer: Arc<RingBuffer>,
audit_shutdown_tx: Option<mpsc::Sender<()>>,
provider_router: Arc<ProviderRouter>,
}
impl ProxyService {
pub fn new(config: ProxyConfig) -> Self {
let ring_buffer = Arc::new(RingBuffer::new(&config.ring_buffer));
let hot_path = StreamingHotPathService::new(config.clone(), ring_buffer.clone());
let mut registry = ProviderRegistry::new();
use crate::providers::bedrock::provider::BedrockProvider;
use crate::providers::bedrock::types::AwsRegion;
if let Ok(endpoint_override) = std::env::var(BEDROCK_ENDPOINT_OVERRIDE_ENV) {
let bedrock_provider = Arc::new(BedrockProvider::with_base_url(endpoint_override));
registry.register(bedrock_provider);
} else {
let bedrock_region = config.bedrock_region.clone().unwrap_or_else(|| {
AwsRegion::try_new("us-east-1".to_string())
.expect("Failed to create AWS region 'us-east-1'")
});
let bedrock_provider = Arc::new(BedrockProvider::new(bedrock_region));
registry.register(bedrock_provider);
}
let provider_router = Arc::new(ProviderRouter::new(Arc::new(registry)));
Self {
hot_path,
ring_buffer,
audit_shutdown_tx: None,
provider_router,
}
}
pub fn ring_buffer(&self) -> Arc<RingBuffer> {
Arc::clone(&self.ring_buffer)
}
pub fn start_audit_processor(&mut self) {
let (processor, shutdown_tx) = AuditPathProcessor::new(Arc::clone(&self.ring_buffer));
tokio::spawn(async move {
processor.run().await;
});
self.audit_shutdown_tx = Some(shutdown_tx);
}
pub fn into_router(mut self, auth_config: crate::proxy::AuthConfig) -> axum::Router {
self.start_audit_processor();
let router = axum::Router::new()
.route(
crate::proxy::headers::paths::HEALTH,
axum::routing::get(health_handler),
)
.route(
crate::proxy::headers::paths::METRICS,
axum::routing::get(metrics_handler),
)
.fallback(proxy_handler)
.with_state(Arc::new(self));
let middleware_stack = ProxyMiddlewareStack::new(auth_config);
middleware_stack.apply_to_router(router)
}
}
async fn proxy_handler(
State(proxy): State<Arc<ProxyService>>,
request: Request<Body>,
) -> Result<Response, ProxyError> {
let request_id = RequestId::new();
let path = request.uri().path();
if path.starts_with("/bedrock/")
|| path.starts_with("/openai/")
|| path.starts_with("/anthropic/")
{
proxy
.provider_router
.route_request(request, request_id)
.await
} else {
let target_url = UrlResolver::extract_target_url(&request)?;
proxy
.hot_path
.forward_request(request, target_url, request_id)
.await
}
}
impl IntoResponse for ProxyError {
fn into_response(self) -> Response {
use crate::proxy::error_response::ErrorResponseExt;
let status = self.status_code();
let error_response = self.to_error_response();
error_response.into_response_with_status(status)
}
}
async fn health_handler() -> &'static str {
"OK"
}
async fn metrics_handler() -> &'static str {
"metrics: placeholder"
}