agentpool 2.2.3__py3-none-any.whl → 2.5.0__py3-none-any.whl

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