@@ -64,6 +64,39 @@ const PROGRAM_TERMS: &[&str] = &[
6464
6565const MCP_TERMS : & [ & str ] = & [ "mcp" , "external tool" , "external server" , "外部工具" ] ;
6666
67+ const STANDALONE_CONVERSATION : & [ & str ] = & [
68+ "hi" ,
69+ "hi there" ,
70+ "hello" ,
71+ "hello there" ,
72+ "hey" ,
73+ "greetings" ,
74+ "good morning" ,
75+ "good afternoon" ,
76+ "good evening" ,
77+ "how are you" ,
78+ "how's it going" ,
79+ "hows it going" ,
80+ "what's up" ,
81+ "whats up" ,
82+ "thanks" ,
83+ "thank you" ,
84+ "你好" ,
85+ "您好" ,
86+ "嗨" ,
87+ "哈喽" ,
88+ "哈啰" ,
89+ "早" ,
90+ "早上好" ,
91+ "上午好" ,
92+ "下午好" ,
93+ "晚上好" ,
94+ "在吗" ,
95+ "你好吗" ,
96+ "谢谢" ,
97+ "多谢" ,
98+ ] ;
99+
67100/// Select the tools that should be exposed to the model for this turn.
68101///
69102/// The executor still owns every registered tool. This function only trims the
@@ -78,7 +111,7 @@ pub fn select_tools_for_messages(
78111}
79112
80113pub fn select_tools_for_prompt ( tools : & [ ToolDefinition ] , prompt : & str ) -> Vec < ToolDefinition > {
81- if tools. is_empty ( ) {
114+ if tools. is_empty ( ) || is_standalone_conversation ( prompt ) {
82115 return Vec :: new ( ) ;
83116 }
84117
@@ -114,6 +147,32 @@ pub fn select_tools_for_prompt(tools: &[ToolDefinition], prompt: &str) -> Vec<To
114147 selected
115148}
116149
150+ /// Return whether a prompt is only a short conversational acknowledgement.
151+ ///
152+ /// This is deliberately exact after whitespace and terminal-punctuation
153+ /// normalization. A greeting that also contains an action must retain the
154+ /// ordinary tool surface.
155+ pub ( crate ) fn is_standalone_conversation ( prompt : & str ) -> bool {
156+ let normalized = prompt
157+ . trim ( )
158+ . trim_matches ( is_conversational_boundary)
159+ . split_whitespace ( )
160+ . collect :: < Vec < _ > > ( )
161+ . join ( " " )
162+ . to_lowercase ( ) ;
163+
164+ STANDALONE_CONVERSATION . contains ( & normalized. as_str ( ) )
165+ }
166+
167+ fn is_conversational_boundary ( character : char ) -> bool {
168+ character. is_ascii_punctuation ( )
169+ || character. is_whitespace ( )
170+ || matches ! (
171+ character,
172+ '。' | ',' | '、' | '!' | '?' | '…' | '~' | '👋'
173+ )
174+ }
175+
117176fn should_include_mcp_tool (
118177 name : & str ,
119178 name_lower : & str ,
@@ -264,6 +323,38 @@ mod tests {
264323 assert ! ( !names. contains( & "mcp__github__create_issue" ) ) ;
265324 }
266325
326+ #[ test]
327+ fn standalone_greetings_do_not_expose_tools ( ) {
328+ let tools = defs ( & [ "read" , "grep" , "bash" , "web_search" , "task" ] ) ;
329+
330+ for prompt in [
331+ "hi" ,
332+ "Hello!" ,
333+ "how are you?" ,
334+ "你好" ,
335+ "您好!" ,
336+ "在吗?" ,
337+ "谢谢" ,
338+ ] {
339+ assert ! (
340+ select_tools_for_prompt( & tools, prompt) . is_empty( ) ,
341+ "standalone greeting exposed tools: {prompt}"
342+ ) ;
343+ }
344+ }
345+
346+ #[ test]
347+ fn greeting_with_an_action_keeps_relevant_tools ( ) {
348+ let selected = select_tools_for_prompt (
349+ & defs ( & [ "read" , "grep" , "web_search" ] ) ,
350+ "Hello! Inspect this repository for the parser implementation." ,
351+ ) ;
352+ let names: Vec < _ > = selected. iter ( ) . map ( |tool| tool. name . as_str ( ) ) . collect ( ) ;
353+
354+ assert ! ( names. contains( & "read" ) ) ;
355+ assert ! ( names. contains( & "grep" ) ) ;
356+ }
357+
267358 #[ test]
268359 fn program_terms_enable_program_tool ( ) {
269360 let selected = select_tools_for_prompt (
0 commit comments