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.
Files changed (250) hide show
  1. acp/__init__.py +0 -4
  2. acp/acp_requests.py +20 -77
  3. acp/agent/connection.py +8 -0
  4. acp/agent/implementations/debug_server/debug_server.py +6 -2
  5. acp/agent/protocol.py +6 -0
  6. acp/client/connection.py +38 -29
  7. acp/client/implementations/default_client.py +3 -2
  8. acp/client/implementations/headless_client.py +2 -2
  9. acp/connection.py +2 -2
  10. acp/notifications.py +18 -49
  11. acp/schema/__init__.py +2 -0
  12. acp/schema/agent_responses.py +21 -0
  13. acp/schema/client_requests.py +3 -3
  14. acp/schema/session_state.py +63 -29
  15. acp/task/supervisor.py +2 -2
  16. acp/utils.py +2 -2
  17. agentpool/__init__.py +2 -0
  18. agentpool/agents/acp_agent/acp_agent.py +278 -263
  19. agentpool/agents/acp_agent/acp_converters.py +150 -17
  20. agentpool/agents/acp_agent/client_handler.py +35 -24
  21. agentpool/agents/acp_agent/session_state.py +14 -6
  22. agentpool/agents/agent.py +471 -643
  23. agentpool/agents/agui_agent/agui_agent.py +104 -107
  24. agentpool/agents/agui_agent/helpers.py +3 -4
  25. agentpool/agents/base_agent.py +485 -32
  26. agentpool/agents/claude_code_agent/FORKING.md +191 -0
  27. agentpool/agents/claude_code_agent/__init__.py +13 -1
  28. agentpool/agents/claude_code_agent/claude_code_agent.py +654 -334
  29. agentpool/agents/claude_code_agent/converters.py +4 -141
  30. agentpool/agents/claude_code_agent/models.py +77 -0
  31. agentpool/agents/claude_code_agent/static_info.py +100 -0
  32. agentpool/agents/claude_code_agent/usage.py +242 -0
  33. agentpool/agents/events/__init__.py +22 -0
  34. agentpool/agents/events/builtin_handlers.py +65 -0
  35. agentpool/agents/events/event_emitter.py +3 -0
  36. agentpool/agents/events/events.py +84 -3
  37. agentpool/agents/events/infer_info.py +145 -0
  38. agentpool/agents/events/processors.py +254 -0
  39. agentpool/agents/interactions.py +41 -6
  40. agentpool/agents/modes.py +13 -0
  41. agentpool/agents/slashed_agent.py +5 -4
  42. agentpool/agents/tool_wrapping.py +18 -6
  43. agentpool/common_types.py +35 -21
  44. agentpool/config_resources/acp_assistant.yml +2 -2
  45. agentpool/config_resources/agents.yml +3 -0
  46. agentpool/config_resources/agents_template.yml +1 -0
  47. agentpool/config_resources/claude_code_agent.yml +9 -8
  48. agentpool/config_resources/external_acp_agents.yml +2 -1
  49. agentpool/delegation/base_team.py +4 -30
  50. agentpool/delegation/pool.py +104 -265
  51. agentpool/delegation/team.py +57 -57
  52. agentpool/delegation/teamrun.py +50 -55
  53. agentpool/functional/run.py +10 -4
  54. agentpool/mcp_server/client.py +73 -38
  55. agentpool/mcp_server/conversions.py +54 -13
  56. agentpool/mcp_server/manager.py +9 -23
  57. agentpool/mcp_server/registries/official_registry_client.py +10 -1
  58. agentpool/mcp_server/tool_bridge.py +114 -79
  59. agentpool/messaging/connection_manager.py +11 -10
  60. agentpool/messaging/event_manager.py +5 -5
  61. agentpool/messaging/message_container.py +6 -30
  62. agentpool/messaging/message_history.py +87 -8
  63. agentpool/messaging/messagenode.py +52 -14
  64. agentpool/messaging/messages.py +2 -26
  65. agentpool/messaging/processing.py +10 -22
  66. agentpool/models/__init__.py +1 -1
  67. agentpool/models/acp_agents/base.py +6 -2
  68. agentpool/models/acp_agents/mcp_capable.py +124 -15
  69. agentpool/models/acp_agents/non_mcp.py +0 -23
  70. agentpool/models/agents.py +66 -66
  71. agentpool/models/agui_agents.py +1 -1
  72. agentpool/models/claude_code_agents.py +111 -17
  73. agentpool/models/file_parsing.py +0 -1
  74. agentpool/models/manifest.py +70 -50
  75. agentpool/prompts/conversion_manager.py +1 -1
  76. agentpool/prompts/prompts.py +5 -2
  77. agentpool/resource_providers/__init__.py +2 -0
  78. agentpool/resource_providers/aggregating.py +4 -2
  79. agentpool/resource_providers/base.py +13 -3
  80. agentpool/resource_providers/codemode/code_executor.py +72 -5
  81. agentpool/resource_providers/codemode/helpers.py +2 -2
  82. agentpool/resource_providers/codemode/provider.py +64 -12
  83. agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
  84. agentpool/resource_providers/codemode/remote_provider.py +9 -12
  85. agentpool/resource_providers/filtering.py +3 -1
  86. agentpool/resource_providers/mcp_provider.py +66 -12
  87. agentpool/resource_providers/plan_provider.py +111 -18
  88. agentpool/resource_providers/pool.py +5 -3
  89. agentpool/resource_providers/resource_info.py +111 -0
  90. agentpool/resource_providers/static.py +2 -2
  91. agentpool/sessions/__init__.py +2 -0
  92. agentpool/sessions/manager.py +2 -3
  93. agentpool/sessions/models.py +9 -6
  94. agentpool/sessions/protocol.py +28 -0
  95. agentpool/sessions/session.py +11 -55
  96. agentpool/storage/manager.py +361 -54
  97. agentpool/talk/registry.py +4 -4
  98. agentpool/talk/talk.py +9 -10
  99. agentpool/testing.py +1 -1
  100. agentpool/tool_impls/__init__.py +6 -0
  101. agentpool/tool_impls/agent_cli/__init__.py +42 -0
  102. agentpool/tool_impls/agent_cli/tool.py +95 -0
  103. agentpool/tool_impls/bash/__init__.py +64 -0
  104. agentpool/tool_impls/bash/helpers.py +35 -0
  105. agentpool/tool_impls/bash/tool.py +171 -0
  106. agentpool/tool_impls/delete_path/__init__.py +70 -0
  107. agentpool/tool_impls/delete_path/tool.py +142 -0
  108. agentpool/tool_impls/download_file/__init__.py +80 -0
  109. agentpool/tool_impls/download_file/tool.py +183 -0
  110. agentpool/tool_impls/execute_code/__init__.py +55 -0
  111. agentpool/tool_impls/execute_code/tool.py +163 -0
  112. agentpool/tool_impls/grep/__init__.py +80 -0
  113. agentpool/tool_impls/grep/tool.py +200 -0
  114. agentpool/tool_impls/list_directory/__init__.py +73 -0
  115. agentpool/tool_impls/list_directory/tool.py +197 -0
  116. agentpool/tool_impls/question/__init__.py +42 -0
  117. agentpool/tool_impls/question/tool.py +127 -0
  118. agentpool/tool_impls/read/__init__.py +104 -0
  119. agentpool/tool_impls/read/tool.py +305 -0
  120. agentpool/tools/__init__.py +2 -1
  121. agentpool/tools/base.py +114 -34
  122. agentpool/tools/manager.py +57 -1
  123. agentpool/ui/base.py +2 -2
  124. agentpool/ui/mock_provider.py +2 -2
  125. agentpool/ui/stdlib_provider.py +2 -2
  126. agentpool/utils/streams.py +21 -96
  127. agentpool/vfs_registry.py +7 -2
  128. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/METADATA +16 -22
  129. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/RECORD +242 -195
  130. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
  131. agentpool_cli/__main__.py +20 -0
  132. agentpool_cli/create.py +1 -1
  133. agentpool_cli/serve_acp.py +59 -1
  134. agentpool_cli/serve_opencode.py +1 -1
  135. agentpool_cli/ui.py +557 -0
  136. agentpool_commands/__init__.py +12 -5
  137. agentpool_commands/agents.py +1 -1
  138. agentpool_commands/pool.py +260 -0
  139. agentpool_commands/session.py +1 -1
  140. agentpool_commands/text_sharing/__init__.py +119 -0
  141. agentpool_commands/text_sharing/base.py +123 -0
  142. agentpool_commands/text_sharing/github_gist.py +80 -0
  143. agentpool_commands/text_sharing/opencode.py +462 -0
  144. agentpool_commands/text_sharing/paste_rs.py +59 -0
  145. agentpool_commands/text_sharing/pastebin.py +116 -0
  146. agentpool_commands/text_sharing/shittycodingagent.py +112 -0
  147. agentpool_commands/utils.py +31 -32
  148. agentpool_config/__init__.py +30 -2
  149. agentpool_config/agentpool_tools.py +498 -0
  150. agentpool_config/converters.py +1 -1
  151. agentpool_config/event_handlers.py +42 -0
  152. agentpool_config/events.py +1 -1
  153. agentpool_config/forward_targets.py +1 -4
  154. agentpool_config/jinja.py +3 -3
  155. agentpool_config/mcp_server.py +1 -5
  156. agentpool_config/nodes.py +1 -1
  157. agentpool_config/observability.py +44 -0
  158. agentpool_config/session.py +0 -3
  159. agentpool_config/storage.py +38 -39
  160. agentpool_config/task.py +3 -3
  161. agentpool_config/tools.py +11 -28
  162. agentpool_config/toolsets.py +22 -90
  163. agentpool_server/a2a_server/agent_worker.py +307 -0
  164. agentpool_server/a2a_server/server.py +23 -18
  165. agentpool_server/acp_server/acp_agent.py +125 -56
  166. agentpool_server/acp_server/commands/acp_commands.py +46 -216
  167. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +8 -7
  168. agentpool_server/acp_server/event_converter.py +651 -0
  169. agentpool_server/acp_server/input_provider.py +53 -10
  170. agentpool_server/acp_server/server.py +1 -11
  171. agentpool_server/acp_server/session.py +90 -410
  172. agentpool_server/acp_server/session_manager.py +8 -34
  173. agentpool_server/agui_server/server.py +3 -1
  174. agentpool_server/mcp_server/server.py +5 -2
  175. agentpool_server/opencode_server/ENDPOINTS.md +53 -14
  176. agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
  177. agentpool_server/opencode_server/__init__.py +0 -8
  178. agentpool_server/opencode_server/converters.py +132 -26
  179. agentpool_server/opencode_server/input_provider.py +160 -8
  180. agentpool_server/opencode_server/models/__init__.py +42 -20
  181. agentpool_server/opencode_server/models/app.py +12 -0
  182. agentpool_server/opencode_server/models/events.py +203 -29
  183. agentpool_server/opencode_server/models/mcp.py +19 -0
  184. agentpool_server/opencode_server/models/message.py +18 -1
  185. agentpool_server/opencode_server/models/parts.py +134 -1
  186. agentpool_server/opencode_server/models/question.py +56 -0
  187. agentpool_server/opencode_server/models/session.py +13 -1
  188. agentpool_server/opencode_server/routes/__init__.py +4 -0
  189. agentpool_server/opencode_server/routes/agent_routes.py +33 -2
  190. agentpool_server/opencode_server/routes/app_routes.py +66 -3
  191. agentpool_server/opencode_server/routes/config_routes.py +66 -5
  192. agentpool_server/opencode_server/routes/file_routes.py +184 -5
  193. agentpool_server/opencode_server/routes/global_routes.py +1 -1
  194. agentpool_server/opencode_server/routes/lsp_routes.py +1 -1
  195. agentpool_server/opencode_server/routes/message_routes.py +122 -66
  196. agentpool_server/opencode_server/routes/permission_routes.py +63 -0
  197. agentpool_server/opencode_server/routes/pty_routes.py +23 -22
  198. agentpool_server/opencode_server/routes/question_routes.py +128 -0
  199. agentpool_server/opencode_server/routes/session_routes.py +139 -68
  200. agentpool_server/opencode_server/routes/tui_routes.py +1 -1
  201. agentpool_server/opencode_server/server.py +47 -2
  202. agentpool_server/opencode_server/state.py +30 -0
  203. agentpool_storage/__init__.py +0 -4
  204. agentpool_storage/base.py +81 -2
  205. agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
  206. agentpool_storage/claude_provider/__init__.py +42 -0
  207. agentpool_storage/{claude_provider.py → claude_provider/provider.py} +190 -8
  208. agentpool_storage/file_provider.py +149 -15
  209. agentpool_storage/memory_provider.py +132 -12
  210. agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
  211. agentpool_storage/opencode_provider/__init__.py +16 -0
  212. agentpool_storage/opencode_provider/helpers.py +414 -0
  213. agentpool_storage/opencode_provider/provider.py +895 -0
  214. agentpool_storage/session_store.py +20 -6
  215. agentpool_storage/sql_provider/sql_provider.py +135 -2
  216. agentpool_storage/sql_provider/utils.py +2 -12
  217. agentpool_storage/zed_provider/__init__.py +16 -0
  218. agentpool_storage/zed_provider/helpers.py +281 -0
  219. agentpool_storage/zed_provider/models.py +130 -0
  220. agentpool_storage/zed_provider/provider.py +442 -0
  221. agentpool_storage/zed_provider.py +803 -0
  222. agentpool_toolsets/__init__.py +0 -2
  223. agentpool_toolsets/builtin/__init__.py +2 -4
  224. agentpool_toolsets/builtin/code.py +4 -4
  225. agentpool_toolsets/builtin/debug.py +115 -40
  226. agentpool_toolsets/builtin/execution_environment.py +54 -165
  227. agentpool_toolsets/builtin/skills.py +0 -77
  228. agentpool_toolsets/builtin/subagent_tools.py +64 -51
  229. agentpool_toolsets/builtin/workers.py +4 -2
  230. agentpool_toolsets/composio_toolset.py +2 -2
  231. agentpool_toolsets/entry_points.py +3 -1
  232. agentpool_toolsets/fsspec_toolset/grep.py +25 -5
  233. agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
  234. agentpool_toolsets/fsspec_toolset/toolset.py +350 -66
  235. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  236. agentpool_toolsets/mcp_discovery/toolset.py +74 -17
  237. agentpool_toolsets/mcp_run_toolset.py +8 -11
  238. agentpool_toolsets/notifications.py +33 -33
  239. agentpool_toolsets/openapi.py +3 -1
  240. agentpool_toolsets/search_toolset.py +3 -1
  241. agentpool_config/resources.py +0 -33
  242. agentpool_server/acp_server/acp_tools.py +0 -43
  243. agentpool_server/acp_server/commands/spawn.py +0 -210
  244. agentpool_storage/opencode_provider.py +0 -730
  245. agentpool_storage/text_log_provider.py +0 -276
  246. agentpool_toolsets/builtin/chain.py +0 -288
  247. agentpool_toolsets/builtin/user_interaction.py +0 -52
  248. agentpool_toolsets/semantic_memory_toolset.py +0 -536
  249. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
  250. {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
+ )