agentpool 2.2.3__py3-none-any.whl → 2.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- acp/__init__.py +0 -4
- acp/acp_requests.py +20 -77
- acp/agent/connection.py +8 -0
- acp/agent/implementations/debug_server/debug_server.py +6 -2
- acp/agent/protocol.py +6 -0
- acp/client/connection.py +38 -29
- acp/client/implementations/default_client.py +3 -2
- acp/client/implementations/headless_client.py +2 -2
- acp/connection.py +2 -2
- acp/notifications.py +18 -49
- acp/schema/__init__.py +2 -0
- acp/schema/agent_responses.py +21 -0
- acp/schema/client_requests.py +3 -3
- acp/schema/session_state.py +63 -29
- acp/task/supervisor.py +2 -2
- acp/utils.py +2 -2
- agentpool/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +278 -263
- agentpool/agents/acp_agent/acp_converters.py +150 -17
- agentpool/agents/acp_agent/client_handler.py +35 -24
- agentpool/agents/acp_agent/session_state.py +14 -6
- agentpool/agents/agent.py +471 -643
- agentpool/agents/agui_agent/agui_agent.py +104 -107
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +485 -32
- agentpool/agents/claude_code_agent/FORKING.md +191 -0
- agentpool/agents/claude_code_agent/__init__.py +13 -1
- agentpool/agents/claude_code_agent/claude_code_agent.py +654 -334
- agentpool/agents/claude_code_agent/converters.py +4 -141
- agentpool/agents/claude_code_agent/models.py +77 -0
- agentpool/agents/claude_code_agent/static_info.py +100 -0
- agentpool/agents/claude_code_agent/usage.py +242 -0
- agentpool/agents/events/__init__.py +22 -0
- agentpool/agents/events/builtin_handlers.py +65 -0
- agentpool/agents/events/event_emitter.py +3 -0
- agentpool/agents/events/events.py +84 -3
- agentpool/agents/events/infer_info.py +145 -0
- agentpool/agents/events/processors.py +254 -0
- agentpool/agents/interactions.py +41 -6
- agentpool/agents/modes.py +13 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +35 -21
- agentpool/config_resources/acp_assistant.yml +2 -2
- agentpool/config_resources/agents.yml +3 -0
- agentpool/config_resources/agents_template.yml +1 -0
- agentpool/config_resources/claude_code_agent.yml +9 -8
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +104 -265
- agentpool/delegation/team.py +57 -57
- agentpool/delegation/teamrun.py +50 -55
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/client.py +73 -38
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +9 -23
- agentpool/mcp_server/registries/official_registry_client.py +10 -1
- agentpool/mcp_server/tool_bridge.py +114 -79
- agentpool/messaging/connection_manager.py +11 -10
- agentpool/messaging/event_manager.py +5 -5
- agentpool/messaging/message_container.py +6 -30
- agentpool/messaging/message_history.py +87 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +2 -26
- agentpool/messaging/processing.py +10 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -2
- agentpool/models/acp_agents/mcp_capable.py +124 -15
- agentpool/models/acp_agents/non_mcp.py +0 -23
- agentpool/models/agents.py +66 -66
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +111 -17
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +70 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/resource_providers/__init__.py +2 -0
- agentpool/resource_providers/aggregating.py +4 -2
- agentpool/resource_providers/base.py +13 -3
- agentpool/resource_providers/codemode/code_executor.py +72 -5
- agentpool/resource_providers/codemode/helpers.py +2 -2
- agentpool/resource_providers/codemode/provider.py +64 -12
- agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
- agentpool/resource_providers/codemode/remote_provider.py +9 -12
- agentpool/resource_providers/filtering.py +3 -1
- agentpool/resource_providers/mcp_provider.py +66 -12
- agentpool/resource_providers/plan_provider.py +111 -18
- agentpool/resource_providers/pool.py +5 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +2 -2
- agentpool/sessions/__init__.py +2 -0
- agentpool/sessions/manager.py +2 -3
- agentpool/sessions/models.py +9 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/storage/manager.py +361 -54
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +1 -1
- agentpool/tool_impls/__init__.py +6 -0
- agentpool/tool_impls/agent_cli/__init__.py +42 -0
- agentpool/tool_impls/agent_cli/tool.py +95 -0
- agentpool/tool_impls/bash/__init__.py +64 -0
- agentpool/tool_impls/bash/helpers.py +35 -0
- agentpool/tool_impls/bash/tool.py +171 -0
- agentpool/tool_impls/delete_path/__init__.py +70 -0
- agentpool/tool_impls/delete_path/tool.py +142 -0
- agentpool/tool_impls/download_file/__init__.py +80 -0
- agentpool/tool_impls/download_file/tool.py +183 -0
- agentpool/tool_impls/execute_code/__init__.py +55 -0
- agentpool/tool_impls/execute_code/tool.py +163 -0
- agentpool/tool_impls/grep/__init__.py +80 -0
- agentpool/tool_impls/grep/tool.py +200 -0
- agentpool/tool_impls/list_directory/__init__.py +73 -0
- agentpool/tool_impls/list_directory/tool.py +197 -0
- agentpool/tool_impls/question/__init__.py +42 -0
- agentpool/tool_impls/question/tool.py +127 -0
- agentpool/tool_impls/read/__init__.py +104 -0
- agentpool/tool_impls/read/tool.py +305 -0
- agentpool/tools/__init__.py +2 -1
- agentpool/tools/base.py +114 -34
- agentpool/tools/manager.py +57 -1
- agentpool/ui/base.py +2 -2
- agentpool/ui/mock_provider.py +2 -2
- agentpool/ui/stdlib_provider.py +2 -2
- agentpool/utils/streams.py +21 -96
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/METADATA +16 -22
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/RECORD +242 -195
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +20 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +59 -1
- agentpool_cli/serve_opencode.py +1 -1
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +12 -5
- agentpool_commands/agents.py +1 -1
- agentpool_commands/pool.py +260 -0
- agentpool_commands/session.py +1 -1
- agentpool_commands/text_sharing/__init__.py +119 -0
- agentpool_commands/text_sharing/base.py +123 -0
- agentpool_commands/text_sharing/github_gist.py +80 -0
- agentpool_commands/text_sharing/opencode.py +462 -0
- agentpool_commands/text_sharing/paste_rs.py +59 -0
- agentpool_commands/text_sharing/pastebin.py +116 -0
- agentpool_commands/text_sharing/shittycodingagent.py +112 -0
- agentpool_commands/utils.py +31 -32
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -0
- agentpool_config/converters.py +1 -1
- agentpool_config/event_handlers.py +42 -0
- agentpool_config/events.py +1 -1
- agentpool_config/forward_targets.py +1 -4
- agentpool_config/jinja.py +3 -3
- agentpool_config/mcp_server.py +1 -5
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +38 -39
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -28
- agentpool_config/toolsets.py +22 -90
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +125 -56
- agentpool_server/acp_server/commands/acp_commands.py +46 -216
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +8 -7
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +1 -11
- agentpool_server/acp_server/session.py +90 -410
- agentpool_server/acp_server/session_manager.py +8 -34
- agentpool_server/agui_server/server.py +3 -1
- agentpool_server/mcp_server/server.py +5 -2
- agentpool_server/opencode_server/ENDPOINTS.md +53 -14
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +0 -8
- agentpool_server/opencode_server/converters.py +132 -26
- agentpool_server/opencode_server/input_provider.py +160 -8
- agentpool_server/opencode_server/models/__init__.py +42 -20
- agentpool_server/opencode_server/models/app.py +12 -0
- agentpool_server/opencode_server/models/events.py +203 -29
- agentpool_server/opencode_server/models/mcp.py +19 -0
- agentpool_server/opencode_server/models/message.py +18 -1
- agentpool_server/opencode_server/models/parts.py +134 -1
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +13 -1
- agentpool_server/opencode_server/routes/__init__.py +4 -0
- agentpool_server/opencode_server/routes/agent_routes.py +33 -2
- agentpool_server/opencode_server/routes/app_routes.py +66 -3
- agentpool_server/opencode_server/routes/config_routes.py +66 -5
- agentpool_server/opencode_server/routes/file_routes.py +184 -5
- agentpool_server/opencode_server/routes/global_routes.py +1 -1
- agentpool_server/opencode_server/routes/lsp_routes.py +1 -1
- agentpool_server/opencode_server/routes/message_routes.py +122 -66
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +23 -22
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +139 -68
- agentpool_server/opencode_server/routes/tui_routes.py +1 -1
- agentpool_server/opencode_server/server.py +47 -2
- agentpool_server/opencode_server/state.py +30 -0
- agentpool_storage/__init__.py +0 -4
- agentpool_storage/base.py +81 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/{claude_provider.py → claude_provider/provider.py} +190 -8
- agentpool_storage/file_provider.py +149 -15
- agentpool_storage/memory_provider.py +132 -12
- agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
- agentpool_storage/opencode_provider/__init__.py +16 -0
- agentpool_storage/opencode_provider/helpers.py +414 -0
- agentpool_storage/opencode_provider/provider.py +895 -0
- agentpool_storage/session_store.py +20 -6
- agentpool_storage/sql_provider/sql_provider.py +135 -2
- agentpool_storage/sql_provider/utils.py +2 -12
- agentpool_storage/zed_provider/__init__.py +16 -0
- agentpool_storage/zed_provider/helpers.py +281 -0
- agentpool_storage/zed_provider/models.py +130 -0
- agentpool_storage/zed_provider/provider.py +442 -0
- agentpool_storage/zed_provider.py +803 -0
- agentpool_toolsets/__init__.py +0 -2
- agentpool_toolsets/builtin/__init__.py +2 -4
- agentpool_toolsets/builtin/code.py +4 -4
- agentpool_toolsets/builtin/debug.py +115 -40
- agentpool_toolsets/builtin/execution_environment.py +54 -165
- agentpool_toolsets/builtin/skills.py +0 -77
- agentpool_toolsets/builtin/subagent_tools.py +64 -51
- agentpool_toolsets/builtin/workers.py +4 -2
- agentpool_toolsets/composio_toolset.py +2 -2
- agentpool_toolsets/entry_points.py +3 -1
- agentpool_toolsets/fsspec_toolset/grep.py +25 -5
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/toolset.py +350 -66
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +74 -17
- agentpool_toolsets/mcp_run_toolset.py +8 -11
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- agentpool_config/resources.py +0 -33
- agentpool_server/acp_server/acp_tools.py +0 -43
- agentpool_server/acp_server/commands/spawn.py +0 -210
- agentpool_storage/opencode_provider.py +0 -730
- agentpool_storage/text_log_provider.py +0 -276
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""AgentCli tool implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from io import StringIO
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from agentpool.agents.context import AgentContext # noqa: TC001
|
|
10
|
+
from agentpool.tools.base import Tool
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from collections.abc import Awaitable, Callable
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _StringOutputWriter:
|
|
18
|
+
"""Output writer that captures output to a string buffer."""
|
|
19
|
+
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
self._buffer = StringIO()
|
|
22
|
+
|
|
23
|
+
async def print(self, message: str) -> None:
|
|
24
|
+
"""Write a message to the buffer."""
|
|
25
|
+
self._buffer.write(message)
|
|
26
|
+
self._buffer.write("\n")
|
|
27
|
+
|
|
28
|
+
def getvalue(self) -> str:
|
|
29
|
+
"""Get the captured output."""
|
|
30
|
+
return self._buffer.getvalue()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class AgentCliTool(Tool[str]):
|
|
35
|
+
"""Tool for executing internal agent management commands.
|
|
36
|
+
|
|
37
|
+
Provides access to the agent's internal CLI for management operations
|
|
38
|
+
like creating agents, managing tools, connecting nodes, etc.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def get_callable(self) -> Callable[..., Awaitable[str]]:
|
|
42
|
+
"""Get the tool callable."""
|
|
43
|
+
return self._execute
|
|
44
|
+
|
|
45
|
+
async def _execute(self, ctx: AgentContext, command: str) -> str:
|
|
46
|
+
"""Execute an internal agent management command.
|
|
47
|
+
|
|
48
|
+
This provides access to the agent's internal CLI for management operations.
|
|
49
|
+
|
|
50
|
+
IMPORTANT: Before using any command for the first time, call "help <command>"
|
|
51
|
+
to learn the correct syntax and available options.
|
|
52
|
+
|
|
53
|
+
Discovery commands:
|
|
54
|
+
- "help" - list all available commands
|
|
55
|
+
- "help <command>" - get detailed usage for a specific command
|
|
56
|
+
|
|
57
|
+
Command categories:
|
|
58
|
+
- Agent/team management: create-agent, create-team, list-agents
|
|
59
|
+
- Tool management: list-tools, register-tool, enable-tool, disable-tool
|
|
60
|
+
- MCP servers: add-mcp-server, add-remote-mcp-server, list-mcp-servers
|
|
61
|
+
- Connections: connect, disconnect, connections
|
|
62
|
+
- Workers: add-worker, remove-worker, list-workers
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
ctx: Agent execution context.
|
|
66
|
+
command: The command to execute. Leading slash is optional.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Command output or error message.
|
|
70
|
+
"""
|
|
71
|
+
from slashed import CommandContext
|
|
72
|
+
|
|
73
|
+
if not ctx.agent.command_store:
|
|
74
|
+
return "No command store available"
|
|
75
|
+
|
|
76
|
+
# Remove leading slash if present
|
|
77
|
+
cmd = command.lstrip("/")
|
|
78
|
+
|
|
79
|
+
# Create output capture
|
|
80
|
+
output = _StringOutputWriter()
|
|
81
|
+
|
|
82
|
+
# Create CommandContext with output capture and AgentContext as data
|
|
83
|
+
cmd_ctx = CommandContext(
|
|
84
|
+
output=output,
|
|
85
|
+
data=ctx,
|
|
86
|
+
command_store=ctx.agent.command_store,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
await ctx.agent.command_store.execute_command(cmd, cmd_ctx)
|
|
91
|
+
result = output.getvalue()
|
|
92
|
+
except Exception as e: # noqa: BLE001
|
|
93
|
+
return f"Command failed: {e}"
|
|
94
|
+
else:
|
|
95
|
+
return result if result else "Command executed successfully."
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Bash command execution tool."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
|
|
7
|
+
from agentpool.tool_impls.bash.tool import BashTool
|
|
8
|
+
from agentpool_config.tools import ToolHints
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from exxec import ExecutionEnvironment
|
|
13
|
+
|
|
14
|
+
__all__ = ["BashTool", "create_bash_tool"]
|
|
15
|
+
|
|
16
|
+
# Tool metadata defaults
|
|
17
|
+
NAME = "bash"
|
|
18
|
+
DESCRIPTION = "Execute a shell command and return the output."
|
|
19
|
+
CATEGORY: Literal["execute"] = "execute"
|
|
20
|
+
HINTS = ToolHints(open_world=True)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_bash_tool(
|
|
24
|
+
*,
|
|
25
|
+
env: ExecutionEnvironment | None = None,
|
|
26
|
+
output_limit: int | None = None,
|
|
27
|
+
timeout: float | None = None,
|
|
28
|
+
name: str = NAME,
|
|
29
|
+
description: str = DESCRIPTION,
|
|
30
|
+
requires_confirmation: bool = False,
|
|
31
|
+
) -> BashTool:
|
|
32
|
+
"""Create a configured BashTool instance.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
env: Execution environment to use. Falls back to agent.env if not set.
|
|
36
|
+
output_limit: Default maximum bytes of output to return.
|
|
37
|
+
timeout: Default command timeout in seconds. None means no timeout.
|
|
38
|
+
name: Tool name override.
|
|
39
|
+
description: Tool description override.
|
|
40
|
+
requires_confirmation: Whether tool execution needs confirmation.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Configured BashTool instance.
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
# Basic
|
|
47
|
+
bash = create_bash_tool()
|
|
48
|
+
|
|
49
|
+
# With limits
|
|
50
|
+
bash = create_bash_tool(output_limit=10000, timeout=30.0)
|
|
51
|
+
|
|
52
|
+
# Require confirmation for dangerous commands
|
|
53
|
+
bash = create_bash_tool(requires_confirmation=True)
|
|
54
|
+
"""
|
|
55
|
+
return BashTool(
|
|
56
|
+
name=name,
|
|
57
|
+
description=description,
|
|
58
|
+
category=CATEGORY,
|
|
59
|
+
hints=HINTS,
|
|
60
|
+
env=env,
|
|
61
|
+
output_limit=output_limit,
|
|
62
|
+
timeout=timeout,
|
|
63
|
+
requires_confirmation=requires_confirmation,
|
|
64
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Helper functions for bash tool output formatting."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def truncate_output(output: str, limit: int) -> tuple[str, bool]:
|
|
7
|
+
"""Truncate output to limit bytes, returning (output, was_truncated)."""
|
|
8
|
+
if len(output.encode()) <= limit:
|
|
9
|
+
return output, False
|
|
10
|
+
truncated = output.encode()[-limit:].decode(errors="ignore")
|
|
11
|
+
return f"...[truncated]\n{truncated}", True
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def format_output(
|
|
15
|
+
stdout: str,
|
|
16
|
+
stderr: str,
|
|
17
|
+
exit_code: int | None,
|
|
18
|
+
truncated: bool,
|
|
19
|
+
) -> str:
|
|
20
|
+
"""Format the final output string."""
|
|
21
|
+
# Combine stdout and stderr
|
|
22
|
+
output = stdout
|
|
23
|
+
if stderr:
|
|
24
|
+
output = f"{stdout}\n\nSTDERR:\n{stderr}" if stdout else f"STDERR:\n{stderr}"
|
|
25
|
+
|
|
26
|
+
# Add metadata only when relevant
|
|
27
|
+
suffix_parts = []
|
|
28
|
+
if truncated:
|
|
29
|
+
suffix_parts.append("[output truncated]")
|
|
30
|
+
if exit_code and exit_code != 0:
|
|
31
|
+
suffix_parts.append(f"Exit code: {exit_code}")
|
|
32
|
+
|
|
33
|
+
if suffix_parts:
|
|
34
|
+
return f"{output}\n\n{' | '.join(suffix_parts)}"
|
|
35
|
+
return output
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Bash command execution tool."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
import re
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
import uuid
|
|
9
|
+
|
|
10
|
+
from exxec.events import (
|
|
11
|
+
OutputEvent,
|
|
12
|
+
ProcessCompletedEvent,
|
|
13
|
+
ProcessErrorEvent,
|
|
14
|
+
ProcessStartedEvent,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from agentpool.agents.context import AgentContext # noqa: TC001
|
|
18
|
+
from agentpool.log import get_logger
|
|
19
|
+
from agentpool.tool_impls.bash.helpers import format_output, truncate_output
|
|
20
|
+
from agentpool.tools.base import Tool, ToolResult
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from collections.abc import Awaitable, Callable
|
|
25
|
+
|
|
26
|
+
from exxec import ExecutionEnvironment
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
logger = get_logger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class BashTool(Tool[ToolResult]):
|
|
34
|
+
"""Execute shell commands and return the output.
|
|
35
|
+
|
|
36
|
+
A standalone, configurable tool for running bash commands. Can be configured
|
|
37
|
+
with a specific execution environment and output limits.
|
|
38
|
+
|
|
39
|
+
Use create_bash_tool() factory for convenient instantiation with defaults.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
# Tool-specific configuration
|
|
43
|
+
env: ExecutionEnvironment | None = None
|
|
44
|
+
"""Execution environment to use. Falls back to agent.env if not set."""
|
|
45
|
+
|
|
46
|
+
output_limit: int | None = None
|
|
47
|
+
"""Default maximum bytes of output to return."""
|
|
48
|
+
|
|
49
|
+
timeout: float | None = None
|
|
50
|
+
"""Default command timeout in seconds. None means no timeout."""
|
|
51
|
+
|
|
52
|
+
def get_callable(self) -> Callable[..., Awaitable[ToolResult]]:
|
|
53
|
+
"""Return the execute method as the callable."""
|
|
54
|
+
return self._execute
|
|
55
|
+
|
|
56
|
+
def _get_env(self, ctx: AgentContext) -> ExecutionEnvironment:
|
|
57
|
+
"""Get execution environment, falling back to agent's env."""
|
|
58
|
+
if self.env is not None:
|
|
59
|
+
return self.env
|
|
60
|
+
return ctx.agent.env
|
|
61
|
+
|
|
62
|
+
async def _execute( # noqa: PLR0915
|
|
63
|
+
self,
|
|
64
|
+
ctx: AgentContext,
|
|
65
|
+
command: str,
|
|
66
|
+
output_limit: int | None = None,
|
|
67
|
+
timeout: float | None = None,
|
|
68
|
+
filter_lines: str | None = None,
|
|
69
|
+
) -> ToolResult:
|
|
70
|
+
"""Execute a shell command and return the output.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
ctx: Agent context for event emission and environment access
|
|
74
|
+
command: Shell command to execute
|
|
75
|
+
output_limit: Maximum bytes of output to return (overrides default)
|
|
76
|
+
timeout: Command timeout in seconds (overrides default)
|
|
77
|
+
filter_lines: Optional regex pattern to filter output lines
|
|
78
|
+
(only matching lines returned)
|
|
79
|
+
"""
|
|
80
|
+
effective_limit = output_limit or self.output_limit
|
|
81
|
+
effective_timeout = timeout if timeout is not None else self.timeout
|
|
82
|
+
process_id: str | None = None
|
|
83
|
+
stdout_parts: list[str] = []
|
|
84
|
+
stderr_parts: list[str] = []
|
|
85
|
+
exit_code: int | None = None
|
|
86
|
+
error_msg: str | None = None
|
|
87
|
+
env = self._get_env(ctx)
|
|
88
|
+
|
|
89
|
+
# Check if we're running in ACP - terminal streams client-side
|
|
90
|
+
from exxec.acp_provider import ACPExecutionEnvironment
|
|
91
|
+
|
|
92
|
+
is_acp = isinstance(env, ACPExecutionEnvironment)
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
async for event in env.stream_command(command, timeout=effective_timeout):
|
|
96
|
+
match event:
|
|
97
|
+
case ProcessStartedEvent(process_id=pid, command=cmd):
|
|
98
|
+
process_id = pid
|
|
99
|
+
await ctx.events.process_started(pid, cmd, success=True)
|
|
100
|
+
case OutputEvent(process_id=pid, data=data, stream=stream):
|
|
101
|
+
if stream == "stderr":
|
|
102
|
+
stderr_parts.append(data)
|
|
103
|
+
else:
|
|
104
|
+
stdout_parts.append(data)
|
|
105
|
+
# Skip progress events for ACP - terminal streams client-side
|
|
106
|
+
if not is_acp:
|
|
107
|
+
await ctx.events.process_output(pid, data)
|
|
108
|
+
case ProcessCompletedEvent(process_id=pid, exit_code=code_):
|
|
109
|
+
exit_code = code_
|
|
110
|
+
# Skip exit event for ACP - completion handled by FunctionToolResultEvent
|
|
111
|
+
if not is_acp:
|
|
112
|
+
combined = "".join(stdout_parts) + "".join(stderr_parts)
|
|
113
|
+
await ctx.events.process_exit(pid, exit_code, final_output=combined)
|
|
114
|
+
case ProcessErrorEvent(error=err, exit_code=code_):
|
|
115
|
+
error_msg = err
|
|
116
|
+
exit_code = code_
|
|
117
|
+
|
|
118
|
+
stdout = "".join(stdout_parts)
|
|
119
|
+
stderr = "".join(stderr_parts)
|
|
120
|
+
|
|
121
|
+
# Apply regex filter if specified
|
|
122
|
+
if filter_lines:
|
|
123
|
+
try:
|
|
124
|
+
pattern = re.compile(filter_lines)
|
|
125
|
+
stdout_lines = [
|
|
126
|
+
line for line in stdout.splitlines(keepends=True) if pattern.search(line)
|
|
127
|
+
]
|
|
128
|
+
stderr_lines = [
|
|
129
|
+
line for line in stderr.splitlines(keepends=True) if pattern.search(line)
|
|
130
|
+
]
|
|
131
|
+
stdout = "".join(stdout_lines)
|
|
132
|
+
stderr = "".join(stderr_lines)
|
|
133
|
+
except re.error as regex_err:
|
|
134
|
+
error_msg = f"Invalid filter regex: {regex_err}"
|
|
135
|
+
return ToolResult(
|
|
136
|
+
content=error_msg,
|
|
137
|
+
metadata={"output": "", "exit": None, "description": command},
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Apply output limit if specified
|
|
141
|
+
truncated = False
|
|
142
|
+
if effective_limit:
|
|
143
|
+
stdout, stdout_truncated = truncate_output(stdout, effective_limit)
|
|
144
|
+
stderr, stderr_truncated = truncate_output(stderr, effective_limit)
|
|
145
|
+
truncated = stdout_truncated or stderr_truncated
|
|
146
|
+
|
|
147
|
+
# Format error response
|
|
148
|
+
if error_msg:
|
|
149
|
+
output = stdout + stderr if stdout or stderr else ""
|
|
150
|
+
result_output = f"{output}\n\nError: {error_msg}\nExit code: {exit_code}"
|
|
151
|
+
return ToolResult(
|
|
152
|
+
content=result_output,
|
|
153
|
+
metadata={"output": output, "exit": exit_code, "description": command},
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
except Exception as e: # noqa: BLE001
|
|
157
|
+
error_id = process_id or f"cmd_{uuid.uuid4().hex[:8]}"
|
|
158
|
+
await ctx.events.process_started(error_id, command, success=False, error=str(e))
|
|
159
|
+
error_msg = f"Error executing command: {e}"
|
|
160
|
+
return ToolResult(
|
|
161
|
+
content=error_msg,
|
|
162
|
+
metadata={"output": "", "exit": None, "description": command},
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Format success response
|
|
166
|
+
formatted_output = format_output(stdout, stderr, exit_code, truncated)
|
|
167
|
+
combined_output = stdout + stderr
|
|
168
|
+
return ToolResult(
|
|
169
|
+
content=formatted_output,
|
|
170
|
+
metadata={"output": combined_output, "exit": exit_code, "description": command},
|
|
171
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Delete path tool."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
|
|
7
|
+
from agentpool.tool_impls.delete_path.tool import DeletePathTool
|
|
8
|
+
from agentpool_config.tools import ToolHints
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from exxec import ExecutionEnvironment
|
|
13
|
+
|
|
14
|
+
__all__ = ["DeletePathTool", "create_delete_path_tool"]
|
|
15
|
+
|
|
16
|
+
# Tool metadata defaults
|
|
17
|
+
NAME = "delete_path"
|
|
18
|
+
DESCRIPTION = """Delete a file or directory from the filesystem.
|
|
19
|
+
|
|
20
|
+
Supports:
|
|
21
|
+
- File deletion
|
|
22
|
+
- Directory deletion with safety checks
|
|
23
|
+
- Recursive directory deletion
|
|
24
|
+
- Empty directory validation"""
|
|
25
|
+
CATEGORY: Literal["delete"] = "delete"
|
|
26
|
+
HINTS = ToolHints(destructive=True)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def create_delete_path_tool(
|
|
30
|
+
*,
|
|
31
|
+
env: ExecutionEnvironment | None = None,
|
|
32
|
+
cwd: str | None = None,
|
|
33
|
+
name: str = NAME,
|
|
34
|
+
description: str = DESCRIPTION,
|
|
35
|
+
requires_confirmation: bool = False,
|
|
36
|
+
) -> DeletePathTool:
|
|
37
|
+
"""Create a configured DeletePathTool instance.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
env: Execution environment to use. Falls back to agent.env if not set.
|
|
41
|
+
cwd: Working directory for resolving relative paths.
|
|
42
|
+
name: Tool name override.
|
|
43
|
+
description: Tool description override.
|
|
44
|
+
requires_confirmation: Whether tool execution needs confirmation.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Configured DeletePathTool instance.
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
# Basic usage
|
|
51
|
+
delete = create_delete_path_tool()
|
|
52
|
+
|
|
53
|
+
# With confirmation required for safety
|
|
54
|
+
delete = create_delete_path_tool(requires_confirmation=True)
|
|
55
|
+
|
|
56
|
+
# With specific environment
|
|
57
|
+
delete = create_delete_path_tool(
|
|
58
|
+
env=my_env,
|
|
59
|
+
cwd="/workspace",
|
|
60
|
+
)
|
|
61
|
+
"""
|
|
62
|
+
return DeletePathTool(
|
|
63
|
+
name=name,
|
|
64
|
+
description=description,
|
|
65
|
+
category=CATEGORY,
|
|
66
|
+
hints=HINTS,
|
|
67
|
+
env=env,
|
|
68
|
+
cwd=cwd,
|
|
69
|
+
requires_confirmation=requires_confirmation,
|
|
70
|
+
)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Delete path tool implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
from agentpool.agents.context import AgentContext # noqa: TC001
|
|
10
|
+
from agentpool.log import get_logger
|
|
11
|
+
from agentpool.tools.base import Tool
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Awaitable, Callable
|
|
16
|
+
|
|
17
|
+
from exxec import ExecutionEnvironment
|
|
18
|
+
from fsspec.asyn import AsyncFileSystem
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class DeletePathTool(Tool[dict[str, Any]]):
|
|
26
|
+
"""Delete files or directories from the filesystem.
|
|
27
|
+
|
|
28
|
+
A standalone tool for deleting paths with:
|
|
29
|
+
- File and directory deletion
|
|
30
|
+
- Recursive directory deletion with safety checks
|
|
31
|
+
- Empty directory validation
|
|
32
|
+
- Detailed operation results
|
|
33
|
+
|
|
34
|
+
Use create_delete_path_tool() factory for convenient instantiation.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Tool-specific configuration
|
|
38
|
+
env: ExecutionEnvironment | None = None
|
|
39
|
+
"""Execution environment to use. Falls back to agent.env if not set."""
|
|
40
|
+
|
|
41
|
+
cwd: str | None = None
|
|
42
|
+
"""Working directory for resolving relative paths."""
|
|
43
|
+
|
|
44
|
+
def get_callable(self) -> Callable[..., Awaitable[dict[str, Any]]]:
|
|
45
|
+
"""Return the delete_path method as the callable."""
|
|
46
|
+
return self._delete_path
|
|
47
|
+
|
|
48
|
+
def _get_fs(self, ctx: AgentContext) -> AsyncFileSystem:
|
|
49
|
+
"""Get filesystem from env, falling back to agent's env if not set."""
|
|
50
|
+
from fsspec.asyn import AsyncFileSystem
|
|
51
|
+
from fsspec.implementations.asyn_wrapper import AsyncFileSystemWrapper
|
|
52
|
+
|
|
53
|
+
if self.env is not None:
|
|
54
|
+
fs = self.env.get_fs()
|
|
55
|
+
return fs if isinstance(fs, AsyncFileSystem) else AsyncFileSystemWrapper(fs)
|
|
56
|
+
fs = ctx.agent.env.get_fs()
|
|
57
|
+
return fs if isinstance(fs, AsyncFileSystem) else AsyncFileSystemWrapper(fs)
|
|
58
|
+
|
|
59
|
+
def _resolve_path(self, path: str, ctx: AgentContext) -> str:
|
|
60
|
+
"""Resolve a potentially relative path to an absolute path."""
|
|
61
|
+
cwd: str | None = None
|
|
62
|
+
if self.cwd:
|
|
63
|
+
cwd = self.cwd
|
|
64
|
+
elif self.env and self.env.cwd:
|
|
65
|
+
cwd = self.env.cwd
|
|
66
|
+
elif ctx.agent.env and ctx.agent.env.cwd:
|
|
67
|
+
cwd = ctx.agent.env.cwd
|
|
68
|
+
|
|
69
|
+
if cwd and not (path.startswith("/") or (len(path) > 1 and path[1] == ":")):
|
|
70
|
+
return str(Path(cwd) / path)
|
|
71
|
+
return path
|
|
72
|
+
|
|
73
|
+
async def _delete_path(
|
|
74
|
+
self,
|
|
75
|
+
ctx: AgentContext,
|
|
76
|
+
path: str,
|
|
77
|
+
recursive: bool = False,
|
|
78
|
+
) -> dict[str, Any]:
|
|
79
|
+
"""Delete a file or directory.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
ctx: Agent context for event emission and filesystem access
|
|
83
|
+
path: Path to delete
|
|
84
|
+
recursive: Whether to delete directories recursively
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Dictionary with operation result
|
|
88
|
+
"""
|
|
89
|
+
path = self._resolve_path(path, ctx)
|
|
90
|
+
msg = f"Deleting path: {path}"
|
|
91
|
+
await ctx.events.tool_call_start(title=msg, kind="delete", locations=[path])
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
# Check if path exists and get its type
|
|
95
|
+
fs = self._get_fs(ctx)
|
|
96
|
+
try:
|
|
97
|
+
info = await fs._info(path)
|
|
98
|
+
path_type = info.get("type", "unknown")
|
|
99
|
+
except FileNotFoundError:
|
|
100
|
+
msg = f"Path does not exist: {path}"
|
|
101
|
+
await ctx.events.file_operation("delete", path=path, success=False, error=msg)
|
|
102
|
+
return {"error": msg}
|
|
103
|
+
except (OSError, ValueError) as e:
|
|
104
|
+
msg = f"Could not check path {path}: {e}"
|
|
105
|
+
await ctx.events.file_operation("delete", path=path, success=False, error=msg)
|
|
106
|
+
return {"error": msg}
|
|
107
|
+
|
|
108
|
+
if path_type == "directory":
|
|
109
|
+
if not recursive:
|
|
110
|
+
try:
|
|
111
|
+
contents = await fs._ls(path)
|
|
112
|
+
if contents: # Check if directory is empty
|
|
113
|
+
error_msg = (
|
|
114
|
+
f"Directory {path} is not empty. "
|
|
115
|
+
f"Use recursive=True to delete non-empty directories"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Emit failure event
|
|
119
|
+
await ctx.events.file_operation(
|
|
120
|
+
"delete", path=path, success=False, error=error_msg
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return {"error": error_msg}
|
|
124
|
+
except (OSError, ValueError):
|
|
125
|
+
pass # Continue with deletion attempt
|
|
126
|
+
|
|
127
|
+
await fs._rm(path, recursive=recursive)
|
|
128
|
+
else: # It's a file
|
|
129
|
+
await fs._rm(path)
|
|
130
|
+
|
|
131
|
+
except Exception as e: # noqa: BLE001
|
|
132
|
+
await ctx.events.file_operation("delete", path=path, success=False, error=str(e))
|
|
133
|
+
return {"error": f"Failed to delete {path}: {e}"}
|
|
134
|
+
else:
|
|
135
|
+
result = {
|
|
136
|
+
"path": path,
|
|
137
|
+
"deleted": True,
|
|
138
|
+
"type": path_type,
|
|
139
|
+
"recursive": recursive,
|
|
140
|
+
}
|
|
141
|
+
await ctx.events.file_operation("delete", path=path, success=True)
|
|
142
|
+
return result
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Download file tool."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
|
|
7
|
+
from agentpool.tool_impls.download_file.tool import DownloadFileTool
|
|
8
|
+
from agentpool_config.tools import ToolHints
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from exxec import ExecutionEnvironment
|
|
13
|
+
|
|
14
|
+
__all__ = ["DownloadFileTool", "create_download_file_tool"]
|
|
15
|
+
|
|
16
|
+
# Tool metadata defaults
|
|
17
|
+
NAME = "download_file"
|
|
18
|
+
DESCRIPTION = """Download a file from a URL to the filesystem.
|
|
19
|
+
|
|
20
|
+
Supports:
|
|
21
|
+
- HTTP/HTTPS downloads
|
|
22
|
+
- Progress tracking
|
|
23
|
+
- Speed monitoring
|
|
24
|
+
- Automatic directory creation
|
|
25
|
+
- Configurable chunk size and timeout"""
|
|
26
|
+
CATEGORY: Literal["read"] = "read"
|
|
27
|
+
HINTS = ToolHints(open_world=True)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def create_download_file_tool(
|
|
31
|
+
*,
|
|
32
|
+
env: ExecutionEnvironment | None = None,
|
|
33
|
+
cwd: str | None = None,
|
|
34
|
+
chunk_size: int = 8192,
|
|
35
|
+
timeout: float = 30.0,
|
|
36
|
+
name: str = NAME,
|
|
37
|
+
description: str = DESCRIPTION,
|
|
38
|
+
requires_confirmation: bool = False,
|
|
39
|
+
) -> DownloadFileTool:
|
|
40
|
+
"""Create a configured DownloadFileTool instance.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
env: Execution environment to use. Falls back to agent.env if not set.
|
|
44
|
+
cwd: Working directory for resolving relative paths.
|
|
45
|
+
chunk_size: Size of chunks to download in bytes (default: 8192).
|
|
46
|
+
timeout: Request timeout in seconds (default: 30.0).
|
|
47
|
+
name: Tool name override.
|
|
48
|
+
description: Tool description override.
|
|
49
|
+
requires_confirmation: Whether tool execution needs confirmation.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Configured DownloadFileTool instance.
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
# Basic usage
|
|
56
|
+
download = create_download_file_tool()
|
|
57
|
+
|
|
58
|
+
# With custom settings
|
|
59
|
+
download = create_download_file_tool(
|
|
60
|
+
chunk_size=16384,
|
|
61
|
+
timeout=60.0,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# With specific environment
|
|
65
|
+
download = create_download_file_tool(
|
|
66
|
+
env=my_env,
|
|
67
|
+
cwd="/workspace/downloads",
|
|
68
|
+
)
|
|
69
|
+
"""
|
|
70
|
+
return DownloadFileTool(
|
|
71
|
+
name=name,
|
|
72
|
+
description=description,
|
|
73
|
+
category=CATEGORY,
|
|
74
|
+
hints=HINTS,
|
|
75
|
+
env=env,
|
|
76
|
+
cwd=cwd,
|
|
77
|
+
chunk_size=chunk_size,
|
|
78
|
+
timeout=timeout,
|
|
79
|
+
requires_confirmation=requires_confirmation,
|
|
80
|
+
)
|