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