1use std::collections::HashMap;
2use std::fs;
3use std::path::Path;
4use serde::{Deserialize, Serialize};
5use crate::atp::ast::*;
6use crate::atp::value::Value as AstValue;
7
8fn extract_string_value(
9 expr: &Option<&Expression>,
10) -> Result<String, String> {
11 match expr {
12 Some(Expression::String(s)) => Ok(s.clone()),
13 Some(Expression::Identifier(s)) => Ok(s.clone()),
14 _ => Ok(String::new()),
15 }
16}
17fn extract_float_value(expr: &Option<&Expression>) -> Result<f64, String> {
18 match expr {
19 Some(Expression::Number(n)) => Ok(*n),
20 _ => Ok(0.0),
21 }
22}
23fn extract_int_value(expr: &Option<&Expression>) -> Result<i64, String> {
24 match expr {
25 Some(Expression::Number(n)) => Ok(*n as i64),
26 _ => Ok(0),
27 }
28}
29fn extract_bool_value(expr: &Option<&Expression>) -> Result<bool, String> {
30 match expr {
31 Some(Expression::Bool(b)) => Ok(*b),
32 _ => Ok(false),
33 }
34}
35fn extract_duration_value(
36 expr: &Option<&Expression>,
37) -> Result<Duration, String> {
38 match expr {
39 Some(Expression::Duration(duration)) => {
40 Ok(Duration {
41 value: duration.value as u64,
42 unit: duration.unit.clone(),
43 })
44 }
45 _ => {
46 Ok(Duration {
47 value: 0,
48 unit: TimeUnit::Seconds,
49 })
50 }
51 }
52}
53fn extract_array_values(
54 expr: &Option<&Expression>,
55) -> Result<Vec<String>, String> {
56 match expr {
57 Some(Expression::Array(items)) => {
58 items
59 .iter()
60 .map(|e| match e {
61 Expression::String(s) => Ok(s.clone()),
62 Expression::Identifier(s) => Ok(s.clone()),
63 _ => Err("Array items must be strings".to_string()),
64 })
65 .collect()
66 }
67 _ => Ok(Vec::new()),
68 }
69}
70fn extract_map_values(
71 expr: &Option<&Expression>,
72) -> Result<std::collections::HashMap<String, String>, String> {
73 match expr {
74 Some(Expression::Object(map)) => {
75 let mut result = std::collections::HashMap::new();
76 for (k, v) in map {
77 let value = match v {
78 Expression::String(s) => s.clone(),
79 Expression::Identifier(s) => s.clone(),
80 Expression::Number(n) => n.to_string(),
81 Expression::Bool(b) => b.to_string(),
82 _ => String::new(),
83 };
84 result.insert(k.clone(), value);
85 }
86 Ok(result)
87 }
88 _ => Ok(std::collections::HashMap::new()),
89 }
90}
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct HelixConfig {
93 pub projects: HashMap<String, ProjectConfig>,
94 pub agents: HashMap<String, AgentConfig>,
95 pub workflows: HashMap<String, WorkflowConfig>,
96 pub memory: Option<MemoryConfig>,
97 pub contexts: HashMap<String, ContextConfig>,
98 pub crews: HashMap<String, CrewConfig>,
99 pub pipelines: HashMap<String, PipelineConfig>,
100 pub plugins: Vec<PluginConfig>,
101 pub databases: HashMap<String, DatabaseConfig>,
102 pub sections: HashMap<String, HashMap<String, Value>>,
103}
104impl Default for HelixConfig {
105 fn default() -> Self {
106 Self {
107 projects: HashMap::new(),
108 agents: HashMap::new(),
109 workflows: HashMap::new(),
110 memory: None,
111 contexts: HashMap::new(),
112 crews: HashMap::new(),
113 pipelines: HashMap::new(),
114 plugins: Vec::new(),
115 databases: HashMap::new(),
116 sections: HashMap::new(),
117 }
118 }
119}
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct ProjectConfig {
122 pub name: String,
123 pub version: String,
124 pub author: String,
125 pub description: Option<String>,
126 pub metadata: HashMap<String, Value>,
127}
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct AgentConfig {
130 pub name: String,
131 pub model: String,
132 pub role: String,
133 pub temperature: Option<f32>,
134 pub max_tokens: Option<u32>,
135 pub capabilities: Vec<String>,
136 pub backstory: Option<String>,
137 pub tools: Vec<String>,
138 pub constraints: Vec<String>,
139}
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct WorkflowConfig {
142 pub name: String,
143 pub trigger: TriggerConfig,
144 pub steps: Vec<StepConfig>,
145 pub pipeline: Option<PipelineConfig>,
146 pub outputs: Vec<String>,
147 pub on_error: Option<String>,
148}
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct StepConfig {
151 pub name: String,
152 pub agent: Option<String>,
153 pub crew: Option<Vec<String>>,
154 pub task: String,
155 pub timeout: Option<Duration>,
156 pub parallel: bool,
157 pub depends_on: Vec<String>,
158 pub retry: Option<RetryConfig>,
159}
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct MemoryConfig {
162 pub provider: String,
163 pub connection: String,
164 pub embeddings: EmbeddingConfig,
165 pub cache_size: Option<usize>,
166 pub persistence: bool,
167}
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct EmbeddingConfig {
170 pub model: String,
171 pub dimensions: u32,
172 pub batch_size: Option<u32>,
173}
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct ContextConfig {
176 pub name: String,
177 pub environment: String,
178 pub debug: bool,
179 pub max_tokens: Option<u64>,
180 pub secrets: HashMap<String, SecretRef>,
181 pub variables: HashMap<String, Value>,
182}
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct CrewConfig {
185 pub name: String,
186 pub agents: Vec<String>,
187 pub process_type: ProcessType,
188 pub manager: Option<String>,
189 pub max_iterations: Option<u32>,
190 pub verbose: bool,
191}
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct PluginConfig {
194 pub name: String,
195 pub source: String,
196 pub version: String,
197 pub config: HashMap<String, Value>,
198}
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct DatabaseConfig {
201 pub name: String,
202 pub path: Option<String>,
203 pub shards: Option<i64>,
204 pub compression: Option<bool>,
205 pub cache_size: Option<i64>,
206 pub vector_index: Option<VectorIndexConfig>,
207 pub properties: HashMap<String, Value>,
208}
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct VectorIndexConfig {
211 pub index_type: String,
212 pub dimensions: i64,
213 pub m: Option<i64>,
214 pub ef_construction: Option<i64>,
215 pub distance_metric: Option<String>,
216}
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub enum Value {
219 String(String),
220 Number(f64),
221 Bool(bool),
222 Null,
223 Array(Vec<Value>),
224 Object(HashMap<String, Value>),
225 Duration(Duration),
226 Reference(String),
227 Identifier(String),
228}
229impl Value {
230 pub fn as_string(&self) -> Option<&str> {
231 match self {
232 Value::String(s) => Some(s),
233 _ => None,
234 }
235 }
236 pub fn as_str(&self) -> Option<&str> {
237 self.as_string()
238 }
239 pub fn as_number(&self) -> Option<f64> {
240 match self {
241 Value::Number(n) => Some(*n),
242 _ => None,
243 }
244 }
245 pub fn as_bool(&self) -> Option<bool> {
246 match self {
247 Value::Bool(b) => Some(*b),
248 _ => None,
249 }
250 }
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
254pub struct Duration {
255 pub value: u64,
256 pub unit: TimeUnit,
257}
258#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
259pub enum TimeUnit {
260 Seconds,
261 Minutes,
262 Hours,
263 Days,
264}
265#[derive(Debug, Clone, Serialize, Deserialize)]
266pub enum TriggerConfig {
267 Manual,
268 Schedule(String),
269 Webhook(String),
270 Event(String),
271 FileWatch(String),
272}
273#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
274pub enum ProcessType {
275 Sequential,
276 Hierarchical,
277 Parallel,
278 Consensus,
279}
280#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct RetryConfig {
282 pub max_attempts: u32,
283 pub delay: Duration,
284 pub backoff: BackoffStrategy,
285}
286#[derive(Debug, Clone, Serialize, Deserialize)]
287pub enum BackoffStrategy {
288 Fixed,
289 Linear,
290 Exponential,
291}
292#[derive(Debug, Clone, Serialize, Deserialize)]
293pub struct PipelineConfig {
294 pub name: String,
295 pub stages: Vec<String>,
296 pub flow: String,
297}
298#[derive(Debug, Clone, Serialize, Deserialize)]
299pub enum SecretRef {
300 Environment(String),
301 Vault(String),
302 File(String),
303}
304pub struct HelixLoader {
305 configs: HashMap<String, HelixConfig>,
306 current_context: Option<String>,
307}
308impl HelixLoader {
309 pub fn new() -> Self {
310 HelixLoader {
311 configs: HashMap::new(),
312 current_context: None,
313 }
314 }
315 pub fn load_file<P: AsRef<Path>>(
316 &mut self,
317 path: P,
318 ) -> Result<HelixConfig, HlxError> {
319 let content = fs::read_to_string(path)?;
320 self.parse(&content)
321 }
322 pub fn parse(&mut self, content: &str) -> Result<HelixConfig, HlxError> {
323 let tokens = crate::atp::lexer::tokenize(content)?;
324 let ast = crate::atp::parser::parse(tokens)?;
325 let config = self.ast_to_config(ast)?;
326 Ok(config)
327 }
328 pub fn ast_to_config(
329 &self,
330 ast: HelixAst,
331 ) -> Result<HelixConfig, HlxError> {
332 let mut config = HelixConfig {
333 projects: HashMap::new(),
334 agents: HashMap::new(),
335 workflows: HashMap::new(),
336 memory: None,
337 contexts: HashMap::new(),
338 crews: HashMap::new(),
339 pipelines: HashMap::new(),
340 plugins: Vec::new(),
341 databases: HashMap::new(),
342 sections: HashMap::new(),
343 };
344 for decl in ast.declarations {
345 match decl {
346 Declaration::Project(p) => {
347 let project = self.convert_project(p)?;
348 config.projects.insert(project.name.clone(), project);
349 }
350 Declaration::Agent(a) => {
351 let agent = self.convert_agent(a)?;
352 config.agents.insert(agent.name.clone(), agent);
353 }
354 Declaration::Workflow(w) => {
355 let workflow = self.convert_workflow(w)?;
356 config.workflows.insert(workflow.name.clone(), workflow);
357 }
358 Declaration::Memory(m) => {
359 config.memory = Some(self.convert_memory(m)?);
360 }
361 Declaration::Context(c) => {
362 let context = self.convert_context(c)?;
363 config.contexts.insert(context.name.clone(), context);
364 }
365 Declaration::Crew(cr) => {
366 let crew = self.convert_crew(cr)?;
367 config.crews.insert(crew.name.clone(), crew);
368 }
369 Declaration::Plugin(p) => {
370 config.plugins.push(self.convert_plugin(p)?);
371 }
372 Declaration::Database(d) => {
373 let database = self.convert_database(d)?;
374 config.databases.insert(database.name.clone(), database);
375 }
376 Declaration::Task(t) => {
377 let task_data: HashMap<String, Value> = t
378 .properties
379 .iter()
380 .map(|(k, v)| (k.clone(), v.to_value()))
381 .collect();
382 config.sections.insert(format!("task.{}", t.name), task_data);
383 }
384 Declaration::Pipeline(p) => {
385 let pipeline = self.convert_pipeline(p)?;
386 config.pipelines.insert(pipeline.name.clone(), pipeline);
387 }
388 Declaration::Load(_l) => {}
389 Declaration::Section(s) => {
390 let section_data: HashMap<String, Value> = s
391 .properties
392 .iter()
393 .map(|(k, v)| (k.clone(), v.to_value()))
394 .collect();
395 config.sections.insert(s.name.clone(), section_data);
396 }
397 }
398 }
399 Ok(config)
400 }
401 fn convert_project(
402 &self,
403 project: ProjectDecl,
404 ) -> Result<ProjectConfig, HlxError> {
405 let mut metadata = HashMap::new();
406 let mut version = String::new();
407 let mut author = String::new();
408 let mut description = None;
409 for (key, expr) in project.properties {
410 let expr_opt = Some(&expr);
411 match key.as_str() {
412 "version" => {
413 version = extract_string_value(&expr_opt).unwrap_or_default();
414 }
415 "author" => {
416 author = extract_string_value(&expr_opt).unwrap_or_default();
417 }
418 "description" => {
419 let desc = extract_string_value(&expr_opt).unwrap_or_default();
420 description = if desc.is_empty() { None } else { Some(desc) };
421 }
422 _ => {
423 metadata.insert(key, self.expression_to_value(expr));
424 }
425 }
426 }
427 Ok(ProjectConfig {
428 name: project.name,
429 version,
430 author,
431 description,
432 metadata,
433 })
434 }
435 fn convert_agent(
436 &self,
437 agent: AgentDecl,
438 ) -> Result<AgentConfig, HlxError> {
439 let mut config = AgentConfig {
440 name: agent.name.clone(),
441 model: String::new(),
442 role: String::new(),
443 temperature: None,
444 max_tokens: None,
445 capabilities: agent.capabilities.unwrap_or_default(),
446 backstory: agent.backstory.map(|b| b.lines.join("\n")),
447 tools: agent.tools.unwrap_or_default(),
448 constraints: Vec::new(),
449 };
450 for (key, expr) in agent.properties {
451 let expr_opt = Some(&expr);
452 match key.as_str() {
453 "model" => {
454 config.model = extract_string_value(&expr_opt).unwrap_or_default();
455 }
456 "role" => {
457 config.role = extract_string_value(&expr_opt).unwrap_or_default();
458 }
459 "temperature" => {
460 config.temperature = extract_float_value(&expr_opt)
461 .ok()
462 .map(|f| f as f32);
463 }
464 "max_tokens" => {
465 config.max_tokens = extract_int_value(&expr_opt)
466 .ok()
467 .map(|i| i as u32);
468 }
469 "custom" | "properties" | "config" => {
470 if let Ok(custom_map) = extract_map_values(&expr_opt) {
471 for (key, value) in custom_map {
472 println!("Agent custom property: {} = {}", key, value);
473 }
474 }
475 }
476 _ => {}
477 }
478 }
479 Ok(config)
480 }
481 fn convert_workflow(
482 &self,
483 workflow: WorkflowDecl,
484 ) -> Result<WorkflowConfig, HlxError> {
485 let trigger = if let Some(t) = workflow.trigger {
486 self.convert_trigger(t)?
487 } else {
488 TriggerConfig::Manual
489 };
490 let mut steps = Vec::new();
491 for step in workflow.steps {
492 steps.push(self.convert_step(step)?);
493 }
494 let pipeline = if let Some(p) = workflow.pipeline {
495 Some(self.convert_pipeline(p)?)
496 } else {
497 None
498 };
499 Ok(WorkflowConfig {
500 name: workflow.name,
501 trigger,
502 steps,
503 pipeline,
504 outputs: Vec::new(),
505 on_error: None,
506 })
507 }
508 fn convert_trigger(
509 &self,
510 expr: Expression,
511 ) -> Result<TriggerConfig, HlxError> {
512 match expr {
513 Expression::String(s) | Expression::Identifier(s) => {
514 match s.as_str() {
515 "manual" => Ok(TriggerConfig::Manual),
516 s if s.starts_with("schedule:") => {
517 Ok(
518 TriggerConfig::Schedule(
519 s.trim_start_matches("schedule:").to_string(),
520 ),
521 )
522 }
523 s if s.starts_with("webhook:") => {
524 Ok(
525 TriggerConfig::Webhook(
526 s.trim_start_matches("webhook:").to_string(),
527 ),
528 )
529 }
530 s if s.starts_with("event:") => {
531 Ok(
532 TriggerConfig::Event(
533 s.trim_start_matches("event:").to_string(),
534 ),
535 )
536 }
537 s if s.starts_with("file:") => {
538 Ok(
539 TriggerConfig::FileWatch(
540 s.trim_start_matches("file:").to_string(),
541 ),
542 )
543 }
544 _ => Ok(TriggerConfig::Manual),
545 }
546 }
547 _ => Ok(TriggerConfig::Manual),
548 }
549 }
550 fn convert_step(&self, step: StepDecl) -> Result<StepConfig, HlxError> {
551 let mut config = StepConfig {
552 name: step.name,
553 agent: step.agent,
554 crew: step.crew,
555 task: step.task.unwrap_or_default(),
556 timeout: None,
557 parallel: false,
558 depends_on: Vec::new(),
559 retry: None,
560 };
561 for (key, expr) in step.properties {
562 let expr_opt = Some(&expr);
563 match key.as_str() {
564 "timeout" => {
565 config.timeout = extract_duration_value(&expr_opt).ok();
566 }
567 "parallel" => {
568 config.parallel = extract_bool_value(&expr_opt).unwrap_or(false);
569 }
570 "depends_on" => {
571 config.depends_on = extract_array_values(&expr_opt)
572 .unwrap_or_default();
573 }
574 "retry" => {
575 if let Some(obj) = expr.as_object() {
576 config.retry = self.convert_retry_config(obj);
577 }
578 }
579 _ => {}
580 }
581 }
582 Ok(config)
583 }
584 fn convert_retry_config(
585 &self,
586 obj: &HashMap<String, Expression>,
587 ) -> Option<RetryConfig> {
588 let max_attempts = obj.get("max_attempts")?.as_number()? as u32;
589 let delay = obj
590 .get("delay")
591 .and_then(|e| self.expression_to_duration(e.clone()))?;
592 let backoff = obj
593 .get("backoff")
594 .and_then(|e| e.as_string())
595 .and_then(|s| match s.as_str() {
596 "fixed" => Some(BackoffStrategy::Fixed),
597 "linear" => Some(BackoffStrategy::Linear),
598 "exponential" => Some(BackoffStrategy::Exponential),
599 _ => None,
600 })
601 .unwrap_or(BackoffStrategy::Fixed);
602 Some(RetryConfig {
603 max_attempts,
604 delay,
605 backoff,
606 })
607 }
608 fn convert_pipeline(
609 &self,
610 pipeline: PipelineDecl,
611 ) -> Result<PipelineConfig, HlxError> {
612 let stages = pipeline
613 .flow
614 .iter()
615 .filter_map(|node| {
616 if let PipelineNode::Step(name) = node {
617 Some(name.clone())
618 } else {
619 None
620 }
621 })
622 .collect();
623 let flow = pipeline
624 .flow
625 .iter()
626 .filter_map(|node| {
627 if let PipelineNode::Step(name) = node {
628 Some(name.clone())
629 } else {
630 None
631 }
632 })
633 .collect::<Vec<_>>()
634 .join(" -> ");
635 Ok(PipelineConfig {
636 name: "default".to_string(),
637 stages,
638 flow,
639 })
640 }
641 fn convert_memory(
642 &self,
643 memory: MemoryDecl,
644 ) -> Result<MemoryConfig, HlxError> {
645 let embeddings = if let Some(e) = memory.embeddings {
646 EmbeddingConfig {
647 model: e.model,
648 dimensions: e.dimensions,
649 batch_size: e
650 .properties
651 .get("batch_size")
652 .and_then(|v| v.as_number())
653 .map(|n| n as u32),
654 }
655 } else {
656 EmbeddingConfig {
657 model: String::new(),
658 dimensions: 0,
659 batch_size: None,
660 }
661 };
662 Ok(MemoryConfig {
663 provider: memory.provider,
664 connection: memory.connection,
665 embeddings,
666 cache_size: memory
667 .properties
668 .get("cache_size")
669 .and_then(|v| v.as_number())
670 .map(|n| n as usize),
671 persistence: memory
672 .properties
673 .get("persistence")
674 .and_then(|v| v.as_bool())
675 .unwrap_or(true),
676 })
677 }
678 fn convert_context(
679 &self,
680 context: ContextDecl,
681 ) -> Result<ContextConfig, HlxError> {
682 let mut secrets = HashMap::new();
683 if let Some(s) = context.secrets {
684 for (key, secret_ref) in s {
685 secrets.insert(key, self.convert_secret_ref(secret_ref));
686 }
687 }
688 let mut variables = HashMap::new();
689 for (key, expr) in &context.properties {
690 if key != "debug" && key != "max_tokens" {
691 variables.insert(key.clone(), expr.to_value());
692 }
693 }
694 Ok(ContextConfig {
695 name: context.name,
696 environment: context.environment,
697 debug: context
698 .properties
699 .get("debug")
700 .and_then(|v| v.as_bool())
701 .unwrap_or(false),
702 max_tokens: context
703 .properties
704 .get("max_tokens")
705 .and_then(|v| v.as_number())
706 .map(|n| n as u64),
707 secrets,
708 variables,
709 })
710 }
711 fn convert_secret_ref(&self, secret_ref: SecretRef) -> SecretRef {
712 match secret_ref {
713 SecretRef::Environment(var) => SecretRef::Environment(var),
714 SecretRef::Vault(path) => SecretRef::Vault(path),
715 SecretRef::File(path) => SecretRef::File(path),
716 }
717 }
718 fn convert_crew(&self, crew: CrewDecl) -> Result<CrewConfig, HlxError> {
719 let process_type = crew
720 .process_type
721 .and_then(|p| match p.as_str() {
722 "sequential" => Some(ProcessType::Sequential),
723 "hierarchical" => Some(ProcessType::Hierarchical),
724 "parallel" => Some(ProcessType::Parallel),
725 "consensus" => Some(ProcessType::Consensus),
726 _ => None,
727 })
728 .unwrap_or(ProcessType::Sequential);
729 Ok(CrewConfig {
730 name: crew.name,
731 agents: crew.agents,
732 process_type,
733 manager: crew.properties.get("manager").and_then(|e| e.as_string()),
734 max_iterations: crew
735 .properties
736 .get("max_iterations")
737 .and_then(|e| e.as_number())
738 .map(|n| n as u32),
739 verbose: crew
740 .properties
741 .get("verbose")
742 .and_then(|e| e.as_bool())
743 .unwrap_or(false),
744 })
745 }
746 fn convert_plugin(
747 &self,
748 plugin: PluginDecl,
749 ) -> Result<PluginConfig, HlxError> {
750 let mut config = HashMap::new();
751 for (key, expr) in plugin.config {
752 config.insert(key, self.expression_to_value(expr));
753 }
754 Ok(PluginConfig {
755 name: plugin.name,
756 source: plugin.source,
757 version: plugin.version.unwrap_or_else(|| "latest".to_string()),
758 config,
759 })
760 }
761 fn convert_database(
762 &self,
763 database: DatabaseDecl,
764 ) -> Result<DatabaseConfig, HlxError> {
765 let mut properties = HashMap::new();
766 for (key, expr) in database.properties {
767 properties.insert(key, self.expression_to_value(expr));
768 }
769 let vector_index = database
770 .vector_index
771 .map(|vi| VectorIndexConfig {
772 index_type: vi.index_type,
773 dimensions: vi.dimensions,
774 m: vi.m,
775 ef_construction: vi.ef_construction,
776 distance_metric: vi.distance_metric,
777 });
778 Ok(DatabaseConfig {
779 name: database.name,
780 path: database.path,
781 shards: database.shards,
782 compression: database.compression,
783 cache_size: database.cache_size,
784 vector_index,
785 properties,
786 })
787 }
788 fn expression_to_value(&self, expr: Expression) -> Value {
789 expr.to_value()
790 }
791 fn expression_to_duration(&self, expr: Expression) -> Option<Duration> {
792 match expr {
793 Expression::Duration(d) => Some(d),
794 _ => None,
795 }
796 }
797 fn convert_ast_value_to_types_value(&self, ast_value: Value) -> Value {
798 ast_value
799 }
800 pub fn load_directory<P: AsRef<Path>>(&mut self, dir: P) -> Result<(), HlxError> {
801 let dir_path = dir.as_ref();
802 for entry in fs::read_dir(dir_path)? {
803 let entry = entry?;
804 let path = entry.path();
805 if path.extension().and_then(|s| s.to_str()) == Some("hlx") {
806 let config = self.load_file(&path)?;
807 let name = path
808 .file_stem()
809 .and_then(|s| s.to_str())
810 .unwrap_or("default")
811 .to_string();
812 self.configs.insert(name, config);
813 }
814 }
815 Ok(())
816 }
817 pub fn get_config(&self, name: &str) -> Option<&HelixConfig> {
818 self.configs.get(name)
819 }
820 pub fn set_context(&mut self, context: String) {
821 self.current_context = Some(context);
822 }
823 pub fn merge_configs(&self, configs: Vec<&HelixConfig>) -> HelixConfig {
824 let mut merged = HelixConfig::default();
825 for config in configs {
826 for (name, project) in &config.projects {
827 merged.projects.insert(name.clone(), project.clone());
828 }
829 for (name, agent) in &config.agents {
830 merged.agents.insert(name.clone(), agent.clone());
831 }
832 for (name, workflow) in &config.workflows {
833 merged.workflows.insert(name.clone(), workflow.clone());
834 }
835 for (name, context) in &config.contexts {
836 merged.contexts.insert(name.clone(), context.clone());
837 }
838 for (name, crew) in &config.crews {
839 merged.crews.insert(name.clone(), crew.clone());
840 }
841 if config.memory.is_some() {
842 merged.memory = config.memory.clone();
843 }
844 merged.plugins.extend(config.plugins.clone());
845 for (section_name, section_data) in &config.sections {
846 merged.sections.insert(section_name.clone(), section_data.clone());
847 }
848 }
849 merged
850 }
851}
852#[derive(Debug, Clone)]
853pub enum DataFormat {
854 Auto,
855 JSON,
856 CSV,
857 Parquet,
858}
859#[derive(Debug, Clone)]
860pub enum TrainingFormat {
861 Preference { prompt_field: String, chosen_field: String, rejected_field: String },
862 Completion {
863 prompt_field: String,
864 completion_field: String,
865 label_field: Option<String>,
866 },
867}
868#[derive(Debug)]
869pub struct GenericJSONDataset {
870 pub data: Vec<serde_json::Value>,
871 pub format: DataFormat,
872 pub schema: Option<serde_json::Value>,
873}
874impl GenericJSONDataset {
875 pub fn new(
876 _paths: &[std::path::PathBuf],
877 _schema: Option<serde_json::Value>,
878 format: DataFormat,
879 ) -> Result<Self, Box<dyn std::error::Error>> {
880 Ok(Self {
881 data: Vec::new(),
882 format,
883 schema: _schema,
884 })
885 }
886 pub fn detect_training_format(
887 &self,
888 ) -> Result<TrainingFormat, Box<dyn std::error::Error>> {
889 if let Some(first) = self.data.first() {
890 if let Some(obj) = first.as_object() {
891 if obj.contains_key("chosen") && obj.contains_key("rejected") {
892 return Ok(TrainingFormat::Preference {
893 prompt_field: "prompt".to_string(),
894 chosen_field: "chosen".to_string(),
895 rejected_field: "rejected".to_string(),
896 });
897 } else if obj.contains_key("completion") {
898 return Ok(TrainingFormat::Completion {
899 prompt_field: "prompt".to_string(),
900 completion_field: "completion".to_string(),
901 label_field: Some("label".to_string()),
902 });
903 }
904 }
905 }
906 Err("Could not detect training format".into())
907 }
908 pub fn to_training_dataset(
909 &self,
910 ) -> Result<TrainingDataset, Box<dyn std::error::Error>> {
911 Ok(TrainingDataset {
912 samples: self
913 .data
914 .iter()
915 .enumerate()
916 .map(|(i, _)| TrainingSample {
917 id: i as u64,
918 prompt: Some("test prompt".to_string()),
919 chosen: Some("test chosen".to_string()),
920 rejected: Some("test rejected".to_string()),
921 completion: Some("test completion".to_string()),
922 label: Some(1.0),
923 })
924 .collect(),
925 })
926 }
927}
928#[derive(Debug)]
929pub struct TrainingSample {
930 pub id: u64,
931 pub prompt: Option<String>,
932 pub chosen: Option<String>,
933 pub rejected: Option<String>,
934 pub completion: Option<String>,
935 pub label: Option<f64>,
936}
937#[derive(Debug)]
938pub struct TrainingDataset {
939 pub samples: Vec<TrainingSample>,
940}
941impl TrainingDataset {
942 pub fn to_algorithm_format(
943 &self,
944 _algorithm: &str,
945 ) -> Result<AlgorithmFormat, Box<dyn std::error::Error>> {
946 Ok(AlgorithmFormat {
947 format_type: _algorithm.to_string(),
948 data: serde_json::json!({ "samples" : self.samples.len() }),
949 })
950 }
951}
952#[derive(Debug)]
953pub struct AlgorithmFormat {
954 pub format_type: String,
955 pub data: serde_json::Value,
956}
957#[derive(Debug)]
958pub enum HlxError {
959 IoError(std::io::Error),
960 ParseError(String),
961 ValidationError(String),
962 ReferenceError(String),
963}
964impl From<std::io::Error> for HlxError {
965 fn from(err: std::io::Error) -> Self {
966 HlxError::IoError(err)
967 }
968}
969impl From<String> for HlxError {
970 fn from(err: String) -> Self {
971 HlxError::ParseError(err)
972 }
973}
974impl From<crate::atp::parser::ParseError> for HlxError {
975 fn from(err: crate::atp::parser::ParseError) -> Self {
976 HlxError::ParseError(err.to_string())
977 }
978}
979impl std::fmt::Display for HlxError {
980 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
981 match self {
982 HlxError::IoError(e) => write!(f, "IO Error: {}", e),
983 HlxError::ParseError(e) => write!(f, "Parse Error: {}", e),
984 HlxError::ValidationError(e) => write!(f, "Validation Error: {}", e),
985 HlxError::ReferenceError(e) => write!(f, "Reference Error: {}", e),
986 }
987 }
988}
989impl std::error::Error for HlxError {}
990impl std::fmt::Display for Value {
991 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
992 match self {
993 Value::String(s) => write!(f, "{}", s),
994 Value::Number(n) => write!(f, "{}", n),
995 Value::Bool(b) => write!(f, "{}", b),
996 Value::Array(arr) => {
997 write!(f, "[")?;
998 for (i, item) in arr.iter().enumerate() {
999 if i > 0 { write!(f, ", ")?; }
1000 write!(f, "{}", item)?;
1001 }
1002 write!(f, "]")
1003 }
1004 Value::Object(obj) => {
1005 write!(f, "{{")?;
1006 for (i, (k, v)) in obj.iter().enumerate() {
1007 if i > 0 { write!(f, ", ")?; }
1008 write!(f, "{}: {}", k, v)?;
1009 }
1010 write!(f, "}}")
1011 }
1012 Value::Null => write!(f, "null"),
1013 Value::Duration(d) => write!(f, "{} {:?}", d.value, d.unit),
1014 Value::Reference(r) => write!(f, "@{}", r),
1015 Value::Identifier(i) => write!(f, "{}", i),
1016 }
1017 }
1018}
1019pub fn load_default_config() -> Result<HelixConfig, HlxError> {
1020 let mut loader = HelixLoader::new();
1021 use std::fs;
1022 let mut paths = Vec::new();
1023 let search_dirs = vec![".", "./config", "~/.maestro", "~/.helix"];
1024 for dir in &search_dirs {
1025 let dir_path = if dir.starts_with("~") {
1026 if let Some(home) = std::env::var_os("HOME") {
1027 let mut home_path = std::path::PathBuf::from(home);
1028 if dir.len() > 1 {
1029 home_path.push(&dir[2..]);
1030 }
1031 home_path
1032 } else {
1033 std::path::PathBuf::from(dir)
1034 }
1035 } else {
1036 std::path::PathBuf::from(dir)
1037 };
1038 if let Ok(entries) = fs::read_dir(&dir_path) {
1039 for entry in entries.flatten() {
1040 let path = entry.path();
1041 if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
1042 if ext == "hlxb" || ext == "hlx" {
1043 if let Some(path_str) = path.to_str() {
1044 paths.push(path_str.to_string());
1045 }
1046 }
1047 }
1048 }
1049 }
1050 }
1051 for path in paths {
1052 if Path::new(&path).exists() {
1053 return loader.load_file(path);
1054 }
1055 }
1056 Err(
1057 HlxError::IoError(
1058 std::io::Error::new(
1059 std::io::ErrorKind::NotFound,
1060 "No .hlxbb configuration file found",
1061 ),
1062 ),
1063 )
1064}