180 lines
6.1 KiB
Python
180 lines
6.1 KiB
Python
from __future__ import annotations
|
|
|
|
import subprocess
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, Any, Callable, Union
|
|
|
|
from .agent_types import AgentPermissions, AgentRuntimeConfig, ToolExecutionResult
|
|
|
|
if TYPE_CHECKING:
|
|
from .account_runtime import AccountRuntime
|
|
from .ask_user_runtime import AskUserRuntime
|
|
from .config_runtime import ConfigRuntime
|
|
from .lsp_runtime import LSPRuntime
|
|
from .mcp_runtime import MCPRuntime
|
|
from .plan_runtime import PlanRuntime
|
|
from .remote_runtime import RemoteRuntime
|
|
from .remote_trigger_runtime import RemoteTriggerRuntime
|
|
from .search_runtime import SearchRuntime
|
|
from .task_runtime import TaskRuntime
|
|
from .team_runtime import TeamRuntime
|
|
from .workflow_runtime import WorkflowRuntime
|
|
from .worktree_runtime import WorktreeRuntime
|
|
|
|
|
|
class ToolPermissionError(RuntimeError):
|
|
"""Raised when the runtime configuration does not allow a tool action."""
|
|
|
|
|
|
class ToolExecutionError(RuntimeError):
|
|
"""Raised when a tool cannot complete because of invalid input or state."""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ToolExecutionContext:
|
|
root: Path
|
|
command_timeout_seconds: float
|
|
max_output_chars: int
|
|
permissions: AgentPermissions
|
|
scratchpad_directory: Path | None = None
|
|
python_env_dir: Path | None = None
|
|
extra_env: dict[str, str] = field(default_factory=dict)
|
|
cancel_event: Any | None = None
|
|
process_registry: Any | None = None
|
|
tool_registry: dict[str, 'AgentTool'] | None = None
|
|
search_runtime: 'SearchRuntime | None' = None
|
|
account_runtime: 'AccountRuntime | None' = None
|
|
ask_user_runtime: 'AskUserRuntime | None' = None
|
|
config_runtime: 'ConfigRuntime | None' = None
|
|
lsp_runtime: 'LSPRuntime | None' = None
|
|
mcp_runtime: 'MCPRuntime | None' = None
|
|
remote_runtime: 'RemoteRuntime | None' = None
|
|
remote_trigger_runtime: 'RemoteTriggerRuntime | None' = None
|
|
plan_runtime: 'PlanRuntime | None' = None
|
|
task_runtime: 'TaskRuntime | None' = None
|
|
team_runtime: 'TeamRuntime | None' = None
|
|
workflow_runtime: 'WorkflowRuntime | None' = None
|
|
worktree_runtime: 'WorktreeRuntime | None' = None
|
|
|
|
|
|
ToolHandler = Callable[
|
|
[dict[str, Any], ToolExecutionContext],
|
|
Union[str, tuple[str, dict[str, Any]]],
|
|
]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AgentTool:
|
|
name: str
|
|
description: str
|
|
parameters: dict[str, Any]
|
|
handler: ToolHandler
|
|
|
|
def to_openai_tool(self) -> dict[str, object]:
|
|
return {
|
|
'type': 'function',
|
|
'function': {
|
|
'name': self.name,
|
|
'description': self.description,
|
|
'parameters': self.parameters,
|
|
},
|
|
}
|
|
|
|
def execute(
|
|
self,
|
|
arguments: dict[str, Any],
|
|
context: ToolExecutionContext,
|
|
) -> ToolExecutionResult:
|
|
try:
|
|
result = self.handler(arguments, context)
|
|
if isinstance(result, tuple):
|
|
content, metadata = result
|
|
else:
|
|
content, metadata = result, {}
|
|
return ToolExecutionResult(
|
|
name=self.name,
|
|
ok=True,
|
|
content=content,
|
|
metadata=metadata,
|
|
)
|
|
except ToolPermissionError as exc:
|
|
return ToolExecutionResult(
|
|
name=self.name,
|
|
ok=False,
|
|
content=str(exc),
|
|
metadata={'error_kind': 'permission_denied'},
|
|
)
|
|
except (ToolExecutionError, OSError, subprocess.SubprocessError) as exc:
|
|
return ToolExecutionResult(
|
|
name=self.name,
|
|
ok=False,
|
|
content=str(exc),
|
|
metadata={'error_kind': 'tool_execution_error'},
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ToolStreamUpdate:
|
|
kind: str
|
|
content: str = ''
|
|
stream: str | None = None
|
|
result: ToolExecutionResult | None = None
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
def build_tool_context(
|
|
config: AgentRuntimeConfig,
|
|
*,
|
|
scratchpad_directory: Path | None = None,
|
|
python_env_dir: Path | None = None,
|
|
extra_env: dict[str, str] | None = None,
|
|
cancel_event: Any | None = None,
|
|
process_registry: Any | None = None,
|
|
tool_registry: dict[str, AgentTool] | None = None,
|
|
search_runtime: 'SearchRuntime | None' = None,
|
|
account_runtime: 'AccountRuntime | None' = None,
|
|
ask_user_runtime: 'AskUserRuntime | None' = None,
|
|
config_runtime: 'ConfigRuntime | None' = None,
|
|
lsp_runtime: 'LSPRuntime | None' = None,
|
|
mcp_runtime: 'MCPRuntime | None' = None,
|
|
remote_runtime: 'RemoteRuntime | None' = None,
|
|
remote_trigger_runtime: 'RemoteTriggerRuntime | None' = None,
|
|
plan_runtime: 'PlanRuntime | None' = None,
|
|
task_runtime: 'TaskRuntime | None' = None,
|
|
team_runtime: 'TeamRuntime | None' = None,
|
|
workflow_runtime: 'WorkflowRuntime | None' = None,
|
|
worktree_runtime: 'WorktreeRuntime | None' = None,
|
|
) -> ToolExecutionContext:
|
|
return ToolExecutionContext(
|
|
root=config.cwd.resolve(),
|
|
command_timeout_seconds=config.command_timeout_seconds,
|
|
max_output_chars=config.max_output_chars,
|
|
permissions=config.permissions,
|
|
scratchpad_directory=scratchpad_directory.resolve() if scratchpad_directory else None,
|
|
python_env_dir=(
|
|
python_env_dir.resolve()
|
|
if python_env_dir
|
|
else config.python_env_dir.resolve()
|
|
if config.python_env_dir
|
|
else None
|
|
),
|
|
extra_env=dict(extra_env or {}),
|
|
cancel_event=cancel_event,
|
|
process_registry=process_registry,
|
|
tool_registry=tool_registry,
|
|
search_runtime=search_runtime,
|
|
account_runtime=account_runtime,
|
|
ask_user_runtime=ask_user_runtime,
|
|
config_runtime=config_runtime,
|
|
lsp_runtime=lsp_runtime,
|
|
mcp_runtime=mcp_runtime,
|
|
remote_runtime=remote_runtime,
|
|
remote_trigger_runtime=remote_trigger_runtime,
|
|
plan_runtime=plan_runtime,
|
|
task_runtime=task_runtime,
|
|
team_runtime=team_runtime,
|
|
workflow_runtime=workflow_runtime,
|
|
worktree_runtime=worktree_runtime,
|
|
)
|