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
|
@@ -8,17 +8,19 @@ from typing import TYPE_CHECKING, Any, Self
|
|
|
8
8
|
|
|
9
9
|
from agentpool.log import get_logger
|
|
10
10
|
from agentpool.resource_providers import ResourceProvider
|
|
11
|
+
from agentpool.resource_providers.resource_info import ResourceInfo
|
|
11
12
|
from agentpool_config.mcp_server import BaseMCPServerConfig
|
|
12
|
-
from agentpool_config.resources import ResourceInfo
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Sequence
|
|
16
17
|
from typing import Literal
|
|
17
18
|
|
|
18
19
|
from fastmcp.client.sampling import SamplingHandler
|
|
20
|
+
from mcp.types import ResourceTemplate
|
|
19
21
|
|
|
20
22
|
from agentpool.prompts.prompts import MCPClientPrompt
|
|
21
|
-
from agentpool.tools.base import Tool
|
|
23
|
+
from agentpool.tools.base import FunctionTool, Tool
|
|
22
24
|
from agentpool_config.mcp_server import MCPServerConfig
|
|
23
25
|
|
|
24
26
|
|
|
@@ -28,6 +30,8 @@ logger = get_logger(__name__)
|
|
|
28
30
|
class MCPResourceProvider(ResourceProvider):
|
|
29
31
|
"""Resource provider for a single MCP server."""
|
|
30
32
|
|
|
33
|
+
kind = "mcp"
|
|
34
|
+
|
|
31
35
|
def __init__(
|
|
32
36
|
self,
|
|
33
37
|
server: MCPServerConfig | str,
|
|
@@ -43,19 +47,14 @@ class MCPResourceProvider(ResourceProvider):
|
|
|
43
47
|
self.server = BaseMCPServerConfig.from_string(server) if isinstance(server, str) else server
|
|
44
48
|
self.source = source
|
|
45
49
|
self.exit_stack = AsyncExitStack()
|
|
50
|
+
|
|
46
51
|
self._accessible_roots = accessible_roots
|
|
47
52
|
self._sampling_callback = sampling_callback
|
|
48
53
|
|
|
49
|
-
# Tool caching
|
|
50
|
-
self._tools_cache: list[Tool] | None = None
|
|
51
54
|
self._saved_enabled_states: dict[str, bool] = {}
|
|
52
|
-
|
|
53
|
-
# Prompt caching
|
|
55
|
+
self._tools_cache: list[FunctionTool] | None = None
|
|
54
56
|
self._prompts_cache: list[MCPClientPrompt] | None = None
|
|
55
|
-
|
|
56
|
-
# Resource caching
|
|
57
57
|
self._resources_cache: list[ResourceInfo] | None = None
|
|
58
|
-
|
|
59
58
|
self.client = MCPClient(
|
|
60
59
|
config=self.server,
|
|
61
60
|
sampling_callback=self._sampling_callback,
|
|
@@ -104,23 +103,29 @@ class MCPResourceProvider(ResourceProvider):
|
|
|
104
103
|
logger.info("MCP tool list changed, refreshing provider cache")
|
|
105
104
|
self._saved_enabled_states = {t.name: t.enabled for t in self._tools_cache or []}
|
|
106
105
|
self._tools_cache = None
|
|
106
|
+
# Notify subscribers via signal
|
|
107
|
+
await self.tools_changed.emit(self.create_change_event("tools"))
|
|
107
108
|
|
|
108
109
|
async def _on_prompts_changed(self) -> None:
|
|
109
110
|
"""Callback when prompts change on the MCP server."""
|
|
110
111
|
logger.info("MCP prompt list changed, refreshing provider cache")
|
|
111
112
|
self._prompts_cache = None
|
|
113
|
+
# Notify subscribers via signal
|
|
114
|
+
await self.prompts_changed.emit(self.create_change_event("prompts"))
|
|
112
115
|
|
|
113
116
|
async def _on_resources_changed(self) -> None:
|
|
114
117
|
"""Callback when resources change on the MCP server."""
|
|
115
118
|
logger.info("MCP resource list changed, refreshing provider cache")
|
|
116
119
|
self._resources_cache = None
|
|
120
|
+
# Notify subscribers via signal
|
|
121
|
+
await self.resources_changed.emit(self.create_change_event("resources"))
|
|
117
122
|
|
|
118
123
|
async def refresh_tools_cache(self) -> None:
|
|
119
124
|
"""Refresh the tools cache by fetching from client."""
|
|
120
125
|
try:
|
|
121
126
|
# Get fresh tools from client
|
|
122
127
|
mcp_tools = await self.client.list_tools()
|
|
123
|
-
all_tools: list[
|
|
128
|
+
all_tools: list[FunctionTool] = []
|
|
124
129
|
|
|
125
130
|
for tool in mcp_tools:
|
|
126
131
|
try:
|
|
@@ -141,7 +146,7 @@ class MCPResourceProvider(ResourceProvider):
|
|
|
141
146
|
logger.exception("Failed to refresh MCP tools cache")
|
|
142
147
|
self._tools_cache = []
|
|
143
148
|
|
|
144
|
-
async def get_tools(self) ->
|
|
149
|
+
async def get_tools(self) -> Sequence[Tool]:
|
|
145
150
|
"""Get cached tools, refreshing if necessary."""
|
|
146
151
|
if self._tools_cache is None:
|
|
147
152
|
await self.refresh_tools_cache()
|
|
@@ -185,7 +190,11 @@ class MCPResourceProvider(ResourceProvider):
|
|
|
185
190
|
|
|
186
191
|
for resource in result:
|
|
187
192
|
try:
|
|
188
|
-
converted = await ResourceInfo.from_mcp_resource(
|
|
193
|
+
converted = await ResourceInfo.from_mcp_resource(
|
|
194
|
+
resource,
|
|
195
|
+
client_name=self.name,
|
|
196
|
+
reader=self.read_resource,
|
|
197
|
+
)
|
|
189
198
|
all_resources.append(converted)
|
|
190
199
|
except Exception:
|
|
191
200
|
logger.exception("Failed to convert resource", name=resource.name)
|
|
@@ -204,6 +213,74 @@ class MCPResourceProvider(ResourceProvider):
|
|
|
204
213
|
|
|
205
214
|
return self._resources_cache or []
|
|
206
215
|
|
|
216
|
+
async def read_resource(self, uri: str) -> list[str]:
|
|
217
|
+
"""Read resource content by URI.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
uri: URI of the resource to read
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
List of text contents from the resource
|
|
224
|
+
|
|
225
|
+
Raises:
|
|
226
|
+
RuntimeError: If resource cannot be read
|
|
227
|
+
"""
|
|
228
|
+
contents = await self.client.read_resource(uri)
|
|
229
|
+
result: list[str] = []
|
|
230
|
+
for content in contents:
|
|
231
|
+
if hasattr(content, "text") and content.text is not None:
|
|
232
|
+
result.append(str(content.text))
|
|
233
|
+
elif hasattr(content, "blob") and content.blob is not None:
|
|
234
|
+
# Binary content - return placeholder or base64
|
|
235
|
+
import base64
|
|
236
|
+
|
|
237
|
+
blob_data = content.blob
|
|
238
|
+
if isinstance(blob_data, str):
|
|
239
|
+
result.append(f"[Binary data: {len(blob_data)} bytes]")
|
|
240
|
+
elif isinstance(blob_data, bytes):
|
|
241
|
+
encoded = base64.b64encode(blob_data).decode("utf-8")
|
|
242
|
+
result.append(encoded)
|
|
243
|
+
else:
|
|
244
|
+
result.append("[Binary data: unknown format]")
|
|
245
|
+
return result
|
|
246
|
+
|
|
247
|
+
async def list_resource_templates(self) -> list[ResourceTemplate]:
|
|
248
|
+
"""Get available resource templates from the MCP server.
|
|
249
|
+
|
|
250
|
+
Resource templates define URI patterns with placeholders that can be
|
|
251
|
+
expanded into concrete resource URIs. For example:
|
|
252
|
+
- Template: "file:///{path}" with path="config.json"
|
|
253
|
+
- Expands to: "file:///config.json"
|
|
254
|
+
|
|
255
|
+
TODO: Decide on integration strategy:
|
|
256
|
+
- Option 1: Templates as separate concept with expand() -> ResourceInfo
|
|
257
|
+
- Option 2: Unified ResourceInfo with is_template flag and read(**kwargs)
|
|
258
|
+
- Option 3: ResourceTemplateInfo class that produces ResourceInfo
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
List of ResourceTemplate objects from the server
|
|
262
|
+
"""
|
|
263
|
+
try:
|
|
264
|
+
return await self.client.list_resource_templates()
|
|
265
|
+
except Exception:
|
|
266
|
+
logger.exception("Failed to list resource templates")
|
|
267
|
+
return []
|
|
268
|
+
|
|
269
|
+
def get_status(self) -> dict[str, str]:
|
|
270
|
+
"""Get connection status for this MCP server.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Status dict with 'status' key and optionally 'error' key.
|
|
274
|
+
Status can be: 'connected', 'disabled', or 'failed'.
|
|
275
|
+
"""
|
|
276
|
+
try:
|
|
277
|
+
if self.client.connected:
|
|
278
|
+
return {"status": "connected"}
|
|
279
|
+
except Exception as e: # noqa: BLE001
|
|
280
|
+
return {"status": "failed", "error": str(e)}
|
|
281
|
+
else:
|
|
282
|
+
return {"status": "disabled"}
|
|
283
|
+
|
|
207
284
|
|
|
208
285
|
if __name__ == "__main__":
|
|
209
286
|
import anyio
|
|
@@ -3,19 +3,26 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
-
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
7
7
|
|
|
8
8
|
from agentpool.agents.context import AgentContext # noqa: TC001
|
|
9
|
+
from agentpool.agents.events import TextContentItem
|
|
9
10
|
from agentpool.resource_providers import ResourceProvider
|
|
11
|
+
from agentpool.tools.base import ToolResult
|
|
12
|
+
from agentpool.utils.streams import TodoPriority, TodoStatus # noqa: TC001
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Sequence
|
|
17
|
+
|
|
13
18
|
from agentpool.tools.base import Tool
|
|
19
|
+
from agentpool.utils.streams import TodoTracker
|
|
14
20
|
|
|
15
21
|
|
|
16
|
-
#
|
|
22
|
+
# Keep PlanEntry for backward compatibility with event emitting
|
|
17
23
|
PlanEntryPriority = Literal["high", "medium", "low"]
|
|
18
24
|
PlanEntryStatus = Literal["pending", "in_progress", "completed"]
|
|
25
|
+
PlanToolMode = Literal["granular", "declarative"]
|
|
19
26
|
|
|
20
27
|
|
|
21
28
|
@dataclass(kw_only=True)
|
|
@@ -50,24 +57,46 @@ class PlanProvider(ResourceProvider):
|
|
|
50
57
|
"""Provides plan-related tools for agent planning and task management.
|
|
51
58
|
|
|
52
59
|
This provider creates tools for managing agent plans and tasks,
|
|
53
|
-
|
|
60
|
+
delegating storage to pool.todos and emitting domain events
|
|
61
|
+
that can be handled by protocol adapters.
|
|
54
62
|
"""
|
|
55
63
|
|
|
56
|
-
|
|
57
|
-
|
|
64
|
+
kind = "tools"
|
|
65
|
+
|
|
66
|
+
def __init__(self, mode: PlanToolMode = "declarative") -> None:
|
|
67
|
+
"""Initialize plan provider.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
mode: Tool mode - 'granular' for separate tools, 'declarative' for
|
|
71
|
+
single set_plan tool.
|
|
72
|
+
"""
|
|
58
73
|
super().__init__(name="plan")
|
|
59
|
-
self.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"""Get
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
self.create_tool(self.update_plan_entry, category="edit"),
|
|
67
|
-
self.create_tool(self.remove_plan_entry, category="delete"),
|
|
68
|
-
]
|
|
74
|
+
self.mode = mode
|
|
75
|
+
|
|
76
|
+
def _get_tracker(self, agent_ctx: AgentContext) -> TodoTracker | None:
|
|
77
|
+
"""Get the TodoTracker from the pool."""
|
|
78
|
+
if agent_ctx.pool is not None:
|
|
79
|
+
return agent_ctx.pool.todos
|
|
80
|
+
return None
|
|
69
81
|
|
|
70
|
-
async def
|
|
82
|
+
async def get_tools(self) -> Sequence[Tool]:
|
|
83
|
+
"""Get plan management tools based on mode."""
|
|
84
|
+
tools: list[Tool] = [self.create_tool(self.get_plan, category="read")]
|
|
85
|
+
|
|
86
|
+
if self.mode == "declarative":
|
|
87
|
+
# Single bulk tool for capable models
|
|
88
|
+
tools.append(self.create_tool(self.set_plan, category="other"))
|
|
89
|
+
else:
|
|
90
|
+
# granular mode (default) - separate tools for simpler models
|
|
91
|
+
tools.extend([
|
|
92
|
+
self.create_tool(self.add_plan_entry, category="other"),
|
|
93
|
+
self.create_tool(self.update_plan_entry, category="edit"),
|
|
94
|
+
self.create_tool(self.remove_plan_entry, category="delete"),
|
|
95
|
+
])
|
|
96
|
+
|
|
97
|
+
return tools
|
|
98
|
+
|
|
99
|
+
async def get_plan(self, agent_ctx: AgentContext) -> ToolResult:
|
|
71
100
|
"""Get the current plan formatted as markdown.
|
|
72
101
|
|
|
73
102
|
Args:
|
|
@@ -76,8 +105,17 @@ class PlanProvider(ResourceProvider):
|
|
|
76
105
|
Returns:
|
|
77
106
|
Markdown-formatted plan with all entries and their status
|
|
78
107
|
"""
|
|
79
|
-
|
|
80
|
-
|
|
108
|
+
tracker = self._get_tracker(agent_ctx)
|
|
109
|
+
if tracker is None or not tracker.entries:
|
|
110
|
+
# Emit progress for empty plan
|
|
111
|
+
await agent_ctx.events.tool_call_progress(
|
|
112
|
+
title="Fetched plan (empty)",
|
|
113
|
+
items=[TextContentItem(text="*No tasks in plan yet.*")],
|
|
114
|
+
)
|
|
115
|
+
return ToolResult(
|
|
116
|
+
content="## Plan\n\n*No plan entries yet.",
|
|
117
|
+
metadata={"todos": []},
|
|
118
|
+
)
|
|
81
119
|
|
|
82
120
|
lines = ["## Plan", ""]
|
|
83
121
|
status_icons = {
|
|
@@ -90,18 +128,117 @@ class PlanProvider(ResourceProvider):
|
|
|
90
128
|
"medium": "🟡",
|
|
91
129
|
"low": "🟢",
|
|
92
130
|
}
|
|
93
|
-
for i, entry in enumerate(
|
|
131
|
+
for i, entry in enumerate(tracker.entries):
|
|
94
132
|
icon = status_icons.get(entry.status, "?")
|
|
95
133
|
priority = priority_labels.get(entry.priority, "")
|
|
96
134
|
lines.append(f"{i}. {icon} {priority} {entry.content} *({entry.status})*")
|
|
97
135
|
|
|
98
|
-
|
|
136
|
+
# Count completed entries for summary
|
|
137
|
+
completed = sum(1 for e in tracker.entries if e.status == "completed")
|
|
138
|
+
total = len(tracker.entries)
|
|
139
|
+
|
|
140
|
+
# Build title with summary
|
|
141
|
+
title = "Fetched plan with 1 task" if total == 1 else f"Fetched plan with {total} tasks"
|
|
142
|
+
if completed > 0:
|
|
143
|
+
title += f" ({completed} completed)"
|
|
144
|
+
|
|
145
|
+
# Emit progress with plan preview
|
|
146
|
+
plan_text = "\n".join(lines)
|
|
147
|
+
await agent_ctx.events.tool_call_progress(
|
|
148
|
+
title=title,
|
|
149
|
+
items=[TextContentItem(text=plan_text)],
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Convert to OpenCode format for metadata
|
|
153
|
+
todos = [{"content": e.content, "status": e.status} for e in tracker.entries]
|
|
154
|
+
|
|
155
|
+
return ToolResult(
|
|
156
|
+
content=plan_text,
|
|
157
|
+
metadata={"todos": todos},
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
async def set_plan(
|
|
161
|
+
self,
|
|
162
|
+
agent_ctx: AgentContext,
|
|
163
|
+
entries: list[dict[str, Any]],
|
|
164
|
+
) -> ToolResult:
|
|
165
|
+
"""Replace the entire plan with new entries (declarative/bulk update).
|
|
166
|
+
|
|
167
|
+
This is more efficient than multiple add/update calls when setting
|
|
168
|
+
or significantly modifying the plan.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
agent_ctx: Agent execution context
|
|
172
|
+
entries: List of plan entries, each with:
|
|
173
|
+
- content (str, required): Task description
|
|
174
|
+
- priority (str, optional): "high", "medium", or "low" (default: "medium")
|
|
175
|
+
- status (str, optional): "pending", "in_progress", or "completed"
|
|
176
|
+
(default: "pending")
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Success message indicating plan was updated
|
|
180
|
+
"""
|
|
181
|
+
tracker = self._get_tracker(agent_ctx)
|
|
182
|
+
if tracker is None:
|
|
183
|
+
return ToolResult(
|
|
184
|
+
content="Error: No pool available for plan tracking",
|
|
185
|
+
metadata={"todos": []},
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Clear existing entries
|
|
189
|
+
tracker.clear()
|
|
190
|
+
|
|
191
|
+
# Add all new entries
|
|
192
|
+
for entry in entries:
|
|
193
|
+
content = entry.get("content", "")
|
|
194
|
+
if not content:
|
|
195
|
+
continue
|
|
196
|
+
priority = entry.get("priority", "medium")
|
|
197
|
+
status = entry.get("status", "pending")
|
|
198
|
+
tracker.add(content, priority=priority, status=status)
|
|
199
|
+
|
|
200
|
+
await self._emit_plan_update(agent_ctx)
|
|
201
|
+
|
|
202
|
+
# Build summary for user feedback
|
|
203
|
+
entry_count = len(tracker.entries)
|
|
204
|
+
if entry_count == 0:
|
|
205
|
+
title = "Cleared plan"
|
|
206
|
+
elif entry_count == 1:
|
|
207
|
+
title = "Set plan with 1 task"
|
|
208
|
+
else:
|
|
209
|
+
title = f"Set plan with {entry_count} tasks"
|
|
210
|
+
|
|
211
|
+
# Format entries list for details
|
|
212
|
+
if tracker.entries:
|
|
213
|
+
lines = ["**New Plan:**"]
|
|
214
|
+
for i, e in enumerate(tracker.entries):
|
|
215
|
+
priority_emoji = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(e.priority, "")
|
|
216
|
+
status_emoji = {"pending": "⬚", "in_progress": "◐", "completed": "✓"}.get(
|
|
217
|
+
e.status, ""
|
|
218
|
+
)
|
|
219
|
+
lines.append(f"{i + 1}. {priority_emoji} {status_emoji} {e.content}")
|
|
220
|
+
details = "\n".join(lines)
|
|
221
|
+
else:
|
|
222
|
+
details = "*Plan is empty*"
|
|
223
|
+
|
|
224
|
+
await agent_ctx.events.tool_call_progress(
|
|
225
|
+
title=title,
|
|
226
|
+
items=[TextContentItem(text=details)],
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Convert to OpenCode format for metadata
|
|
230
|
+
todos = [{"content": e.content, "status": e.status} for e in tracker.entries]
|
|
231
|
+
|
|
232
|
+
return ToolResult(
|
|
233
|
+
content=f"Plan updated with {entry_count} entries",
|
|
234
|
+
metadata={"todos": todos},
|
|
235
|
+
)
|
|
99
236
|
|
|
100
237
|
async def add_plan_entry(
|
|
101
238
|
self,
|
|
102
239
|
agent_ctx: AgentContext,
|
|
103
240
|
content: str,
|
|
104
|
-
priority:
|
|
241
|
+
priority: TodoPriority = "medium",
|
|
105
242
|
index: int | None = None,
|
|
106
243
|
) -> str:
|
|
107
244
|
"""Add a new plan entry.
|
|
@@ -115,18 +252,24 @@ class PlanProvider(ResourceProvider):
|
|
|
115
252
|
Returns:
|
|
116
253
|
Success message indicating entry was added
|
|
117
254
|
"""
|
|
118
|
-
|
|
119
|
-
if
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return f"Error: Index {index} out of range (0-{len(self._current_plan)})"
|
|
125
|
-
self._current_plan.insert(index, entry)
|
|
126
|
-
entry_index = index
|
|
255
|
+
tracker = self._get_tracker(agent_ctx)
|
|
256
|
+
if tracker is None:
|
|
257
|
+
return "Error: No pool available for plan tracking"
|
|
258
|
+
|
|
259
|
+
entry = tracker.add(content, priority=priority, index=index)
|
|
260
|
+
entry_index = tracker.entries.index(entry)
|
|
127
261
|
|
|
128
262
|
await self._emit_plan_update(agent_ctx)
|
|
129
263
|
|
|
264
|
+
# User feedback
|
|
265
|
+
priority_emoji = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(priority, "")
|
|
266
|
+
title = f"Added task {entry_index + 1} {priority_emoji}"
|
|
267
|
+
details = f"**Task {entry_index + 1}**: {content}"
|
|
268
|
+
await agent_ctx.events.tool_call_progress(
|
|
269
|
+
title=title,
|
|
270
|
+
items=[TextContentItem(text=details)],
|
|
271
|
+
)
|
|
272
|
+
|
|
130
273
|
return f"Added plan entry at index {entry_index}: {content!r} (priority={priority!r})"
|
|
131
274
|
|
|
132
275
|
async def update_plan_entry(
|
|
@@ -134,8 +277,8 @@ class PlanProvider(ResourceProvider):
|
|
|
134
277
|
agent_ctx: AgentContext,
|
|
135
278
|
index: int,
|
|
136
279
|
content: str | None = None,
|
|
137
|
-
status:
|
|
138
|
-
priority:
|
|
280
|
+
status: TodoStatus | None = None,
|
|
281
|
+
priority: TodoPriority | None = None,
|
|
139
282
|
) -> str:
|
|
140
283
|
"""Update an existing plan entry.
|
|
141
284
|
|
|
@@ -149,28 +292,40 @@ class PlanProvider(ResourceProvider):
|
|
|
149
292
|
Returns:
|
|
150
293
|
Success message indicating what was updated
|
|
151
294
|
"""
|
|
152
|
-
|
|
153
|
-
|
|
295
|
+
tracker = self._get_tracker(agent_ctx)
|
|
296
|
+
if tracker is None:
|
|
297
|
+
return "Error: No pool available for plan tracking"
|
|
154
298
|
|
|
155
|
-
|
|
156
|
-
|
|
299
|
+
if index < 0 or index >= len(tracker.entries):
|
|
300
|
+
return f"Error: Index {index} out of range (0-{len(tracker.entries) - 1})"
|
|
157
301
|
|
|
302
|
+
updates = []
|
|
158
303
|
if content is not None:
|
|
159
|
-
entry.content = content
|
|
160
304
|
updates.append(f"content to {content!r}")
|
|
161
|
-
|
|
162
305
|
if status is not None:
|
|
163
|
-
entry.status = status
|
|
164
306
|
updates.append(f"status to {status!r}")
|
|
165
|
-
|
|
166
307
|
if priority is not None:
|
|
167
|
-
entry.priority = priority
|
|
168
308
|
updates.append(f"priority to {priority!r}")
|
|
169
309
|
|
|
170
310
|
if not updates:
|
|
171
311
|
return "No changes specified"
|
|
172
312
|
|
|
313
|
+
tracker.update_by_index(index, content=content, status=status, priority=priority)
|
|
314
|
+
|
|
173
315
|
await self._emit_plan_update(agent_ctx)
|
|
316
|
+
|
|
317
|
+
# Build title with key info
|
|
318
|
+
entry = tracker.entries[index]
|
|
319
|
+
status_emoji = {"pending": "⬚", "in_progress": "◐", "completed": "✓"}.get(entry.status, "")
|
|
320
|
+
title = f"Updated task {index + 1} {status_emoji}"
|
|
321
|
+
|
|
322
|
+
# Send detailed content
|
|
323
|
+
details = f"**Task {index + 1}**: {entry.content}\n\nChanges: {', '.join(updates)}"
|
|
324
|
+
await agent_ctx.events.tool_call_progress(
|
|
325
|
+
title=title,
|
|
326
|
+
items=[TextContentItem(text=details)],
|
|
327
|
+
)
|
|
328
|
+
|
|
174
329
|
return f"Updated entry {index}: {', '.join(updates)}"
|
|
175
330
|
|
|
176
331
|
async def remove_plan_entry(self, agent_ctx: AgentContext, index: int) -> str:
|
|
@@ -183,14 +338,41 @@ class PlanProvider(ResourceProvider):
|
|
|
183
338
|
Returns:
|
|
184
339
|
Success message indicating entry was removed
|
|
185
340
|
"""
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
341
|
+
tracker = self._get_tracker(agent_ctx)
|
|
342
|
+
if tracker is None:
|
|
343
|
+
return "Error: No pool available for plan tracking"
|
|
344
|
+
|
|
345
|
+
if index < 0 or index >= len(tracker.entries):
|
|
346
|
+
return f"Error: Index {index} out of range (0-{len(tracker.entries) - 1})"
|
|
347
|
+
|
|
348
|
+
removed_entry = tracker.remove_by_index(index)
|
|
189
349
|
await self._emit_plan_update(agent_ctx)
|
|
190
|
-
|
|
350
|
+
|
|
351
|
+
if removed_entry is None:
|
|
352
|
+
return f"Error: Could not remove entry at index {index}"
|
|
353
|
+
|
|
354
|
+
# User feedback
|
|
355
|
+
remaining = len(tracker.entries)
|
|
356
|
+
title = f"Removed task {index + 1}"
|
|
357
|
+
details = f"**Removed**: {removed_entry.content}\n\nRemaining tasks: {remaining}"
|
|
358
|
+
await agent_ctx.events.tool_call_progress(
|
|
359
|
+
title=title,
|
|
360
|
+
items=[TextContentItem(text=details)],
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
if tracker.entries:
|
|
191
364
|
return f"Removed entry {index}: {removed_entry.content!r}, remaining entries reindexed"
|
|
192
365
|
return f"Removed entry {index}: {removed_entry.content!r}, plan is now empty"
|
|
193
366
|
|
|
194
367
|
async def _emit_plan_update(self, agent_ctx: AgentContext) -> None:
|
|
195
368
|
"""Emit plan update event."""
|
|
196
|
-
|
|
369
|
+
tracker = self._get_tracker(agent_ctx)
|
|
370
|
+
if tracker is None:
|
|
371
|
+
return
|
|
372
|
+
|
|
373
|
+
# Convert TodoEntry to PlanEntry for event compatibility
|
|
374
|
+
entries = [
|
|
375
|
+
PlanEntry(content=e.content, priority=e.priority, status=e.status)
|
|
376
|
+
for e in tracker.entries
|
|
377
|
+
]
|
|
378
|
+
await agent_ctx.events.plan_updated(entries)
|
|
@@ -9,10 +9,12 @@ from agentpool.resource_providers import ResourceProvider
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
|
|
12
14
|
from agentpool import AgentPool
|
|
13
15
|
from agentpool.prompts.prompts import BasePrompt
|
|
14
|
-
from agentpool.
|
|
15
|
-
from
|
|
16
|
+
from agentpool.resource_providers.resource_info import ResourceInfo
|
|
17
|
+
from agentpool.tools import Tool
|
|
16
18
|
|
|
17
19
|
logger = get_logger(__name__)
|
|
18
20
|
|
|
@@ -20,6 +22,8 @@ logger = get_logger(__name__)
|
|
|
20
22
|
class PoolResourceProvider(ResourceProvider):
|
|
21
23
|
"""Provider that exposes an AgentPool's resources."""
|
|
22
24
|
|
|
25
|
+
kind = "tools"
|
|
26
|
+
|
|
23
27
|
def __init__(
|
|
24
28
|
self,
|
|
25
29
|
pool: AgentPool[Any],
|
|
@@ -41,7 +45,7 @@ class PoolResourceProvider(ResourceProvider):
|
|
|
41
45
|
self.zed_mode = zed_mode
|
|
42
46
|
self.include_team_members = include_team_members
|
|
43
47
|
|
|
44
|
-
async def get_tools(self) ->
|
|
48
|
+
async def get_tools(self) -> Sequence[Tool]:
|
|
45
49
|
"""Get tools from all agents in pool."""
|
|
46
50
|
team_tools = [team.to_tool() for team in self.pool.teams.values()]
|
|
47
51
|
agents = list(self.pool.agents.values())
|