use crate::config::Config;
use crate::core::audit::AuditLogger;
use crate::core::budget::{BudgetManager, UnifiedBudgetLimits};
use crate::core::cache::{DualCacheConfig, LLMCache, LLMCacheConfig};
use crate::core::guardrails::GuardrailEngine;
use crate::core::ip_access::IpAccessControl;
use crate::core::keys::{DatabaseKeyRepository, KeyManager};
use crate::core::observability::RuntimeObservability;
use crate::core::pricing_service::PricingService;
use crate::core::teams::TeamManager;
use crate::core::virtual_keys::RuntimeVirtualKeyManager;
use crate::server::routes::ai::budgeted::BudgetedExecutor;
use crate::storage::database::SeaOrmTeamRepository;
use crate::storage::redis::RedisPool;
use crate::utils::sync::AtomicValue;
use std::sync::Arc;
use std::time::Duration;
use tracing::error;
#[derive(Clone)]
pub struct AppState {
pub config: AtomicValue<Config>,
pub auth: Arc<crate::auth::AuthSystem>,
pub unified_router: Arc<crate::core::router::UnifiedRouter>,
pub storage: Arc<crate::storage::StorageLayer>,
pub pricing: Arc<PricingService>,
pub budget_limits: Arc<UnifiedBudgetLimits>,
pub budget_manager: Arc<BudgetManager>,
pub team_manager: Arc<TeamManager>,
pub key_manager: RuntimeVirtualKeyManager,
pub(crate) budgeted: BudgetedExecutor,
pub response_cache: Option<Arc<LLMCache>>,
pub callbacks: RuntimeObservability,
pub audit_logger: Arc<AuditLogger>,
pub guardrails: Arc<GuardrailEngine>,
pub ip_access: Arc<IpAccessControl>,
}
impl AppState {
pub fn new_with_unified_router(
config: Config,
auth: crate::auth::AuthSystem,
unified_router: crate::core::router::UnifiedRouter,
storage: crate::storage::StorageLayer,
pricing: Arc<PricingService>,
budget_limits: Arc<UnifiedBudgetLimits>,
) -> Self {
let storage = Arc::new(storage);
let response_cache = build_response_cache(&config, storage.redis.clone());
let key_manager = KeyManager::new(DatabaseKeyRepository::new(storage.clone()))
.with_hmac_secret(config.gateway.auth.api_key_hmac_secret.clone());
let budget_manager = Arc::new(BudgetManager::new());
let budgeted = BudgetedExecutor::new(
budget_limits.clone(),
budget_manager.clone(),
pricing.clone(),
key_manager.clone(),
);
let team_manager = Arc::new(TeamManager::new(Arc::new(SeaOrmTeamRepository::new(
storage.database.clone(),
))));
Self {
config: AtomicValue::new(config),
auth: Arc::new(auth),
unified_router: Arc::new(unified_router),
storage,
pricing,
budget_limits,
budget_manager,
team_manager,
key_manager,
budgeted,
response_cache,
callbacks: RuntimeObservability::disabled(),
audit_logger: Arc::new(AuditLogger::disabled()),
guardrails: Arc::new(GuardrailEngine::disabled()),
ip_access: Arc::new(IpAccessControl::disabled()),
}
}
pub fn with_callbacks(mut self, callbacks: RuntimeObservability) -> Self {
self.callbacks = callbacks;
self
}
pub fn with_audit_logger(mut self, audit_logger: Arc<AuditLogger>) -> Self {
self.audit_logger = audit_logger;
self
}
pub fn with_request_policies(
mut self,
guardrails: Arc<GuardrailEngine>,
ip_access: Arc<IpAccessControl>,
) -> Self {
self.guardrails = guardrails;
self.ip_access = ip_access;
self
}
pub fn config(&self) -> Arc<Config> {
self.config.load()
}
}
fn build_response_cache(config: &Config, redis: Arc<RedisPool>) -> Option<Arc<LLMCache>> {
if !config.gateway.cache.enabled {
return None;
}
if config.gateway.cache.ttl == 0 {
error!("cache.enabled=true requires cache.ttl > 0; response cache disabled");
return None;
}
let ttl = Duration::from_secs(config.gateway.cache.ttl);
let redis_pool = (!redis.is_noop()).then_some(redis);
let cache_config = if redis_pool.is_some() {
DualCacheConfig::default()
} else {
DualCacheConfig::memory_only()
}
.with_max_size(config.gateway.cache.max_size)
.with_ttl(ttl);
let llm_config = LLMCacheConfig {
cache_config,
chat_ttl: ttl,
embedding_ttl: ttl,
user_specific: true,
semantic_cache_enabled: false,
similarity_threshold: config.gateway.cache.similarity_threshold,
};
let cache = Arc::new(LLMCache::new(llm_config, redis_pool));
cache.start_cleanup_tasks();
Some(cache)
}