Tool definitions are now &'static str (name, description, parameters_json) instead of runtime-constructed serde_json::Value. No more json!() macro, no more ToolDef::new() for tool definitions. The JSON schema strings are written directly as string literals. When sent to the API, they can be interpolated without serialization/deserialization. Multi-tool modules return fixed-size arrays instead of Vecs: - memory: [Tool; 12], journal: [Tool; 3] - channels: [Tool; 4] - control: [Tool; 3] - web: [Tool; 2] ToolDef/FunctionDef remain for backward compat (API wire format, summarize_args) but are no longer used in tool definitions. Co-Authored-By: Proof of Concept <poc@bcachefs.org> Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
70 lines
2.2 KiB
Rust
70 lines
2.2 KiB
Rust
// tools/glob_tool.rs — Find files by pattern
|
|
//
|
|
// Fast file discovery using glob patterns. Returns matching paths
|
|
// sorted by modification time (newest first), which is usually
|
|
// what you want when exploring a codebase.
|
|
|
|
use anyhow::{Context, Result};
|
|
use serde::Deserialize;
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Deserialize)]
|
|
struct Args {
|
|
pattern: String,
|
|
#[serde(default = "default_path")]
|
|
path: String,
|
|
}
|
|
|
|
fn default_path() -> String { ".".into() }
|
|
|
|
pub fn tool() -> super::Tool {
|
|
super::Tool {
|
|
name: "glob",
|
|
description: "Find files matching a glob pattern. Returns file paths sorted by modification time (newest first).",
|
|
parameters_json: r#"{"type":"object","properties":{"pattern":{"type":"string","description":"Glob pattern to match files (e.g. '**/*.rs')"},"path":{"type":"string","description":"Directory to search in (default: current directory)"}},"required":["pattern"]}"#,
|
|
handler: |_a, v| Box::pin(async move { glob_search(&v) }),
|
|
}
|
|
}
|
|
|
|
fn glob_search(args: &serde_json::Value) -> Result<String> {
|
|
let a: Args = serde_json::from_value(args.clone())
|
|
.context("invalid glob arguments")?;
|
|
|
|
let full_pattern = if a.pattern.starts_with('/') {
|
|
a.pattern.clone()
|
|
} else {
|
|
format!("{}/{}", a.path, a.pattern)
|
|
};
|
|
|
|
let mut entries: Vec<(PathBuf, std::time::SystemTime)> = Vec::new();
|
|
|
|
for entry in glob::glob(&full_pattern)
|
|
.with_context(|| format!("Invalid glob pattern: {}", full_pattern))?
|
|
{
|
|
if let Ok(path) = entry {
|
|
if path.is_file() {
|
|
let mtime = path
|
|
.metadata()
|
|
.and_then(|m| m.modified())
|
|
.unwrap_or(std::time::SystemTime::UNIX_EPOCH);
|
|
entries.push((path, mtime));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort by modification time, newest first
|
|
entries.sort_by(|a, b| b.1.cmp(&a.1));
|
|
|
|
if entries.is_empty() {
|
|
return Ok("No files matched.".to_string());
|
|
}
|
|
|
|
let mut output = String::new();
|
|
for (path, _) in &entries {
|
|
output.push_str(&path.display().to_string());
|
|
output.push('\n');
|
|
}
|
|
|
|
output.push_str(&format!("\n({} files matched)", entries.len()));
|
|
Ok(super::truncate_output(output, 30000))
|
|
}
|