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
|
@@ -9,7 +9,7 @@ This is the reverse of the conversion done in acp_server/session.py handle_event
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
import base64
|
|
12
|
-
from typing import TYPE_CHECKING, Any, overload
|
|
12
|
+
from typing import TYPE_CHECKING, Any, assert_never, overload
|
|
13
13
|
|
|
14
14
|
from pydantic_ai import (
|
|
15
15
|
AudioUrl,
|
|
@@ -53,12 +53,24 @@ from agentpool.agents.events import (
|
|
|
53
53
|
if TYPE_CHECKING:
|
|
54
54
|
from collections.abc import Sequence
|
|
55
55
|
|
|
56
|
-
from pydantic_ai import UserContent
|
|
57
|
-
|
|
58
|
-
from acp.schema import
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
from pydantic_ai import FinishReason, UserContent
|
|
57
|
+
|
|
58
|
+
from acp.schema import (
|
|
59
|
+
ContentBlock,
|
|
60
|
+
HttpMcpServer,
|
|
61
|
+
McpServer,
|
|
62
|
+
SessionConfigOption,
|
|
63
|
+
SessionModelState,
|
|
64
|
+
SessionModeState,
|
|
65
|
+
SessionUpdate,
|
|
66
|
+
SseMcpServer,
|
|
67
|
+
StdioMcpServer,
|
|
68
|
+
StopReason,
|
|
69
|
+
ToolCallContent,
|
|
70
|
+
ToolCallLocation,
|
|
71
|
+
)
|
|
61
72
|
from agentpool.agents.events import RichAgentStreamEvent, ToolCallContentItem
|
|
73
|
+
from agentpool.agents.modes import ModeCategory, ModeInfo
|
|
62
74
|
from agentpool_config.mcp_server import (
|
|
63
75
|
MCPServerConfig,
|
|
64
76
|
SSEMCPServerConfig,
|
|
@@ -66,15 +78,122 @@ if TYPE_CHECKING:
|
|
|
66
78
|
StreamableHTTPMCPServerConfig,
|
|
67
79
|
)
|
|
68
80
|
|
|
81
|
+
STOP_REASON_MAP: dict[StopReason, FinishReason] = {
|
|
82
|
+
"end_turn": "stop",
|
|
83
|
+
"max_tokens": "length",
|
|
84
|
+
"max_turn_requests": "length",
|
|
85
|
+
"refusal": "content_filter",
|
|
86
|
+
"cancelled": "error",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_modes(
|
|
91
|
+
config_options: list[SessionConfigOption],
|
|
92
|
+
available_modes: SessionModeState | None,
|
|
93
|
+
available_models: SessionModelState | None,
|
|
94
|
+
) -> list[ModeCategory]:
|
|
95
|
+
from acp.schema import SessionConfigSelectGroup
|
|
96
|
+
from agentpool.agents.modes import ModeCategory, ModeInfo
|
|
97
|
+
|
|
98
|
+
categories: list[ModeCategory] = []
|
|
99
|
+
|
|
100
|
+
if config_options:
|
|
101
|
+
for config_opt in config_options:
|
|
102
|
+
# Extract options from the config (ungrouped or grouped)
|
|
103
|
+
mode_infos: list[ModeInfo] = []
|
|
104
|
+
if isinstance(config_opt.options, list):
|
|
105
|
+
for opt_item in config_opt.options:
|
|
106
|
+
if isinstance(opt_item, SessionConfigSelectGroup):
|
|
107
|
+
mode_infos.extend(
|
|
108
|
+
ModeInfo(
|
|
109
|
+
id=sub_opt.value,
|
|
110
|
+
name=sub_opt.name,
|
|
111
|
+
description=sub_opt.description or "",
|
|
112
|
+
category_id=config_opt.id,
|
|
113
|
+
)
|
|
114
|
+
for sub_opt in opt_item.options
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
# Ungrouped options
|
|
118
|
+
mode_infos.append(
|
|
119
|
+
ModeInfo(
|
|
120
|
+
id=opt_item.value,
|
|
121
|
+
name=opt_item.name,
|
|
122
|
+
description=opt_item.description or "",
|
|
123
|
+
category_id=config_opt.id,
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
categories.append(
|
|
128
|
+
ModeCategory(
|
|
129
|
+
id=config_opt.id,
|
|
130
|
+
name=config_opt.name,
|
|
131
|
+
available_modes=mode_infos,
|
|
132
|
+
current_mode_id=config_opt.current_value,
|
|
133
|
+
category=config_opt.category or "other",
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
return categories
|
|
137
|
+
|
|
138
|
+
# Legacy: Convert ACP SessionModeState to ModeCategory
|
|
139
|
+
if available_modes:
|
|
140
|
+
acp_modes = available_modes
|
|
141
|
+
modes = [
|
|
142
|
+
ModeInfo(
|
|
143
|
+
id=m.id,
|
|
144
|
+
name=m.name,
|
|
145
|
+
description=m.description or "",
|
|
146
|
+
category_id="permissions",
|
|
147
|
+
)
|
|
148
|
+
for m in acp_modes.available_modes
|
|
149
|
+
]
|
|
150
|
+
categories.append(
|
|
151
|
+
ModeCategory(
|
|
152
|
+
id="permissions",
|
|
153
|
+
name="Mode",
|
|
154
|
+
available_modes=modes,
|
|
155
|
+
current_mode_id=acp_modes.current_mode_id,
|
|
156
|
+
category="mode",
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Legacy: Convert ACP SessionModelState to ModeCategory
|
|
161
|
+
if available_models:
|
|
162
|
+
acp_models = available_models
|
|
163
|
+
models = [
|
|
164
|
+
ModeInfo(
|
|
165
|
+
id=m.model_id,
|
|
166
|
+
name=m.name,
|
|
167
|
+
description=m.description or "",
|
|
168
|
+
category_id="model",
|
|
169
|
+
)
|
|
170
|
+
for m in acp_models.available_models
|
|
171
|
+
]
|
|
172
|
+
categories.append(
|
|
173
|
+
ModeCategory(
|
|
174
|
+
id="model",
|
|
175
|
+
name="Model",
|
|
176
|
+
available_modes=models,
|
|
177
|
+
current_mode_id=acp_models.current_model_id,
|
|
178
|
+
category="model",
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
return categories
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def to_finish_reason(stop_reason: StopReason) -> FinishReason:
|
|
186
|
+
return STOP_REASON_MAP.get(stop_reason, "stop")
|
|
187
|
+
|
|
69
188
|
|
|
70
189
|
def convert_acp_locations(
|
|
71
|
-
locations:
|
|
190
|
+
locations: Sequence[ToolCallLocation] | None,
|
|
72
191
|
) -> list[LocationContentItem]:
|
|
73
192
|
"""Convert ACP ToolCallLocation list to native LocationContentItem list."""
|
|
74
193
|
return [LocationContentItem(path=loc.path, line=loc.line) for loc in locations or []]
|
|
75
194
|
|
|
76
195
|
|
|
77
|
-
def convert_acp_content(content:
|
|
196
|
+
def convert_acp_content(content: Sequence[ToolCallContent] | None) -> list[ToolCallContentItem]:
|
|
78
197
|
"""Convert ACP ToolCallContent list to native ToolCallContentItem list."""
|
|
79
198
|
if not content:
|
|
80
199
|
return []
|
|
@@ -189,42 +308,70 @@ def acp_to_native_event(update: SessionUpdate) -> RichAgentStreamEvent[Any] | No
|
|
|
189
308
|
# Text message chunks -> PartDeltaEvent with TextPartDelta
|
|
190
309
|
case AgentMessageChunk(content=TextContentBlock(text=text)):
|
|
191
310
|
return PartDeltaEvent(index=0, delta=TextPartDelta(content_delta=text))
|
|
311
|
+
|
|
192
312
|
# Thought chunks -> PartDeltaEvent with ThinkingPartDelta
|
|
193
313
|
case AgentThoughtChunk(content=TextContentBlock(text=text)):
|
|
194
314
|
return PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=text))
|
|
195
|
-
|
|
315
|
+
|
|
316
|
+
# User message echo - usually ignored
|
|
196
317
|
case UserMessageChunk():
|
|
197
|
-
return None
|
|
318
|
+
return None
|
|
319
|
+
|
|
198
320
|
# Tool call start -> ToolCallStartEvent
|
|
199
|
-
case ToolCallStart(
|
|
321
|
+
case ToolCallStart(
|
|
322
|
+
tool_call_id=tool_call_id,
|
|
323
|
+
title=title,
|
|
324
|
+
kind=kind,
|
|
325
|
+
content=content,
|
|
326
|
+
locations=locations,
|
|
327
|
+
raw_input=raw_input,
|
|
328
|
+
):
|
|
200
329
|
return ToolCallStartEvent(
|
|
201
|
-
tool_call_id=
|
|
202
|
-
tool_name=
|
|
203
|
-
title=
|
|
204
|
-
kind=
|
|
205
|
-
content=convert_acp_content(list(
|
|
206
|
-
locations=convert_acp_locations(list(
|
|
207
|
-
raw_input=
|
|
330
|
+
tool_call_id=tool_call_id,
|
|
331
|
+
tool_name=title, # ACP uses title, not separate tool_name
|
|
332
|
+
title=title,
|
|
333
|
+
kind=kind or "other",
|
|
334
|
+
content=convert_acp_content(list(content) if content else None),
|
|
335
|
+
locations=convert_acp_locations(list(locations) if locations else None),
|
|
336
|
+
raw_input=raw_input or {},
|
|
208
337
|
)
|
|
209
338
|
|
|
210
|
-
# Tool call progress -> ToolCallProgressEvent
|
|
211
|
-
case ToolCallProgress(
|
|
212
|
-
|
|
339
|
+
# Tool call progress -> ToolCallProgressEvent or ToolCallCompleteEvent
|
|
340
|
+
case ToolCallProgress(
|
|
341
|
+
tool_call_id=tool_call_id,
|
|
342
|
+
status=status,
|
|
343
|
+
title=title,
|
|
344
|
+
content=content,
|
|
345
|
+
raw_output=raw_output,
|
|
346
|
+
):
|
|
347
|
+
# If completed, return ToolCallCompleteEvent for metadata injection
|
|
348
|
+
if status == "completed":
|
|
349
|
+
from agentpool.agents.events import ToolCallCompleteEvent
|
|
350
|
+
|
|
351
|
+
return ToolCallCompleteEvent(
|
|
352
|
+
tool_call_id=tool_call_id,
|
|
353
|
+
tool_name=title or "unknown",
|
|
354
|
+
tool_input={}, # ACP doesn't provide input in progress updates
|
|
355
|
+
tool_result=str(raw_output) if raw_output else "",
|
|
356
|
+
agent_name="", # Will be set by agent
|
|
357
|
+
message_id="",
|
|
358
|
+
metadata=None, # Will be injected by agent from metadata accumulator
|
|
359
|
+
)
|
|
360
|
+
# Otherwise return progress event
|
|
213
361
|
return ToolCallProgressEvent(
|
|
214
|
-
tool_call_id=
|
|
215
|
-
status=
|
|
216
|
-
title=
|
|
217
|
-
items=
|
|
218
|
-
message=str(
|
|
362
|
+
tool_call_id=tool_call_id,
|
|
363
|
+
status=status or "in_progress",
|
|
364
|
+
title=title,
|
|
365
|
+
items=convert_acp_content(list(content) if content else None),
|
|
366
|
+
message=str(raw_output) if raw_output else None,
|
|
219
367
|
)
|
|
220
368
|
|
|
221
369
|
# Plan update -> PlanUpdateEvent
|
|
222
|
-
case AgentPlanUpdate(entries=
|
|
370
|
+
case AgentPlanUpdate(entries=entries):
|
|
223
371
|
from agentpool.resource_providers.plan_provider import PlanEntry
|
|
224
372
|
|
|
225
373
|
native_entries = [
|
|
226
|
-
PlanEntry(content=e.content, priority=e.priority, status=e.status)
|
|
227
|
-
for e in acp_entries
|
|
374
|
+
PlanEntry(content=e.content, priority=e.priority, status=e.status) for e in entries
|
|
228
375
|
]
|
|
229
376
|
return PlanUpdateEvent(entries=native_entries)
|
|
230
377
|
|
|
@@ -233,24 +380,27 @@ def acp_to_native_event(update: SessionUpdate) -> RichAgentStreamEvent[Any] | No
|
|
|
233
380
|
|
|
234
381
|
|
|
235
382
|
@overload
|
|
236
|
-
def mcp_config_to_acp(config: StdioMCPServerConfig) -> StdioMcpServer
|
|
383
|
+
def mcp_config_to_acp(config: StdioMCPServerConfig) -> StdioMcpServer: ...
|
|
237
384
|
|
|
238
385
|
|
|
239
386
|
@overload
|
|
240
|
-
def mcp_config_to_acp(config: SSEMCPServerConfig) -> SseMcpServer
|
|
387
|
+
def mcp_config_to_acp(config: SSEMCPServerConfig) -> SseMcpServer: ...
|
|
241
388
|
|
|
242
389
|
|
|
243
390
|
@overload
|
|
244
|
-
def mcp_config_to_acp(config: StreamableHTTPMCPServerConfig) -> HttpMcpServer
|
|
391
|
+
def mcp_config_to_acp(config: StreamableHTTPMCPServerConfig) -> HttpMcpServer: ...
|
|
245
392
|
|
|
246
393
|
|
|
247
394
|
@overload
|
|
248
|
-
def mcp_config_to_acp(config: MCPServerConfig) -> McpServer
|
|
395
|
+
def mcp_config_to_acp(config: MCPServerConfig) -> McpServer: ...
|
|
249
396
|
|
|
250
397
|
|
|
251
|
-
def mcp_config_to_acp(config: MCPServerConfig) -> McpServer
|
|
398
|
+
def mcp_config_to_acp(config: MCPServerConfig) -> McpServer:
|
|
252
399
|
"""Convert native MCPServerConfig to ACP McpServer format.
|
|
253
400
|
|
|
401
|
+
If the config has tool filtering (enabled_tools or disabled_tools),
|
|
402
|
+
the server is wrapped with mcp-filter proxy to apply the filtering.
|
|
403
|
+
|
|
254
404
|
Args:
|
|
255
405
|
config: agentpool MCP server configuration
|
|
256
406
|
|
|
@@ -265,21 +415,29 @@ def mcp_config_to_acp(config: MCPServerConfig) -> McpServer | None:
|
|
|
265
415
|
StreamableHTTPMCPServerConfig,
|
|
266
416
|
)
|
|
267
417
|
|
|
418
|
+
# If filtering is configured, wrap with mcp-filter first
|
|
419
|
+
if config.needs_tool_filtering():
|
|
420
|
+
config = config.wrap_with_mcp_filter()
|
|
421
|
+
|
|
268
422
|
match config:
|
|
269
423
|
case StdioMCPServerConfig(command=command, args=args):
|
|
270
424
|
env_vars = config.get_env_vars() if hasattr(config, "get_env_vars") else {}
|
|
425
|
+
env_list = [EnvVariable(name=k, value=v) for k, v in env_vars.items()]
|
|
271
426
|
return StdioMcpServer(
|
|
272
427
|
name=config.name or command,
|
|
273
428
|
command=command,
|
|
274
429
|
args=list(args) if args else [],
|
|
275
|
-
env=
|
|
430
|
+
env=env_list,
|
|
276
431
|
)
|
|
432
|
+
|
|
277
433
|
case SSEMCPServerConfig(url=url):
|
|
278
434
|
return SseMcpServer(name=config.name or str(url), url=url, headers=[])
|
|
435
|
+
|
|
279
436
|
case StreamableHTTPMCPServerConfig(url=url):
|
|
280
437
|
return HttpMcpServer(name=config.name or str(url), url=url, headers=[])
|
|
281
|
-
|
|
282
|
-
|
|
438
|
+
|
|
439
|
+
case _ as unreachable:
|
|
440
|
+
assert_never(unreachable)
|
|
283
441
|
|
|
284
442
|
|
|
285
443
|
def mcp_configs_to_acp(configs: Sequence[MCPServerConfig]) -> list[McpServer]:
|
|
@@ -291,4 +449,4 @@ def mcp_configs_to_acp(configs: Sequence[MCPServerConfig]) -> list[McpServer]:
|
|
|
291
449
|
Returns:
|
|
292
450
|
List of ACP-compatible McpServer instances (skips unconvertible configs)
|
|
293
451
|
"""
|
|
294
|
-
return [
|
|
452
|
+
return [mcp_config_to_acp(config) for config in configs]
|
|
@@ -5,7 +5,6 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import TYPE_CHECKING, Any
|
|
8
|
-
import uuid
|
|
9
8
|
|
|
10
9
|
import anyio
|
|
11
10
|
|
|
@@ -21,13 +20,16 @@ from acp.schema import (
|
|
|
21
20
|
WriteTextFileResponse,
|
|
22
21
|
)
|
|
23
22
|
from agentpool.log import get_logger
|
|
24
|
-
from agentpool.tools.base import Tool
|
|
25
23
|
|
|
26
24
|
|
|
27
25
|
if TYPE_CHECKING:
|
|
26
|
+
from collections.abc import Sequence
|
|
27
|
+
|
|
28
28
|
from exxec import ExecutionEnvironment
|
|
29
|
+
from slashed import Command
|
|
29
30
|
|
|
30
31
|
from acp.schema import (
|
|
32
|
+
AvailableCommand,
|
|
31
33
|
CreateTerminalRequest,
|
|
32
34
|
KillTerminalCommandRequest,
|
|
33
35
|
ReadTextFileRequest,
|
|
@@ -75,7 +77,7 @@ class ACPClientHandler(Client):
|
|
|
75
77
|
self.state = state
|
|
76
78
|
self._input_provider = input_provider
|
|
77
79
|
self._update_event = asyncio.Event()
|
|
78
|
-
# Map ACP terminal IDs to process manager IDs
|
|
80
|
+
# Map ACP terminal IDs to process manager IDs (for local execution only)
|
|
79
81
|
self._terminal_to_process: dict[str, str] = {}
|
|
80
82
|
# Copy tool confirmation mode from agent (can be updated via set_tool_confirmation_mode)
|
|
81
83
|
self.tool_confirmation_mode: ToolConfirmationMode = agent.tool_confirmation_mode
|
|
@@ -98,11 +100,93 @@ class ACPClientHandler(Client):
|
|
|
98
100
|
return self._agent.config.allow_terminal
|
|
99
101
|
|
|
100
102
|
async def session_update(self, params: SessionNotification[Any]) -> None:
|
|
101
|
-
"""Handle session update notifications from the agent.
|
|
103
|
+
"""Handle session update notifications from the agent.
|
|
104
|
+
|
|
105
|
+
Some updates are state changes (mode, model, config) that should update
|
|
106
|
+
session state. Others are stream events (text chunks, tool calls) that
|
|
107
|
+
should be queued for the run_stream consumer.
|
|
108
|
+
"""
|
|
109
|
+
from acp.schema import (
|
|
110
|
+
AvailableCommandsUpdate,
|
|
111
|
+
ConfigOptionUpdate,
|
|
112
|
+
CurrentModelUpdate,
|
|
113
|
+
CurrentModeUpdate,
|
|
114
|
+
)
|
|
102
115
|
from agentpool.agents.acp_agent.acp_converters import acp_to_native_event
|
|
103
116
|
|
|
104
117
|
update = params.update
|
|
105
|
-
|
|
118
|
+
|
|
119
|
+
# Handle state updates - these modify session state, not stream events
|
|
120
|
+
match update:
|
|
121
|
+
case CurrentModeUpdate(current_mode_id=mode_id):
|
|
122
|
+
if self.state.modes:
|
|
123
|
+
self.state.modes.current_mode_id = mode_id
|
|
124
|
+
# Find ModeInfo and emit signal
|
|
125
|
+
for acp_mode in self.state.modes.available_modes:
|
|
126
|
+
if acp_mode.id == mode_id:
|
|
127
|
+
from agentpool.agents.modes import ModeInfo
|
|
128
|
+
|
|
129
|
+
mode_info = ModeInfo(
|
|
130
|
+
id=acp_mode.id,
|
|
131
|
+
name=acp_mode.name,
|
|
132
|
+
description=acp_mode.description or "",
|
|
133
|
+
category_id="remote",
|
|
134
|
+
)
|
|
135
|
+
await self._agent.state_updated.emit(mode_info)
|
|
136
|
+
break
|
|
137
|
+
self.state.current_mode_id = mode_id
|
|
138
|
+
logger.debug("Mode updated", mode_id=mode_id)
|
|
139
|
+
self._update_event.set()
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
case CurrentModelUpdate(current_model_id=model_id):
|
|
143
|
+
self.state.current_model_id = model_id
|
|
144
|
+
if self.state.models:
|
|
145
|
+
self.state.models.current_model_id = model_id
|
|
146
|
+
# Find ModelInfo and emit signal
|
|
147
|
+
for acp_model in self.state.models.available_models:
|
|
148
|
+
if acp_model.model_id == model_id:
|
|
149
|
+
from tokonomics.model_discovery.model_info import (
|
|
150
|
+
ModelInfo as TokoModelInfo,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
model_info = TokoModelInfo(
|
|
154
|
+
id=acp_model.model_id,
|
|
155
|
+
name=acp_model.name,
|
|
156
|
+
description=acp_model.description,
|
|
157
|
+
)
|
|
158
|
+
await self._agent.state_updated.emit(model_info)
|
|
159
|
+
break
|
|
160
|
+
logger.debug("Model updated", model_id=model_id)
|
|
161
|
+
self._update_event.set()
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
case ConfigOptionUpdate():
|
|
165
|
+
await self._agent.state_updated.emit(update)
|
|
166
|
+
logger.debug("Config option updated", update=update)
|
|
167
|
+
self._update_event.set()
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
case AvailableCommandsUpdate():
|
|
171
|
+
self.state.available_commands = update
|
|
172
|
+
# Populate command store with remote commands
|
|
173
|
+
self._populate_command_store(update.available_commands)
|
|
174
|
+
# Emit to parent session - remote commands will be merged with local ones.
|
|
175
|
+
# The "way back" works because session.split_commands() only extracts
|
|
176
|
+
# LOCAL commands; remote commands pass through to the agent prompt.
|
|
177
|
+
await self._agent.state_updated.emit(update)
|
|
178
|
+
logger.debug("Available commands updated", count=len(update.available_commands))
|
|
179
|
+
self._update_event.set()
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
# TODO: AgentPlanUpdate handling is complex and needs design work.
|
|
183
|
+
# Options:
|
|
184
|
+
# 1. Update pool.todos - requires merging with existing todos
|
|
185
|
+
# 2. Pass through to UI - but then todos aren't centrally managed
|
|
186
|
+
# 3. Switch to agent-owned todos instead of pool-owned
|
|
187
|
+
# For now, AgentPlanUpdate falls through to stream events.
|
|
188
|
+
|
|
189
|
+
# All other updates are stream events - convert and queue
|
|
106
190
|
if native_event := acp_to_native_event(update):
|
|
107
191
|
self.state.events.append(native_event)
|
|
108
192
|
self._update_event.set()
|
|
@@ -138,11 +222,21 @@ class ACPClientHandler(Client):
|
|
|
138
222
|
|
|
139
223
|
if self._input_provider:
|
|
140
224
|
ctx = self._agent.get_context() # Use the agent's NodeContext
|
|
225
|
+
# Attach tool call metadata for permission event matching
|
|
226
|
+
ctx.tool_call_id = params.tool_call.tool_call_id
|
|
227
|
+
ctx.tool_name = params.tool_call.title
|
|
228
|
+
args = (
|
|
229
|
+
params.tool_call.raw_input if isinstance(params.tool_call.raw_input, dict) else {}
|
|
230
|
+
)
|
|
231
|
+
ctx.tool_input = args
|
|
141
232
|
# Create a dummy tool representation from ACP params
|
|
142
|
-
|
|
143
|
-
|
|
233
|
+
from agentpool.tools import FunctionTool
|
|
234
|
+
|
|
235
|
+
tool = FunctionTool(
|
|
236
|
+
callable=lambda: None, name=params.tool_call.tool_call_id, description=name
|
|
237
|
+
)
|
|
144
238
|
try:
|
|
145
|
-
result = await self._input_provider.get_tool_confirmation(ctx, tool=tool, args=
|
|
239
|
+
result = await self._input_provider.get_tool_confirmation(ctx, tool=tool, args=args)
|
|
146
240
|
# Map confirmation result to ACP response
|
|
147
241
|
if result == "allow":
|
|
148
242
|
option_id = params.options[0].option_id if params.options else "allow"
|
|
@@ -177,9 +271,10 @@ class ACPClientHandler(Client):
|
|
|
177
271
|
logger.debug("Read file", path=params.path, num_chars=len(content))
|
|
178
272
|
return ReadTextFileResponse(content=content)
|
|
179
273
|
|
|
180
|
-
except FileNotFoundError:
|
|
181
|
-
|
|
182
|
-
|
|
274
|
+
except (FileNotFoundError, KeyError):
|
|
275
|
+
# Match Zed behavior: return empty string for non-existent files
|
|
276
|
+
logger.debug("File not found, returning empty string", path=params.path)
|
|
277
|
+
return ReadTextFileResponse(content="")
|
|
183
278
|
except Exception:
|
|
184
279
|
logger.exception("Failed to read file", path=params.path)
|
|
185
280
|
raise
|
|
@@ -202,11 +297,18 @@ class ACPClientHandler(Client):
|
|
|
202
297
|
raise
|
|
203
298
|
|
|
204
299
|
async def create_terminal(self, params: CreateTerminalRequest) -> CreateTerminalResponse:
|
|
205
|
-
"""Create a new terminal session via
|
|
300
|
+
"""Create a new terminal session via the configured ExecutionEnvironment.
|
|
301
|
+
|
|
302
|
+
The ProcessManager implementation determines where the terminal runs
|
|
303
|
+
(local, Docker, E2B, SSH, or forwarded to parent ACP client like Zed).
|
|
304
|
+
|
|
305
|
+
The terminal_id returned by process_manager.start_process() is used directly.
|
|
306
|
+
"""
|
|
206
307
|
if not self.allow_terminal:
|
|
207
308
|
raise RuntimeError("Terminal operations not allowed")
|
|
309
|
+
|
|
208
310
|
try:
|
|
209
|
-
|
|
311
|
+
terminal_id = await self.env.process_manager.start_process(
|
|
210
312
|
command=params.command,
|
|
211
313
|
args=list(params.args) if params.args else None,
|
|
212
314
|
cwd=params.cwd,
|
|
@@ -216,10 +318,10 @@ class ACPClientHandler(Client):
|
|
|
216
318
|
logger.exception("Failed to create terminal", command=params.command)
|
|
217
319
|
raise
|
|
218
320
|
else:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
logger.info(
|
|
321
|
+
# Use the ID from process_manager directly - for ACPProcessManager this
|
|
322
|
+
# is already the parent's terminal ID (e.g., Zed's)
|
|
323
|
+
self._terminal_to_process[terminal_id] = terminal_id
|
|
324
|
+
logger.info("Created terminal", terminal_id=terminal_id, command=params.command)
|
|
223
325
|
return CreateTerminalResponse(terminal_id=terminal_id)
|
|
224
326
|
|
|
225
327
|
async def terminal_output(self, params: TerminalOutputRequest) -> TerminalOutputResponse:
|
|
@@ -231,8 +333,7 @@ class ACPClientHandler(Client):
|
|
|
231
333
|
if terminal_id not in self._terminal_to_process:
|
|
232
334
|
raise ValueError(f"Terminal {terminal_id} not found")
|
|
233
335
|
|
|
234
|
-
|
|
235
|
-
proc_output = await self.env.process_manager.get_output(process_id)
|
|
336
|
+
proc_output = await self.env.process_manager.get_output(terminal_id)
|
|
236
337
|
output = proc_output.combined or proc_output.stdout or ""
|
|
237
338
|
return TerminalOutputResponse(output=output, truncated=proc_output.truncated)
|
|
238
339
|
|
|
@@ -247,8 +348,7 @@ class ACPClientHandler(Client):
|
|
|
247
348
|
if terminal_id not in self._terminal_to_process:
|
|
248
349
|
raise ValueError(f"Terminal {terminal_id} not found")
|
|
249
350
|
|
|
250
|
-
|
|
251
|
-
exit_code = await self.env.process_manager.wait_for_exit(process_id)
|
|
351
|
+
exit_code = await self.env.process_manager.wait_for_exit(terminal_id)
|
|
252
352
|
logger.debug("Terminal exited", terminal_id=terminal_id, exit_code=exit_code)
|
|
253
353
|
return WaitForTerminalExitResponse(exit_code=exit_code)
|
|
254
354
|
|
|
@@ -263,8 +363,7 @@ class ACPClientHandler(Client):
|
|
|
263
363
|
if terminal_id not in self._terminal_to_process:
|
|
264
364
|
raise ValueError(f"Terminal {terminal_id} not found")
|
|
265
365
|
|
|
266
|
-
|
|
267
|
-
await self.env.process_manager.kill_process(process_id)
|
|
366
|
+
await self.env.process_manager.kill_process(terminal_id)
|
|
268
367
|
logger.info("Killed terminal", terminal_id=terminal_id)
|
|
269
368
|
return KillTerminalCommandResponse()
|
|
270
369
|
|
|
@@ -276,8 +375,8 @@ class ACPClientHandler(Client):
|
|
|
276
375
|
terminal_id = params.terminal_id
|
|
277
376
|
if terminal_id not in self._terminal_to_process:
|
|
278
377
|
raise ValueError(f"Terminal {terminal_id} not found")
|
|
279
|
-
|
|
280
|
-
await self.env.process_manager.release_process(
|
|
378
|
+
|
|
379
|
+
await self.env.process_manager.release_process(terminal_id)
|
|
281
380
|
del self._terminal_to_process[terminal_id]
|
|
282
381
|
logger.info("Released terminal", terminal_id=terminal_id)
|
|
283
382
|
return ReleaseTerminalResponse()
|
|
@@ -301,13 +400,79 @@ class ACPClientHandler(Client):
|
|
|
301
400
|
"""Handle extension notifications."""
|
|
302
401
|
logger.debug("Extension notification", method=method)
|
|
303
402
|
|
|
403
|
+
def _populate_command_store(self, commands: Sequence[AvailableCommand]) -> None:
|
|
404
|
+
"""Populate the agent's command store with remote ACP commands.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
commands: List of AvailableCommand objects from the remote agent
|
|
408
|
+
"""
|
|
409
|
+
store = self._agent.command_store
|
|
410
|
+
|
|
411
|
+
for cmd in commands:
|
|
412
|
+
command = self._create_acp_command(cmd)
|
|
413
|
+
# Unregister if already exists (in case of update)
|
|
414
|
+
if store.get_command(cmd.name):
|
|
415
|
+
store.unregister_command(cmd.name)
|
|
416
|
+
store.register_command(command)
|
|
417
|
+
|
|
418
|
+
logger.debug("Populated command store", command_count=len(store.list_commands()))
|
|
419
|
+
|
|
420
|
+
def _create_acp_command(self, cmd: AvailableCommand) -> Command:
|
|
421
|
+
"""Create a slashed Command from an ACP AvailableCommand.
|
|
422
|
+
|
|
423
|
+
The command, when executed, sends a prompt with the slash command
|
|
424
|
+
to the remote ACP agent.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
cmd: AvailableCommand from remote agent
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
A slashed Command that sends the command to the remote agent
|
|
431
|
+
"""
|
|
432
|
+
from pydantic_ai import PartDeltaEvent, TextPartDelta
|
|
433
|
+
from slashed import Command
|
|
434
|
+
|
|
435
|
+
name = cmd.name
|
|
436
|
+
description = cmd.description
|
|
437
|
+
input_hint = cmd.input.root.hint if cmd.input else None
|
|
438
|
+
|
|
439
|
+
async def execute_command(
|
|
440
|
+
ctx: Any,
|
|
441
|
+
args: list[str],
|
|
442
|
+
kwargs: dict[str, str],
|
|
443
|
+
) -> None:
|
|
444
|
+
"""Execute the remote ACP slash command."""
|
|
445
|
+
# Build command string
|
|
446
|
+
args_str = " ".join(args) if args else ""
|
|
447
|
+
if kwargs:
|
|
448
|
+
kwargs_str = " ".join(f"{k}={v}" for k, v in kwargs.items())
|
|
449
|
+
args_str = f"{args_str} {kwargs_str}".strip()
|
|
450
|
+
|
|
451
|
+
full_command = f"/{name} {args_str}".strip()
|
|
452
|
+
|
|
453
|
+
# Execute via agent run_stream - the slash command goes as a prompt
|
|
454
|
+
async for event in self._agent.run_stream(full_command):
|
|
455
|
+
# Extract text from PartDeltaEvent with TextPartDelta
|
|
456
|
+
if isinstance(event, PartDeltaEvent):
|
|
457
|
+
delta = event.delta
|
|
458
|
+
if isinstance(delta, TextPartDelta):
|
|
459
|
+
await ctx.print(delta.content_delta)
|
|
460
|
+
|
|
461
|
+
return Command.from_raw(
|
|
462
|
+
execute_command,
|
|
463
|
+
name=name,
|
|
464
|
+
description=description,
|
|
465
|
+
category="remote",
|
|
466
|
+
usage=input_hint,
|
|
467
|
+
)
|
|
468
|
+
|
|
304
469
|
|
|
305
470
|
if __name__ == "__main__":
|
|
306
471
|
from agentpool.agents.acp_agent import ACPAgent
|
|
307
472
|
|
|
308
473
|
async def main() -> None:
|
|
309
474
|
"""Demo: Basic call to an ACP agent."""
|
|
310
|
-
args = ["run", "agentpool", "serve-acp"
|
|
475
|
+
args = ["run", "agentpool", "serve-acp"]
|
|
311
476
|
cwd = str(Path.cwd())
|
|
312
477
|
async with ACPAgent(command="uv", args=args, cwd=cwd, event_handlers=["detailed"]) as agent:
|
|
313
478
|
print("Response (streaming): ", end="", flush=True)
|