Files
deer-flow/backend/src/sandbox/tools.py

143 lines
5.2 KiB
Python
Raw Normal View History

2026-01-14 12:32:34 +08:00
from langchain.tools import ToolRuntime, tool
from langgraph.typing import ContextT
2026-01-14 07:19:34 +08:00
2026-01-14 12:32:34 +08:00
from src.agents.thread_state import ThreadState
from src.sandbox.sandbox import Sandbox
2026-01-14 07:19:34 +08:00
from src.sandbox.sandbox_provider import get_sandbox_provider
2026-01-14 12:32:34 +08:00
def sandbox_from_runtime(runtime: ToolRuntime[ContextT, ThreadState] | None = None) -> Sandbox:
if runtime is None:
raise ValueError("No sandbox found: No runtime found")
sandbox_state = runtime.state.get("sandbox")
if sandbox_state is None:
raise ValueError("No sandbox found: No sandbox state found in runtime")
sandbox_id = sandbox_state.get("sandbox_id")
if sandbox_id is None:
raise ValueError("No sandbox ID found: No sandbox ID found in sandbox state")
sandbox = get_sandbox_provider().get(sandbox_id)
if sandbox is None:
raise ValueError(f"No sandbox found: sandbox with ID {sandbox_id} not found")
return sandbox
2026-01-14 07:19:34 +08:00
@tool("bash", parse_docstring=True)
2026-01-14 12:32:34 +08:00
def bash_tool(runtime: ToolRuntime[ContextT, ThreadState], description: str, command: str) -> str:
2026-01-14 09:12:03 +08:00
"""Execute a bash command in a Linux environment.
- Use `python` to run Python code.
- Use `pip install` to install Python packages.
2026-01-14 07:19:34 +08:00
Args:
2026-01-14 12:32:34 +08:00
description: Explain why you are running this command in short words. ALWAYS PROVIDE THIS PARAMETER FIRST.
2026-01-14 07:19:34 +08:00
command: The bash command to execute. Always use absolute paths for files and directories.
"""
try:
2026-01-14 12:32:34 +08:00
sandbox = sandbox_from_runtime(runtime)
2026-01-14 07:19:34 +08:00
return sandbox.execute_command(command)
except Exception as e:
return f"Error: {e}"
@tool("ls", parse_docstring=True)
2026-01-14 12:32:34 +08:00
def ls_tool(runtime: ToolRuntime[ContextT, ThreadState], description: str, path: str) -> str:
2026-01-14 07:19:34 +08:00
"""List the contents of a directory up to 2 levels deep in tree format.
Args:
2026-01-14 12:32:34 +08:00
description: Explain why you are listing this directory in short words. ALWAYS PROVIDE THIS PARAMETER FIRST.
2026-01-14 07:19:34 +08:00
path: The **absolute** path to the directory to list.
"""
try:
2026-01-14 12:32:34 +08:00
sandbox = sandbox_from_runtime(runtime)
2026-01-14 07:19:34 +08:00
children = sandbox.list_dir(path)
if not children:
return "(empty)"
return "\n".join(children)
except Exception as e:
return f"Error: {e}"
@tool("read_file", parse_docstring=True)
def read_file_tool(
2026-01-14 12:32:34 +08:00
runtime: ToolRuntime[ContextT, ThreadState],
2026-01-14 07:19:34 +08:00
description: str,
path: str,
view_range: tuple[int, int] | None = None,
) -> str:
"""Read the contents of a text file.
Args:
2026-01-14 12:32:34 +08:00
description: Explain why you are viewing this file in short words. ALWAYS PROVIDE THIS PARAMETER FIRST.
2026-01-14 07:19:34 +08:00
path: The **absolute** path to the file to read.
view_range: The range of lines to view. The range is inclusive and starts at 1. For example, (1, 10) will view the first 10 lines of the file.
"""
try:
2026-01-14 12:32:34 +08:00
sandbox = sandbox_from_runtime(runtime)
2026-01-14 07:19:34 +08:00
content = sandbox.read_file(path)
if not content:
return "(empty)"
if view_range:
start, end = view_range
content = "\n".join(content.splitlines()[start - 1 : end])
return content
except Exception as e:
return f"Error: {e}"
@tool("write_file", parse_docstring=True)
def write_file_tool(
2026-01-14 12:32:34 +08:00
runtime: ToolRuntime[ContextT, ThreadState],
2026-01-14 07:19:34 +08:00
description: str,
path: str,
content: str,
append: bool = False,
) -> str:
"""Write text content to a file.
Args:
2026-01-14 12:32:34 +08:00
description: Explain why you are writing to this file in short words. ALWAYS PROVIDE THIS PARAMETER FIRST.
path: The **absolute** path to the file to write to. ALWAYS PROVIDE THIS PARAMETER SECOND.
content: The content to write to the file. ALWAYS PROVIDE THIS PARAMETER THIRD.
2026-01-14 07:19:34 +08:00
"""
try:
2026-01-14 12:32:34 +08:00
sandbox = sandbox_from_runtime(runtime)
2026-01-14 07:19:34 +08:00
sandbox.write_file(path, content, append)
return "OK"
except Exception as e:
return f"Error: {e}"
@tool("str_replace", parse_docstring=True)
def str_replace_tool(
2026-01-14 12:32:34 +08:00
runtime: ToolRuntime[ContextT, ThreadState],
2026-01-14 07:19:34 +08:00
description: str,
path: str,
old_str: str,
new_str: str,
replace_all: bool = False,
) -> str:
"""Replace a substring in a file with another substring.
If `replace_all` is False (default), the substring to replace must appear **exactly once** in the file.
Args:
2026-01-14 12:32:34 +08:00
description: Explain why you are replacing the substring in short words. ALWAYS PROVIDE THIS PARAMETER FIRST.
path: The **absolute** path to the file to replace the substring in. ALWAYS PROVIDE THIS PARAMETER SECOND.
old_str: The substring to replace. ALWAYS PROVIDE THIS PARAMETER THIRD.
new_str: The new substring. ALWAYS PROVIDE THIS PARAMETER FOURTH.
2026-01-14 07:19:34 +08:00
replace_all: Whether to replace all occurrences of the substring. If False, only the first occurrence will be replaced. Default is False.
"""
try:
2026-01-14 12:32:34 +08:00
sandbox = sandbox_from_runtime(runtime)
2026-01-14 07:19:34 +08:00
content = sandbox.read_file(path)
if not content:
return "OK"
if replace_all:
content = content.replace(old_str, new_str)
else:
content = content.replace(old_str, new_str, 1)
sandbox.write_file(path, content)
return "OK"
except Exception as e:
return f"Error: {e}"