agentpool 2.2.3__py3-none-any.whl → 2.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- acp/__init__.py +0 -4
- acp/acp_requests.py +20 -77
- acp/agent/connection.py +8 -0
- acp/agent/implementations/debug_server/debug_server.py +6 -2
- acp/agent/protocol.py +6 -0
- acp/client/connection.py +38 -29
- acp/client/implementations/default_client.py +3 -2
- acp/client/implementations/headless_client.py +2 -2
- acp/connection.py +2 -2
- acp/notifications.py +18 -49
- acp/schema/__init__.py +2 -0
- acp/schema/agent_responses.py +21 -0
- acp/schema/client_requests.py +3 -3
- acp/schema/session_state.py +63 -29
- acp/task/supervisor.py +2 -2
- acp/utils.py +2 -2
- agentpool/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +278 -263
- agentpool/agents/acp_agent/acp_converters.py +150 -17
- agentpool/agents/acp_agent/client_handler.py +35 -24
- agentpool/agents/acp_agent/session_state.py +14 -6
- agentpool/agents/agent.py +471 -643
- agentpool/agents/agui_agent/agui_agent.py +104 -107
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +485 -32
- agentpool/agents/claude_code_agent/FORKING.md +191 -0
- agentpool/agents/claude_code_agent/__init__.py +13 -1
- agentpool/agents/claude_code_agent/claude_code_agent.py +654 -334
- agentpool/agents/claude_code_agent/converters.py +4 -141
- agentpool/agents/claude_code_agent/models.py +77 -0
- agentpool/agents/claude_code_agent/static_info.py +100 -0
- agentpool/agents/claude_code_agent/usage.py +242 -0
- agentpool/agents/events/__init__.py +22 -0
- agentpool/agents/events/builtin_handlers.py +65 -0
- agentpool/agents/events/event_emitter.py +3 -0
- agentpool/agents/events/events.py +84 -3
- agentpool/agents/events/infer_info.py +145 -0
- agentpool/agents/events/processors.py +254 -0
- agentpool/agents/interactions.py +41 -6
- agentpool/agents/modes.py +13 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +35 -21
- agentpool/config_resources/acp_assistant.yml +2 -2
- agentpool/config_resources/agents.yml +3 -0
- agentpool/config_resources/agents_template.yml +1 -0
- agentpool/config_resources/claude_code_agent.yml +9 -8
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +104 -265
- agentpool/delegation/team.py +57 -57
- agentpool/delegation/teamrun.py +50 -55
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/client.py +73 -38
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +9 -23
- agentpool/mcp_server/registries/official_registry_client.py +10 -1
- agentpool/mcp_server/tool_bridge.py +114 -79
- agentpool/messaging/connection_manager.py +11 -10
- agentpool/messaging/event_manager.py +5 -5
- agentpool/messaging/message_container.py +6 -30
- agentpool/messaging/message_history.py +87 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +2 -26
- agentpool/messaging/processing.py +10 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -2
- agentpool/models/acp_agents/mcp_capable.py +124 -15
- agentpool/models/acp_agents/non_mcp.py +0 -23
- agentpool/models/agents.py +66 -66
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +111 -17
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +70 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/resource_providers/__init__.py +2 -0
- agentpool/resource_providers/aggregating.py +4 -2
- agentpool/resource_providers/base.py +13 -3
- agentpool/resource_providers/codemode/code_executor.py +72 -5
- agentpool/resource_providers/codemode/helpers.py +2 -2
- agentpool/resource_providers/codemode/provider.py +64 -12
- agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
- agentpool/resource_providers/codemode/remote_provider.py +9 -12
- agentpool/resource_providers/filtering.py +3 -1
- agentpool/resource_providers/mcp_provider.py +66 -12
- agentpool/resource_providers/plan_provider.py +111 -18
- agentpool/resource_providers/pool.py +5 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +2 -2
- agentpool/sessions/__init__.py +2 -0
- agentpool/sessions/manager.py +2 -3
- agentpool/sessions/models.py +9 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/storage/manager.py +361 -54
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +1 -1
- agentpool/tool_impls/__init__.py +6 -0
- agentpool/tool_impls/agent_cli/__init__.py +42 -0
- agentpool/tool_impls/agent_cli/tool.py +95 -0
- agentpool/tool_impls/bash/__init__.py +64 -0
- agentpool/tool_impls/bash/helpers.py +35 -0
- agentpool/tool_impls/bash/tool.py +171 -0
- agentpool/tool_impls/delete_path/__init__.py +70 -0
- agentpool/tool_impls/delete_path/tool.py +142 -0
- agentpool/tool_impls/download_file/__init__.py +80 -0
- agentpool/tool_impls/download_file/tool.py +183 -0
- agentpool/tool_impls/execute_code/__init__.py +55 -0
- agentpool/tool_impls/execute_code/tool.py +163 -0
- agentpool/tool_impls/grep/__init__.py +80 -0
- agentpool/tool_impls/grep/tool.py +200 -0
- agentpool/tool_impls/list_directory/__init__.py +73 -0
- agentpool/tool_impls/list_directory/tool.py +197 -0
- agentpool/tool_impls/question/__init__.py +42 -0
- agentpool/tool_impls/question/tool.py +127 -0
- agentpool/tool_impls/read/__init__.py +104 -0
- agentpool/tool_impls/read/tool.py +305 -0
- agentpool/tools/__init__.py +2 -1
- agentpool/tools/base.py +114 -34
- agentpool/tools/manager.py +57 -1
- agentpool/ui/base.py +2 -2
- agentpool/ui/mock_provider.py +2 -2
- agentpool/ui/stdlib_provider.py +2 -2
- agentpool/utils/streams.py +21 -96
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/METADATA +16 -22
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/RECORD +242 -195
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +20 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +59 -1
- agentpool_cli/serve_opencode.py +1 -1
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +12 -5
- agentpool_commands/agents.py +1 -1
- agentpool_commands/pool.py +260 -0
- agentpool_commands/session.py +1 -1
- agentpool_commands/text_sharing/__init__.py +119 -0
- agentpool_commands/text_sharing/base.py +123 -0
- agentpool_commands/text_sharing/github_gist.py +80 -0
- agentpool_commands/text_sharing/opencode.py +462 -0
- agentpool_commands/text_sharing/paste_rs.py +59 -0
- agentpool_commands/text_sharing/pastebin.py +116 -0
- agentpool_commands/text_sharing/shittycodingagent.py +112 -0
- agentpool_commands/utils.py +31 -32
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -0
- agentpool_config/converters.py +1 -1
- agentpool_config/event_handlers.py +42 -0
- agentpool_config/events.py +1 -1
- agentpool_config/forward_targets.py +1 -4
- agentpool_config/jinja.py +3 -3
- agentpool_config/mcp_server.py +1 -5
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +38 -39
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -28
- agentpool_config/toolsets.py +22 -90
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +125 -56
- agentpool_server/acp_server/commands/acp_commands.py +46 -216
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +8 -7
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +1 -11
- agentpool_server/acp_server/session.py +90 -410
- agentpool_server/acp_server/session_manager.py +8 -34
- agentpool_server/agui_server/server.py +3 -1
- agentpool_server/mcp_server/server.py +5 -2
- agentpool_server/opencode_server/ENDPOINTS.md +53 -14
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +0 -8
- agentpool_server/opencode_server/converters.py +132 -26
- agentpool_server/opencode_server/input_provider.py +160 -8
- agentpool_server/opencode_server/models/__init__.py +42 -20
- agentpool_server/opencode_server/models/app.py +12 -0
- agentpool_server/opencode_server/models/events.py +203 -29
- agentpool_server/opencode_server/models/mcp.py +19 -0
- agentpool_server/opencode_server/models/message.py +18 -1
- agentpool_server/opencode_server/models/parts.py +134 -1
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +13 -1
- agentpool_server/opencode_server/routes/__init__.py +4 -0
- agentpool_server/opencode_server/routes/agent_routes.py +33 -2
- agentpool_server/opencode_server/routes/app_routes.py +66 -3
- agentpool_server/opencode_server/routes/config_routes.py +66 -5
- agentpool_server/opencode_server/routes/file_routes.py +184 -5
- agentpool_server/opencode_server/routes/global_routes.py +1 -1
- agentpool_server/opencode_server/routes/lsp_routes.py +1 -1
- agentpool_server/opencode_server/routes/message_routes.py +122 -66
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +23 -22
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +139 -68
- agentpool_server/opencode_server/routes/tui_routes.py +1 -1
- agentpool_server/opencode_server/server.py +47 -2
- agentpool_server/opencode_server/state.py +30 -0
- agentpool_storage/__init__.py +0 -4
- agentpool_storage/base.py +81 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/{claude_provider.py → claude_provider/provider.py} +190 -8
- agentpool_storage/file_provider.py +149 -15
- agentpool_storage/memory_provider.py +132 -12
- agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
- agentpool_storage/opencode_provider/__init__.py +16 -0
- agentpool_storage/opencode_provider/helpers.py +414 -0
- agentpool_storage/opencode_provider/provider.py +895 -0
- agentpool_storage/session_store.py +20 -6
- agentpool_storage/sql_provider/sql_provider.py +135 -2
- agentpool_storage/sql_provider/utils.py +2 -12
- agentpool_storage/zed_provider/__init__.py +16 -0
- agentpool_storage/zed_provider/helpers.py +281 -0
- agentpool_storage/zed_provider/models.py +130 -0
- agentpool_storage/zed_provider/provider.py +442 -0
- agentpool_storage/zed_provider.py +803 -0
- agentpool_toolsets/__init__.py +0 -2
- agentpool_toolsets/builtin/__init__.py +2 -4
- agentpool_toolsets/builtin/code.py +4 -4
- agentpool_toolsets/builtin/debug.py +115 -40
- agentpool_toolsets/builtin/execution_environment.py +54 -165
- agentpool_toolsets/builtin/skills.py +0 -77
- agentpool_toolsets/builtin/subagent_tools.py +64 -51
- agentpool_toolsets/builtin/workers.py +4 -2
- agentpool_toolsets/composio_toolset.py +2 -2
- agentpool_toolsets/entry_points.py +3 -1
- agentpool_toolsets/fsspec_toolset/grep.py +25 -5
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/toolset.py +350 -66
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +74 -17
- agentpool_toolsets/mcp_run_toolset.py +8 -11
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- agentpool_config/resources.py +0 -33
- agentpool_server/acp_server/acp_tools.py +0 -43
- agentpool_server/acp_server/commands/spawn.py +0 -210
- agentpool_storage/opencode_provider.py +0 -730
- agentpool_storage/text_log_provider.py +0 -276
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""List directory tool."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
|
|
7
|
+
from agentpool.tool_impls.list_directory.tool import ListDirectoryTool
|
|
8
|
+
from agentpool_config.tools import ToolHints
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from exxec import ExecutionEnvironment
|
|
13
|
+
|
|
14
|
+
__all__ = ["ListDirectoryTool", "create_list_directory_tool"]
|
|
15
|
+
|
|
16
|
+
# Tool metadata defaults
|
|
17
|
+
NAME = "list_directory"
|
|
18
|
+
DESCRIPTION = """List files in a directory with filtering support.
|
|
19
|
+
|
|
20
|
+
Supports:
|
|
21
|
+
- Glob pattern matching (*, **, *.py, etc.)
|
|
22
|
+
- Exclude patterns for filtering
|
|
23
|
+
- Recursive directory traversal with depth control
|
|
24
|
+
- Safety limits to prevent overwhelming output"""
|
|
25
|
+
CATEGORY: Literal["read"] = "read"
|
|
26
|
+
HINTS = ToolHints(read_only=True, idempotent=True)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def create_list_directory_tool(
|
|
30
|
+
*,
|
|
31
|
+
env: ExecutionEnvironment | None = None,
|
|
32
|
+
cwd: str | None = None,
|
|
33
|
+
max_items: int = 500,
|
|
34
|
+
name: str = NAME,
|
|
35
|
+
description: str = DESCRIPTION,
|
|
36
|
+
requires_confirmation: bool = False,
|
|
37
|
+
) -> ListDirectoryTool:
|
|
38
|
+
"""Create a configured ListDirectoryTool instance.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
env: Execution environment to use. Falls back to agent.env if not set.
|
|
42
|
+
cwd: Working directory for resolving relative paths.
|
|
43
|
+
max_items: Maximum number of items to return (safety limit, default: 500).
|
|
44
|
+
name: Tool name override.
|
|
45
|
+
description: Tool description override.
|
|
46
|
+
requires_confirmation: Whether tool execution needs confirmation.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Configured ListDirectoryTool instance.
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
# Basic usage
|
|
53
|
+
ls = create_list_directory_tool()
|
|
54
|
+
|
|
55
|
+
# With custom limits
|
|
56
|
+
ls = create_list_directory_tool(max_items=1000)
|
|
57
|
+
|
|
58
|
+
# With specific environment
|
|
59
|
+
ls = create_list_directory_tool(
|
|
60
|
+
env=my_env,
|
|
61
|
+
cwd="/workspace",
|
|
62
|
+
)
|
|
63
|
+
"""
|
|
64
|
+
return ListDirectoryTool(
|
|
65
|
+
name=name,
|
|
66
|
+
description=description,
|
|
67
|
+
category=CATEGORY,
|
|
68
|
+
hints=HINTS,
|
|
69
|
+
env=env,
|
|
70
|
+
cwd=cwd,
|
|
71
|
+
max_items=max_items,
|
|
72
|
+
requires_confirmation=requires_confirmation,
|
|
73
|
+
)
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""List directory tool implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from fnmatch import fnmatch
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
from upathtools import is_directory
|
|
12
|
+
|
|
13
|
+
from agentpool.agents.context import AgentContext # noqa: TC001
|
|
14
|
+
from agentpool.log import get_logger
|
|
15
|
+
from agentpool.tools.base import Tool, ToolResult
|
|
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 ListDirectoryTool(Tool[ToolResult]):
|
|
30
|
+
"""List files in a directory with filtering support.
|
|
31
|
+
|
|
32
|
+
A standalone tool for listing directory contents with:
|
|
33
|
+
- Glob pattern matching (*, **, *.py, etc.)
|
|
34
|
+
- Exclude patterns for filtering
|
|
35
|
+
- Recursive directory traversal with depth control
|
|
36
|
+
- Formatted markdown output
|
|
37
|
+
|
|
38
|
+
Use create_list_directory_tool() factory for convenient instantiation.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
# Tool-specific configuration
|
|
42
|
+
env: ExecutionEnvironment | None = None
|
|
43
|
+
"""Execution environment to use. Falls back to agent.env if not set."""
|
|
44
|
+
|
|
45
|
+
cwd: str | None = None
|
|
46
|
+
"""Working directory for resolving relative paths."""
|
|
47
|
+
|
|
48
|
+
max_items: int = 500
|
|
49
|
+
"""Maximum number of items to return (safety limit)."""
|
|
50
|
+
|
|
51
|
+
def get_callable(self) -> Callable[..., Awaitable[ToolResult]]:
|
|
52
|
+
"""Return the list_directory method as the callable."""
|
|
53
|
+
return self._list_directory
|
|
54
|
+
|
|
55
|
+
def _get_fs(self, ctx: AgentContext) -> AsyncFileSystem:
|
|
56
|
+
"""Get filesystem from env, falling back to agent's env if not set."""
|
|
57
|
+
from fsspec.asyn import AsyncFileSystem
|
|
58
|
+
from fsspec.implementations.asyn_wrapper import AsyncFileSystemWrapper
|
|
59
|
+
|
|
60
|
+
if self.env is not None:
|
|
61
|
+
fs = self.env.get_fs()
|
|
62
|
+
return fs if isinstance(fs, AsyncFileSystem) else AsyncFileSystemWrapper(fs)
|
|
63
|
+
fs = ctx.agent.env.get_fs()
|
|
64
|
+
return fs if isinstance(fs, AsyncFileSystem) else AsyncFileSystemWrapper(fs)
|
|
65
|
+
|
|
66
|
+
def _resolve_path(self, path: str, ctx: AgentContext) -> str:
|
|
67
|
+
"""Resolve a potentially relative path to an absolute path."""
|
|
68
|
+
cwd: str | None = None
|
|
69
|
+
if self.cwd:
|
|
70
|
+
cwd = self.cwd
|
|
71
|
+
elif self.env and self.env.cwd:
|
|
72
|
+
cwd = self.env.cwd
|
|
73
|
+
elif ctx.agent.env and ctx.agent.env.cwd:
|
|
74
|
+
cwd = ctx.agent.env.cwd
|
|
75
|
+
|
|
76
|
+
if cwd and not (path.startswith("/") or (len(path) > 1 and path[1] == ":")):
|
|
77
|
+
return str(Path(cwd) / path)
|
|
78
|
+
return path
|
|
79
|
+
|
|
80
|
+
async def _list_directory(
|
|
81
|
+
self,
|
|
82
|
+
ctx: AgentContext,
|
|
83
|
+
path: str,
|
|
84
|
+
*,
|
|
85
|
+
pattern: str = "*",
|
|
86
|
+
exclude: list[str] | None = None,
|
|
87
|
+
max_depth: int = 1,
|
|
88
|
+
) -> ToolResult:
|
|
89
|
+
"""List files in a directory with filtering support.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
ctx: Agent context for event emission and filesystem access
|
|
93
|
+
path: Base directory to list
|
|
94
|
+
pattern: Glob pattern to match files against. Use "*.py" to match Python
|
|
95
|
+
files in current directory only, or "**/*.py" to match recursively.
|
|
96
|
+
The max_depth parameter limits how deep "**" patterns search.
|
|
97
|
+
exclude: List of patterns to exclude (uses fnmatch against relative paths)
|
|
98
|
+
max_depth: Maximum directory depth to search (default: 1 = current dir only).
|
|
99
|
+
Only affects recursive "**" patterns.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Markdown-formatted directory listing
|
|
103
|
+
"""
|
|
104
|
+
from agentpool_toolsets.fsspec_toolset.helpers import format_directory_listing
|
|
105
|
+
|
|
106
|
+
path = self._resolve_path(path, ctx)
|
|
107
|
+
msg = f"Listing directory: {path}"
|
|
108
|
+
await ctx.events.tool_call_start(title=msg, kind="read", locations=[path])
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
fs = self._get_fs(ctx)
|
|
112
|
+
# Check if path exists
|
|
113
|
+
if not await fs._exists(path):
|
|
114
|
+
error_msg = f"Path does not exist: {path}"
|
|
115
|
+
await ctx.events.file_operation("list", path=path, success=False, error=error_msg)
|
|
116
|
+
return ToolResult(
|
|
117
|
+
content=f"Error: {error_msg}",
|
|
118
|
+
metadata={"count": 0, "truncated": False},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Build glob path
|
|
122
|
+
glob_pattern = f"{path.rstrip('/')}/{pattern}"
|
|
123
|
+
paths = await fs._glob(glob_pattern, maxdepth=max_depth, detail=True)
|
|
124
|
+
|
|
125
|
+
files: list[dict[str, Any]] = []
|
|
126
|
+
dirs: list[dict[str, Any]] = []
|
|
127
|
+
|
|
128
|
+
# Safety check - prevent returning too many items
|
|
129
|
+
total_found = len(paths)
|
|
130
|
+
if total_found > self.max_items:
|
|
131
|
+
suggestions = []
|
|
132
|
+
if pattern == "*":
|
|
133
|
+
suggestions.append("Use a more specific pattern like '*.py', '*.txt', etc.")
|
|
134
|
+
if max_depth > 1:
|
|
135
|
+
suggestions.append(f"Reduce max_depth from {max_depth} to 1 or 2.")
|
|
136
|
+
if not exclude:
|
|
137
|
+
suggestions.append("Use exclude parameter to filter out unwanted directories.")
|
|
138
|
+
|
|
139
|
+
suggestion_text = " ".join(suggestions) if suggestions else ""
|
|
140
|
+
error_msg = f"Error: Too many items ({total_found:,}). {suggestion_text}"
|
|
141
|
+
return ToolResult(
|
|
142
|
+
content=error_msg,
|
|
143
|
+
metadata={"count": total_found, "truncated": True},
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
for file_path, file_info in paths.items(): # pyright: ignore[reportAttributeAccessIssue]
|
|
147
|
+
rel_path = os.path.relpath(str(file_path), path)
|
|
148
|
+
|
|
149
|
+
# Skip excluded patterns
|
|
150
|
+
if exclude and any(fnmatch(rel_path, pat) for pat in exclude):
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
# Use type from glob detail info, falling back to isdir only if needed
|
|
154
|
+
is_dir = await is_directory(fs, file_path, entry_type=file_info.get("type")) # pyright: ignore[reportArgumentType]
|
|
155
|
+
|
|
156
|
+
item_info = {
|
|
157
|
+
"name": Path(file_path).name, # pyright: ignore[reportArgumentType]
|
|
158
|
+
"path": file_path,
|
|
159
|
+
"relative_path": rel_path,
|
|
160
|
+
"size": file_info.get("size", 0),
|
|
161
|
+
"type": "directory" if is_dir else "file",
|
|
162
|
+
}
|
|
163
|
+
if "mtime" in file_info:
|
|
164
|
+
item_info["modified"] = file_info["mtime"]
|
|
165
|
+
|
|
166
|
+
if is_dir:
|
|
167
|
+
dirs.append(item_info)
|
|
168
|
+
else:
|
|
169
|
+
files.append(item_info)
|
|
170
|
+
|
|
171
|
+
await ctx.events.file_operation("list", path=path, success=True)
|
|
172
|
+
result = format_directory_listing(path, dirs, files, pattern)
|
|
173
|
+
# Emit formatted content for UI display
|
|
174
|
+
from agentpool.agents.events import TextContentItem
|
|
175
|
+
|
|
176
|
+
await ctx.events.tool_call_progress(
|
|
177
|
+
title=f"Listed: {path}",
|
|
178
|
+
items=[TextContentItem(text=result)],
|
|
179
|
+
replace_content=True,
|
|
180
|
+
)
|
|
181
|
+
except (OSError, ValueError, FileNotFoundError) as e:
|
|
182
|
+
await ctx.events.file_operation("list", path=path, success=False, error=str(e))
|
|
183
|
+
error_msg = (
|
|
184
|
+
f"Error: Could not list directory: {path}. Ensure path is absolute and exists."
|
|
185
|
+
)
|
|
186
|
+
return ToolResult(
|
|
187
|
+
content=error_msg,
|
|
188
|
+
metadata={"count": 0, "truncated": False},
|
|
189
|
+
)
|
|
190
|
+
else:
|
|
191
|
+
total_items = len(files) + len(dirs)
|
|
192
|
+
# Check if we hit the limit (truncation)
|
|
193
|
+
was_truncated = total_items >= self.max_items
|
|
194
|
+
return ToolResult(
|
|
195
|
+
content=result,
|
|
196
|
+
metadata={"count": total_items, "truncated": was_truncated},
|
|
197
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""User interaction tool for asking clarifying questions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from agentpool.tool_impls.question.tool import QuestionTool
|
|
8
|
+
from agentpool_config.tools import ToolHints
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = ["QuestionTool", "create_question_tool"]
|
|
12
|
+
|
|
13
|
+
# Tool metadata defaults
|
|
14
|
+
NAME = "question"
|
|
15
|
+
DESCRIPTION = "Ask the user a clarifying question during processing."
|
|
16
|
+
CATEGORY: Literal["other"] = "other"
|
|
17
|
+
HINTS = ToolHints(open_world=True)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def create_question_tool(
|
|
21
|
+
*,
|
|
22
|
+
name: str = NAME,
|
|
23
|
+
description: str = DESCRIPTION,
|
|
24
|
+
requires_confirmation: bool = False,
|
|
25
|
+
) -> QuestionTool:
|
|
26
|
+
"""Create a configured QuestionTool instance.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
name: Tool name override.
|
|
30
|
+
description: Tool description override.
|
|
31
|
+
requires_confirmation: Whether tool execution needs confirmation.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Configured QuestionTool instance.
|
|
35
|
+
"""
|
|
36
|
+
return QuestionTool(
|
|
37
|
+
name=name,
|
|
38
|
+
description=description,
|
|
39
|
+
category=CATEGORY,
|
|
40
|
+
hints=HINTS,
|
|
41
|
+
requires_confirmation=requires_confirmation,
|
|
42
|
+
)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""AskUser tool implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import TYPE_CHECKING, Any, assert_never
|
|
7
|
+
|
|
8
|
+
from agentpool.agents.context import AgentContext # noqa: TC001
|
|
9
|
+
from agentpool.tools.base import Tool, ToolResult
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Awaitable, Callable
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class QuestionOption:
|
|
18
|
+
"""Option for a question in OpenCode format.
|
|
19
|
+
|
|
20
|
+
Represents a single choice that can be presented to the user.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
label: str
|
|
24
|
+
"""Display text (1-5 words, concise)."""
|
|
25
|
+
|
|
26
|
+
description: str
|
|
27
|
+
"""Explanation of the choice."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class OpenCodeQuestionInfo:
|
|
32
|
+
"""Question information in OpenCode format.
|
|
33
|
+
|
|
34
|
+
This matches OpenCode's QuestionInfo schema used by the TUI.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
question: str
|
|
38
|
+
"""Complete question text."""
|
|
39
|
+
|
|
40
|
+
header: str
|
|
41
|
+
"""Very short label (max 12 chars) - used for tab headers in the TUI."""
|
|
42
|
+
|
|
43
|
+
options: list[QuestionOption]
|
|
44
|
+
"""Available choices for the user to select from."""
|
|
45
|
+
|
|
46
|
+
multiple: bool = False
|
|
47
|
+
"""Allow selecting multiple choices."""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class QuestionTool(Tool[ToolResult]):
|
|
52
|
+
"""Tool for asking the user clarifying questions.
|
|
53
|
+
|
|
54
|
+
Enables agents to ask users for additional information or clarification
|
|
55
|
+
when needed to complete a task effectively.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def get_callable(self) -> Callable[..., Awaitable[ToolResult]]:
|
|
59
|
+
"""Get the tool callable."""
|
|
60
|
+
return self._execute
|
|
61
|
+
|
|
62
|
+
async def _execute(
|
|
63
|
+
self,
|
|
64
|
+
ctx: AgentContext,
|
|
65
|
+
prompt: str,
|
|
66
|
+
response_schema: dict[str, Any] | None = None,
|
|
67
|
+
) -> ToolResult:
|
|
68
|
+
"""Ask the user a clarifying question.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
ctx: Agent execution context.
|
|
72
|
+
prompt: Question to ask the user.
|
|
73
|
+
response_schema: Optional JSON schema for structured response.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
The user's response as a string.
|
|
77
|
+
"""
|
|
78
|
+
from mcp.types import ElicitRequestFormParams, ElicitResult, ErrorData
|
|
79
|
+
|
|
80
|
+
schema = response_schema or {"type": "string"}
|
|
81
|
+
params = ElicitRequestFormParams(message=prompt, requestedSchema=schema)
|
|
82
|
+
result = await ctx.handle_elicitation(params)
|
|
83
|
+
|
|
84
|
+
match result:
|
|
85
|
+
case ElicitResult(action="accept", content=content):
|
|
86
|
+
# Content is a dict with "value" key per MCP spec
|
|
87
|
+
if isinstance(content, dict) and "value" in content:
|
|
88
|
+
value = content["value"]
|
|
89
|
+
# Handle list responses (multi-select)
|
|
90
|
+
if isinstance(value, list):
|
|
91
|
+
answer_str = ", ".join(str(v) for v in value)
|
|
92
|
+
# OpenCode expects array of arrays (one per question)
|
|
93
|
+
return ToolResult(
|
|
94
|
+
content=answer_str,
|
|
95
|
+
metadata={
|
|
96
|
+
"answers": [value]
|
|
97
|
+
}, # Single question, array of selected values
|
|
98
|
+
)
|
|
99
|
+
# Single answer
|
|
100
|
+
answer_str = str(value)
|
|
101
|
+
return ToolResult(
|
|
102
|
+
content=answer_str,
|
|
103
|
+
metadata={"answers": [[answer_str]]}, # Single question, single answer
|
|
104
|
+
)
|
|
105
|
+
# Fallback for plain content
|
|
106
|
+
answer_str = str(content)
|
|
107
|
+
return ToolResult(
|
|
108
|
+
content=answer_str,
|
|
109
|
+
metadata={"answers": [[answer_str]]},
|
|
110
|
+
)
|
|
111
|
+
case ElicitResult(action="cancel"):
|
|
112
|
+
return ToolResult(
|
|
113
|
+
content="User cancelled the request",
|
|
114
|
+
metadata={"answers": []},
|
|
115
|
+
)
|
|
116
|
+
case ElicitResult():
|
|
117
|
+
return ToolResult(
|
|
118
|
+
content="User declined to answer",
|
|
119
|
+
metadata={"answers": []},
|
|
120
|
+
)
|
|
121
|
+
case ErrorData(message=message):
|
|
122
|
+
return ToolResult(
|
|
123
|
+
content=f"Error: {message}",
|
|
124
|
+
metadata={"answers": []},
|
|
125
|
+
)
|
|
126
|
+
case _ as unreachable:
|
|
127
|
+
assert_never(unreachable)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Read file tool."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
|
|
7
|
+
from agentpool.tool_impls.read.tool import ReadTool
|
|
8
|
+
from agentpool_config.tools import ToolHints
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from exxec import ExecutionEnvironment
|
|
13
|
+
|
|
14
|
+
from agentpool.prompts.conversion_manager import ConversionManager
|
|
15
|
+
|
|
16
|
+
__all__ = ["ReadTool", "create_read_tool"]
|
|
17
|
+
|
|
18
|
+
# Tool metadata defaults
|
|
19
|
+
NAME = "read"
|
|
20
|
+
DESCRIPTION = """Read the content of a text file, or use vision capabilities
|
|
21
|
+
to read images or documents.
|
|
22
|
+
|
|
23
|
+
Supports:
|
|
24
|
+
- Text files with optional line-based partial reads
|
|
25
|
+
- Binary files (images, PDFs, audio, video) returned as BinaryContent
|
|
26
|
+
- Automatic image resizing for better model compatibility
|
|
27
|
+
- Structure maps for large code files
|
|
28
|
+
- Multiple text encodings"""
|
|
29
|
+
CATEGORY: Literal["read"] = "read"
|
|
30
|
+
HINTS = ToolHints(read_only=True, idempotent=True)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def create_read_tool(
|
|
34
|
+
*,
|
|
35
|
+
env: ExecutionEnvironment | None = None,
|
|
36
|
+
converter: ConversionManager | None = None,
|
|
37
|
+
cwd: str | None = None,
|
|
38
|
+
max_file_size_kb: int = 64,
|
|
39
|
+
max_image_size: int | None = 2000,
|
|
40
|
+
max_image_bytes: int | None = None,
|
|
41
|
+
large_file_tokens: int = 12_000,
|
|
42
|
+
map_max_tokens: int = 2048,
|
|
43
|
+
name: str = NAME,
|
|
44
|
+
description: str = DESCRIPTION,
|
|
45
|
+
requires_confirmation: bool = False,
|
|
46
|
+
) -> ReadTool:
|
|
47
|
+
"""Create a configured ReadTool instance.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
env: Execution environment to use. Falls back to agent.env if not set.
|
|
51
|
+
converter: Optional converter for binary files. If set, converts supported
|
|
52
|
+
file types to markdown instead of returning BinaryContent.
|
|
53
|
+
cwd: Working directory for resolving relative paths.
|
|
54
|
+
max_file_size_kb: Maximum file size in KB for read operations (default: 64KB).
|
|
55
|
+
max_image_size: Max width/height for images in pixels. Larger images are
|
|
56
|
+
auto-resized. Set to None to disable.
|
|
57
|
+
max_image_bytes: Max file size for images in bytes. Images exceeding this
|
|
58
|
+
are compressed. Default: None (uses 4.5MB).
|
|
59
|
+
large_file_tokens: Token threshold for switching to structure map (default: 12000).
|
|
60
|
+
map_max_tokens: Maximum tokens for structure map output (default: 2048).
|
|
61
|
+
name: Tool name override.
|
|
62
|
+
description: Tool description override.
|
|
63
|
+
requires_confirmation: Whether tool execution needs confirmation.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Configured ReadTool instance.
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
# Basic usage
|
|
70
|
+
read = create_read_tool()
|
|
71
|
+
|
|
72
|
+
# With custom limits
|
|
73
|
+
read = create_read_tool(
|
|
74
|
+
max_file_size_kb=128,
|
|
75
|
+
max_image_size=1500,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# With specific environment and cwd
|
|
79
|
+
read = create_read_tool(
|
|
80
|
+
env=my_env,
|
|
81
|
+
cwd="/workspace/project",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# With converter for automatic markdown conversion
|
|
85
|
+
from agentpool.prompts.conversion_manager import ConversionManager
|
|
86
|
+
read = create_read_tool(
|
|
87
|
+
converter=ConversionManager(),
|
|
88
|
+
)
|
|
89
|
+
"""
|
|
90
|
+
return ReadTool(
|
|
91
|
+
name=name,
|
|
92
|
+
description=description,
|
|
93
|
+
category=CATEGORY,
|
|
94
|
+
hints=HINTS,
|
|
95
|
+
env=env,
|
|
96
|
+
converter=converter,
|
|
97
|
+
cwd=cwd,
|
|
98
|
+
max_file_size_kb=max_file_size_kb,
|
|
99
|
+
max_image_size=max_image_size,
|
|
100
|
+
max_image_bytes=max_image_bytes,
|
|
101
|
+
large_file_tokens=large_file_tokens,
|
|
102
|
+
map_max_tokens=map_max_tokens,
|
|
103
|
+
requires_confirmation=requires_confirmation,
|
|
104
|
+
)
|