From 23823c82daa8a9f9fe84bfe2e147321a807244d1 Mon Sep 17 00:00:00 2001 From: Dita Aji Pratama Date: Wed, 29 Jul 2026 15:51:46 +0700 Subject: [PATCH] New Tools --- hendrik.py | 2 ++ tools/coder.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/hendrik.py b/hendrik.py index 04e9328..3cc341a 100644 --- a/hendrik.py +++ b/hendrik.py @@ -30,6 +30,8 @@ tools_definition = [ gadget.tools_mapping( schema = coder.schema_git_operation, handler = coder.git_operation ), gadget.tools_mapping( schema = coder.schema_workspace_change, handler = coder.workspace_change ), + gadget.tools_mapping( schema = coder.schema_search_grep, handler = coder.search_grep ), + gadget.tools_mapping( schema = coder.schema_search_glob, handler = coder.search_glob ), # Carrack Tools gadget.tools_mapping( schema = carrack.schema_sendhttprequest, handler = carrack.sendhttprequest ), diff --git a/tools/coder.py b/tools/coder.py index 5c1cd40..c203888 100644 --- a/tools/coder.py +++ b/tools/coder.py @@ -1,6 +1,7 @@ import os import subprocess import re +import fnmatch import glob as glob_module # Global state to track the agent's current working directory @@ -220,3 +221,76 @@ def workspace_change(path): return f"Error: The path {target_path} does not exist or is not a directory." except Exception as e: return f"Error changing workspace: {str(e)}" + +schema_search_grep = { + "type": "function", + "function": { + "name": "search_grep", + "description": "Search file contents using regular expressions. Fast content search across your codebase. Supports full regex syntax and file pattern filtering.", + "parameters": { + "type": "object", + "properties": { + "pattern": {"type": "string", "description": "Regex pattern to search for in file contents"}, + "path": {"type": "string", "description": "Directory to search in (default: current working directory)", "default": "."}, + "include": {"type": "string", "description": "File pattern to include (e.g. '*.py', '*.{ts,tsx}'). If not provided, all files are searched."} + }, + "required": ["pattern"] + } + } +} + +def search_grep(pattern, path=".", include=None): + try: + search_path = os.path.join(get_current_workspace(), path) if not os.path.isabs(path) else path + results = [] + for root, dirs, files in os.walk(search_path): + for file in files: + if include and not fnmatch.fnmatch(file, include): + continue + file_path = os.path.join(root, file) + try: + with open(file_path, 'r', encoding='utf-8', errors='replace') as f: + for i, line in enumerate(f.readlines(), 1): + if re.search(pattern, line): + results.append(f"{file_path}:{i}: {line.strip()}") + except: + continue + return "\n".join(results[:100]) if results else "No matches found" + except Exception as e: + return f"Error searching: {str(e)}" + +schema_search_glob = { + "type": "function", + "function": { + "name": "search_glob", + "description": "Find files by pattern matching. Search for files using glob patterns like `**/*.html` or `modules/**/*.py`. Returns matching file paths sorted by modification time.", + "parameters": { + "type": "object", + "properties": { + "pattern": {"type": "string", "description": "Glob pattern to match files (e.g. '**/*.html', 'modules/**/*.py')"}, + "path": {"type": "string", "description": "Directory to search in (default: current working directory)", "default": "."} + }, + "required": ["pattern"] + } + } +} + +def search_glob(pattern, path="."): + try: + search_path = os.path.join(get_current_workspace(), path) if not os.path.isabs(path) else path + files = glob_module.glob(f"{search_path}/{pattern}", recursive=True) + def _isfile(f): + try: + return os.path.isfile(f) + except OSError: + return False + files = [f for f in files if _isfile(f)] + def _mtime(f): + try: + return os.path.getmtime(f) + except OSError: + return 0 + files.sort(key=_mtime, reverse=True) + return "\n".join(files[:500]) if files else "No files found" + except Exception as e: + return f"Error searching: {str(e)}"