agent harness initialize repo
This commit is contained in:
3
agent-with-tools/tools/__init__.py
Normal file
3
agent-with-tools/tools/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from tools.registry import get_tool_registry, get_tool_schemas
|
||||
|
||||
__all__ = ["get_tool_registry", "get_tool_schemas"]
|
||||
57
agent-with-tools/tools/filesystem.py
Normal file
57
agent-with-tools/tools/filesystem.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import glob as glob_module
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def read_file(path: str, offset: int = 1, limit: int = 200) -> str:
|
||||
"""Read lines from a file, with optional offset and limit."""
|
||||
p = Path(path)
|
||||
if not p.exists():
|
||||
return f"Error: file not found: {path}"
|
||||
lines = p.read_text(errors="replace").splitlines()
|
||||
selected = lines[offset - 1: offset - 1 + limit]
|
||||
return "\n".join(f"{offset + i}: {line}" for i, line in enumerate(selected))
|
||||
|
||||
|
||||
def glob_files(pattern: str, path: str = ".") -> str:
|
||||
"""Find files matching a glob pattern inside a directory."""
|
||||
matches = glob_module.glob(f"{path}/**/{pattern}", recursive=True)
|
||||
matches += glob_module.glob(f"{path}/{pattern}")
|
||||
unique = sorted(set(matches))
|
||||
return "\n".join(unique) if unique else "(no matches)"
|
||||
|
||||
|
||||
def grep(pattern: str, path: str = ".", include: str = "*") -> str:
|
||||
"""Search file contents for a regex pattern, optionally filtering by filename glob."""
|
||||
results = []
|
||||
for filepath in glob_module.glob(f"{path}/**/{include}", recursive=True):
|
||||
fp = Path(filepath)
|
||||
if not fp.is_file():
|
||||
continue
|
||||
try:
|
||||
for i, line in enumerate(fp.read_text(errors="replace").splitlines(), 1):
|
||||
if re.search(pattern, line):
|
||||
results.append(f"{filepath}:{i}: {line}")
|
||||
except OSError:
|
||||
pass
|
||||
return "\n".join(results) if results else "(no matches)"
|
||||
|
||||
|
||||
def write_file(path: str, content: str) -> str:
|
||||
"""Write content to a file, creating it if it does not exist."""
|
||||
p = Path(path)
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
p.write_text(content)
|
||||
return f"Wrote {len(content)} bytes to {path}"
|
||||
|
||||
|
||||
def edit_file(path: str, old_string: str, new_string: str) -> str:
|
||||
"""Replace the first occurrence of old_string with new_string in a file."""
|
||||
p = Path(path)
|
||||
if not p.exists():
|
||||
return f"Error: file not found: {path}"
|
||||
original = p.read_text()
|
||||
if old_string not in original:
|
||||
return f"Error: string not found in {path}"
|
||||
p.write_text(original.replace(old_string, new_string, 1))
|
||||
return f"Edited {path}"
|
||||
131
agent-with-tools/tools/registry.py
Normal file
131
agent-with-tools/tools/registry.py
Normal file
@@ -0,0 +1,131 @@
|
||||
from tools.filesystem import read_file, glob_files, grep, write_file, edit_file
|
||||
from tools.shell import run_bash
|
||||
from tools.web import webfetch
|
||||
|
||||
|
||||
def get_tool_registry():
|
||||
return {
|
||||
"run_bash": run_bash,
|
||||
"read_file": read_file,
|
||||
"glob_files": glob_files,
|
||||
"grep": grep,
|
||||
"write_file": write_file,
|
||||
"edit_file": edit_file,
|
||||
"webfetch": webfetch,
|
||||
}
|
||||
|
||||
|
||||
def get_tool_schemas():
|
||||
return [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "run_bash",
|
||||
"description": "Run a bash command on the user's machine and return the output.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "The bash command to execute.",
|
||||
}
|
||||
},
|
||||
"required": ["command"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "read_file",
|
||||
"description": "Read lines from a file. Returns lines prefixed with line numbers.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {"type": "string", "description": "Absolute or relative path to the file."},
|
||||
"offset": {"type": "integer", "description": "First line to read (1-indexed). Defaults to 1."},
|
||||
"limit": {"type": "integer", "description": "Maximum number of lines to return. Defaults to 200."},
|
||||
},
|
||||
"required": ["path"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "glob_files",
|
||||
"description": "Find files matching a glob pattern (e.g. '**/*.py') inside a directory.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pattern": {"type": "string", "description": "Glob pattern to match against file names."},
|
||||
"path": {"type": "string", "description": "Root directory to search in. Defaults to '.'."},
|
||||
},
|
||||
"required": ["pattern"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "grep",
|
||||
"description": "Search file contents for a regex pattern and return matching lines with file paths and line numbers.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pattern": {"type": "string", "description": "Regular expression to search for."},
|
||||
"path": {"type": "string", "description": "Directory to search in. Defaults to '.'."},
|
||||
"include": {"type": "string", "description": "Filename glob to restrict which files are searched (e.g. '*.py'). Defaults to '*'."},
|
||||
},
|
||||
"required": ["pattern"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "write_file",
|
||||
"description": "Write content to a file, creating it (and any missing parent directories) if it does not exist.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {"type": "string", "description": "Path of the file to write."},
|
||||
"content": {"type": "string", "description": "Full content to write to the file."},
|
||||
},
|
||||
"required": ["path", "content"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "edit_file",
|
||||
"description": "Replace the first occurrence of a string in a file with a new string.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {"type": "string", "description": "Path of the file to edit."},
|
||||
"old_string": {"type": "string", "description": "Exact string to find and replace."},
|
||||
"new_string": {"type": "string", "description": "String to replace it with."},
|
||||
},
|
||||
"required": ["path", "old_string", "new_string"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "webfetch",
|
||||
"description": (
|
||||
"Fetch a public URL (http/https only) and return its full plain-text content (up to 2 MB)."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {"type": "string", "description": "The URL to fetch (http/https)."},
|
||||
},
|
||||
"required": ["url"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
12
agent-with-tools/tools/shell.py
Normal file
12
agent-with-tools/tools/shell.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import subprocess
|
||||
|
||||
|
||||
def run_bash(command: str) -> str:
|
||||
"""Run a bash command and return its output."""
|
||||
result = subprocess.run(
|
||||
command, shell=True, text=True, capture_output=True
|
||||
)
|
||||
output = result.stdout
|
||||
if result.stderr:
|
||||
output += f"\nSTDERR:\n{result.stderr}"
|
||||
return output or "(no output)"
|
||||
41
agent-with-tools/tools/web.py
Normal file
41
agent-with-tools/tools/web.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import re
|
||||
import urllib.request
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
def webfetch(url: str) -> str:
|
||||
"""Fetch a URL and return its full plain-text content (up to 2 MB)."""
|
||||
try:
|
||||
parsed = urlparse(url)
|
||||
if parsed.scheme not in ("http", "https"):
|
||||
return f"Error fetching {url}: unsupported scheme '{parsed.scheme}'. Only http and https are allowed."
|
||||
max_bytes = 2 * 1024 * 1024
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "agent/1.0"})
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
content_type = resp.headers.get_content_type()
|
||||
if content_type and content_type not in (
|
||||
"text/html",
|
||||
"text/plain",
|
||||
"application/xhtml+xml",
|
||||
):
|
||||
return f"Error fetching {url}: unsupported content type '{content_type}'."
|
||||
charset = resp.headers.get_content_charset() or "utf-8"
|
||||
raw_chunks = []
|
||||
total = 0
|
||||
while True:
|
||||
chunk = resp.read(65536)
|
||||
if not chunk:
|
||||
break
|
||||
total += len(chunk)
|
||||
if total > max_bytes:
|
||||
raw_chunks.append(chunk[: max_bytes - (total - len(chunk))])
|
||||
break
|
||||
raw_chunks.append(chunk)
|
||||
raw = b"".join(raw_chunks).decode(charset, errors="replace")
|
||||
soup = BeautifulSoup(raw, "html.parser")
|
||||
text = soup.get_text(separator="\n", strip=True)
|
||||
return re.sub(r"\n{3,}", "\n\n", text).strip()
|
||||
except Exception as e:
|
||||
return f"Error fetching {url}: {e}"
|
||||
Reference in New Issue
Block a user