llm_coding_tools_rig/absolute/
glob.rs1use llm_coding_tools_core::operations::glob_files;
4use llm_coding_tools_core::path::AbsolutePathResolver;
5use llm_coding_tools_core::tool_names;
6use llm_coding_tools_core::{GlobOutput, ToolContext, ToolError};
7use rig::completion::ToolDefinition;
8use rig::tool::Tool;
9use schemars::{schema_for, JsonSchema};
10use serde::Deserialize;
11
12#[derive(Debug, Deserialize, JsonSchema)]
14pub struct GlobArgs {
15 pub pattern: String,
17 pub path: String,
19}
20
21#[derive(Debug, Default, Clone, Copy)]
23pub struct GlobTool;
24
25impl GlobTool {
26 #[inline]
28 pub fn new() -> Self {
29 Self
30 }
31}
32
33impl Tool for GlobTool {
34 const NAME: &'static str = tool_names::GLOB;
35
36 type Error = ToolError;
37 type Args = GlobArgs;
38 type Output = GlobOutput;
39
40 async fn definition(&self, _prompt: String) -> ToolDefinition {
41 ToolDefinition {
42 name: <Self as Tool>::NAME.to_string(),
43 description: "Find files matching a glob pattern. Respects .gitignore and \
44 returns paths sorted by modification time (newest first)."
45 .to_string(),
46 parameters: serde_json::to_value(schema_for!(GlobArgs))
47 .expect("schema serialization should not fail"),
48 }
49 }
50
51 async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
52 let resolver = AbsolutePathResolver;
53 glob_files(&resolver, &args.pattern, &args.path)
54 }
55}
56
57impl ToolContext for GlobTool {
58 const NAME: &'static str = tool_names::GLOB;
59
60 fn context(&self) -> &'static str {
61 llm_coding_tools_core::context::GLOB_ABSOLUTE
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use std::fs::{self, File};
69 use tempfile::TempDir;
70
71 #[tokio::test]
72 async fn finds_matching_files() {
73 let dir = TempDir::new().unwrap();
74 fs::create_dir_all(dir.path().join("src")).unwrap();
75 File::create(dir.path().join("src/lib.rs")).unwrap();
76 let tool = GlobTool::new();
77 let result = tool
78 .call(GlobArgs {
79 pattern: "**/*.rs".to_string(),
80 path: dir.path().to_string_lossy().to_string(),
81 })
82 .await
83 .unwrap();
84 assert!(result.files.iter().any(|f| f.ends_with("lib.rs")));
85 }
86
87 #[tokio::test]
88 async fn rejects_relative_path() {
89 let tool = GlobTool::new();
90 let result = tool
91 .call(GlobArgs {
92 pattern: "*.rs".to_string(),
93 path: "relative/path".to_string(),
94 })
95 .await;
96 assert!(matches!(result, Err(ToolError::InvalidPath(_))));
97 }
98}