New crates
- platform-nats: async_nats JetStream bridge; pull/push consumers, explicit ACK,
subject prefixing under provisioning.>, 6 stream definitions on startup
- platform-db: SurrealDB pool (embedded RocksDB solo, Surreal<Mem> tests,
WebSocket server multi-user); migrate() with DEFINE TABLE IF NOT EXISTS DDL
Service integrations
- orchestrator: NATS pub on task state transitions, execution_logs → SurrealDB,
webhook handler (HMAC-SHA256), AuditCollector (batch INSERT, 100-event/1s flush)
- control-center: solo_auth_middleware (intentional bypass, --mode solo only),
NATS session events, WebSocket bridge via JetStream subscription (no polling)
- vault-service: NATS lease flow; credentials over HTTPS only (lease_id in NATS);
SurrealDB storage backend with MVCC retry + exponential backoff
- secretumvault: complete SurrealDB backend replacing HashMap; 9 unit + 19 integration tests
- extension-registry: NATS lifecycle events, vault:// credential resolver with TTL cache,
cache invalidation via provisioning.workspace.*.deploy.done
Clippy workspace clean
cargo clippy --workspace -- -D warnings: 0 errors
Patterns fixed: derivable_impls (#[default] on enum variants), excessive_nesting
(let-else, boolean arithmetic in retain, extracted helpers), io_error_other,
redundant_closure, iter_kv_map, manual_range_contains, pathbuf_instead_of_path
150 lines
4.0 KiB
Rust
150 lines
4.0 KiB
Rust
use std::collections::HashMap;
|
|
use std::path::Path;
|
|
|
|
use anyhow::Context;
|
|
use platform_config::ConfigLoader;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// AI Service configuration
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
pub struct AiServiceConfig {
|
|
pub ai_service: AiServiceSettings,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
pub struct AiServiceSettings {
|
|
pub server: ServerConfig,
|
|
pub rag: RagConfig,
|
|
pub mcp: McpConfig,
|
|
pub dag: DagConfig,
|
|
#[serde(default)]
|
|
pub monitoring: Option<MonitoringConfig>,
|
|
#[serde(default)]
|
|
pub logging: Option<LoggingConfig>,
|
|
#[serde(default)]
|
|
pub build: Option<DockerBuildConfig>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ServerConfig {
|
|
pub host: String,
|
|
pub port: u16,
|
|
#[serde(default)]
|
|
pub workers: Option<usize>,
|
|
}
|
|
|
|
impl Default for ServerConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
host: "127.0.0.1".to_string(),
|
|
port: 8082,
|
|
workers: Some(4),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
pub struct RagConfig {
|
|
pub enabled: bool,
|
|
pub rag_service_url: Option<String>,
|
|
pub timeout: Option<u64>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
pub struct McpConfig {
|
|
pub enabled: bool,
|
|
pub mcp_service_url: Option<String>,
|
|
pub timeout: Option<u64>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
pub struct DagConfig {
|
|
pub max_concurrent_tasks: Option<usize>,
|
|
pub task_timeout: Option<u64>,
|
|
pub retry_attempts: Option<u32>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MonitoringConfig {
|
|
pub enabled: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct LoggingConfig {
|
|
pub level: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DockerBuildConfig {
|
|
pub base_image: String,
|
|
#[serde(default)]
|
|
pub build_args: HashMap<String, String>,
|
|
}
|
|
|
|
impl AiServiceConfig {
|
|
pub fn load() -> anyhow::Result<Self> {
|
|
let config_json = platform_config::load_service_config_from_ncl("ai-service")
|
|
.context("Failed to load ai-service configuration from Nickel")?;
|
|
|
|
serde_json::from_value(config_json)
|
|
.context("Failed to deserialize ai-service configuration")
|
|
}
|
|
}
|
|
|
|
impl ConfigLoader for AiServiceConfig {
|
|
fn service_name() -> &'static str {
|
|
"ai-service"
|
|
}
|
|
|
|
fn load_from_hierarchy() -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>>
|
|
{
|
|
if let Some(path) = platform_config::resolve_config_path(Self::service_name()) {
|
|
return Self::from_path(&path);
|
|
}
|
|
Ok(Self::default())
|
|
}
|
|
|
|
fn apply_env_overrides(
|
|
&mut self,
|
|
) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
if let Ok(host) = std::env::var("AI_SERVICE_SERVER_HOST") {
|
|
self.ai_service.server.host = host;
|
|
}
|
|
if let Ok(port) = std::env::var("AI_SERVICE_SERVER_PORT") {
|
|
if let Ok(p) = port.parse() {
|
|
self.ai_service.server.port = p;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn from_path<P: AsRef<Path>>(
|
|
path: P,
|
|
) -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
|
let path = path.as_ref();
|
|
let json_value = platform_config::format::load_config(path)
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
|
|
|
|
serde_json::from_value(json_value).map_err(|e| {
|
|
let err_msg = format!("Failed to deserialize ai-service config: {}", e);
|
|
Box::new(std::io::Error::new(
|
|
std::io::ErrorKind::InvalidData,
|
|
err_msg,
|
|
)) as Box<dyn std::error::Error + Send + Sync>
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_default_config() {
|
|
let config = AiServiceConfig::default();
|
|
assert_eq!(config.ai_service.server.port, 8082);
|
|
assert!(!config.ai_service.rag.enabled);
|
|
assert!(!config.ai_service.mcp.enabled);
|
|
}
|
|
}
|