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
14pub 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#[derive(Clone)]
60pub struct AppState {
61 pub proxy_state: ProxyState,
62 pub exec_ctx: Arc<ExecutionContext>,
63 pub shutdown_token: CancellationToken,
65 pub llm_api_base: String,
67 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}