agentpool 2.1.9__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 (311) hide show
  1. acp/__init__.py +13 -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/bridge/README.md +15 -2
  7. acp/bridge/__init__.py +3 -2
  8. acp/bridge/__main__.py +60 -19
  9. acp/bridge/ws_server.py +173 -0
  10. acp/bridge/ws_server_cli.py +89 -0
  11. acp/client/connection.py +38 -29
  12. acp/client/implementations/default_client.py +3 -2
  13. acp/client/implementations/headless_client.py +2 -2
  14. acp/connection.py +2 -2
  15. acp/notifications.py +20 -50
  16. acp/schema/__init__.py +2 -0
  17. acp/schema/agent_responses.py +21 -0
  18. acp/schema/client_requests.py +3 -3
  19. acp/schema/session_state.py +63 -29
  20. acp/stdio.py +39 -9
  21. acp/task/supervisor.py +2 -2
  22. acp/transports.py +362 -2
  23. acp/utils.py +17 -4
  24. agentpool/__init__.py +6 -1
  25. agentpool/agents/__init__.py +2 -0
  26. agentpool/agents/acp_agent/acp_agent.py +407 -277
  27. agentpool/agents/acp_agent/acp_converters.py +196 -38
  28. agentpool/agents/acp_agent/client_handler.py +191 -26
  29. agentpool/agents/acp_agent/session_state.py +17 -6
  30. agentpool/agents/agent.py +607 -572
  31. agentpool/agents/agui_agent/__init__.py +0 -2
  32. agentpool/agents/agui_agent/agui_agent.py +176 -110
  33. agentpool/agents/agui_agent/agui_converters.py +0 -131
  34. agentpool/agents/agui_agent/helpers.py +3 -4
  35. agentpool/agents/base_agent.py +632 -17
  36. agentpool/agents/claude_code_agent/FORKING.md +191 -0
  37. agentpool/agents/claude_code_agent/__init__.py +13 -1
  38. agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
  39. agentpool/agents/claude_code_agent/converters.py +74 -143
  40. agentpool/agents/claude_code_agent/history.py +474 -0
  41. agentpool/agents/claude_code_agent/models.py +77 -0
  42. agentpool/agents/claude_code_agent/static_info.py +100 -0
  43. agentpool/agents/claude_code_agent/usage.py +242 -0
  44. agentpool/agents/context.py +40 -0
  45. agentpool/agents/events/__init__.py +24 -0
  46. agentpool/agents/events/builtin_handlers.py +67 -1
  47. agentpool/agents/events/event_emitter.py +32 -2
  48. agentpool/agents/events/events.py +104 -3
  49. agentpool/agents/events/infer_info.py +145 -0
  50. agentpool/agents/events/processors.py +254 -0
  51. agentpool/agents/interactions.py +41 -6
  52. agentpool/agents/modes.py +67 -0
  53. agentpool/agents/slashed_agent.py +5 -4
  54. agentpool/agents/tool_call_accumulator.py +213 -0
  55. agentpool/agents/tool_wrapping.py +18 -6
  56. agentpool/common_types.py +56 -21
  57. agentpool/config_resources/__init__.py +38 -1
  58. agentpool/config_resources/acp_assistant.yml +2 -2
  59. agentpool/config_resources/agents.yml +3 -0
  60. agentpool/config_resources/agents_template.yml +1 -0
  61. agentpool/config_resources/claude_code_agent.yml +10 -6
  62. agentpool/config_resources/external_acp_agents.yml +2 -1
  63. agentpool/delegation/base_team.py +4 -30
  64. agentpool/delegation/pool.py +136 -289
  65. agentpool/delegation/team.py +58 -57
  66. agentpool/delegation/teamrun.py +51 -55
  67. agentpool/diagnostics/__init__.py +53 -0
  68. agentpool/diagnostics/lsp_manager.py +1593 -0
  69. agentpool/diagnostics/lsp_proxy.py +41 -0
  70. agentpool/diagnostics/lsp_proxy_script.py +229 -0
  71. agentpool/diagnostics/models.py +398 -0
  72. agentpool/functional/run.py +10 -4
  73. agentpool/mcp_server/__init__.py +0 -2
  74. agentpool/mcp_server/client.py +76 -32
  75. agentpool/mcp_server/conversions.py +54 -13
  76. agentpool/mcp_server/manager.py +34 -54
  77. agentpool/mcp_server/registries/official_registry_client.py +35 -1
  78. agentpool/mcp_server/tool_bridge.py +186 -139
  79. agentpool/messaging/__init__.py +0 -2
  80. agentpool/messaging/compaction.py +72 -197
  81. agentpool/messaging/connection_manager.py +11 -10
  82. agentpool/messaging/event_manager.py +5 -5
  83. agentpool/messaging/message_container.py +6 -30
  84. agentpool/messaging/message_history.py +99 -8
  85. agentpool/messaging/messagenode.py +52 -14
  86. agentpool/messaging/messages.py +54 -35
  87. agentpool/messaging/processing.py +12 -22
  88. agentpool/models/__init__.py +1 -1
  89. agentpool/models/acp_agents/base.py +6 -24
  90. agentpool/models/acp_agents/mcp_capable.py +126 -157
  91. agentpool/models/acp_agents/non_mcp.py +129 -95
  92. agentpool/models/agents.py +98 -76
  93. agentpool/models/agui_agents.py +1 -1
  94. agentpool/models/claude_code_agents.py +144 -19
  95. agentpool/models/file_parsing.py +0 -1
  96. agentpool/models/manifest.py +113 -50
  97. agentpool/prompts/conversion_manager.py +1 -1
  98. agentpool/prompts/prompts.py +5 -2
  99. agentpool/repomap.py +1 -1
  100. agentpool/resource_providers/__init__.py +11 -1
  101. agentpool/resource_providers/aggregating.py +56 -5
  102. agentpool/resource_providers/base.py +70 -4
  103. agentpool/resource_providers/codemode/code_executor.py +72 -5
  104. agentpool/resource_providers/codemode/helpers.py +2 -2
  105. agentpool/resource_providers/codemode/provider.py +64 -12
  106. agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
  107. agentpool/resource_providers/codemode/remote_provider.py +9 -12
  108. agentpool/resource_providers/filtering.py +3 -1
  109. agentpool/resource_providers/mcp_provider.py +89 -12
  110. agentpool/resource_providers/plan_provider.py +228 -46
  111. agentpool/resource_providers/pool.py +7 -3
  112. agentpool/resource_providers/resource_info.py +111 -0
  113. agentpool/resource_providers/static.py +4 -2
  114. agentpool/sessions/__init__.py +4 -1
  115. agentpool/sessions/manager.py +33 -5
  116. agentpool/sessions/models.py +59 -6
  117. agentpool/sessions/protocol.py +28 -0
  118. agentpool/sessions/session.py +11 -55
  119. agentpool/skills/registry.py +13 -8
  120. agentpool/storage/manager.py +572 -49
  121. agentpool/talk/registry.py +4 -4
  122. agentpool/talk/talk.py +9 -10
  123. agentpool/testing.py +538 -20
  124. agentpool/tool_impls/__init__.py +6 -0
  125. agentpool/tool_impls/agent_cli/__init__.py +42 -0
  126. agentpool/tool_impls/agent_cli/tool.py +95 -0
  127. agentpool/tool_impls/bash/__init__.py +64 -0
  128. agentpool/tool_impls/bash/helpers.py +35 -0
  129. agentpool/tool_impls/bash/tool.py +171 -0
  130. agentpool/tool_impls/delete_path/__init__.py +70 -0
  131. agentpool/tool_impls/delete_path/tool.py +142 -0
  132. agentpool/tool_impls/download_file/__init__.py +80 -0
  133. agentpool/tool_impls/download_file/tool.py +183 -0
  134. agentpool/tool_impls/execute_code/__init__.py +55 -0
  135. agentpool/tool_impls/execute_code/tool.py +163 -0
  136. agentpool/tool_impls/grep/__init__.py +80 -0
  137. agentpool/tool_impls/grep/tool.py +200 -0
  138. agentpool/tool_impls/list_directory/__init__.py +73 -0
  139. agentpool/tool_impls/list_directory/tool.py +197 -0
  140. agentpool/tool_impls/question/__init__.py +42 -0
  141. agentpool/tool_impls/question/tool.py +127 -0
  142. agentpool/tool_impls/read/__init__.py +104 -0
  143. agentpool/tool_impls/read/tool.py +305 -0
  144. agentpool/tools/__init__.py +2 -1
  145. agentpool/tools/base.py +114 -34
  146. agentpool/tools/manager.py +57 -1
  147. agentpool/ui/base.py +2 -2
  148. agentpool/ui/mock_provider.py +2 -2
  149. agentpool/ui/stdlib_provider.py +2 -2
  150. agentpool/utils/file_watcher.py +269 -0
  151. agentpool/utils/identifiers.py +121 -0
  152. agentpool/utils/pydantic_ai_helpers.py +46 -0
  153. agentpool/utils/streams.py +616 -2
  154. agentpool/utils/subprocess_utils.py +155 -0
  155. agentpool/utils/token_breakdown.py +461 -0
  156. agentpool/vfs_registry.py +7 -2
  157. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
  158. agentpool-2.5.0.dist-info/RECORD +579 -0
  159. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
  160. agentpool_cli/__main__.py +24 -0
  161. agentpool_cli/create.py +1 -1
  162. agentpool_cli/serve_acp.py +100 -21
  163. agentpool_cli/serve_agui.py +87 -0
  164. agentpool_cli/serve_opencode.py +119 -0
  165. agentpool_cli/ui.py +557 -0
  166. agentpool_commands/__init__.py +42 -5
  167. agentpool_commands/agents.py +75 -2
  168. agentpool_commands/history.py +62 -0
  169. agentpool_commands/mcp.py +176 -0
  170. agentpool_commands/models.py +56 -3
  171. agentpool_commands/pool.py +260 -0
  172. agentpool_commands/session.py +1 -1
  173. agentpool_commands/text_sharing/__init__.py +119 -0
  174. agentpool_commands/text_sharing/base.py +123 -0
  175. agentpool_commands/text_sharing/github_gist.py +80 -0
  176. agentpool_commands/text_sharing/opencode.py +462 -0
  177. agentpool_commands/text_sharing/paste_rs.py +59 -0
  178. agentpool_commands/text_sharing/pastebin.py +116 -0
  179. agentpool_commands/text_sharing/shittycodingagent.py +112 -0
  180. agentpool_commands/tools.py +57 -0
  181. agentpool_commands/utils.py +80 -30
  182. agentpool_config/__init__.py +30 -2
  183. agentpool_config/agentpool_tools.py +498 -0
  184. agentpool_config/builtin_tools.py +77 -22
  185. agentpool_config/commands.py +24 -1
  186. agentpool_config/compaction.py +258 -0
  187. agentpool_config/converters.py +1 -1
  188. agentpool_config/event_handlers.py +42 -0
  189. agentpool_config/events.py +1 -1
  190. agentpool_config/forward_targets.py +1 -4
  191. agentpool_config/jinja.py +3 -3
  192. agentpool_config/mcp_server.py +132 -6
  193. agentpool_config/nodes.py +1 -1
  194. agentpool_config/observability.py +44 -0
  195. agentpool_config/session.py +0 -3
  196. agentpool_config/storage.py +82 -38
  197. agentpool_config/task.py +3 -3
  198. agentpool_config/tools.py +11 -22
  199. agentpool_config/toolsets.py +109 -233
  200. agentpool_server/a2a_server/agent_worker.py +307 -0
  201. agentpool_server/a2a_server/server.py +23 -18
  202. agentpool_server/acp_server/acp_agent.py +234 -181
  203. agentpool_server/acp_server/commands/acp_commands.py +151 -156
  204. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
  205. agentpool_server/acp_server/event_converter.py +651 -0
  206. agentpool_server/acp_server/input_provider.py +53 -10
  207. agentpool_server/acp_server/server.py +24 -90
  208. agentpool_server/acp_server/session.py +173 -331
  209. agentpool_server/acp_server/session_manager.py +8 -34
  210. agentpool_server/agui_server/server.py +3 -1
  211. agentpool_server/mcp_server/server.py +5 -2
  212. agentpool_server/opencode_server/.rules +95 -0
  213. agentpool_server/opencode_server/ENDPOINTS.md +401 -0
  214. agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
  215. agentpool_server/opencode_server/__init__.py +19 -0
  216. agentpool_server/opencode_server/command_validation.py +172 -0
  217. agentpool_server/opencode_server/converters.py +975 -0
  218. agentpool_server/opencode_server/dependencies.py +24 -0
  219. agentpool_server/opencode_server/input_provider.py +421 -0
  220. agentpool_server/opencode_server/models/__init__.py +250 -0
  221. agentpool_server/opencode_server/models/agent.py +53 -0
  222. agentpool_server/opencode_server/models/app.py +72 -0
  223. agentpool_server/opencode_server/models/base.py +26 -0
  224. agentpool_server/opencode_server/models/common.py +23 -0
  225. agentpool_server/opencode_server/models/config.py +37 -0
  226. agentpool_server/opencode_server/models/events.py +821 -0
  227. agentpool_server/opencode_server/models/file.py +88 -0
  228. agentpool_server/opencode_server/models/mcp.py +44 -0
  229. agentpool_server/opencode_server/models/message.py +179 -0
  230. agentpool_server/opencode_server/models/parts.py +323 -0
  231. agentpool_server/opencode_server/models/provider.py +81 -0
  232. agentpool_server/opencode_server/models/pty.py +43 -0
  233. agentpool_server/opencode_server/models/question.py +56 -0
  234. agentpool_server/opencode_server/models/session.py +111 -0
  235. agentpool_server/opencode_server/routes/__init__.py +29 -0
  236. agentpool_server/opencode_server/routes/agent_routes.py +473 -0
  237. agentpool_server/opencode_server/routes/app_routes.py +202 -0
  238. agentpool_server/opencode_server/routes/config_routes.py +302 -0
  239. agentpool_server/opencode_server/routes/file_routes.py +571 -0
  240. agentpool_server/opencode_server/routes/global_routes.py +94 -0
  241. agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
  242. agentpool_server/opencode_server/routes/message_routes.py +761 -0
  243. agentpool_server/opencode_server/routes/permission_routes.py +63 -0
  244. agentpool_server/opencode_server/routes/pty_routes.py +300 -0
  245. agentpool_server/opencode_server/routes/question_routes.py +128 -0
  246. agentpool_server/opencode_server/routes/session_routes.py +1276 -0
  247. agentpool_server/opencode_server/routes/tui_routes.py +139 -0
  248. agentpool_server/opencode_server/server.py +475 -0
  249. agentpool_server/opencode_server/state.py +151 -0
  250. agentpool_server/opencode_server/time_utils.py +8 -0
  251. agentpool_storage/__init__.py +12 -0
  252. agentpool_storage/base.py +184 -2
  253. agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
  254. agentpool_storage/claude_provider/__init__.py +42 -0
  255. agentpool_storage/claude_provider/provider.py +1089 -0
  256. agentpool_storage/file_provider.py +278 -15
  257. agentpool_storage/memory_provider.py +193 -12
  258. agentpool_storage/models.py +3 -0
  259. agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
  260. agentpool_storage/opencode_provider/__init__.py +16 -0
  261. agentpool_storage/opencode_provider/helpers.py +414 -0
  262. agentpool_storage/opencode_provider/provider.py +895 -0
  263. agentpool_storage/project_store.py +325 -0
  264. agentpool_storage/session_store.py +26 -6
  265. agentpool_storage/sql_provider/__init__.py +4 -2
  266. agentpool_storage/sql_provider/models.py +48 -0
  267. agentpool_storage/sql_provider/sql_provider.py +269 -3
  268. agentpool_storage/sql_provider/utils.py +12 -13
  269. agentpool_storage/zed_provider/__init__.py +16 -0
  270. agentpool_storage/zed_provider/helpers.py +281 -0
  271. agentpool_storage/zed_provider/models.py +130 -0
  272. agentpool_storage/zed_provider/provider.py +442 -0
  273. agentpool_storage/zed_provider.py +803 -0
  274. agentpool_toolsets/__init__.py +0 -2
  275. agentpool_toolsets/builtin/__init__.py +2 -12
  276. agentpool_toolsets/builtin/code.py +96 -57
  277. agentpool_toolsets/builtin/debug.py +118 -48
  278. agentpool_toolsets/builtin/execution_environment.py +115 -230
  279. agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
  280. agentpool_toolsets/builtin/skills.py +9 -4
  281. agentpool_toolsets/builtin/subagent_tools.py +64 -51
  282. agentpool_toolsets/builtin/workers.py +4 -2
  283. agentpool_toolsets/composio_toolset.py +2 -2
  284. agentpool_toolsets/entry_points.py +3 -1
  285. agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
  286. agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
  287. agentpool_toolsets/fsspec_toolset/grep.py +99 -7
  288. agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
  289. agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
  290. agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
  291. agentpool_toolsets/mcp_discovery/__init__.py +5 -0
  292. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  293. agentpool_toolsets/mcp_discovery/toolset.py +511 -0
  294. agentpool_toolsets/mcp_run_toolset.py +87 -12
  295. agentpool_toolsets/notifications.py +33 -33
  296. agentpool_toolsets/openapi.py +3 -1
  297. agentpool_toolsets/search_toolset.py +3 -1
  298. agentpool-2.1.9.dist-info/RECORD +0 -474
  299. agentpool_config/resources.py +0 -33
  300. agentpool_server/acp_server/acp_tools.py +0 -43
  301. agentpool_server/acp_server/commands/spawn.py +0 -210
  302. agentpool_storage/text_log_provider.py +0 -275
  303. agentpool_toolsets/builtin/agent_management.py +0 -239
  304. agentpool_toolsets/builtin/chain.py +0 -288
  305. agentpool_toolsets/builtin/history.py +0 -36
  306. agentpool_toolsets/builtin/integration.py +0 -85
  307. agentpool_toolsets/builtin/tool_management.py +0 -90
  308. agentpool_toolsets/builtin/user_interaction.py +0 -52
  309. agentpool_toolsets/semantic_memory_toolset.py +0 -536
  310. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
  311. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -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
+ )
@@ -0,0 +1,183 @@
1
+ """Download file tool implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ import time
8
+ from typing import TYPE_CHECKING, Any
9
+ from urllib.parse import urlparse
10
+
11
+ import anyio
12
+
13
+ from agentpool.agents.context import AgentContext # noqa: TC001
14
+ from agentpool.log import get_logger
15
+ from agentpool.tools.base import Tool
16
+
17
+
18
+ if TYPE_CHECKING:
19
+ from collections.abc import Awaitable, Callable
20
+
21
+ from exxec import ExecutionEnvironment
22
+ from fsspec.asyn import AsyncFileSystem
23
+
24
+
25
+ logger = get_logger(__name__)
26
+
27
+
28
+ @dataclass
29
+ class DownloadFileTool(Tool[dict[str, Any]]):
30
+ """Download files from URLs to the filesystem.
31
+
32
+ A standalone tool for downloading files with:
33
+ - HTTP/HTTPS URL downloads
34
+ - Progress tracking
35
+ - Configurable chunk size
36
+ - Speed monitoring
37
+ - Automatic directory creation
38
+
39
+ Use create_download_file_tool() factory for convenient instantiation.
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
+ cwd: str | None = None
47
+ """Working directory for resolving relative paths."""
48
+
49
+ chunk_size: int = 8192
50
+ """Size of chunks to download (bytes)."""
51
+
52
+ timeout: float = 30.0
53
+ """Request timeout in seconds."""
54
+
55
+ def get_callable(self) -> Callable[..., Awaitable[dict[str, Any]]]:
56
+ """Return the download_file method as the callable."""
57
+ return self._download_file
58
+
59
+ def _get_fs(self, ctx: AgentContext) -> AsyncFileSystem:
60
+ """Get filesystem from env, falling back to agent's env if not set."""
61
+ from fsspec.asyn import AsyncFileSystem
62
+ from fsspec.implementations.asyn_wrapper import AsyncFileSystemWrapper
63
+
64
+ if self.env is not None:
65
+ fs = self.env.get_fs()
66
+ return fs if isinstance(fs, AsyncFileSystem) else AsyncFileSystemWrapper(fs)
67
+ fs = ctx.agent.env.get_fs()
68
+ return fs if isinstance(fs, AsyncFileSystem) else AsyncFileSystemWrapper(fs)
69
+
70
+ def _resolve_path(self, path: str, ctx: AgentContext) -> str:
71
+ """Resolve a potentially relative path to an absolute path."""
72
+ cwd: str | None = None
73
+ if self.cwd:
74
+ cwd = self.cwd
75
+ elif self.env and self.env.cwd:
76
+ cwd = self.env.cwd
77
+ elif ctx.agent.env and ctx.agent.env.cwd:
78
+ cwd = ctx.agent.env.cwd
79
+
80
+ if cwd and not (path.startswith("/") or (len(path) > 1 and path[1] == ":")):
81
+ return str(Path(cwd) / path)
82
+ return path
83
+
84
+ async def _write(self, ctx: AgentContext, path: str, content: bytes) -> None:
85
+ """Write bytes to a file."""
86
+ await self._get_fs(ctx)._pipe_file(path, content)
87
+
88
+ async def _download_file(
89
+ self,
90
+ ctx: AgentContext,
91
+ url: str,
92
+ target_dir: str = "downloads",
93
+ chunk_size: int | None = None,
94
+ ) -> dict[str, Any]:
95
+ """Download a file from URL to the filesystem.
96
+
97
+ Args:
98
+ ctx: Agent context for event emission and filesystem access
99
+ url: URL to download from
100
+ target_dir: Directory to save the file (relative to cwd if set)
101
+ chunk_size: Size of chunks to download (overrides default)
102
+
103
+ Returns:
104
+ Status information about the download
105
+ """
106
+ import httpx
107
+
108
+ effective_chunk_size = chunk_size or self.chunk_size
109
+ start_time = time.time()
110
+
111
+ # Resolve target directory
112
+ target_dir = self._resolve_path(target_dir, ctx)
113
+
114
+ msg = f"Downloading: {url}"
115
+ await ctx.events.tool_call_start(title=msg, kind="read", locations=[url])
116
+
117
+ # Extract filename from URL
118
+ filename = Path(urlparse(url).path).name or "downloaded_file"
119
+ full_path = f"{target_dir.rstrip('/')}/{filename}"
120
+
121
+ try:
122
+ fs = self._get_fs(ctx)
123
+ # Ensure target directory exists
124
+ await fs._makedirs(target_dir, exist_ok=True)
125
+
126
+ async with (
127
+ httpx.AsyncClient(verify=False) as client,
128
+ client.stream("GET", url, timeout=self.timeout) as response,
129
+ ):
130
+ response.raise_for_status()
131
+
132
+ total = (
133
+ int(response.headers["Content-Length"])
134
+ if "Content-Length" in response.headers
135
+ else None
136
+ )
137
+
138
+ # Collect all data
139
+ data = bytearray()
140
+ async for chunk in response.aiter_bytes(effective_chunk_size):
141
+ data.extend(chunk)
142
+ size = len(data)
143
+
144
+ if total and (size % (effective_chunk_size * 100) == 0 or size == total):
145
+ progress = size / total * 100
146
+ speed_mbps = (size / 1_048_576) / (time.time() - start_time)
147
+ progress_msg = f"\r{filename}: {progress:.1f}% ({speed_mbps:.1f} MB/s)"
148
+ await ctx.events.progress(progress, 100, progress_msg)
149
+ await anyio.sleep(0)
150
+
151
+ # Write to filesystem
152
+ await self._write(ctx, full_path, bytes(data))
153
+
154
+ duration = time.time() - start_time
155
+ size_mb = len(data) / 1_048_576
156
+
157
+ await ctx.events.file_operation("read", path=full_path, success=True)
158
+
159
+ return {
160
+ "path": full_path,
161
+ "filename": filename,
162
+ "size_bytes": len(data),
163
+ "size_mb": round(size_mb, 2),
164
+ "duration_seconds": round(duration, 2),
165
+ "speed_mbps": round(size_mb / duration, 2) if duration > 0 else 0,
166
+ }
167
+
168
+ except httpx.ConnectError as e:
169
+ error_msg = f"Connection error downloading {url}: {e}"
170
+ await ctx.events.file_operation("read", path=url, success=False, error=error_msg)
171
+ return {"error": error_msg}
172
+ except httpx.TimeoutException:
173
+ error_msg = f"Timeout downloading {url}"
174
+ await ctx.events.file_operation("read", path=url, success=False, error=error_msg)
175
+ return {"error": error_msg}
176
+ except httpx.HTTPStatusError as e:
177
+ error_msg = f"HTTP error {e.response.status_code} downloading {url}"
178
+ await ctx.events.file_operation("read", path=url, success=False, error=error_msg)
179
+ return {"error": error_msg}
180
+ except Exception as e: # noqa: BLE001
181
+ error_msg = f"Error downloading {url}: {e!s}"
182
+ await ctx.events.file_operation("read", path=url, success=False, error=error_msg)
183
+ return {"error": error_msg}
@@ -0,0 +1,55 @@
1
+ """Python code execution tool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Literal
6
+
7
+ from agentpool.tool_impls.execute_code.tool import ExecuteCodeTool
8
+ from agentpool_config.tools import ToolHints
9
+
10
+
11
+ if TYPE_CHECKING:
12
+ from exxec import ExecutionEnvironment
13
+
14
+ __all__ = ["ExecuteCodeTool", "create_execute_code_tool"]
15
+
16
+ # Tool metadata defaults
17
+ NAME = "execute_code"
18
+ DESCRIPTION = "Execute Python code and return the result."
19
+ CATEGORY: Literal["execute"] = "execute"
20
+ HINTS = ToolHints()
21
+
22
+
23
+ def create_execute_code_tool(
24
+ *,
25
+ env: ExecutionEnvironment | None = None,
26
+ name: str = NAME,
27
+ description: str = DESCRIPTION,
28
+ requires_confirmation: bool = False,
29
+ ) -> ExecuteCodeTool:
30
+ """Create a configured ExecuteCodeTool instance.
31
+
32
+ Args:
33
+ env: Execution environment to use. Falls back to agent.env if not set.
34
+ name: Tool name override.
35
+ description: Tool description override.
36
+ requires_confirmation: Whether tool execution needs confirmation.
37
+
38
+ Returns:
39
+ Configured ExecuteCodeTool instance.
40
+
41
+ Example:
42
+ # Basic
43
+ exec_code = create_execute_code_tool()
44
+
45
+ # Require confirmation
46
+ exec_code = create_execute_code_tool(requires_confirmation=True)
47
+ """
48
+ return ExecuteCodeTool(
49
+ name=name,
50
+ description=description,
51
+ category=CATEGORY,
52
+ hints=HINTS,
53
+ env=env,
54
+ requires_confirmation=requires_confirmation,
55
+ )
@@ -0,0 +1,163 @@
1
+ """Python code execution tool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from datetime import UTC, datetime
7
+ import json
8
+ from typing import TYPE_CHECKING
9
+ import uuid
10
+
11
+ from exxec.events import (
12
+ OutputEvent,
13
+ ProcessCompletedEvent,
14
+ ProcessErrorEvent,
15
+ ProcessStartedEvent,
16
+ )
17
+
18
+ from agentpool.agents.context import AgentContext # noqa: TC001
19
+ from agentpool.log import get_logger
20
+ from agentpool.tools.base import Tool
21
+
22
+
23
+ if TYPE_CHECKING:
24
+ from collections.abc import Awaitable, Callable
25
+
26
+ from exxec import ExecutionEnvironment
27
+ from fsspec.asyn import AsyncFileSystem
28
+
29
+
30
+ logger = get_logger(__name__)
31
+
32
+
33
+ @dataclass
34
+ class ExecuteCodeTool(Tool[str]):
35
+ """Execute Python code and return the result.
36
+
37
+ A standalone tool for executing Python code in a sandboxed environment.
38
+
39
+ Use create_execute_code_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
+ def __post_init__(self) -> None:
47
+ """Initialize filesystem for script history after dataclass init."""
48
+ from fsspec.implementations.asyn_wrapper import AsyncFileSystemWrapper
49
+ from fsspec.implementations.memory import MemoryFileSystem
50
+
51
+ self._memory_fs = MemoryFileSystem()
52
+ self._fs = AsyncFileSystemWrapper(self._memory_fs)
53
+
54
+ def get_callable(self) -> Callable[..., Awaitable[str]]:
55
+ """Return the execute method as the callable."""
56
+ return self._execute
57
+
58
+ def _get_env(self, ctx: AgentContext) -> ExecutionEnvironment:
59
+ """Get execution environment, falling back to agent's env."""
60
+ if self.env is not None:
61
+ return self.env
62
+ return ctx.agent.env
63
+
64
+ def get_fs(self) -> AsyncFileSystem:
65
+ """Get filesystem view of script history.
66
+
67
+ Returns:
68
+ AsyncFileSystem containing:
69
+ - scripts/{timestamp}_{title}.py - Executed scripts
70
+ - scripts/{timestamp}_{title}.json - Execution metadata
71
+ """
72
+ return self._fs
73
+
74
+ async def _execute(
75
+ self,
76
+ ctx: AgentContext,
77
+ code: str,
78
+ title: str,
79
+ ) -> str:
80
+ """Execute Python code and return the result.
81
+
82
+ Args:
83
+ ctx: Agent context for event emission and environment access
84
+ code: Python code to execute
85
+ title: Short descriptive title for this script (3-4 words)
86
+ """
87
+ process_id: str | None = None
88
+ output_parts: list[str] = []
89
+ exit_code: int | None = None
90
+ error_msg: str | None = None
91
+
92
+ # Check if we're running in ACP - terminal streams client-side
93
+ from exxec.acp_provider import ACPExecutionEnvironment
94
+
95
+ env = self._get_env(ctx)
96
+ is_acp = isinstance(env, ACPExecutionEnvironment)
97
+
98
+ try:
99
+ async for event in env.stream_code(code):
100
+ match event:
101
+ case ProcessStartedEvent(process_id=pid, command=cmd):
102
+ process_id = pid
103
+ await ctx.events.process_started(pid, cmd, success=True)
104
+
105
+ case OutputEvent(data=data):
106
+ output_parts.append(data)
107
+ # Skip progress events for ACP - terminal streams client-side
108
+ if process_id and not is_acp:
109
+ await ctx.events.process_output(process_id, data)
110
+
111
+ case ProcessCompletedEvent(exit_code=code_):
112
+ exit_code = code_
113
+ # Skip exit event for ACP - completion handled by FunctionToolResultEvent
114
+ if process_id and not is_acp:
115
+ out = "".join(output_parts)
116
+ await ctx.events.process_exit(process_id, exit_code, final_output=out)
117
+
118
+ case ProcessErrorEvent(error=err, exit_code=code_):
119
+ error_msg = err
120
+ exit_code = code_
121
+ # Skip exit event for ACP - completion handled by FunctionToolResultEvent
122
+ if process_id and not is_acp:
123
+ await ctx.events.process_exit(
124
+ process_id, exit_code or 1, final_output=err
125
+ )
126
+
127
+ combined_output = "".join(output_parts)
128
+
129
+ # Format error response
130
+ if error_msg:
131
+ result_str = f"{combined_output}\n\nError: {error_msg}\nExit code: {exit_code}"
132
+ elif exit_code and exit_code != 0:
133
+ result_str = f"{combined_output}\n\nExit code: {exit_code}"
134
+ else:
135
+ result_str = combined_output
136
+
137
+ except Exception as e: # noqa: BLE001
138
+ error_id = process_id or f"code_{uuid.uuid4().hex[:8]}"
139
+ await ctx.events.process_started(error_id, "execute_code", success=False, error=str(e))
140
+ exit_code = 1
141
+ error_msg = str(e)
142
+ result_str = f"Error executing code: {e}"
143
+ finally:
144
+ # Save to filesystem
145
+ end_time = datetime.now(UTC)
146
+ timestamp = end_time.strftime("%Y%m%d_%H%M%S")
147
+
148
+ # Write script file
149
+ script_path = f"scripts/{timestamp}_{title}.py"
150
+ self._memory_fs.pipe(script_path, code.encode("utf-8"))
151
+
152
+ # Write metadata file
153
+ metadata = {
154
+ "title": title,
155
+ "timestamp": end_time.isoformat(),
156
+ "exit_code": exit_code or 0,
157
+ "result": result_str,
158
+ "error": error_msg,
159
+ }
160
+ metadata_path = f"scripts/{timestamp}_{title}.json"
161
+ self._memory_fs.pipe(metadata_path, json.dumps(metadata, indent=2).encode("utf-8"))
162
+
163
+ return result_str
@@ -0,0 +1,80 @@
1
+ """Grep search tool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Literal
6
+
7
+ from agentpool.tool_impls.grep.tool import GrepTool
8
+ from agentpool_config.tools import ToolHints
9
+
10
+
11
+ if TYPE_CHECKING:
12
+ from exxec import ExecutionEnvironment
13
+
14
+ __all__ = ["GrepTool", "create_grep_tool"]
15
+
16
+ # Tool metadata defaults
17
+ NAME = "grep"
18
+ DESCRIPTION = """Search file contents for patterns using grep.
19
+
20
+ Supports:
21
+ - Regex pattern matching
22
+ - File filtering with glob patterns
23
+ - Context lines before/after matches
24
+ - Fast subprocess grep (ripgrep/grep) with Python fallback
25
+ - Case-sensitive and case-insensitive search"""
26
+ CATEGORY: Literal["search"] = "search"
27
+ HINTS = ToolHints(read_only=True, idempotent=True)
28
+
29
+
30
+ def create_grep_tool(
31
+ *,
32
+ env: ExecutionEnvironment | None = None,
33
+ cwd: str | None = None,
34
+ max_output_kb: int = 64,
35
+ use_subprocess_grep: bool = True,
36
+ name: str = NAME,
37
+ description: str = DESCRIPTION,
38
+ requires_confirmation: bool = False,
39
+ ) -> GrepTool:
40
+ """Create a configured GrepTool 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
+ max_output_kb: Maximum output size in KB (default: 64KB).
46
+ use_subprocess_grep: Use ripgrep/grep subprocess if available (default: True).
47
+ name: Tool name override.
48
+ description: Tool description override.
49
+ requires_confirmation: Whether tool execution needs confirmation.
50
+
51
+ Returns:
52
+ Configured GrepTool instance.
53
+
54
+ Example:
55
+ # Basic usage
56
+ grep = create_grep_tool()
57
+
58
+ # With custom limits
59
+ grep = create_grep_tool(
60
+ max_output_kb=128,
61
+ use_subprocess_grep=False, # Force Python implementation
62
+ )
63
+
64
+ # With specific environment
65
+ grep = create_grep_tool(
66
+ env=my_env,
67
+ cwd="/workspace",
68
+ )
69
+ """
70
+ return GrepTool(
71
+ name=name,
72
+ description=description,
73
+ category=CATEGORY,
74
+ hints=HINTS,
75
+ env=env,
76
+ cwd=cwd,
77
+ max_output_kb=max_output_kb,
78
+ use_subprocess_grep=use_subprocess_grep,
79
+ requires_confirmation=requires_confirmation,
80
+ )