From 20eddca8717aeb861e26f3129f5e603214563af7 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Thu, 18 Dec 2025 11:23:30 -0700 Subject: [PATCH] add summary tool --- signal-gateway-code-tool/src/config.rs | 4 + signal-gateway-code-tool/src/lib.rs | 82 ++++++++++++++++++- signal-gateway-code-tool/tests/integration.rs | 1 + 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/signal-gateway-code-tool/src/config.rs b/signal-gateway-code-tool/src/config.rs index b2f5a38..81d3c5c 100644 --- a/signal-gateway-code-tool/src/config.rs +++ b/signal-gateway-code-tool/src/config.rs @@ -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, } #[cfg(test)] diff --git a/signal-gateway-code-tool/src/lib.rs b/signal-gateway-code-tool/src/lib.rs index e531d80..3360972 100644 --- a/signal-gateway-code-tool/src/lib.rs +++ b/signal-gateway-code-tool/src/lib.rs @@ -47,6 +47,7 @@ pub struct CodeTool { config: CodeToolConfig, source: ResolvedSource, glob_filter: Option, + summary: Option>, get_sha: ShaCallback, client: reqwest::Client, cache: Mutex>, @@ -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::>() .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, } +#[derive(Deserialize)] +struct SummaryInput { + app: String, +} + #[async_trait] impl ToolExecutor for CodeToolTools { fn tools(&self) -> Vec { - 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 { @@ -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}")), } } diff --git a/signal-gateway-code-tool/tests/integration.rs b/signal-gateway-code-tool/tests/integration.rs index 77d07b8..167fb46 100644 --- a/signal-gateway-code-tool/tests/integration.rs +++ b/signal-gateway-code-tool/tests/integration.rs @@ -28,6 +28,7 @@ fn create_test_repo_code_with_glob(glob: Vec) -> CodeTool { }, glob, include_non_utf8: false, + summary_file: None, }; let sha = TEST_SHA.to_string();