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
|
@@ -7,54 +7,26 @@ between agents and ACP clients through the JSON-RPC protocol.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import asyncio
|
|
10
|
-
from collections.abc import AsyncGenerator
|
|
11
10
|
from dataclasses import dataclass, field
|
|
12
11
|
import re
|
|
13
|
-
from typing import TYPE_CHECKING, Any
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
14
13
|
|
|
14
|
+
import anyio
|
|
15
15
|
from exxec.acp_provider import ACPExecutionEnvironment
|
|
16
16
|
import logfire
|
|
17
|
-
from pydantic_ai import
|
|
18
|
-
FinalResultEvent,
|
|
19
|
-
FunctionToolCallEvent,
|
|
20
|
-
FunctionToolResultEvent,
|
|
21
|
-
PartDeltaEvent,
|
|
22
|
-
PartStartEvent,
|
|
23
|
-
RetryPromptPart,
|
|
24
|
-
TextPart,
|
|
25
|
-
TextPartDelta,
|
|
26
|
-
ThinkingPart,
|
|
27
|
-
ThinkingPartDelta,
|
|
28
|
-
ToolCallPartDelta,
|
|
29
|
-
ToolReturnPart,
|
|
30
|
-
UsageLimitExceeded,
|
|
31
|
-
UserPromptPart,
|
|
32
|
-
)
|
|
17
|
+
from pydantic_ai import UsageLimitExceeded, UserPromptPart
|
|
33
18
|
from slashed import Command, CommandStore
|
|
19
|
+
from tokonomics.model_discovery.model_info import ModelInfo
|
|
34
20
|
|
|
35
21
|
from acp import RequestPermissionRequest
|
|
36
22
|
from acp.acp_requests import ACPRequests
|
|
37
23
|
from acp.filesystem import ACPFileSystem
|
|
38
24
|
from acp.notifications import ACPNotifications
|
|
39
|
-
from acp.schema import
|
|
40
|
-
AvailableCommand,
|
|
41
|
-
ClientCapabilities,
|
|
42
|
-
ContentToolCallContent,
|
|
43
|
-
PlanEntry,
|
|
44
|
-
TerminalToolCallContent,
|
|
45
|
-
ToolCallLocation,
|
|
46
|
-
)
|
|
47
|
-
from acp.tool_call_state import ToolCallState
|
|
48
|
-
from acp.utils import generate_tool_title, infer_tool_kind, to_acp_content_blocks
|
|
25
|
+
from acp.schema import AvailableCommand, ClientCapabilities, SessionNotification
|
|
49
26
|
from agentpool import Agent, AgentContext # noqa: TC001
|
|
50
27
|
from agentpool.agents import SlashedAgent
|
|
51
28
|
from agentpool.agents.acp_agent import ACPAgent
|
|
52
|
-
from agentpool.agents.
|
|
53
|
-
PlanUpdateEvent,
|
|
54
|
-
StreamCompleteEvent,
|
|
55
|
-
ToolCallProgressEvent,
|
|
56
|
-
ToolCallStartEvent,
|
|
57
|
-
)
|
|
29
|
+
from agentpool.agents.modes import ModeInfo
|
|
58
30
|
from agentpool.log import get_logger
|
|
59
31
|
from agentpool_commands import get_commands
|
|
60
32
|
from agentpool_commands.base import NodeCommand
|
|
@@ -62,20 +34,20 @@ from agentpool_server.acp_server.converters import (
|
|
|
62
34
|
convert_acp_mcp_server_to_config,
|
|
63
35
|
from_acp_content,
|
|
64
36
|
)
|
|
37
|
+
from agentpool_server.acp_server.event_converter import ACPEventConverter
|
|
65
38
|
from agentpool_server.acp_server.input_provider import ACPInputProvider
|
|
66
39
|
|
|
67
40
|
|
|
68
41
|
if TYPE_CHECKING:
|
|
69
42
|
from collections.abc import Callable, Sequence
|
|
70
43
|
|
|
71
|
-
from pydantic_ai import
|
|
72
|
-
SystemPromptPart,
|
|
73
|
-
UserContent,
|
|
74
|
-
)
|
|
44
|
+
from pydantic_ai import SystemPromptPart, UserContent
|
|
75
45
|
from slashed import CommandContext
|
|
76
46
|
|
|
77
47
|
from acp import Client, RequestPermissionResponse
|
|
78
48
|
from acp.schema import (
|
|
49
|
+
AvailableCommandsUpdate,
|
|
50
|
+
ConfigOptionUpdate,
|
|
79
51
|
ContentBlock,
|
|
80
52
|
Implementation,
|
|
81
53
|
McpServer,
|
|
@@ -84,7 +56,6 @@ if TYPE_CHECKING:
|
|
|
84
56
|
from agentpool import AgentPool
|
|
85
57
|
from agentpool.agents import AGUIAgent
|
|
86
58
|
from agentpool.agents.claude_code_agent import ClaudeCodeAgent
|
|
87
|
-
from agentpool.agents.events import RichAgentStreamEvent
|
|
88
59
|
from agentpool.prompts.manager import PromptManager
|
|
89
60
|
from agentpool.prompts.prompts import MCPClientPrompt
|
|
90
61
|
from agentpool_server.acp_server.acp_agent import AgentPoolACPAgent
|
|
@@ -129,13 +100,27 @@ def _is_slash_command(text: str) -> bool:
|
|
|
129
100
|
|
|
130
101
|
def split_commands(
|
|
131
102
|
contents: Sequence[UserContent],
|
|
103
|
+
command_store: CommandStore,
|
|
132
104
|
) -> tuple[list[str], list[UserContent]]:
|
|
105
|
+
"""Split content into local slash commands and pass-through content.
|
|
106
|
+
|
|
107
|
+
Only commands that exist in the local command_store are extracted.
|
|
108
|
+
Remote commands (from nested ACP agents) stay in non_command_content
|
|
109
|
+
so they flow through to the agent and reach the nested server.
|
|
110
|
+
"""
|
|
133
111
|
commands: list[str] = []
|
|
134
112
|
non_command_content: list[UserContent] = []
|
|
135
113
|
for item in contents:
|
|
136
|
-
if
|
|
114
|
+
# Check if this is a LOCAL command we handle
|
|
115
|
+
if (
|
|
116
|
+
isinstance(item, str)
|
|
117
|
+
and _is_slash_command(item)
|
|
118
|
+
and (match := SLASH_PATTERN.match(item.strip()))
|
|
119
|
+
and command_store.get_command(match.group(1))
|
|
120
|
+
):
|
|
137
121
|
commands.append(item.strip())
|
|
138
122
|
else:
|
|
123
|
+
# Not a local command - pass through (may be remote command or regular text)
|
|
139
124
|
non_command_content.append(item)
|
|
140
125
|
return commands, non_command_content
|
|
141
126
|
|
|
@@ -223,6 +208,12 @@ class ACPSession:
|
|
|
223
208
|
manager: ACPSessionManager | None = None
|
|
224
209
|
"""Session manager for managing sessions. Used for session management commands."""
|
|
225
210
|
|
|
211
|
+
subagent_display_mode: Literal["inline", "tool_box"] = "tool_box"
|
|
212
|
+
"""How to display subagent output:
|
|
213
|
+
- 'inline': Subagent output flows into main message stream
|
|
214
|
+
- 'tool_box': Subagent output contained in the tool call's progress box (default)
|
|
215
|
+
"""
|
|
216
|
+
|
|
226
217
|
def __post_init__(self) -> None:
|
|
227
218
|
"""Initialize session state and set up providers."""
|
|
228
219
|
from agentpool_server.acp_server.commands import get_commands as get_acp_commands
|
|
@@ -231,8 +222,7 @@ class ACPSession:
|
|
|
231
222
|
self.log = logger.bind(session_id=self.session_id)
|
|
232
223
|
self._task_lock = asyncio.Lock()
|
|
233
224
|
self._cancelled = False
|
|
234
|
-
self.
|
|
235
|
-
self._tool_call_states: dict[str, ToolCallState] = {}
|
|
225
|
+
self._current_converter: ACPEventConverter | None = None
|
|
236
226
|
self.fs = ACPFileSystem(self.client, session_id=self.session_id)
|
|
237
227
|
cmds = [
|
|
238
228
|
*get_commands(
|
|
@@ -243,10 +233,10 @@ class ACPSession:
|
|
|
243
233
|
),
|
|
244
234
|
*get_acp_commands(),
|
|
245
235
|
]
|
|
246
|
-
self.command_store = CommandStore(
|
|
236
|
+
self.command_store = CommandStore(commands=cmds)
|
|
247
237
|
self.command_store._initialize_sync()
|
|
248
238
|
self._update_callbacks: list[Callable[[], None]] = []
|
|
249
|
-
|
|
239
|
+
self._remote_commands: list[AvailableCommand] = [] # Commands from nested ACP agents
|
|
250
240
|
self.staged_content = StagedContent()
|
|
251
241
|
# Inject Zed-specific instructions if client is Zed
|
|
252
242
|
if self.client_info and self.client_info.name and "zed" in self.client_info.name.lower():
|
|
@@ -294,44 +284,47 @@ class ACPSession:
|
|
|
294
284
|
return response
|
|
295
285
|
|
|
296
286
|
agent.acp_permission_callback = permission_callback
|
|
297
|
-
self.log.info("Created ACP session", current_agent=self.current_agent_name)
|
|
298
287
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
288
|
+
# Subscribe to state change signal for all agents
|
|
289
|
+
agent.state_updated.connect(self._on_state_updated)
|
|
290
|
+
self.log.info("Created ACP session", current_agent=self.current_agent_name)
|
|
302
291
|
|
|
303
|
-
def
|
|
304
|
-
self,
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
292
|
+
async def _on_state_updated(
|
|
293
|
+
self, state: ModeInfo | ModelInfo | AvailableCommandsUpdate | ConfigOptionUpdate
|
|
294
|
+
) -> None:
|
|
295
|
+
"""Handle state update signal from agent - forward to ACP client."""
|
|
296
|
+
from acp.schema import (
|
|
297
|
+
AvailableCommandsUpdate as ACPAvailableCommandsUpdate,
|
|
298
|
+
ConfigOptionUpdate as ACPConfigOptionUpdate,
|
|
299
|
+
CurrentModelUpdate,
|
|
300
|
+
CurrentModeUpdate,
|
|
301
|
+
SessionNotification,
|
|
302
|
+
)
|
|
310
303
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
304
|
+
update: CurrentModeUpdate | CurrentModelUpdate | ACPConfigOptionUpdate
|
|
305
|
+
match state:
|
|
306
|
+
case ModeInfo(id=mode_id):
|
|
307
|
+
update = CurrentModeUpdate(current_mode_id=mode_id)
|
|
308
|
+
self.log.debug("Forwarding mode change to client", mode_id=mode_id)
|
|
309
|
+
case ModelInfo(id=model_id):
|
|
310
|
+
update = CurrentModelUpdate(current_model_id=model_id)
|
|
311
|
+
self.log.debug("Forwarding model change to client", model_id=model_id)
|
|
312
|
+
case ACPAvailableCommandsUpdate(available_commands=cmds):
|
|
313
|
+
# Store remote commands and send merged list
|
|
314
|
+
self._remote_commands = list(cmds)
|
|
315
|
+
await self.send_available_commands_update()
|
|
316
|
+
self.log.debug("Merged and sent commands update to client")
|
|
317
|
+
return
|
|
318
|
+
case ACPConfigOptionUpdate():
|
|
319
|
+
update = state
|
|
320
|
+
self.log.debug("Forwarding config option update to client")
|
|
315
321
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
"""
|
|
319
|
-
if tool_call_id not in self._tool_call_states:
|
|
320
|
-
state = ToolCallState(
|
|
321
|
-
notifications=self.notifications,
|
|
322
|
-
tool_call_id=tool_call_id,
|
|
323
|
-
tool_name=tool_name,
|
|
324
|
-
title=generate_tool_title(tool_name, tool_input),
|
|
325
|
-
kind=infer_tool_kind(tool_name),
|
|
326
|
-
raw_input=tool_input,
|
|
327
|
-
)
|
|
328
|
-
self._tool_call_states[tool_call_id] = state
|
|
329
|
-
return self._tool_call_states[tool_call_id]
|
|
322
|
+
notification = SessionNotification(session_id=self.session_id, update=update)
|
|
323
|
+
await self.client.session_update(notification) # pyright: ignore[reportArgumentType]
|
|
330
324
|
|
|
331
|
-
def
|
|
332
|
-
"""
|
|
333
|
-
self.
|
|
334
|
-
self._current_tool_inputs.pop(tool_call_id, None)
|
|
325
|
+
async def initialize(self) -> None:
|
|
326
|
+
"""Initialize async resources. Must be called after construction."""
|
|
327
|
+
await self.acp_env.__aenter__()
|
|
335
328
|
|
|
336
329
|
async def initialize_mcp_servers(self) -> None:
|
|
337
330
|
"""Initialize MCP servers if any are configured."""
|
|
@@ -339,26 +332,32 @@ class ACPSession:
|
|
|
339
332
|
return
|
|
340
333
|
self.log.info("Initializing MCP servers", server_count=len(self.mcp_servers))
|
|
341
334
|
cfgs = [convert_acp_mcp_server_to_config(s) for s in self.mcp_servers]
|
|
342
|
-
#
|
|
343
|
-
|
|
344
|
-
for _cfg in cfgs:
|
|
335
|
+
# Add each MCP server to the current agent's MCP manager dynamically
|
|
336
|
+
for cfg in cfgs:
|
|
345
337
|
try:
|
|
346
|
-
|
|
347
|
-
self.log.info(
|
|
348
|
-
|
|
338
|
+
await self.agent.mcp.setup_server(cfg)
|
|
339
|
+
self.log.info(
|
|
340
|
+
"Added MCP server to agent", server_name=cfg.name, agent=self.current_agent_name
|
|
341
|
+
)
|
|
349
342
|
except Exception:
|
|
350
|
-
self.log.exception("Failed to
|
|
343
|
+
self.log.exception("Failed to setup MCP server", server_name=cfg.name)
|
|
351
344
|
# Don't fail session creation, just log the error
|
|
345
|
+
# Register MCP prompts as commands after all servers are added
|
|
346
|
+
try:
|
|
347
|
+
await self._register_mcp_prompts_as_commands()
|
|
348
|
+
except Exception:
|
|
349
|
+
self.log.exception("Failed to register MCP prompts as commands")
|
|
352
350
|
|
|
353
351
|
async def init_project_context(self) -> None:
|
|
354
|
-
"""Load AGENTS.md file and
|
|
352
|
+
"""Load AGENTS.md/CLAUDE.md file and stage as initial context.
|
|
355
353
|
|
|
356
|
-
|
|
354
|
+
The project context is staged as user message content rather than system prompts,
|
|
355
|
+
which ensures it's available for all agent types and avoids timing issues with
|
|
356
|
+
agent initialization.
|
|
357
357
|
"""
|
|
358
358
|
if info := await self.requests.read_agent_rules(self.cwd):
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
agent.sys_prompts.prompts.append(prompt)
|
|
359
|
+
# Stage as user message to be prepended to first prompt
|
|
360
|
+
self.staged_content.add_text(f"## Project Information\n\n{info}")
|
|
362
361
|
|
|
363
362
|
async def init_client_skills(self) -> None:
|
|
364
363
|
"""Discover and load skills from client-side .claude/skills directory.
|
|
@@ -377,7 +376,14 @@ class ACPSession:
|
|
|
377
376
|
self.log.exception("Failed to discover client-side skills", error=e)
|
|
378
377
|
|
|
379
378
|
@property
|
|
380
|
-
def agent(
|
|
379
|
+
def agent(
|
|
380
|
+
self,
|
|
381
|
+
) -> (
|
|
382
|
+
Agent[ACPSession, str]
|
|
383
|
+
| ACPAgent[ACPSession]
|
|
384
|
+
| AGUIAgent[ACPSession]
|
|
385
|
+
| ClaudeCodeAgent[ACPSession]
|
|
386
|
+
):
|
|
381
387
|
"""Get the currently active agent."""
|
|
382
388
|
if self.current_agent_name in self.agent_pool.agents:
|
|
383
389
|
return self.agent_pool.get_agent(self.current_agent_name, deps_type=ACPSession)
|
|
@@ -421,6 +427,10 @@ class ACPSession:
|
|
|
421
427
|
This actively interrupts the running agent by calling its interrupt() method,
|
|
422
428
|
which handles protocol-specific cancellation (e.g., sending CancelNotification
|
|
423
429
|
for ACP agents, calling SDK interrupt for ClaudeCodeAgent, etc.).
|
|
430
|
+
|
|
431
|
+
Note:
|
|
432
|
+
Tool call cleanup is handled in process_prompt() to avoid race conditions
|
|
433
|
+
with the converter state being modified from multiple async contexts.
|
|
424
434
|
"""
|
|
425
435
|
self._cancelled = True
|
|
426
436
|
self.log.info("Session cancelled, interrupting agent")
|
|
@@ -450,7 +460,7 @@ class ACPSession:
|
|
|
450
460
|
if not contents:
|
|
451
461
|
self.log.warning("Empty prompt received")
|
|
452
462
|
return "refusal"
|
|
453
|
-
commands, non_command_content = split_commands(contents)
|
|
463
|
+
commands, non_command_content = split_commands(contents, self.command_store)
|
|
454
464
|
async with self._task_lock:
|
|
455
465
|
if commands: # Process commands if found
|
|
456
466
|
for command in commands:
|
|
@@ -470,19 +480,62 @@ class ACPSession:
|
|
|
470
480
|
has_staged=staged is not None,
|
|
471
481
|
)
|
|
472
482
|
event_count = 0
|
|
473
|
-
|
|
483
|
+
# Create a new event converter for this prompt
|
|
484
|
+
converter = ACPEventConverter(subagent_display_mode=self.subagent_display_mode)
|
|
485
|
+
self._current_converter = converter # Track for cancellation
|
|
474
486
|
|
|
475
487
|
try: # Use the session's persistent input provider
|
|
476
488
|
async for event in self.agent.run_stream(
|
|
477
|
-
*all_content,
|
|
489
|
+
*all_content,
|
|
490
|
+
input_provider=self.input_provider,
|
|
491
|
+
deps=self,
|
|
492
|
+
conversation_id=self.session_id, # Tie agent conversation to ACP session
|
|
478
493
|
):
|
|
479
494
|
if self._cancelled:
|
|
495
|
+
self.log.info("Cancelled during event loop, cleaning up tool calls")
|
|
496
|
+
# Send cancellation notifications for any pending tool calls
|
|
497
|
+
# This happens in the same async context as the converter
|
|
498
|
+
async for cancel_update in converter.cancel_pending_tools():
|
|
499
|
+
notification = SessionNotification(
|
|
500
|
+
session_id=self.session_id, update=cancel_update
|
|
501
|
+
)
|
|
502
|
+
await self.client.session_update(notification) # pyright: ignore[reportArgumentType]
|
|
503
|
+
# CRITICAL: Allow time for client to process tool completion notifications
|
|
504
|
+
# before sending PromptResponse. Without this delay, the client may receive
|
|
505
|
+
# and process the PromptResponse before the tool notifications, causing UI
|
|
506
|
+
# state desync where subsequent prompts appear stuck/unresponsive.
|
|
507
|
+
# This is needed because even though send() awaits the write, the client
|
|
508
|
+
# may process messages asynchronously or out of order.
|
|
509
|
+
await anyio.sleep(0.05)
|
|
510
|
+
self._current_converter = None
|
|
480
511
|
return "cancelled"
|
|
481
512
|
|
|
482
513
|
event_count += 1
|
|
483
|
-
|
|
514
|
+
async for update in converter.convert(event):
|
|
515
|
+
notification = SessionNotification(
|
|
516
|
+
session_id=self.session_id, update=update
|
|
517
|
+
)
|
|
518
|
+
await self.client.session_update(notification) # pyright: ignore[reportArgumentType]
|
|
519
|
+
# Yield control to allow notifications to be sent immediately
|
|
520
|
+
await anyio.sleep(0.01)
|
|
484
521
|
self.log.info("Streaming finished", events_processed=event_count)
|
|
485
522
|
|
|
523
|
+
except asyncio.CancelledError:
|
|
524
|
+
# Task was cancelled (e.g., via interrupt()) - return proper stop reason
|
|
525
|
+
# This is critical: CancelledError doesn't inherit from Exception,
|
|
526
|
+
# so we must catch it explicitly to send the PromptResponse
|
|
527
|
+
self.log.info("Stream cancelled via CancelledError, cleaning up tool calls")
|
|
528
|
+
# Send cancellation notifications for any pending tool calls
|
|
529
|
+
async for cancel_update in converter.cancel_pending_tools():
|
|
530
|
+
notification = SessionNotification(
|
|
531
|
+
session_id=self.session_id, update=cancel_update
|
|
532
|
+
)
|
|
533
|
+
await self.client.session_update(notification) # pyright: ignore[reportArgumentType]
|
|
534
|
+
# CRITICAL: Allow time for client to process tool completion notifications
|
|
535
|
+
# before sending PromptResponse. See comment in cancellation branch above.
|
|
536
|
+
await anyio.sleep(0.05)
|
|
537
|
+
self._current_converter = None
|
|
538
|
+
return "cancelled"
|
|
486
539
|
except UsageLimitExceeded as e:
|
|
487
540
|
self.log.info("Usage limit exceeded", error=str(e))
|
|
488
541
|
error_msg = str(e) # Determine which limit was hit based on error
|
|
@@ -495,6 +548,7 @@ class ACPSession:
|
|
|
495
548
|
return "refusal"
|
|
496
549
|
return "max_tokens" # Default to max_tokens for other usage limits
|
|
497
550
|
except Exception as e:
|
|
551
|
+
self._current_converter = None # Clear converter reference
|
|
498
552
|
self.log.exception("Error during streaming")
|
|
499
553
|
# Send error notification asynchronously to avoid blocking response
|
|
500
554
|
self.acp_agent.tasks.create_task(
|
|
@@ -503,228 +557,21 @@ class ACPSession:
|
|
|
503
557
|
)
|
|
504
558
|
return "end_turn"
|
|
505
559
|
else:
|
|
560
|
+
# Title generation is now handled automatically by log_conversation
|
|
561
|
+
self._current_converter = None # Clear converter reference
|
|
506
562
|
return "end_turn"
|
|
507
563
|
|
|
508
564
|
async def _send_error_notification(self, message: str) -> None:
|
|
509
565
|
"""Send error notification, with exception handling."""
|
|
566
|
+
if self._cancelled:
|
|
567
|
+
return
|
|
510
568
|
try:
|
|
511
569
|
await self.notifications.send_agent_text(message)
|
|
512
570
|
except Exception:
|
|
513
571
|
self.log.exception("Failed to send error notification")
|
|
514
572
|
|
|
515
|
-
async def handle_event(self, event: RichAgentStreamEvent[Any]) -> None: # noqa: PLR0915
|
|
516
|
-
match event:
|
|
517
|
-
case (
|
|
518
|
-
PartStartEvent(part=TextPart(content=delta))
|
|
519
|
-
| PartDeltaEvent(delta=TextPartDelta(content_delta=delta))
|
|
520
|
-
):
|
|
521
|
-
await self.notifications.send_agent_text(delta)
|
|
522
|
-
|
|
523
|
-
case (
|
|
524
|
-
PartStartEvent(part=ThinkingPart(content=delta))
|
|
525
|
-
| PartDeltaEvent(delta=ThinkingPartDelta(content_delta=delta))
|
|
526
|
-
):
|
|
527
|
-
await self.notifications.send_agent_thought(delta or "\n")
|
|
528
|
-
|
|
529
|
-
case PartStartEvent(part=part):
|
|
530
|
-
self.log.debug("Received unhandled PartStartEvent", part=part)
|
|
531
|
-
|
|
532
|
-
# Tool call streaming delta - create/update state and start notification
|
|
533
|
-
case PartDeltaEvent(delta=ToolCallPartDelta() as delta):
|
|
534
|
-
if delta_part := delta.as_part():
|
|
535
|
-
tool_call_id = delta_part.tool_call_id
|
|
536
|
-
try:
|
|
537
|
-
tool_input = delta_part.args_as_dict()
|
|
538
|
-
except ValueError:
|
|
539
|
-
# Args still streaming, not valid JSON yet - skip this delta
|
|
540
|
-
pass
|
|
541
|
-
else:
|
|
542
|
-
self._current_tool_inputs[tool_call_id] = tool_input
|
|
543
|
-
# Create state and send initial notification
|
|
544
|
-
state = self._get_or_create_tool_state(
|
|
545
|
-
tool_call_id=tool_call_id,
|
|
546
|
-
tool_name=delta_part.tool_name,
|
|
547
|
-
tool_input=tool_input,
|
|
548
|
-
)
|
|
549
|
-
await state.start()
|
|
550
|
-
|
|
551
|
-
# Tool call started - create/update state and start notification
|
|
552
|
-
case FunctionToolCallEvent(part=part):
|
|
553
|
-
tool_call_id = part.tool_call_id
|
|
554
|
-
try:
|
|
555
|
-
tool_input = part.args_as_dict()
|
|
556
|
-
except ValueError as e:
|
|
557
|
-
# Args might be malformed - use empty dict and log
|
|
558
|
-
self.log.warning(
|
|
559
|
-
"Failed to parse tool args", tool_name=part.tool_name, error=str(e)
|
|
560
|
-
)
|
|
561
|
-
tool_input = {}
|
|
562
|
-
self._current_tool_inputs[tool_call_id] = tool_input
|
|
563
|
-
# Create state and send initial notification
|
|
564
|
-
state = self._get_or_create_tool_state(
|
|
565
|
-
tool_call_id=tool_call_id,
|
|
566
|
-
tool_name=part.tool_name,
|
|
567
|
-
tool_input=tool_input,
|
|
568
|
-
)
|
|
569
|
-
await state.start()
|
|
570
|
-
|
|
571
|
-
# Tool completed successfully - update state and finalize
|
|
572
|
-
case FunctionToolResultEvent(
|
|
573
|
-
result=ToolReturnPart(content=content, tool_name=tool_name) as result,
|
|
574
|
-
tool_call_id=tool_call_id,
|
|
575
|
-
):
|
|
576
|
-
if isinstance(content, AsyncGenerator):
|
|
577
|
-
full_content = ""
|
|
578
|
-
async for chunk in content:
|
|
579
|
-
full_content += str(chunk)
|
|
580
|
-
# Stream progress through state
|
|
581
|
-
if tool_state := self._tool_call_states.get(tool_call_id):
|
|
582
|
-
await tool_state.update(status="in_progress", raw_output=chunk)
|
|
583
|
-
|
|
584
|
-
# Replace the AsyncGenerator with the full content to prevent errors
|
|
585
|
-
result.content = full_content
|
|
586
|
-
final_output = full_content
|
|
587
|
-
else:
|
|
588
|
-
final_output = result.content
|
|
589
|
-
|
|
590
|
-
# Complete tool call through state (preserves accumulated content/locations)
|
|
591
|
-
if complete_state := self._tool_call_states.get(tool_call_id):
|
|
592
|
-
# Only add return value as content if no content was emitted during execution
|
|
593
|
-
if complete_state.has_content:
|
|
594
|
-
# Content already provided via progress events - just set raw_output
|
|
595
|
-
await complete_state.complete(raw_output=final_output)
|
|
596
|
-
else:
|
|
597
|
-
# No content yet - convert return value for display
|
|
598
|
-
converted_blocks = to_acp_content_blocks(final_output)
|
|
599
|
-
content_items = [
|
|
600
|
-
ContentToolCallContent(content=block) for block in converted_blocks
|
|
601
|
-
]
|
|
602
|
-
await complete_state.complete(
|
|
603
|
-
raw_output=final_output,
|
|
604
|
-
content=content_items,
|
|
605
|
-
)
|
|
606
|
-
self._cleanup_tool_state(tool_call_id)
|
|
607
|
-
|
|
608
|
-
# Tool failed with retry - update state with error
|
|
609
|
-
case FunctionToolResultEvent(
|
|
610
|
-
result=RetryPromptPart(tool_name=tool_name) as result,
|
|
611
|
-
tool_call_id=tool_call_id,
|
|
612
|
-
):
|
|
613
|
-
error_message = result.model_response()
|
|
614
|
-
if fail_state := self._tool_call_states.get(tool_call_id):
|
|
615
|
-
await fail_state.fail(error=error_message)
|
|
616
|
-
self._cleanup_tool_state(tool_call_id)
|
|
617
|
-
|
|
618
|
-
# Tool emits its own start event - update state with better title/content
|
|
619
|
-
case ToolCallStartEvent(
|
|
620
|
-
tool_call_id=tool_call_id,
|
|
621
|
-
tool_name=tool_name,
|
|
622
|
-
title=title,
|
|
623
|
-
kind=kind,
|
|
624
|
-
locations=loc_items,
|
|
625
|
-
raw_input=raw_input,
|
|
626
|
-
):
|
|
627
|
-
self.log.debug(
|
|
628
|
-
"Tool call start event", tool_name=tool_name, tool_call_id=tool_call_id
|
|
629
|
-
)
|
|
630
|
-
# Get or create state (may already exist from FunctionToolCallEvent)
|
|
631
|
-
state = self._get_or_create_tool_state(
|
|
632
|
-
tool_call_id=tool_call_id,
|
|
633
|
-
tool_name=tool_name,
|
|
634
|
-
tool_input=raw_input or {},
|
|
635
|
-
)
|
|
636
|
-
# Convert LocationContentItem objects to ACP format
|
|
637
|
-
acp_locations = [
|
|
638
|
-
ToolCallLocation(path=loc.path, line=loc.line) for loc in loc_items
|
|
639
|
-
]
|
|
640
|
-
# Update state with tool-provided details (better title, content, locations)
|
|
641
|
-
await state.update(title=title, kind=kind, locations=acp_locations or None)
|
|
642
|
-
|
|
643
|
-
# Tool progress event - update state with title and content
|
|
644
|
-
case ToolCallProgressEvent(
|
|
645
|
-
tool_call_id=tool_call_id,
|
|
646
|
-
title=title,
|
|
647
|
-
status=status,
|
|
648
|
-
items=items,
|
|
649
|
-
) if tool_call_id and tool_call_id in self._tool_call_states:
|
|
650
|
-
progress_state = self._tool_call_states[tool_call_id]
|
|
651
|
-
self.log.debug("Progress event", tool_call_id=tool_call_id, title=title)
|
|
652
|
-
|
|
653
|
-
# Convert items to ACP content
|
|
654
|
-
from agentpool.agents.events import (
|
|
655
|
-
DiffContentItem,
|
|
656
|
-
FileContentItem,
|
|
657
|
-
LocationContentItem,
|
|
658
|
-
TerminalContentItem,
|
|
659
|
-
TextContentItem,
|
|
660
|
-
)
|
|
661
|
-
from agentpool_server.acp_server.syntax_detection import (
|
|
662
|
-
format_zed_code_block,
|
|
663
|
-
)
|
|
664
|
-
|
|
665
|
-
acp_content: list[Any] = []
|
|
666
|
-
location_paths: list[str] = []
|
|
667
|
-
|
|
668
|
-
for item in items:
|
|
669
|
-
match item:
|
|
670
|
-
case TerminalContentItem(terminal_id=tid):
|
|
671
|
-
acp_content.append(TerminalToolCallContent(terminal_id=tid))
|
|
672
|
-
case TextContentItem(text=text):
|
|
673
|
-
acp_content.append(ContentToolCallContent.text(text=text))
|
|
674
|
-
case FileContentItem(
|
|
675
|
-
content=file_content,
|
|
676
|
-
path=file_path,
|
|
677
|
-
start_line=start_line,
|
|
678
|
-
end_line=end_line,
|
|
679
|
-
):
|
|
680
|
-
# Format as Zed-compatible code block with clickable path
|
|
681
|
-
formatted = format_zed_code_block(
|
|
682
|
-
file_content, file_path, start_line, end_line
|
|
683
|
-
)
|
|
684
|
-
acp_content.append(ContentToolCallContent.text(text=formatted))
|
|
685
|
-
# Also add path to locations for "follow along" feature
|
|
686
|
-
location_paths.append(file_path)
|
|
687
|
-
case DiffContentItem(path=diff_path, old_text=old, new_text=new):
|
|
688
|
-
# Send diff via direct notification
|
|
689
|
-
await self.notifications.file_edit_progress(
|
|
690
|
-
tool_call_id=tool_call_id,
|
|
691
|
-
path=diff_path,
|
|
692
|
-
old_text=old or "",
|
|
693
|
-
new_text=new,
|
|
694
|
-
status=status,
|
|
695
|
-
changed_lines=[],
|
|
696
|
-
)
|
|
697
|
-
case LocationContentItem(path=loc_path):
|
|
698
|
-
location_paths.append(loc_path)
|
|
699
|
-
|
|
700
|
-
await progress_state.update(
|
|
701
|
-
title=title,
|
|
702
|
-
status="in_progress",
|
|
703
|
-
content=acp_content if acp_content else None,
|
|
704
|
-
locations=location_paths if location_paths else None,
|
|
705
|
-
)
|
|
706
|
-
|
|
707
|
-
case FinalResultEvent():
|
|
708
|
-
self.log.debug("Final result received")
|
|
709
|
-
|
|
710
|
-
case StreamCompleteEvent():
|
|
711
|
-
pass
|
|
712
|
-
|
|
713
|
-
case PlanUpdateEvent(entries=entries, tool_call_id=tool_call_id):
|
|
714
|
-
acp_entries = [
|
|
715
|
-
PlanEntry(content=e.content, priority=e.priority, status=e.status)
|
|
716
|
-
for e in entries
|
|
717
|
-
]
|
|
718
|
-
await self.notifications.update_plan(acp_entries)
|
|
719
|
-
|
|
720
|
-
case _:
|
|
721
|
-
self.log.debug("Unhandled event", event_type=type(event).__name__)
|
|
722
|
-
|
|
723
573
|
async def close(self) -> None:
|
|
724
574
|
"""Close the session and cleanup resources."""
|
|
725
|
-
self._current_tool_inputs.clear()
|
|
726
|
-
self._tool_call_states.clear()
|
|
727
|
-
|
|
728
575
|
try:
|
|
729
576
|
await self.acp_env.__aexit__(None, None, None)
|
|
730
577
|
except Exception:
|
|
@@ -743,9 +590,14 @@ class ACPSession:
|
|
|
743
590
|
self.log.exception("Error closing session")
|
|
744
591
|
|
|
745
592
|
async def send_available_commands_update(self) -> None:
|
|
746
|
-
"""Send current available commands to client.
|
|
593
|
+
"""Send current available commands to client.
|
|
594
|
+
|
|
595
|
+
Merges local commands from command_store with any remote commands
|
|
596
|
+
from nested ACP agents.
|
|
597
|
+
"""
|
|
747
598
|
try:
|
|
748
|
-
commands = self.get_acp_commands()
|
|
599
|
+
commands = self.get_acp_commands() # Local commands
|
|
600
|
+
commands.extend(self._remote_commands) # Merge remote commands
|
|
749
601
|
await self.notifications.update_commands(commands)
|
|
750
602
|
except Exception:
|
|
751
603
|
self.log.exception("Failed to send available commands update")
|
|
@@ -802,11 +654,10 @@ class ACPSession:
|
|
|
802
654
|
Returns:
|
|
803
655
|
List of ACP AvailableCommand objects compatible with current node
|
|
804
656
|
"""
|
|
805
|
-
all_commands = self.command_store.list_commands()
|
|
806
657
|
current_node = self.agent
|
|
807
658
|
# Filter commands by node compatibility
|
|
808
659
|
compatible_commands = []
|
|
809
|
-
for cmd in
|
|
660
|
+
for cmd in self.command_store.list_commands():
|
|
810
661
|
cmd_cls = cmd if isinstance(cmd, type) else type(cmd)
|
|
811
662
|
# Check if command supports current node type
|
|
812
663
|
if issubclass(cmd_cls, NodeCommand) and not cmd_cls.supports_node(current_node): # type: ignore[union-attr]
|
|
@@ -860,11 +711,7 @@ class ACPSession:
|
|
|
860
711
|
)
|
|
861
712
|
|
|
862
713
|
def register_update_callback(self, callback: Callable[[], None]) -> None:
|
|
863
|
-
"""Register callback for command updates.
|
|
864
|
-
|
|
865
|
-
Args:
|
|
866
|
-
callback: Function to call when commands are updated
|
|
867
|
-
"""
|
|
714
|
+
"""Register callback for command updates."""
|
|
868
715
|
self._update_callbacks.append(callback)
|
|
869
716
|
|
|
870
717
|
def create_mcp_command(self, prompt: MCPClientPrompt) -> Command:
|
|
@@ -885,16 +732,13 @@ class ACPSession:
|
|
|
885
732
|
) -> None:
|
|
886
733
|
"""Execute the MCP prompt with parsed arguments."""
|
|
887
734
|
# Map parsed args to prompt parameters
|
|
888
|
-
|
|
889
|
-
result = {}
|
|
890
735
|
# Map positional args to prompt parameter names
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
try:
|
|
897
|
-
# Get prompt components
|
|
736
|
+
result = {
|
|
737
|
+
prompt.arguments[i]["name"]: arg_value
|
|
738
|
+
for i, arg_value in enumerate(args)
|
|
739
|
+
if i < len(prompt.arguments)
|
|
740
|
+
} | kwargs
|
|
741
|
+
try: # Get prompt components
|
|
898
742
|
components = await prompt.get_components(result or None)
|
|
899
743
|
self.staged_content.add(components)
|
|
900
744
|
# Send confirmation
|
|
@@ -905,15 +749,13 @@ class ACPSession:
|
|
|
905
749
|
logger.exception("MCP prompt execution failed", prompt=prompt.name)
|
|
906
750
|
await ctx.print(f"❌ Prompt error: {e}")
|
|
907
751
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
return Command(
|
|
912
|
-
execute_func=execute_prompt,
|
|
752
|
+
usage = " ".join(f"<{i['name']}>" for i in args) if (args := prompt.arguments) else None
|
|
753
|
+
return Command.from_raw(
|
|
754
|
+
execute_prompt,
|
|
913
755
|
name=prompt.name,
|
|
914
756
|
description=prompt.description or f"MCP prompt: {prompt.name}",
|
|
915
757
|
category="mcp",
|
|
916
|
-
usage=
|
|
758
|
+
usage=usage,
|
|
917
759
|
)
|
|
918
760
|
|
|
919
761
|
def create_prompt_hub_command(
|
|
@@ -960,8 +802,8 @@ class ACPSession:
|
|
|
960
802
|
# Create command name - prefix with provider if not builtin
|
|
961
803
|
command_name = f"{provider}_{name}" if provider != "builtin" else name
|
|
962
804
|
|
|
963
|
-
return Command(
|
|
964
|
-
|
|
805
|
+
return Command.from_raw(
|
|
806
|
+
execute_prompt,
|
|
965
807
|
name=command_name,
|
|
966
808
|
description=f"Prompt hub: {provider}:{name}",
|
|
967
809
|
category="prompts",
|