89 lines
2.8 KiB
Rust
89 lines
2.8 KiB
Rust
use std::sync::Arc;
|
|
|
|
use async_trait::async_trait;
|
|
use llm_engine::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
|
|
use schemars::JsonSchema;
|
|
use serde::Deserialize;
|
|
use workdir::{GlobRequest, WorkdirPath, WorkdirSessionHandle};
|
|
|
|
use crate::ToolsError;
|
|
|
|
const RESULT_LIMIT: usize = 1000;
|
|
|
|
#[derive(Debug, Deserialize, JsonSchema)]
|
|
struct GlobParams {
|
|
/// Glob pattern, for example `**/*.rs` or `src/**/test_*.py`.
|
|
pattern: String,
|
|
/// Logical Workdir-relative directory. Defaults to the Workdir root.
|
|
#[serde(default)]
|
|
path: Option<String>,
|
|
}
|
|
|
|
struct GlobTool {
|
|
session: WorkdirSessionHandle,
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Tool for GlobTool {
|
|
async fn execute(
|
|
&self,
|
|
input_json: &str,
|
|
_ctx: llm_engine::tool::ToolExecutionContext,
|
|
) -> Result<ToolOutput, ToolError> {
|
|
let params: GlobParams = serde_json::from_str(input_json)
|
|
.map_err(|error| ToolError::InvalidArgument(format!("invalid Glob input: {error}")))?;
|
|
let path = match params.path {
|
|
Some(path) => WorkdirPath::new(&path).map_err(ToolsError::from)?,
|
|
None => WorkdirPath::root(),
|
|
};
|
|
let pattern = params.pattern;
|
|
tracing::debug!(%pattern, %path, "Glob");
|
|
let result = self
|
|
.session
|
|
.glob(GlobRequest {
|
|
pattern: pattern.clone(),
|
|
path,
|
|
limit: RESULT_LIMIT,
|
|
})
|
|
.await
|
|
.map_err(ToolsError::from)?;
|
|
let mut body = result
|
|
.paths
|
|
.iter()
|
|
.map(ToString::to_string)
|
|
.collect::<Vec<_>>()
|
|
.join("\n");
|
|
if !body.is_empty() {
|
|
body.push('\n');
|
|
}
|
|
let summary = if result.paths.is_empty() {
|
|
format!("No files found matching {pattern}")
|
|
} else if result.truncated {
|
|
format!(
|
|
"Found {}+ files matching {pattern} (truncated to {RESULT_LIMIT})",
|
|
result.paths.len()
|
|
)
|
|
} else {
|
|
format!("Found {} file(s) matching {pattern}", result.paths.len())
|
|
};
|
|
Ok(ToolOutput {
|
|
summary,
|
|
content: (!body.is_empty()).then_some(body),
|
|
attachments: Vec::new(),
|
|
})
|
|
}
|
|
}
|
|
|
|
pub fn glob_tool(session: WorkdirSessionHandle) -> ToolDefinition {
|
|
Arc::new(move || {
|
|
let schema = schemars::schema_for!(GlobParams);
|
|
let meta = ToolMeta::new("Glob")
|
|
.description("Find files matching a glob pattern inside the bound Workdir. Results are sorted and capped at 1000 entries. Paths are Workdir-relative.")
|
|
.input_schema(serde_json::to_value(schema).expect("Glob schema serialization"));
|
|
let tool: Arc<dyn Tool> = Arc::new(GlobTool {
|
|
session: session.clone(),
|
|
});
|
|
(meta, tool)
|
|
})
|
|
}
|