busbar_sf_agentscript/graph/
mod.rs1mod builder;
43pub mod dependencies;
44mod edges;
45mod error;
46pub mod export;
47mod nodes;
48mod queries;
49pub mod render;
50mod validation;
51
52#[cfg(feature = "wasm")]
53pub mod wasm;
54
55pub use builder::RefGraphBuilder;
56pub use dependencies::{extract_dependencies, Dependency, DependencyReport, DependencyType};
57pub use edges::RefEdge;
58pub use error::{GraphBuildError, ValidationError};
59pub use export::{EdgeRepr, GraphExport, GraphRepr, NodeRepr, ValidationResultRepr};
60pub use nodes::RefNode;
61pub use queries::QueryResult;
62pub use render::{render_actions_view, render_full_view, render_graphml, render_topic_flow};
63pub use validation::ValidationResult;
64
65use petgraph::graph::{DiGraph, NodeIndex};
66use std::collections::HashMap;
67
68#[derive(Debug)]
73pub struct RefGraph {
74 graph: DiGraph<RefNode, RefEdge>,
76
77 topics: HashMap<String, NodeIndex>,
79
80 action_defs: HashMap<(String, String), NodeIndex>,
82
83 reasoning_actions: HashMap<(String, String), NodeIndex>,
85
86 variables: HashMap<String, NodeIndex>,
88
89 start_agent: Option<NodeIndex>,
91
92 unresolved_references: Vec<ValidationError>,
94}
95
96impl RefGraph {
97 pub fn from_ast(ast: &crate::AgentFile) -> Result<Self, GraphBuildError> {
102 RefGraphBuilder::new().build(ast)
103 }
104
105 pub fn inner(&self) -> &DiGraph<RefNode, RefEdge> {
107 &self.graph
108 }
109
110 pub fn get_node(&self, index: NodeIndex) -> Option<&RefNode> {
112 self.graph.node_weight(index)
113 }
114
115 pub fn get_topic(&self, name: &str) -> Option<NodeIndex> {
117 self.topics.get(name).copied()
118 }
119
120 pub fn get_action_def(&self, topic: &str, action: &str) -> Option<NodeIndex> {
122 self.action_defs
123 .get(&(topic.to_string(), action.to_string()))
124 .copied()
125 }
126
127 pub fn get_reasoning_action(&self, topic: &str, action: &str) -> Option<NodeIndex> {
129 self.reasoning_actions
130 .get(&(topic.to_string(), action.to_string()))
131 .copied()
132 }
133
134 pub fn get_variable(&self, name: &str) -> Option<NodeIndex> {
136 self.variables.get(name).copied()
137 }
138
139 pub fn get_start_agent(&self) -> Option<NodeIndex> {
141 self.start_agent
142 }
143
144 pub fn topic_names(&self) -> impl Iterator<Item = &str> {
146 self.topics.keys().map(|s| s.as_str())
147 }
148
149 pub fn variable_names(&self) -> impl Iterator<Item = &str> {
151 self.variables.keys().map(|s| s.as_str())
152 }
153
154 pub fn node_count(&self) -> usize {
156 self.graph.node_count()
157 }
158
159 pub fn edge_count(&self) -> usize {
161 self.graph.edge_count()
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_empty_graph() {
171 let source = r#"config:
173 agent_name: "Test"
174
175start_agent topic_selector:
176 description: "Route to topics"
177 reasoning:
178 instructions: "Select the best topic"
179 actions:
180 go_help: @utils.transition to @topic.help
181 description: "Go to help topic"
182
183topic help:
184 description: "Help topic"
185 reasoning:
186 instructions: "Provide help"
187"#;
188 let ast = crate::parse(source).unwrap();
189 let graph = RefGraph::from_ast(&ast).unwrap();
190
191 assert!(graph.node_count() > 0);
192 assert!(graph.get_topic("help").is_some());
193 assert!(graph.get_start_agent().is_some());
194 }
195}