11use anyhow:: Result ;
22use async_trait:: async_trait;
3+ use serde:: { Deserialize , Serialize } ;
34use serde_json:: json;
45use std:: path:: PathBuf ;
56use std:: sync:: Arc ;
@@ -10,6 +11,51 @@ use crate::core::git_history::GitHistoryAnalyzer;
1011use crate :: core:: symbol_graph:: SymbolGraph ;
1112use crate :: core:: symbol_index:: SymbolIndex ;
1213
14+ /// Metadata about an available agent tool, for the API catalog.
15+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
16+ pub struct AgentToolInfo {
17+ pub name : String ,
18+ pub description : String ,
19+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
20+ pub requires : Option < String > ,
21+ }
22+
23+ /// Return a static catalog of all agent tools with their descriptions.
24+ pub fn list_all_tool_info ( ) -> Vec < AgentToolInfo > {
25+ vec ! [
26+ AgentToolInfo {
27+ name: "read_file" . to_string( ) ,
28+ description: "Read the contents of a file in the repository. Returns the file content with line numbers." . to_string( ) ,
29+ requires: None ,
30+ } ,
31+ AgentToolInfo {
32+ name: "search_codebase" . to_string( ) ,
33+ description: "Search the codebase for a regex pattern. Returns matching lines with file paths and line numbers." . to_string( ) ,
34+ requires: None ,
35+ } ,
36+ AgentToolInfo {
37+ name: "lookup_symbol" . to_string( ) ,
38+ description: "Look up a symbol (function, struct, class, etc.) in the codebase index. Returns definition locations and code snippets." . to_string( ) ,
39+ requires: Some ( "symbol index" . to_string( ) ) ,
40+ } ,
41+ AgentToolInfo {
42+ name: "get_definitions" . to_string( ) ,
43+ description: "Get the definitions of specific symbols as they appear in a given file, using the symbol index for precise lookup." . to_string( ) ,
44+ requires: Some ( "symbol index" . to_string( ) ) ,
45+ } ,
46+ AgentToolInfo {
47+ name: "get_related_symbols" . to_string( ) ,
48+ description: "Find symbols related to the given symbols (callers, callees, implementations, etc.) using the symbol graph." . to_string( ) ,
49+ requires: Some ( "symbol graph" . to_string( ) ) ,
50+ } ,
51+ AgentToolInfo {
52+ name: "get_file_history" . to_string( ) ,
53+ description: "Get git history and churn metrics for a file: commit count, bug fix count, distinct authors, risk score." . to_string( ) ,
54+ requires: Some ( "git history" . to_string( ) ) ,
55+ } ,
56+ ]
57+ }
58+
1359/// Maximum bytes returned by any single tool execution (8 KB).
1460const MAX_TOOL_OUTPUT_BYTES : usize = 8 * 1024 ;
1561
@@ -30,22 +76,43 @@ pub trait ReviewTool: Send + Sync {
3076}
3177
3278/// Build the standard set of review tools from the given context.
33- pub fn build_review_tools ( ctx : Arc < ReviewToolContext > ) -> Vec < Box < dyn ReviewTool > > {
34- let mut tools: Vec < Box < dyn ReviewTool > > = vec ! [
35- Box :: new( ReadFileTool { ctx: ctx. clone( ) } ) ,
36- Box :: new( SearchCodebaseTool { ctx: ctx. clone( ) } ) ,
37- ] ;
79+ ///
80+ /// If `enabled_filter` is `Some`, only tools whose names appear in the list are included.
81+ /// If `None`, all available tools are included.
82+ pub fn build_review_tools (
83+ ctx : Arc < ReviewToolContext > ,
84+ enabled_filter : Option < & [ String ] > ,
85+ ) -> Vec < Box < dyn ReviewTool > > {
86+ let is_enabled = |name : & str | -> bool {
87+ match enabled_filter {
88+ None => true ,
89+ Some ( list) => list. iter ( ) . any ( |s| s == name) ,
90+ }
91+ } ;
92+
93+ let mut tools: Vec < Box < dyn ReviewTool > > = Vec :: new ( ) ;
94+
95+ if is_enabled ( "read_file" ) {
96+ tools. push ( Box :: new ( ReadFileTool { ctx : ctx. clone ( ) } ) ) ;
97+ }
98+ if is_enabled ( "search_codebase" ) {
99+ tools. push ( Box :: new ( SearchCodebaseTool { ctx : ctx. clone ( ) } ) ) ;
100+ }
38101
39102 if ctx. symbol_index . is_some ( ) {
40- tools. push ( Box :: new ( LookupSymbolTool { ctx : ctx. clone ( ) } ) ) ;
41- tools. push ( Box :: new ( GetDefinitionsTool { ctx : ctx. clone ( ) } ) ) ;
103+ if is_enabled ( "lookup_symbol" ) {
104+ tools. push ( Box :: new ( LookupSymbolTool { ctx : ctx. clone ( ) } ) ) ;
105+ }
106+ if is_enabled ( "get_definitions" ) {
107+ tools. push ( Box :: new ( GetDefinitionsTool { ctx : ctx. clone ( ) } ) ) ;
108+ }
42109 }
43110
44- if ctx. symbol_graph . is_some ( ) {
111+ if ctx. symbol_graph . is_some ( ) && is_enabled ( "get_related_symbols" ) {
45112 tools. push ( Box :: new ( GetRelatedSymbolsTool { ctx : ctx. clone ( ) } ) ) ;
46113 }
47114
48- if ctx. git_history . is_some ( ) {
115+ if ctx. git_history . is_some ( ) && is_enabled ( "get_file_history" ) {
49116 tools. push ( Box :: new ( GetFileHistoryTool { ctx : ctx. clone ( ) } ) ) ;
50117 }
51118
@@ -501,7 +568,7 @@ mod tests {
501568 symbol_graph : None ,
502569 git_history : None ,
503570 } ) ;
504- let tools = build_review_tools ( ctx) ;
571+ let tools = build_review_tools ( ctx, None ) ;
505572 // At minimum: read_file + search_codebase
506573 assert_eq ! ( tools. len( ) , 2 ) ;
507574 assert_eq ! ( tools[ 0 ] . name( ) , "read_file" ) ;
@@ -517,7 +584,7 @@ mod tests {
517584 symbol_graph : Some ( Arc :: new ( SymbolGraph :: new ( ) ) ) ,
518585 git_history : Some ( Arc :: new ( GitHistoryAnalyzer :: new ( ) ) ) ,
519586 } ) ;
520- let tools = build_review_tools ( ctx) ;
587+ let tools = build_review_tools ( ctx, None ) ;
521588 assert_eq ! ( tools. len( ) , 6 ) ;
522589 let names: Vec < & str > = tools. iter ( ) . map ( |t| t. name ( ) ) . collect ( ) ;
523590 assert ! ( names. contains( & "read_file" ) ) ;
@@ -528,6 +595,32 @@ mod tests {
528595 assert ! ( names. contains( & "get_file_history" ) ) ;
529596 }
530597
598+ #[ test]
599+ fn test_build_review_tools_filtered ( ) {
600+ let ctx = Arc :: new ( ReviewToolContext {
601+ repo_path : PathBuf :: from ( "/tmp/test" ) ,
602+ context_fetcher : Arc :: new ( ContextFetcher :: new ( PathBuf :: from ( "/tmp/test" ) ) ) ,
603+ symbol_index : Some ( Arc :: new ( SymbolIndex :: default ( ) ) ) ,
604+ symbol_graph : Some ( Arc :: new ( SymbolGraph :: new ( ) ) ) ,
605+ git_history : Some ( Arc :: new ( GitHistoryAnalyzer :: new ( ) ) ) ,
606+ } ) ;
607+ let enabled = vec ! [ "read_file" . to_string( ) , "search_codebase" . to_string( ) ] ;
608+ let tools = build_review_tools ( ctx, Some ( & enabled) ) ;
609+ assert_eq ! ( tools. len( ) , 2 ) ;
610+ assert_eq ! ( tools[ 0 ] . name( ) , "read_file" ) ;
611+ assert_eq ! ( tools[ 1 ] . name( ) , "search_codebase" ) ;
612+ }
613+
614+ #[ test]
615+ fn test_list_all_tool_info ( ) {
616+ let info = list_all_tool_info ( ) ;
617+ assert_eq ! ( info. len( ) , 6 ) ;
618+ assert_eq ! ( info[ 0 ] . name, "read_file" ) ;
619+ assert ! ( info[ 0 ] . requires. is_none( ) ) ;
620+ assert_eq ! ( info[ 2 ] . name, "lookup_symbol" ) ;
621+ assert ! ( info[ 2 ] . requires. is_some( ) ) ;
622+ }
623+
531624 #[ test]
532625 fn test_tool_definitions_have_required_fields ( ) {
533626 let ctx = Arc :: new ( ReviewToolContext {
@@ -537,7 +630,7 @@ mod tests {
537630 symbol_graph : Some ( Arc :: new ( SymbolGraph :: new ( ) ) ) ,
538631 git_history : Some ( Arc :: new ( GitHistoryAnalyzer :: new ( ) ) ) ,
539632 } ) ;
540- let tools = build_review_tools ( ctx) ;
633+ let tools = build_review_tools ( ctx, None ) ;
541634 for tool in & tools {
542635 let def = tool. definition ( ) ;
543636 assert ! ( !def. name. is_empty( ) ) ;
0 commit comments