@@ -72,125 +72,69 @@ pub trait Tool: Send + Sync {
7272 }
7373}
7474
75- fn enabled ( config : & AgentConfig , name : & str ) -> bool {
76- config. tools . iter ( ) . any ( |t| t == name)
77- }
78-
79- fn push_if_enabled ( out : & mut Vec < String > , config : & AgentConfig , name : & str ) {
80- if enabled ( config, name) && !out. iter ( ) . any ( |t| t == name) {
81- out. push ( name. to_string ( ) ) ;
82- }
83- }
84-
75+ /// Choose the tools to expose for a given prompt.
76+ ///
77+ /// `Fixed` always sends the whole configured pool. `Auto` (the default) keeps
78+ /// the full working set available for any real request and sends *no* tools
79+ /// only for obvious small talk (greetings, thanks) — the model still decides
80+ /// whether to call them. This mirrors how modern harnesses work: the agent
81+ /// always has read/edit/write/shell on hand, so "build me a site" results in
82+ /// files written to disk instead of code dumped into the chat. The old
83+ /// keyword-bucket heuristic guessed wrong on phrasings like "build a bio site"
84+ /// (no edit tools surfaced), which forced the model to print code it couldn't
85+ /// save.
8586pub fn select_tool_names ( config : & AgentConfig , prompt : & str ) -> Vec < String > {
86- if config. tool_selection == ToolSelection :: Fixed {
87+ if config. tool_selection == ToolSelection :: Fixed || ! is_small_talk ( prompt ) {
8788 return config. tools . clone ( ) ;
8889 }
90+ Vec :: new ( )
91+ }
8992
90- let lower = prompt. to_lowercase ( ) ;
91- let fileish = [
92- "file" ,
93- "files" ,
94- "repo" ,
95- "repository" ,
96- "code" ,
97- "src" ,
98- "read" ,
99- "open" ,
100- "search" ,
101- "grep" ,
102- "find" ,
103- "list" ,
104- "directory" ,
105- "folder" ,
106- "where is" ,
107- "inspect" ,
108- "review" ,
109- ]
110- . iter ( )
111- . any ( |needle| lower. contains ( needle) ) ;
112- let editish = [
113- "edit" ,
114- "change" ,
115- "modify" ,
116- "update" ,
117- "fix" ,
118- "implement" ,
119- "add support" ,
120- "refactor" ,
121- "write" ,
122- "create" ,
123- "delete" ,
124- "patch" ,
125- "build it" ,
126- ]
127- . iter ( )
128- . any ( |needle| lower. contains ( needle) ) ;
129- let shellish = [
130- "run " , "execute" , "command" , "terminal" , "shell" , "test" , "cargo " , "npm " , "git " ,
131- "build" , "lint" , "check" ,
132- ]
133- . iter ( )
134- . any ( |needle| lower. contains ( needle) ) ;
135- let testish = [ "failing" , "verify" , "unit test" , "test suite" ]
136- . iter ( )
137- . any ( |needle| lower. contains ( needle) )
138- || ( lower. contains ( "test" ) && !lower. contains ( "latest" ) ) ;
139- let shipish = [
140- "ship" ,
141- "ready to commit" ,
142- "ready to ship" ,
143- "shipcheck" ,
144- "handoff" ,
145- "release" ,
146- ]
147- . iter ( )
148- . any ( |needle| lower. contains ( needle) ) ;
149- let batchish = [
150- "multi-file" ,
151- "multiple files" ,
152- "across files" ,
153- "batch edit" ,
154- "coordinated" ,
155- ]
156- . iter ( )
157- . any ( |needle| lower. contains ( needle) ) ;
158-
159- let mut out = Vec :: new ( ) ;
160- if fileish || editish {
161- if config. project_memory . enabled {
162- push_if_enabled ( & mut out, config, "repo_search" ) ;
163- }
164- push_if_enabled ( & mut out, config, "file_read" ) ;
165- push_if_enabled ( & mut out, config, "grep" ) ;
166- push_if_enabled ( & mut out, config, "list_dir" ) ;
167- push_if_enabled ( & mut out, config, "glob" ) ;
168- // Delegated read-only investigation keeps deep exploration out of the
169- // parent context.
170- push_if_enabled ( & mut out, config, "task" ) ;
171- }
172- if editish {
173- push_if_enabled ( & mut out, config, "file_edit" ) ;
174- push_if_enabled ( & mut out, config, "apply_patch" ) ;
175- push_if_enabled ( & mut out, config, "file_write" ) ;
176- }
177- // Multi-step work (editing or running things) benefits from a visible plan.
178- if editish || shellish {
179- push_if_enabled ( & mut out, config, "update_plan" ) ;
93+ /// Conservatively detect throwaway social messages (greetings, thanks) so we
94+ /// can skip sending tool schemas for them. Deliberately narrow: a false
95+ /// negative just sends tools the model won't use, but a false positive would
96+ /// starve a real request of its tools, so we only match very short, clearly
97+ /// social inputs.
98+ fn is_small_talk ( prompt : & str ) -> bool {
99+ let t = prompt. trim ( ) . to_lowercase ( ) ;
100+ let t = t. trim_end_matches ( [ '.' , '!' , '?' , ' ' ] ) ;
101+ if t. is_empty ( ) {
102+ return true ;
180103 }
181- if shellish {
182- push_if_enabled ( & mut out, config, "shell" ) ;
183- }
184- if testish || shellish {
185- push_if_enabled ( & mut out, config, "run_tests" ) ;
186- }
187- if batchish && editish {
188- push_if_enabled ( & mut out, config, "batch_edit" ) ;
189- }
190- if shipish {
191- push_if_enabled ( & mut out, config, "ship_status" ) ;
192- }
193- out
104+ const EXACT : & [ & str ] = & [
105+ "hi" ,
106+ "hii" ,
107+ "hey" ,
108+ "hello" ,
109+ "yo" ,
110+ "sup" ,
111+ "thanks" ,
112+ "thank you" ,
113+ "thx" ,
114+ "ty" ,
115+ "ok" ,
116+ "okay" ,
117+ "k" ,
118+ "cool" ,
119+ "nice" ,
120+ "great" ,
121+ "awesome" ,
122+ "hi there" ,
123+ "hey there" ,
124+ "hello there" ,
125+ "good morning" ,
126+ "good afternoon" ,
127+ "good evening" ,
128+ "how are you" ,
129+ "how's it going" ,
130+ "who are you" ,
131+ "what are you" ,
132+ "what can you do" ,
133+ "gm" ,
134+ "bye" ,
135+ "goodbye" ,
136+ ] ;
137+ EXACT . contains ( & t)
194138}
195139
196140/// Returns true when a tool result indicates the workspace was mutated (for ship-loop hooks).
@@ -329,85 +273,73 @@ mod tests {
329273 use crate :: config:: ToolSelection ;
330274
331275 #[ test]
332- fn auto_chat_uses_no_tools ( ) {
276+ fn small_talk_sends_no_tools ( ) {
333277 let config = AgentConfig {
334278 tool_selection : ToolSelection :: Auto ,
335279 ..Default :: default ( )
336280 } ;
337- assert ! ( select_tool_names( & config, "hello there" ) . is_empty( ) ) ;
281+ for greeting in [
282+ "hi" ,
283+ "hello there" ,
284+ "thanks" ,
285+ "ok" ,
286+ " hey! " ,
287+ "Good morning" ,
288+ ] {
289+ assert ! (
290+ select_tool_names( & config, greeting) . is_empty( ) ,
291+ "{greeting:?} should be treated as small talk"
292+ ) ;
293+ }
338294 }
339295
340296 #[ test]
341- fn auto_file_question_uses_read_tools ( ) {
297+ fn real_request_sends_the_full_pool ( ) {
342298 let config = AgentConfig {
343299 tool_selection : ToolSelection :: Auto ,
344300 ..Default :: default ( )
345301 } ;
346- let names = select_tool_names ( & config , "search the repo for config" ) ;
347- assert ! ( names . contains ( & "file_read" . to_string ( ) ) ) ;
348- assert ! ( names . contains ( & "grep" . to_string ( ) ) ) ;
349- assert ! ( names . contains ( & "list_dir" . to_string ( ) ) ) ;
302+ assert_eq ! (
303+ select_tool_names ( & config , "what files are in src?" ) ,
304+ config . tools
305+ ) ;
350306 }
351307
352308 #[ test]
353- fn repo_search_is_opt_in_not_default ( ) {
354- // repo_search is no longer in the default pool; auto-selection can't
355- // surface it unless the user enables it.
309+ fn build_request_includes_write_tools ( ) {
310+ // Regression: "build a bio site" used to surface no edit tools, forcing
311+ // the model to dump code into the chat. It must now get file_write +
312+ // file_edit + shell so it can actually create files.
356313 let config = AgentConfig {
357314 tool_selection : ToolSelection :: Auto ,
358315 ..Default :: default ( )
359316 } ;
360- assert ! ( !config. tools. contains( & "repo_search" . to_string( ) ) ) ;
361- let names = select_tool_names ( & config, "search the repo for config" ) ;
362- assert ! ( !names. contains( & "repo_search" . to_string( ) ) ) ;
317+ let names = select_tool_names ( & config, "build a bio site" ) ;
318+ assert ! ( names. contains( & "file_write" . to_string( ) ) ) ;
319+ assert ! ( names. contains( & "file_edit" . to_string( ) ) ) ;
320+ assert ! ( names. contains( & "shell" . to_string( ) ) ) ;
363321 }
364322
365323 #[ test]
366- fn auto_repo_search_requires_memory_enabled ( ) {
367- // When repo_search IS enabled, auto-selection still gates it on memory.
368- let base_tools = vec ! [
369- "repo_search" . to_string( ) ,
370- "file_read" . to_string( ) ,
371- "grep" . to_string( ) ,
372- "list_dir" . to_string( ) ,
373- ] ;
374- let enabled = AgentConfig {
375- tools : base_tools. clone ( ) ,
376- ..Default :: default ( )
377- } ;
378- assert ! ( select_tool_names( & enabled, "search the repo for config" )
379- . contains( & "repo_search" . to_string( ) ) ) ;
380-
381- let disabled = AgentConfig {
382- tools : base_tools,
383- project_memory : crate :: config:: ProjectMemoryConfig {
384- enabled : false ,
385- ..Default :: default ( )
386- } ,
387- ..Default :: default ( )
388- } ;
389- assert ! ( !select_tool_names( & disabled, "search the repo for config" )
390- . contains( & "repo_search" . to_string( ) ) ) ;
391- }
392-
393- #[ test]
394- fn auto_verify_prompt_includes_run_tests ( ) {
324+ fn auto_only_sends_configured_tools ( ) {
395325 let config = AgentConfig {
396- tools : vec ! [ "run_tests" . into( ) , "file_read" . into( ) , "file_edit" . into( ) ] ,
326+ tool_selection : ToolSelection :: Auto ,
327+ tools : vec ! [ "file_read" . into( ) , "shell" . into( ) ] ,
397328 ..Default :: default ( )
398329 } ;
399- let names = select_tool_names ( & config, "verify the failing unit test" ) ;
400- assert ! ( names. contains( & "run_tests" . to_string( ) ) ) ;
330+ assert_eq ! (
331+ select_tool_names( & config, "do some real work" ) ,
332+ vec![ "file_read" . to_string( ) , "shell" . to_string( ) ]
333+ ) ;
401334 }
402335
403336 #[ test]
404- fn auto_ship_prompt_includes_ship_status ( ) {
337+ fn fixed_mode_sends_full_pool_even_for_greetings ( ) {
405338 let config = AgentConfig {
406- tools : vec ! [ "ship_status" . into ( ) , "file_read" . into ( ) ] ,
339+ tool_selection : ToolSelection :: Fixed ,
407340 ..Default :: default ( )
408341 } ;
409- let names = select_tool_names ( & config, "is this ready to ship?" ) ;
410- assert ! ( names. contains( & "ship_status" . to_string( ) ) ) ;
342+ assert_eq ! ( select_tool_names( & config, "hi" ) , config. tools) ;
411343 }
412344
413345 #[ test]
0 commit comments