add summary tool

This commit is contained in:
Chris Beck
2025-12-18 11:23:30 -07:00
parent b0dd92aee8
commit 20eddca871
3 changed files with 84 additions and 3 deletions
+4
View File
@@ -76,6 +76,10 @@ pub struct CodeToolConfig {
/// By default (false), non-UTF-8 files are skipped entirely.
#[serde(default)]
pub include_non_utf8: bool,
/// Path to a file containing a summary/description of the codebase.
/// If present, the agent can request a summary of the repository.
#[serde(default)]
pub summary_file: Option<String>,
}
#[cfg(test)]
+79 -3
View File
@@ -47,6 +47,7 @@ pub struct CodeTool {
config: CodeToolConfig,
source: ResolvedSource,
glob_filter: Option<GlobSet>,
summary: Option<Box<str>>,
get_sha: ShaCallback,
client: reqwest::Client,
cache: Mutex<Option<CachedTarball>>,
@@ -91,10 +92,18 @@ impl CodeTool {
})?)
};
// Load summary from file if configured
let summary = config
.summary_file
.as_ref()
.map(|path| std::fs::read_to_string(path).map(|s| s.into_boxed_str()))
.transpose()?;
Ok(Self {
config,
source,
glob_filter,
summary,
get_sha,
client: reqwest::Client::new(),
cache: Mutex::new(None),
@@ -106,6 +115,16 @@ impl CodeTool {
&self.config.name
}
/// Check if this code tool has a summary available.
pub fn has_summary(&self) -> bool {
self.summary.is_some()
}
/// Get the summary of the codebase, if available.
pub fn summary(&self) -> Option<&str> {
self.summary.as_deref()
}
/// Get the current tarball, downloading or reading from file as needed.
///
/// Returns a mutex guard containing the cached tarball. This method never fails;
@@ -482,6 +501,15 @@ impl CodeToolTools {
.collect::<Vec<_>>()
.join(", ")
}
/// Get list of app names that have summaries available.
fn apps_with_summaries(&self) -> Vec<&str> {
self.apps
.iter()
.filter(|a| a.has_summary())
.map(|a| a.name())
.collect()
}
}
#[derive(Deserialize)]
@@ -513,10 +541,15 @@ struct SearchInput {
path_prefix: Option<String>,
}
#[derive(Deserialize)]
struct SummaryInput {
app: String,
}
#[async_trait]
impl ToolExecutor for CodeToolTools {
fn tools(&self) -> Vec<Tool> {
vec![
let mut tools = vec![
Tool {
name: "code_ls",
description: "List files in a directory of an application's source code.",
@@ -605,11 +638,35 @@ impl ToolExecutor for CodeToolTools {
"required": ["app", "pattern"]
}),
},
]
];
// Only include summary tool if at least one app has a summary
let apps_with_summaries = self.apps_with_summaries();
if !apps_with_summaries.is_empty() {
tools.push(Tool {
name: "code_summary",
description: "Get a summary/overview of an application's codebase.",
input_schema: serde_json::json!({
"type": "object",
"properties": {
"app": {
"type": "string",
"description": format!("Name of the application. Apps with summaries: {}", apps_with_summaries.join(", "))
}
},
"required": ["app"]
}),
});
}
tools
}
fn has_tool(&self, name: &str) -> bool {
matches!(name, "code_ls" | "code_find" | "code_read" | "code_search")
matches!(
name,
"code_ls" | "code_find" | "code_read" | "code_search" | "code_summary"
)
}
async fn execute(&self, name: &str, input: &serde_json::Value) -> Result<ToolResult, String> {
@@ -670,6 +727,25 @@ impl ToolExecutor for CodeToolTools {
.await?;
Ok(ToolResult::new(result))
}
"code_summary" => {
let input: SummaryInput = serde_json::from_value(input.clone())
.map_err(|e| format!("Invalid input: {e}"))?;
let app = self.find_app(&input.app).ok_or_else(|| {
format!(
"Unknown app '{}'. Available: {}",
input.app,
self.app_names()
)
})?;
let summary = app.summary().ok_or_else(|| {
format!(
"No summary available for '{}'. Apps with summaries: {}",
input.app,
self.apps_with_summaries().join(", ")
)
})?;
Ok(ToolResult::new(summary.to_string()))
}
_ => Err(format!("Unknown tool: {name}")),
}
}
@@ -28,6 +28,7 @@ fn create_test_repo_code_with_glob(glob: Vec<String>) -> CodeTool {
},
glob,
include_non_utf8: false,
summary_file: None,
};
let sha = TEST_SHA.to_string();