mdx_rust_analysis/
finders.rs1use std::path::Path;
6use tree_sitter::{Parser, Tree};
7
8#[derive(Debug, Clone)]
9pub struct ExtractedPrompt {
10 pub file: String,
11 pub line: usize,
12 pub text: String,
13}
14
15#[derive(Debug, Clone)]
16pub struct ExtractedTool {
17 pub file: String,
18 pub name: String,
19 pub description: Option<String>,
20}
21
22#[derive(Debug, Clone)]
23pub struct AgentEntrypoint {
24 pub name: String,
25 pub file: String,
26 pub line: usize,
27}
28
29pub fn parse_rust_source(source: &str) -> Option<Tree> {
31 let mut parser = Parser::new();
32 parser.set_language(tree_sitter_rust::language()).ok()?;
33 parser.parse(source, None)
34}
35
36pub fn find_run_agent_functions(source: &str) -> Vec<AgentEntrypoint> {
38 let Some(tree) = parse_rust_source(source) else {
39 return vec![];
40 };
41
42 let mut entries = vec![];
43 let root = tree.root_node();
44
45 for node in root.children(&mut root.walk()) {
46 if node.kind() == "function_item" {
47 if let Some(name_node) = node.child_by_field_name("name") {
48 let name = name_node.utf8_text(source.as_bytes()).unwrap_or("");
49 if name.contains("run_agent") || name == "run" || name.contains("agent") {
50 let line = node.start_position().row + 1;
51 entries.push(AgentEntrypoint {
52 name: name.to_string(),
53 file: "<unknown>".to_string(),
54 line,
55 });
56 }
57 }
58 }
59 }
60 entries
61}
62
63pub fn find_preambles(source: &str, file_path: &Path) -> Vec<ExtractedPrompt> {
66 let mut prompts = vec![];
67
68 if let Some(tree) = parse_rust_source(source) {
70 let root = tree.root_node();
71 let _cursor = root.walk();
72
73 fn walk(
74 node: tree_sitter::Node,
75 source: &str,
76 file_path: &Path,
77 prompts: &mut Vec<ExtractedPrompt>,
78 ) {
79 if node.kind() == "call_expression" {
80 let text = node.utf8_text(source.as_bytes()).unwrap_or("");
81 if text.contains("preamble(") {
82 if let Some(start) = text.find("preamble(\"") {
84 let after = &text[start + 10..];
85 if let Some(end) = after.find('"') {
86 let content = &after[..end];
87 if !content.is_empty() && content.len() > 3 {
88 let line = node.start_position().row + 1;
89 prompts.push(ExtractedPrompt {
90 file: file_path.display().to_string(),
91 line,
92 text: content.to_string(),
93 });
94 }
95 }
96 }
97 }
98 }
99 let mut c = node.walk();
100 for child in node.children(&mut c) {
101 walk(child, source, file_path, prompts);
102 }
103 }
104
105 walk(root, source, file_path, &mut prompts);
106 }
107
108 if prompts.is_empty() {
110 for line in source.lines() {
111 if line.contains(".preamble(\"") {
112 if let Some(start) = line.find(".preamble(\"") {
113 let after = &line[start + 11..];
114 if let Some(end) = after.find('"') {
115 let content = &after[..end];
116 if !content.is_empty() {
117 prompts.push(ExtractedPrompt {
118 file: file_path.display().to_string(),
119 line: prompts.len() + 1, text: content.to_string(),
121 });
122 }
123 }
124 }
125 }
126 }
127 }
128
129 prompts
130}
131
132pub fn find_tools(source: &str, file_path: &Path) -> Vec<ExtractedTool> {
134 let mut tools = vec![];
135
136 for line in source.lines() {
137 if line.contains(".tool(") || line.contains("tool(") {
138 let name = if let Some(start) = line.find(".tool(") {
140 let s = &line[start + 6..];
141 s.split(|c: char| !c.is_alphanumeric() && c != '_')
142 .find(|p| !p.is_empty())
143 .unwrap_or("tool")
144 .to_string()
145 } else {
146 "tool".to_string()
147 };
148
149 tools.push(ExtractedTool {
150 file: file_path.display().to_string(),
151 name,
152 description: None,
153 });
154 }
155 }
156
157 tools
158}
159
160pub fn looks_like_rig_agent(source: &str) -> bool {
162 source.contains("rig::") || source.contains(".agent(") || source.contains("preamble(")
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_find_preambles_basic() {
171 let source = r#"
172 let agent = client.agent("gpt-4o").preamble("You are helpful. Think step by step.").build();
173 "#;
174 let prompts = find_preambles(source, std::path::Path::new("test.rs"));
175 assert!(!prompts.is_empty());
176 assert!(prompts[0].text.contains("Think step by step"));
177 }
178
179 #[test]
180 fn test_looks_like_rig_agent() {
181 assert!(looks_like_rig_agent(
182 "let x = client.agent(\"..\").preamble(\"hi\")"
183 ));
184 assert!(!looks_like_rig_agent("fn main() {}"));
185 }
186
187 #[test]
188 fn test_find_run_agent_functions() {
189 let source = "pub async fn run_agent(input: Input) -> Result<Output> { Ok(()) }";
190 let fns = find_run_agent_functions(source);
191 assert!(!fns.is_empty());
192 }
193}