Skip to main content

bamboo_agent_core/composition/
expr.rs

1//! Tool Expression DSL — Serializable tool composition language.
2//!
3//! `ToolExpr`, `Condition`, and `ParallelWait` are defined in
4//! `bamboo_domain::composition` (pure domain types).
5//! This file keeps `CompositionError` (which depends on `ToolError`)
6//! and re-exports the domain types for backward compatibility.
7
8use crate::tools::ToolError;
9
10// Re-export domain types
11pub use bamboo_domain::composition::{Condition, ParallelWait, ToolExpr};
12
13/// Composition error types
14#[derive(Debug, Clone)]
15pub enum CompositionError {
16    ToolError(ToolError),
17    VariableNotFound(String),
18    InvalidExpression(String),
19    MaxRetriesExceeded,
20}
21
22impl std::fmt::Display for CompositionError {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        match self {
25            CompositionError::ToolError(e) => write!(f, "Tool error: {}", e),
26            CompositionError::VariableNotFound(v) => write!(f, "Variable not found: {}", v),
27            CompositionError::InvalidExpression(e) => write!(f, "Invalid expression: {}", e),
28            CompositionError::MaxRetriesExceeded => write!(f, "Maximum retry attempts exceeded"),
29        }
30    }
31}
32
33impl std::error::Error for CompositionError {
34    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
35        match self {
36            CompositionError::ToolError(e) => Some(e),
37            _ => None,
38        }
39    }
40}
41
42impl From<ToolError> for CompositionError {
43    fn from(e: ToolError) -> Self {
44        CompositionError::ToolError(e)
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use serde_json::json;
52
53    #[test]
54    fn test_call_expr() {
55        let expr = ToolExpr::call("read_file", json!({"path": "/tmp/test"}));
56        match expr {
57            ToolExpr::Call { tool, args } => {
58                assert_eq!(tool, "read_file");
59                assert_eq!(args["path"], "/tmp/test");
60            }
61            _ => panic!("Expected Call variant"),
62        }
63    }
64
65    #[test]
66    fn test_yaml_roundtrip() {
67        let expr = ToolExpr::sequence(vec![
68            ToolExpr::call("step1", json!({"arg": 1})),
69            ToolExpr::call("step2", json!({"arg": 2})),
70        ]);
71        let yaml = expr.to_yaml().unwrap();
72        let deserialized = ToolExpr::from_yaml(&yaml).unwrap();
73        assert_eq!(expr, deserialized);
74    }
75}