enact-core 0.0.2

Core agent runtime for Enact - Graph-Native AI agents
Documentation
//! GraphCallable - Wraps CompiledGraph to implement Callable trait
//!
//! This allows graphs to be registered in the CallableRegistry and executed
//! through the unified agent/stream endpoint alongside other callable types.

use super::Callable;
use crate::graph::CompiledGraph;
use async_trait::async_trait;
use std::sync::Arc;

/// A callable backed by a compiled graph
///
/// This wrapper allows `CompiledGraph` to be used as a `Callable`, enabling
/// graphs to be registered in the `CallableRegistry` and executed through
/// the unified agent/stream endpoint.
pub struct GraphCallable {
    name: String,
    graph: Arc<CompiledGraph>,
}

impl GraphCallable {
    /// Create a new GraphCallable
    ///
    /// # Arguments
    /// * `name` - The name of this callable
    /// * `graph` - The compiled graph to wrap
    pub fn new(name: impl Into<String>, graph: Arc<CompiledGraph>) -> Self {
        Self {
            name: name.into(),
            graph,
        }
    }

    /// Get a reference to the underlying graph
    pub fn graph(&self) -> &CompiledGraph {
        &self.graph
    }
}

#[async_trait]
impl Callable for GraphCallable {
    fn name(&self) -> &str {
        &self.name
    }

    fn description(&self) -> Option<&str> {
        None // Could be extended to store description
    }

    async fn run(&self, input: &str) -> anyhow::Result<String> {
        // Run the graph with the input
        let state = self.graph.run(input).await?;

        // Convert NodeState to String
        // NodeState contains a serde_json::Value, so we serialize it
        if let Some(s) = state.as_str() {
            Ok(s.to_string())
        } else {
            // If not a string, serialize the JSON value
            Ok(serde_json::to_string(&state.data)?)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::graph::CompiledGraph;
    use std::collections::HashMap;

    #[tokio::test]
    async fn test_graph_callable_name() {
        // Create a minimal graph for testing
        let graph = Arc::new(CompiledGraph {
            nodes: HashMap::new(),
            edges: vec![],
            conditional_edges: vec![],
            entry_point: "start".to_string(),
        });

        let callable = GraphCallable::new("test-graph", graph);
        assert_eq!(callable.name(), "test-graph");
    }

    #[tokio::test]
    async fn test_graph_callable_run() {
        // Create a minimal graph that just returns the input
        // Note: This is a simplified test - a real graph would have nodes
        let graph = Arc::new(CompiledGraph {
            nodes: HashMap::new(),
            edges: vec![],
            conditional_edges: vec![],
            entry_point: "start".to_string(),
        });

        let callable = GraphCallable::new("test-graph", graph);

        // The graph will fail because it has no nodes, but we can test the structure
        let result = callable.run("test input").await;
        // This will fail because the graph has no nodes, but that's expected
        assert!(result.is_err());
    }
}