ceylon_next/runner/
config.rs1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct CeylonConfig {
10 pub package: PackageConfig,
11 pub agent: AgentConfig,
12 #[serde(default)]
13 pub tools: ToolsConfig,
14 #[serde(default)]
15 pub memory: MemoryConfig,
16 #[serde(default)]
17 pub build: BuildConfig,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct PackageConfig {
22 pub name: String,
23 pub version: String,
24 pub description: Option<String>,
25 pub authors: Option<Vec<String>>,
26 pub license: Option<String>,
27 pub keywords: Option<Vec<String>>,
28 pub repository: Option<String>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct AgentConfig {
33 pub name: String,
34 pub model: String,
35 pub system_prompt_file: Option<String>,
36 #[serde(default = "default_max_retries")]
37 pub max_retries: u32,
38 #[serde(default = "default_timeout")]
39 pub timeout: u64,
40 #[serde(default)]
41 pub analyze_goals: bool,
42 pub temperature: Option<f32>,
43 pub max_tokens: Option<u32>,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, Default)]
47pub struct ToolsConfig {
48 #[serde(default)]
49 pub include: Vec<String>,
50 #[serde(default)]
51 pub custom: Vec<String>,
52 #[serde(default)]
53 pub config: HashMap<String, toml::Value>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct MemoryConfig {
58 #[serde(default = "default_memory_backend")]
59 pub backend: String,
60 pub path: Option<String>,
61 #[serde(default)]
62 pub vector_enabled: bool,
63 pub vector_provider: Option<String>,
64}
65
66impl Default for MemoryConfig {
67 fn default() -> Self {
68 Self {
69 backend: default_memory_backend(),
70 path: None,
71 vector_enabled: false,
72 vector_provider: None,
73 }
74 }
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, Default)]
78pub struct BuildConfig {
79 #[serde(default)]
80 pub compile: bool,
81 pub target_dir: Option<String>,
82 #[serde(default)]
83 pub include: Vec<String>,
84}
85
86fn default_max_retries() -> u32 {
87 3
88}
89
90fn default_timeout() -> u64 {
91 120
92}
93
94fn default_memory_backend() -> String {
95 "in-memory".to_string()
96}
97
98impl CeylonConfig {
99 pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
101 let content = fs::read_to_string(path.as_ref())
102 .context("Failed to read ceylon.toml file")?;
103
104 let config: CeylonConfig = toml::from_str(&content)
105 .context("Failed to parse ceylon.toml")?;
106
107 Ok(config)
108 }
109
110 pub fn load_from_current_dir() -> Result<Self> {
112 Self::load("./ceylon.toml")
113 }
114
115 pub fn load_from_dir<P: AsRef<Path>>(dir: P) -> Result<Self> {
117 let config_path = dir.as_ref().join("ceylon.toml");
118 Self::load(config_path)
119 }
120
121 pub fn system_prompt_path(&self, base_dir: &Path) -> Option<PathBuf> {
123 self.agent.system_prompt_file.as_ref().map(|p| {
124 let path = Path::new(p);
125 if path.is_absolute() {
126 path.to_path_buf()
127 } else {
128 base_dir.join(path)
129 }
130 })
131 }
132
133 pub fn read_system_prompt(&self, base_dir: &Path) -> Result<Option<String>> {
135 if let Some(prompt_path) = self.system_prompt_path(base_dir) {
136 let content = fs::read_to_string(&prompt_path)
137 .with_context(|| format!("Failed to read system prompt from {:?}", prompt_path))?;
138 Ok(Some(content))
139 } else {
140 Ok(None)
141 }
142 }
143
144 pub fn memory_path(&self, base_dir: &Path) -> Option<PathBuf> {
146 self.memory.path.as_ref().map(|p| {
147 let path = Path::new(p);
148 if path.is_absolute() {
149 path.to_path_buf()
150 } else {
151 base_dir.join(path)
152 }
153 })
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn test_parse_config() {
163 let toml_content = r#"
164[package]
165name = "test-agent"
166version = "1.0.0"
167description = "Test agent"
168
169[agent]
170name = "TestAgent"
171model = "openai::gpt-4"
172system_prompt_file = "./prompts/system.txt"
173max_retries = 5
174timeout = 180
175analyze_goals = true
176
177[tools]
178include = ["calculator"]
179custom = ["csv_analyzer"]
180
181[memory]
182backend = "sqlite"
183path = "./data/memory.db"
184vector_enabled = true
185
186[build]
187compile = true
188 "#;
189
190 let config: CeylonConfig = toml::from_str(toml_content).unwrap();
191
192 assert_eq!(config.package.name, "test-agent");
193 assert_eq!(config.agent.name, "TestAgent");
194 assert_eq!(config.agent.model, "openai::gpt-4");
195 assert_eq!(config.agent.max_retries, 5);
196 assert_eq!(config.agent.timeout, 180);
197 assert_eq!(config.memory.backend, "sqlite");
198 assert!(config.memory.vector_enabled);
199 assert!(config.build.compile);
200 }
201}