@@ -180,12 +180,12 @@ def format_file_context(file_contents: List[Dict[str, str]]) -> str:
180180
181181# Patterns to detect tool calls in text responses
182182TOOL_CALL_PATTERNS = [
183- # Pattern: <function/name>{"arg": "value"}</function
184- r'<function/(\w +)>\s*(\{[^}]*\})\s*</function' ,
185- # Pattern: <function name="name">{"arg": "value"}</function>
186- r'<function\s+name="(\w +)">\s*(\{[^}]*\})\s*</function>' ,
187- # Pattern: ```json\n{"name": "func", "arguments": {...}}```
188- r'```json\s*\{\s*"name"\s*:\s*"(\w +)"\s*,\s*"arguments"\s*:\s*(\{[^}]*\})\s*\}\s*```' ,
183+ # Pattern: <function/name>{"arg": "value"}</function (also handles <functions/name>)
184+ r'<functions?/([\w/] +)>\s*(\{[^}]*\})\s*</function' ,
185+ # Pattern: <function name="name">{"arg": "value"}</function> (also handles prefixed names)
186+ r'<function\s+name="([\w./] +)">\s*(\{[^}]*\})\s*</function>' ,
187+ # Pattern: ```json\n{"name": "func", "arguments": {...}}``` (also handles prefixed names)
188+ r'```json\s*\{\s*"name"\s*:\s*"([\w./] +)"\s*,\s*"arguments"\s*:\s*(\{[^}]*\})\s*\}\s*```' ,
189189]
190190
191191# ═══════════════════════════════════════════════════════════════════════════════
@@ -405,18 +405,54 @@ def _save_conversation(self):
405405 """Save the current conversation state"""
406406 history_manager .update_conversation (self .messages )
407407
408+ def _normalize_tool_name (self , name : str ) -> str :
409+ """
410+ Normalize a tool name by stripping common prefixes.
411+ Some models add prefixes like 'functions/' or 'tools.' to tool names.
412+ """
413+ prefixes_to_strip = [
414+ "repo_browser." , "repo_browser/" ,
415+ "functions." , "functions/" ,
416+ "tools." , "tools/" ,
417+ "file_ops." , "file_ops/" ,
418+ "system." , "system/"
419+ ]
420+ for prefix in prefixes_to_strip :
421+ if name .startswith (prefix ):
422+ return name [len (prefix ):]
423+ return name
424+
408425 def _filter_valid_tool_calls (self , tool_calls : List [Any ]) -> List [Any ]:
409426 """
410427 Filter out tool calls for unknown/invalid tools.
411428 Some models (especially Llama) may hallucinate tool names like 'commentary'.
429+ Also normalizes tool names by stripping common prefixes (functions/, tools., etc.)
412430 """
413431 valid_calls = []
432+ invalid_tools = []
433+
414434 for tc in tool_calls :
415435 tool_name = tc .name if hasattr (tc , 'name' ) else tc .get ('name' , '' )
416- if tool_name in TOOLS :
436+ # Normalize the tool name by stripping prefixes
437+ normalized_name = self ._normalize_tool_name (tool_name )
438+
439+ if normalized_name in TOOLS :
440+ # Update the tool call with the normalized name
441+ if hasattr (tc , 'name' ):
442+ tc .name = normalized_name
443+ elif isinstance (tc , dict ):
444+ tc ['name' ] = normalized_name
417445 valid_calls .append (tc )
418446 else :
447+ invalid_tools .append (tool_name )
419448 log_debug (f"Ignoring unknown tool call: { tool_name } " )
449+
450+ # If invalid tools were detected, show warning
451+ if invalid_tools :
452+ display_warning (f"Ignored invalid tool(s): { ', ' .join (invalid_tools )} " )
453+ available = list (TOOLS .keys ())[:15 ] # Show first 15 tools
454+ log_debug (f"Available tools: { available } " )
455+
420456 return valid_calls
421457
422458 def _parse_tool_calls_from_text (self , text : str ) -> List [ToolCall ]:
@@ -433,15 +469,16 @@ def _parse_tool_calls_from_text(self, text: str) -> List[ToolCall]:
433469 func_name = match [0 ]
434470 args_str = match [1 ]
435471
436- # Validate that it's a known tool
437- if func_name in TOOLS :
472+ # Normalize and validate that it's a known tool
473+ normalized_name = self ._normalize_tool_name (func_name )
474+ if normalized_name in TOOLS :
438475 call_id += 1
439476 tool_calls .append (ToolCall (
440477 id = f"text_call_{ call_id } " ,
441- name = func_name ,
478+ name = normalized_name ,
442479 arguments = args_str
443480 ))
444- log_debug (f"Parsed tool call from text: { func_name } " )
481+ log_debug (f"Parsed tool call from text: { normalized_name } " )
445482
446483 return tool_calls
447484
0 commit comments