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.
- acp/__init__.py +13 -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/bridge/README.md +15 -2
- acp/bridge/__init__.py +3 -2
- acp/bridge/__main__.py +60 -19
- acp/bridge/ws_server.py +173 -0
- acp/bridge/ws_server_cli.py +89 -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 +20 -50
- 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/stdio.py +39 -9
- acp/task/supervisor.py +2 -2
- acp/transports.py +362 -2
- acp/utils.py +17 -4
- agentpool/__init__.py +6 -1
- agentpool/agents/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +407 -277
- agentpool/agents/acp_agent/acp_converters.py +196 -38
- agentpool/agents/acp_agent/client_handler.py +191 -26
- agentpool/agents/acp_agent/session_state.py +17 -6
- agentpool/agents/agent.py +607 -572
- agentpool/agents/agui_agent/__init__.py +0 -2
- agentpool/agents/agui_agent/agui_agent.py +176 -110
- agentpool/agents/agui_agent/agui_converters.py +0 -131
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +632 -17
- 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 +1058 -291
- agentpool/agents/claude_code_agent/converters.py +74 -143
- agentpool/agents/claude_code_agent/history.py +474 -0
- 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/context.py +40 -0
- agentpool/agents/events/__init__.py +24 -0
- agentpool/agents/events/builtin_handlers.py +67 -1
- agentpool/agents/events/event_emitter.py +32 -2
- agentpool/agents/events/events.py +104 -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 +67 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_call_accumulator.py +213 -0
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +56 -21
- agentpool/config_resources/__init__.py +38 -1
- 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 +10 -6
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +136 -289
- agentpool/delegation/team.py +58 -57
- agentpool/delegation/teamrun.py +51 -55
- agentpool/diagnostics/__init__.py +53 -0
- agentpool/diagnostics/lsp_manager.py +1593 -0
- agentpool/diagnostics/lsp_proxy.py +41 -0
- agentpool/diagnostics/lsp_proxy_script.py +229 -0
- agentpool/diagnostics/models.py +398 -0
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/__init__.py +0 -2
- agentpool/mcp_server/client.py +76 -32
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +34 -54
- agentpool/mcp_server/registries/official_registry_client.py +35 -1
- agentpool/mcp_server/tool_bridge.py +186 -139
- agentpool/messaging/__init__.py +0 -2
- agentpool/messaging/compaction.py +72 -197
- 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 +99 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +54 -35
- agentpool/messaging/processing.py +12 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -24
- agentpool/models/acp_agents/mcp_capable.py +126 -157
- agentpool/models/acp_agents/non_mcp.py +129 -95
- agentpool/models/agents.py +98 -76
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +144 -19
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +113 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/repomap.py +1 -1
- agentpool/resource_providers/__init__.py +11 -1
- agentpool/resource_providers/aggregating.py +56 -5
- agentpool/resource_providers/base.py +70 -4
- 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 +89 -12
- agentpool/resource_providers/plan_provider.py +228 -46
- agentpool/resource_providers/pool.py +7 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +4 -2
- agentpool/sessions/__init__.py +4 -1
- agentpool/sessions/manager.py +33 -5
- agentpool/sessions/models.py +59 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/skills/registry.py +13 -8
- agentpool/storage/manager.py +572 -49
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +538 -20
- 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/file_watcher.py +269 -0
- agentpool/utils/identifiers.py +121 -0
- agentpool/utils/pydantic_ai_helpers.py +46 -0
- agentpool/utils/streams.py +616 -2
- agentpool/utils/subprocess_utils.py +155 -0
- agentpool/utils/token_breakdown.py +461 -0
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
- agentpool-2.5.0.dist-info/RECORD +579 -0
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +24 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +100 -21
- agentpool_cli/serve_agui.py +87 -0
- agentpool_cli/serve_opencode.py +119 -0
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +42 -5
- agentpool_commands/agents.py +75 -2
- agentpool_commands/history.py +62 -0
- agentpool_commands/mcp.py +176 -0
- agentpool_commands/models.py +56 -3
- 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/tools.py +57 -0
- agentpool_commands/utils.py +80 -30
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -0
- agentpool_config/builtin_tools.py +77 -22
- agentpool_config/commands.py +24 -1
- agentpool_config/compaction.py +258 -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 +132 -6
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +82 -38
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -22
- agentpool_config/toolsets.py +109 -233
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +234 -181
- agentpool_server/acp_server/commands/acp_commands.py +151 -156
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +24 -90
- agentpool_server/acp_server/session.py +173 -331
- 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/.rules +95 -0
- agentpool_server/opencode_server/ENDPOINTS.md +401 -0
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +19 -0
- agentpool_server/opencode_server/command_validation.py +172 -0
- agentpool_server/opencode_server/converters.py +975 -0
- agentpool_server/opencode_server/dependencies.py +24 -0
- agentpool_server/opencode_server/input_provider.py +421 -0
- agentpool_server/opencode_server/models/__init__.py +250 -0
- agentpool_server/opencode_server/models/agent.py +53 -0
- agentpool_server/opencode_server/models/app.py +72 -0
- agentpool_server/opencode_server/models/base.py +26 -0
- agentpool_server/opencode_server/models/common.py +23 -0
- agentpool_server/opencode_server/models/config.py +37 -0
- agentpool_server/opencode_server/models/events.py +821 -0
- agentpool_server/opencode_server/models/file.py +88 -0
- agentpool_server/opencode_server/models/mcp.py +44 -0
- agentpool_server/opencode_server/models/message.py +179 -0
- agentpool_server/opencode_server/models/parts.py +323 -0
- agentpool_server/opencode_server/models/provider.py +81 -0
- agentpool_server/opencode_server/models/pty.py +43 -0
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +111 -0
- agentpool_server/opencode_server/routes/__init__.py +29 -0
- agentpool_server/opencode_server/routes/agent_routes.py +473 -0
- agentpool_server/opencode_server/routes/app_routes.py +202 -0
- agentpool_server/opencode_server/routes/config_routes.py +302 -0
- agentpool_server/opencode_server/routes/file_routes.py +571 -0
- agentpool_server/opencode_server/routes/global_routes.py +94 -0
- agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
- agentpool_server/opencode_server/routes/message_routes.py +761 -0
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +300 -0
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +1276 -0
- agentpool_server/opencode_server/routes/tui_routes.py +139 -0
- agentpool_server/opencode_server/server.py +475 -0
- agentpool_server/opencode_server/state.py +151 -0
- agentpool_server/opencode_server/time_utils.py +8 -0
- agentpool_storage/__init__.py +12 -0
- agentpool_storage/base.py +184 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/claude_provider/provider.py +1089 -0
- agentpool_storage/file_provider.py +278 -15
- agentpool_storage/memory_provider.py +193 -12
- agentpool_storage/models.py +3 -0
- 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/project_store.py +325 -0
- agentpool_storage/session_store.py +26 -6
- agentpool_storage/sql_provider/__init__.py +4 -2
- agentpool_storage/sql_provider/models.py +48 -0
- agentpool_storage/sql_provider/sql_provider.py +269 -3
- agentpool_storage/sql_provider/utils.py +12 -13
- 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 -12
- agentpool_toolsets/builtin/code.py +96 -57
- agentpool_toolsets/builtin/debug.py +118 -48
- agentpool_toolsets/builtin/execution_environment.py +115 -230
- agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
- agentpool_toolsets/builtin/skills.py +9 -4
- 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/__init__.py +13 -1
- agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
- agentpool_toolsets/fsspec_toolset/grep.py +99 -7
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
- agentpool_toolsets/mcp_discovery/__init__.py +5 -0
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +511 -0
- agentpool_toolsets/mcp_run_toolset.py +87 -12
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- agentpool-2.1.9.dist-info/RECORD +0 -474
- 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/text_log_provider.py +0 -275
- agentpool_toolsets/builtin/agent_management.py +0 -239
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/history.py +0 -36
- agentpool_toolsets/builtin/integration.py +0 -85
- agentpool_toolsets/builtin/tool_management.py +0 -90
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {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
|
+
)
|