Skip to main content

agentic_server/
app.rs

1use std::sync::Arc;
2
3use axum::Router;
4use axum::routing::{get, post};
5use http::HeaderValue;
6use tokio_util::sync::CancellationToken;
7use tower_http::cors::{AllowOrigin, Any, CorsLayer};
8
9use agentic_core::executor::ExecutionContext;
10use agentic_core::proxy::ProxyState;
11
12use crate::handler::{conversations, health, models, ready, responses, responses_ws};
13
14/// Server-level configuration read from environment variables.
15pub struct ServerConfig {
16    pub cors_allowed_origins: Vec<String>,
17}
18
19impl ServerConfig {
20    #[must_use]
21    pub fn from_env() -> Self {
22        let cors_allowed_origins = std::env::var("CORS_ALLOWED_ORIGINS")
23            .ok()
24            .map(|s| {
25                s.split(',')
26                    .map(str::trim)
27                    .filter(|o| !o.is_empty())
28                    .map(str::to_owned)
29                    .collect::<Vec<_>>()
30            })
31            .unwrap_or_default();
32        Self { cors_allowed_origins }
33    }
34
35    fn cors_layer(&self) -> CorsLayer {
36        let allow_origin = if self.cors_allowed_origins.is_empty() {
37            AllowOrigin::any()
38        } else {
39            let origins: Vec<HeaderValue> = self
40                .cors_allowed_origins
41                .iter()
42                .filter_map(|o| o.parse().ok())
43                .collect();
44            AllowOrigin::list(origins)
45        };
46
47        CorsLayer::new()
48            .allow_origin(allow_origin)
49            .allow_methods(Any)
50            .allow_headers(Any)
51    }
52}
53
54/// Shared application state injected into every handler.
55///
56/// Both states are always present:
57/// - `proxy_state` handles `store=false` requests (direct passthrough to vLLM)
58/// - `exec_ctx` handles `store=true` requests (stateful executor with DB)
59#[derive(Clone)]
60pub struct AppState {
61    pub proxy_state: ProxyState,
62    pub exec_ctx: Arc<ExecutionContext>,
63    /// Shared cancellation signal used to drain long-lived handlers.
64    pub shutdown_token: CancellationToken,
65    /// vLLM base URL — used by the `/ready` health probe.
66    pub llm_api_base: String,
67    /// Server-configured API key; used as fallback when the request carries no
68    /// `Authorization` header on the executor path.
69    pub openai_api_key: Option<String>,
70}
71
72pub fn build_router(state: AppState, server_config: &ServerConfig) -> Router {
73    Router::new()
74        .route("/health", get(health))
75        .route("/ready", get(ready))
76        .route("/v1/conversations", post(conversations))
77        .route("/v1/models", get(models))
78        .route("/v1/responses", post(responses).get(responses_ws))
79        .layer(server_config.cors_layer())
80        .with_state(state)
81}