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
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
"""Convert agent stream events to ACP notifications.
|
|
2
|
+
|
|
3
|
+
This module provides a stateful converter that transforms agent stream events
|
|
4
|
+
into ACP session update objects. The converter tracks tool call state but does
|
|
5
|
+
not perform any I/O - it yields notification objects that can be emitted
|
|
6
|
+
by the caller.
|
|
7
|
+
|
|
8
|
+
This separation enables easy testing without mocks.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from collections.abc import AsyncGenerator
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
16
|
+
|
|
17
|
+
from pydantic_ai import (
|
|
18
|
+
BuiltinToolCallPart,
|
|
19
|
+
BuiltinToolReturnPart,
|
|
20
|
+
FinalResultEvent,
|
|
21
|
+
FunctionToolCallEvent,
|
|
22
|
+
FunctionToolResultEvent,
|
|
23
|
+
PartDeltaEvent,
|
|
24
|
+
PartStartEvent,
|
|
25
|
+
RetryPromptPart,
|
|
26
|
+
TextPart,
|
|
27
|
+
TextPartDelta,
|
|
28
|
+
ThinkingPart,
|
|
29
|
+
ThinkingPartDelta,
|
|
30
|
+
ToolCallPartDelta,
|
|
31
|
+
ToolReturnPart,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
from acp.schema import (
|
|
35
|
+
AgentMessageChunk,
|
|
36
|
+
AgentPlanUpdate,
|
|
37
|
+
AgentThoughtChunk,
|
|
38
|
+
ContentToolCallContent,
|
|
39
|
+
ToolCallLocation,
|
|
40
|
+
ToolCallProgress,
|
|
41
|
+
ToolCallStart,
|
|
42
|
+
)
|
|
43
|
+
from acp.utils import generate_tool_title, infer_tool_kind, to_acp_content_blocks
|
|
44
|
+
from agentpool.agents.events import (
|
|
45
|
+
CompactionEvent,
|
|
46
|
+
DiffContentItem,
|
|
47
|
+
FileContentItem,
|
|
48
|
+
LocationContentItem,
|
|
49
|
+
PlanUpdateEvent,
|
|
50
|
+
StreamCompleteEvent,
|
|
51
|
+
SubAgentEvent,
|
|
52
|
+
TerminalContentItem,
|
|
53
|
+
TextContentItem,
|
|
54
|
+
ToolCallProgressEvent,
|
|
55
|
+
ToolCallStartEvent,
|
|
56
|
+
)
|
|
57
|
+
from agentpool.log import get_logger
|
|
58
|
+
from agentpool.utils.pydantic_ai_helpers import safe_args_as_dict
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if TYPE_CHECKING:
|
|
62
|
+
from collections.abc import AsyncIterator
|
|
63
|
+
|
|
64
|
+
from acp.schema.tool_call import ToolCallContent, ToolCallKind
|
|
65
|
+
from agentpool.agents.events import RichAgentStreamEvent
|
|
66
|
+
|
|
67
|
+
logger = get_logger(__name__)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Type alias for all session updates the converter can yield
|
|
71
|
+
ACPSessionUpdate = (
|
|
72
|
+
AgentMessageChunk | AgentThoughtChunk | ToolCallStart | ToolCallProgress | AgentPlanUpdate
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ============================================================================
|
|
77
|
+
# Internal tool state tracking
|
|
78
|
+
# ============================================================================
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class _ToolState:
|
|
83
|
+
"""Internal state for a single tool call."""
|
|
84
|
+
|
|
85
|
+
tool_call_id: str
|
|
86
|
+
tool_name: str
|
|
87
|
+
title: str
|
|
88
|
+
kind: ToolCallKind
|
|
89
|
+
raw_input: dict[str, Any]
|
|
90
|
+
started: bool = False
|
|
91
|
+
has_content: bool = False
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ============================================================================
|
|
95
|
+
# Event Converter
|
|
96
|
+
# ============================================================================
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class ACPEventConverter:
|
|
101
|
+
"""Converts agent stream events to ACP session updates.
|
|
102
|
+
|
|
103
|
+
Stateful converter that tracks tool calls and subagent content,
|
|
104
|
+
yielding ACP schema objects without performing I/O.
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
```python
|
|
108
|
+
converter = ACPEventConverter()
|
|
109
|
+
async for event in agent.run_stream(...):
|
|
110
|
+
async for update in converter.convert(event):
|
|
111
|
+
await client.session_update(SessionNotification(session_id=sid, update=update))
|
|
112
|
+
```
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
subagent_display_mode: Literal["inline", "tool_box"] = "tool_box"
|
|
116
|
+
"""How to display subagent output."""
|
|
117
|
+
|
|
118
|
+
# Internal state
|
|
119
|
+
_tool_states: dict[str, _ToolState] = field(default_factory=dict)
|
|
120
|
+
"""Active tool call states."""
|
|
121
|
+
|
|
122
|
+
_current_tool_inputs: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
123
|
+
"""Current tool inputs by tool_call_id."""
|
|
124
|
+
|
|
125
|
+
_subagent_headers: set[str] = field(default_factory=set)
|
|
126
|
+
"""Track which subagent headers have been sent (for inline mode)."""
|
|
127
|
+
|
|
128
|
+
_subagent_content: dict[str, list[str]] = field(default_factory=dict)
|
|
129
|
+
"""Accumulated content per subagent (for tool_box mode)."""
|
|
130
|
+
|
|
131
|
+
def reset(self) -> None:
|
|
132
|
+
"""Reset converter state for a new run."""
|
|
133
|
+
self._tool_states.clear()
|
|
134
|
+
self._current_tool_inputs.clear()
|
|
135
|
+
self._subagent_headers.clear()
|
|
136
|
+
self._subagent_content.clear()
|
|
137
|
+
|
|
138
|
+
async def cancel_pending_tools(self) -> AsyncIterator[ToolCallProgress]:
|
|
139
|
+
"""Cancel all pending tool calls.
|
|
140
|
+
|
|
141
|
+
Yields ToolCallProgress notifications with status="completed" for all
|
|
142
|
+
tool calls that were started but not completed. This should be called
|
|
143
|
+
when the stream is interrupted to properly clean up client-side state.
|
|
144
|
+
|
|
145
|
+
Note:
|
|
146
|
+
Uses status="completed" since ACP doesn't have a "cancelled" status.
|
|
147
|
+
This signals to the client that we're done with these tool calls.
|
|
148
|
+
|
|
149
|
+
Yields:
|
|
150
|
+
ToolCallProgress notifications for each pending tool call
|
|
151
|
+
"""
|
|
152
|
+
for tool_call_id, state in list(self._tool_states.items()):
|
|
153
|
+
if state.started:
|
|
154
|
+
yield ToolCallProgress(
|
|
155
|
+
tool_call_id=tool_call_id,
|
|
156
|
+
status="completed",
|
|
157
|
+
)
|
|
158
|
+
# Clean up all state
|
|
159
|
+
self.reset()
|
|
160
|
+
|
|
161
|
+
def _get_or_create_tool_state(
|
|
162
|
+
self,
|
|
163
|
+
tool_call_id: str,
|
|
164
|
+
tool_name: str,
|
|
165
|
+
tool_input: dict[str, Any],
|
|
166
|
+
) -> _ToolState:
|
|
167
|
+
"""Get existing tool state or create a new one."""
|
|
168
|
+
if tool_call_id not in self._tool_states:
|
|
169
|
+
self._tool_states[tool_call_id] = _ToolState(
|
|
170
|
+
tool_call_id=tool_call_id,
|
|
171
|
+
tool_name=tool_name,
|
|
172
|
+
title=generate_tool_title(tool_name, tool_input),
|
|
173
|
+
kind=infer_tool_kind(tool_name),
|
|
174
|
+
raw_input=tool_input,
|
|
175
|
+
)
|
|
176
|
+
return self._tool_states[tool_call_id]
|
|
177
|
+
|
|
178
|
+
def _cleanup_tool_state(self, tool_call_id: str) -> None:
|
|
179
|
+
"""Remove tool state after completion."""
|
|
180
|
+
self._tool_states.pop(tool_call_id, None)
|
|
181
|
+
self._current_tool_inputs.pop(tool_call_id, None)
|
|
182
|
+
|
|
183
|
+
async def convert( # noqa: PLR0915
|
|
184
|
+
self, event: RichAgentStreamEvent[Any]
|
|
185
|
+
) -> AsyncIterator[ACPSessionUpdate]:
|
|
186
|
+
"""Convert an agent event to zero or more ACP session updates."""
|
|
187
|
+
from acp.schema import (
|
|
188
|
+
FileEditToolCallContent,
|
|
189
|
+
PlanEntry as ACPPlanEntry,
|
|
190
|
+
TerminalToolCallContent,
|
|
191
|
+
)
|
|
192
|
+
from agentpool_server.acp_server.syntax_detection import format_zed_code_block
|
|
193
|
+
|
|
194
|
+
match event:
|
|
195
|
+
# Text output
|
|
196
|
+
case (
|
|
197
|
+
PartStartEvent(part=TextPart(content=delta))
|
|
198
|
+
| PartDeltaEvent(delta=TextPartDelta(content_delta=delta))
|
|
199
|
+
):
|
|
200
|
+
yield AgentMessageChunk.text(text=delta)
|
|
201
|
+
|
|
202
|
+
# Thinking/reasoning
|
|
203
|
+
case (
|
|
204
|
+
PartStartEvent(part=ThinkingPart(content=delta))
|
|
205
|
+
| PartDeltaEvent(delta=ThinkingPartDelta(content_delta=delta))
|
|
206
|
+
):
|
|
207
|
+
yield AgentThoughtChunk.text(text=delta or "\n")
|
|
208
|
+
|
|
209
|
+
# Builtin tool call started (e.g., WebSearchTool, CodeExecutionTool)
|
|
210
|
+
case PartStartEvent(part=BuiltinToolCallPart() as part):
|
|
211
|
+
tool_call_id = part.tool_call_id
|
|
212
|
+
tool_input = safe_args_as_dict(part, default={})
|
|
213
|
+
self._current_tool_inputs[tool_call_id] = tool_input
|
|
214
|
+
state = self._get_or_create_tool_state(tool_call_id, part.tool_name, tool_input)
|
|
215
|
+
if not state.started:
|
|
216
|
+
state.started = True
|
|
217
|
+
yield ToolCallStart(
|
|
218
|
+
tool_call_id=tool_call_id,
|
|
219
|
+
title=state.title,
|
|
220
|
+
kind=state.kind,
|
|
221
|
+
raw_input=state.raw_input,
|
|
222
|
+
status="pending",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Builtin tool completed
|
|
226
|
+
case PartStartEvent(part=BuiltinToolReturnPart() as part):
|
|
227
|
+
tool_call_id = part.tool_call_id
|
|
228
|
+
tool_state = self._tool_states.get(tool_call_id)
|
|
229
|
+
final_output = part.content
|
|
230
|
+
|
|
231
|
+
if tool_state and tool_state.has_content:
|
|
232
|
+
yield ToolCallProgress(
|
|
233
|
+
tool_call_id=tool_call_id,
|
|
234
|
+
status="completed",
|
|
235
|
+
raw_output=final_output,
|
|
236
|
+
)
|
|
237
|
+
else:
|
|
238
|
+
converted = to_acp_content_blocks(final_output)
|
|
239
|
+
content = [ContentToolCallContent(content=block) for block in converted]
|
|
240
|
+
yield ToolCallProgress(
|
|
241
|
+
tool_call_id=tool_call_id,
|
|
242
|
+
status="completed",
|
|
243
|
+
raw_output=final_output,
|
|
244
|
+
content=content,
|
|
245
|
+
)
|
|
246
|
+
self._cleanup_tool_state(tool_call_id)
|
|
247
|
+
|
|
248
|
+
case PartStartEvent(part=part):
|
|
249
|
+
logger.debug("Received unhandled PartStartEvent", part=part)
|
|
250
|
+
|
|
251
|
+
# Tool call streaming delta
|
|
252
|
+
case PartDeltaEvent(delta=ToolCallPartDelta() as delta):
|
|
253
|
+
if delta_part := delta.as_part():
|
|
254
|
+
tool_call_id = delta_part.tool_call_id
|
|
255
|
+
try:
|
|
256
|
+
tool_input = delta_part.args_as_dict()
|
|
257
|
+
except ValueError:
|
|
258
|
+
pass # Args still streaming, not valid JSON yet
|
|
259
|
+
else:
|
|
260
|
+
self._current_tool_inputs[tool_call_id] = tool_input
|
|
261
|
+
state = self._get_or_create_tool_state(
|
|
262
|
+
tool_call_id, delta_part.tool_name, tool_input
|
|
263
|
+
)
|
|
264
|
+
if not state.started:
|
|
265
|
+
state.started = True
|
|
266
|
+
yield ToolCallStart(
|
|
267
|
+
tool_call_id=tool_call_id,
|
|
268
|
+
title=state.title,
|
|
269
|
+
kind=state.kind,
|
|
270
|
+
raw_input=state.raw_input,
|
|
271
|
+
status="pending",
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Function tool call started
|
|
275
|
+
case FunctionToolCallEvent(part=part):
|
|
276
|
+
tool_call_id = part.tool_call_id
|
|
277
|
+
tool_input = safe_args_as_dict(part, default={})
|
|
278
|
+
self._current_tool_inputs[tool_call_id] = tool_input
|
|
279
|
+
state = self._get_or_create_tool_state(tool_call_id, part.tool_name, tool_input)
|
|
280
|
+
if not state.started:
|
|
281
|
+
state.started = True
|
|
282
|
+
yield ToolCallStart(
|
|
283
|
+
tool_call_id=tool_call_id,
|
|
284
|
+
title=state.title,
|
|
285
|
+
kind=state.kind,
|
|
286
|
+
raw_input=state.raw_input,
|
|
287
|
+
status="pending",
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Tool completed successfully
|
|
291
|
+
case FunctionToolResultEvent(
|
|
292
|
+
result=ToolReturnPart(content=content) as result,
|
|
293
|
+
tool_call_id=tool_call_id,
|
|
294
|
+
):
|
|
295
|
+
# Handle async generator content
|
|
296
|
+
if isinstance(content, AsyncGenerator):
|
|
297
|
+
full_content = ""
|
|
298
|
+
async for chunk in content:
|
|
299
|
+
full_content += str(chunk)
|
|
300
|
+
if tool_call_id in self._tool_states:
|
|
301
|
+
yield ToolCallProgress(
|
|
302
|
+
tool_call_id=tool_call_id,
|
|
303
|
+
status="in_progress",
|
|
304
|
+
raw_output=chunk,
|
|
305
|
+
)
|
|
306
|
+
result.content = full_content
|
|
307
|
+
final_output = full_content
|
|
308
|
+
else:
|
|
309
|
+
final_output = result.content
|
|
310
|
+
|
|
311
|
+
tool_state = self._tool_states.get(tool_call_id)
|
|
312
|
+
if tool_state and tool_state.has_content:
|
|
313
|
+
yield ToolCallProgress(
|
|
314
|
+
tool_call_id=tool_call_id,
|
|
315
|
+
status="completed",
|
|
316
|
+
raw_output=final_output,
|
|
317
|
+
)
|
|
318
|
+
else:
|
|
319
|
+
converted = to_acp_content_blocks(final_output)
|
|
320
|
+
content_items = [ContentToolCallContent(content=block) for block in converted]
|
|
321
|
+
yield ToolCallProgress(
|
|
322
|
+
tool_call_id=tool_call_id,
|
|
323
|
+
status="completed",
|
|
324
|
+
raw_output=final_output,
|
|
325
|
+
content=content_items,
|
|
326
|
+
)
|
|
327
|
+
self._cleanup_tool_state(tool_call_id)
|
|
328
|
+
|
|
329
|
+
# Tool failed with retry
|
|
330
|
+
case FunctionToolResultEvent(
|
|
331
|
+
result=RetryPromptPart() as result,
|
|
332
|
+
tool_call_id=tool_call_id,
|
|
333
|
+
):
|
|
334
|
+
error_message = result.model_response()
|
|
335
|
+
yield ToolCallProgress(
|
|
336
|
+
tool_call_id=tool_call_id,
|
|
337
|
+
status="failed",
|
|
338
|
+
content=[ContentToolCallContent.text(text=f"Error: {error_message}")],
|
|
339
|
+
)
|
|
340
|
+
self._cleanup_tool_state(tool_call_id)
|
|
341
|
+
|
|
342
|
+
# Tool emits its own start event
|
|
343
|
+
case ToolCallStartEvent(
|
|
344
|
+
tool_call_id=tool_call_id,
|
|
345
|
+
tool_name=tool_name,
|
|
346
|
+
title=title,
|
|
347
|
+
kind=kind,
|
|
348
|
+
locations=loc_items,
|
|
349
|
+
raw_input=raw_input,
|
|
350
|
+
):
|
|
351
|
+
state = self._get_or_create_tool_state(tool_call_id, tool_name, raw_input or {})
|
|
352
|
+
acp_locations = [ToolCallLocation(path=i.path, line=i.line) for i in loc_items]
|
|
353
|
+
# If not started, send start notification
|
|
354
|
+
if not state.started:
|
|
355
|
+
state.started = True
|
|
356
|
+
yield ToolCallStart(
|
|
357
|
+
tool_call_id=tool_call_id,
|
|
358
|
+
title=title,
|
|
359
|
+
kind=kind,
|
|
360
|
+
raw_input=raw_input,
|
|
361
|
+
locations=acp_locations or None,
|
|
362
|
+
status="pending",
|
|
363
|
+
)
|
|
364
|
+
else:
|
|
365
|
+
# Send update with tool-provided details
|
|
366
|
+
yield ToolCallProgress(
|
|
367
|
+
tool_call_id=tool_call_id,
|
|
368
|
+
title=title,
|
|
369
|
+
kind=kind,
|
|
370
|
+
locations=acp_locations or None,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
# Tool progress event - create state if needed (tool may emit progress before SDK event)
|
|
374
|
+
case ToolCallProgressEvent(
|
|
375
|
+
tool_call_id=tool_call_id,
|
|
376
|
+
title=title,
|
|
377
|
+
# status=status,
|
|
378
|
+
items=items,
|
|
379
|
+
progress=progress,
|
|
380
|
+
total=total,
|
|
381
|
+
message=message,
|
|
382
|
+
) if tool_call_id:
|
|
383
|
+
# Get or create state - handles race where tool emits before SDK event
|
|
384
|
+
state = self._get_or_create_tool_state(tool_call_id, "unknown", {})
|
|
385
|
+
# Emit start if this is the first event for this tool call
|
|
386
|
+
if not state.started:
|
|
387
|
+
state.started = True
|
|
388
|
+
yield ToolCallStart(
|
|
389
|
+
tool_call_id=tool_call_id,
|
|
390
|
+
title=title or state.title,
|
|
391
|
+
kind=state.kind,
|
|
392
|
+
raw_input=state.raw_input,
|
|
393
|
+
status="pending",
|
|
394
|
+
)
|
|
395
|
+
acp_content: list[ToolCallContent] = []
|
|
396
|
+
progress_locations: list[ToolCallLocation] = []
|
|
397
|
+
for item in items:
|
|
398
|
+
match item:
|
|
399
|
+
case TerminalContentItem(terminal_id=tid):
|
|
400
|
+
acp_content.append(TerminalToolCallContent(terminal_id=tid))
|
|
401
|
+
case TextContentItem(text=text):
|
|
402
|
+
acp_content.append(ContentToolCallContent.text(text=text))
|
|
403
|
+
case FileContentItem(
|
|
404
|
+
content=file_content,
|
|
405
|
+
path=file_path,
|
|
406
|
+
start_line=start_line,
|
|
407
|
+
end_line=end_line,
|
|
408
|
+
):
|
|
409
|
+
formatted = format_zed_code_block(
|
|
410
|
+
file_content, file_path, start_line, end_line
|
|
411
|
+
)
|
|
412
|
+
acp_content.append(ContentToolCallContent.text(text=formatted))
|
|
413
|
+
progress_locations.append(
|
|
414
|
+
ToolCallLocation(path=file_path, line=start_line or 0)
|
|
415
|
+
)
|
|
416
|
+
case DiffContentItem(path=diff_path, old_text=old, new_text=new):
|
|
417
|
+
acp_content.append(
|
|
418
|
+
FileEditToolCallContent(path=diff_path, old_text=old, new_text=new)
|
|
419
|
+
)
|
|
420
|
+
progress_locations.append(ToolCallLocation(path=diff_path))
|
|
421
|
+
state.has_content = True
|
|
422
|
+
case LocationContentItem(path=loc_path, line=loc_line):
|
|
423
|
+
location = ToolCallLocation(path=loc_path, line=loc_line)
|
|
424
|
+
progress_locations.append(location)
|
|
425
|
+
|
|
426
|
+
# Build title: use provided title, or format MCP numeric progress
|
|
427
|
+
effective_title = title
|
|
428
|
+
if not effective_title and (progress is not None or message):
|
|
429
|
+
# MCP-style numeric progress - format into title
|
|
430
|
+
if progress is not None and total:
|
|
431
|
+
pct = int(progress / total * 100)
|
|
432
|
+
effective_title = f"{message} ({pct}%)" if message else f"Progress: {pct}%"
|
|
433
|
+
elif message:
|
|
434
|
+
effective_title = message
|
|
435
|
+
|
|
436
|
+
# TODO: Progress events shouldn't control completion status.
|
|
437
|
+
# The file_operation helper sets status="completed" on success, but that's
|
|
438
|
+
# emitted mid-operation (before content display). Only FunctionToolResultEvent
|
|
439
|
+
# should mark a tool as completed. For now, hardcode in_progress.
|
|
440
|
+
yield ToolCallProgress(
|
|
441
|
+
tool_call_id=tool_call_id,
|
|
442
|
+
title=effective_title,
|
|
443
|
+
status="in_progress",
|
|
444
|
+
content=acp_content or None,
|
|
445
|
+
locations=progress_locations or None,
|
|
446
|
+
)
|
|
447
|
+
if acp_content:
|
|
448
|
+
state.has_content = True
|
|
449
|
+
|
|
450
|
+
case FinalResultEvent():
|
|
451
|
+
pass # No notification needed
|
|
452
|
+
|
|
453
|
+
case StreamCompleteEvent():
|
|
454
|
+
pass # No notification needed
|
|
455
|
+
|
|
456
|
+
case PlanUpdateEvent(entries=entries):
|
|
457
|
+
acp_entries = [
|
|
458
|
+
ACPPlanEntry(content=e.content, priority=e.priority, status=e.status)
|
|
459
|
+
for e in entries
|
|
460
|
+
]
|
|
461
|
+
yield AgentPlanUpdate(entries=acp_entries)
|
|
462
|
+
|
|
463
|
+
case CompactionEvent(trigger=trigger, phase=phase):
|
|
464
|
+
if phase == "starting":
|
|
465
|
+
if trigger == "auto":
|
|
466
|
+
text = (
|
|
467
|
+
"\n\n---\n\n"
|
|
468
|
+
"š¦ **Context compaction** triggered. "
|
|
469
|
+
"Summarizing conversation..."
|
|
470
|
+
"\n\n---\n\n"
|
|
471
|
+
)
|
|
472
|
+
else:
|
|
473
|
+
text = (
|
|
474
|
+
"\n\n---\n\n"
|
|
475
|
+
"š¦ **Manual compaction** requested. "
|
|
476
|
+
"Summarizing conversation..."
|
|
477
|
+
"\n\n---\n\n"
|
|
478
|
+
)
|
|
479
|
+
yield AgentMessageChunk.text(text=text)
|
|
480
|
+
|
|
481
|
+
case SubAgentEvent(
|
|
482
|
+
source_name=source_name,
|
|
483
|
+
source_type=source_type,
|
|
484
|
+
event=inner_event,
|
|
485
|
+
depth=depth,
|
|
486
|
+
):
|
|
487
|
+
if self.subagent_display_mode == "tool_box":
|
|
488
|
+
async for update in self._convert_subagent_tool_box(
|
|
489
|
+
source_name, source_type, inner_event, depth
|
|
490
|
+
):
|
|
491
|
+
yield update
|
|
492
|
+
else:
|
|
493
|
+
async for update in self._convert_subagent_inline(
|
|
494
|
+
source_name, source_type, inner_event, depth
|
|
495
|
+
):
|
|
496
|
+
yield update
|
|
497
|
+
|
|
498
|
+
case _:
|
|
499
|
+
logger.debug("Unhandled event", event_type=type(event).__name__)
|
|
500
|
+
|
|
501
|
+
async def _convert_subagent_inline(
|
|
502
|
+
self,
|
|
503
|
+
source_name: str,
|
|
504
|
+
source_type: Literal["agent", "team_parallel", "team_sequential"],
|
|
505
|
+
inner_event: RichAgentStreamEvent[Any],
|
|
506
|
+
depth: int,
|
|
507
|
+
) -> AsyncIterator[ACPSessionUpdate]:
|
|
508
|
+
"""Convert subagent event to inline text notifications."""
|
|
509
|
+
indent = " " * depth
|
|
510
|
+
icon = "š¤" if source_type == "agent" else "š„"
|
|
511
|
+
|
|
512
|
+
match inner_event:
|
|
513
|
+
case (
|
|
514
|
+
PartStartEvent(part=TextPart(content=delta))
|
|
515
|
+
| PartDeltaEvent(delta=TextPartDelta(content_delta=delta))
|
|
516
|
+
):
|
|
517
|
+
header_key = f"{source_name}:{depth}"
|
|
518
|
+
if header_key not in self._subagent_headers:
|
|
519
|
+
self._subagent_headers.add(header_key)
|
|
520
|
+
yield AgentMessageChunk.text(text=f"\n{indent}{icon} **{source_name}**: ")
|
|
521
|
+
yield AgentMessageChunk.text(text=delta)
|
|
522
|
+
|
|
523
|
+
case (
|
|
524
|
+
PartStartEvent(part=ThinkingPart(content=delta))
|
|
525
|
+
| PartDeltaEvent(delta=ThinkingPartDelta(content_delta=delta))
|
|
526
|
+
):
|
|
527
|
+
yield AgentThoughtChunk.text(text=f"{indent}[{source_name}] {delta or ''}")
|
|
528
|
+
|
|
529
|
+
case FunctionToolCallEvent(part=part):
|
|
530
|
+
text = f"\n{indent}š§ [{source_name}] Using tool: {part.tool_name}\n"
|
|
531
|
+
yield AgentMessageChunk.text(text=text)
|
|
532
|
+
|
|
533
|
+
case FunctionToolResultEvent(
|
|
534
|
+
result=ToolReturnPart(content=content, tool_name=tool_name),
|
|
535
|
+
):
|
|
536
|
+
result_str = str(content)
|
|
537
|
+
if len(result_str) > 200: # noqa: PLR2004
|
|
538
|
+
result_str = result_str[:200] + "..."
|
|
539
|
+
text = f"{indent}ā
[{source_name}] {tool_name}: {result_str}\n"
|
|
540
|
+
yield AgentMessageChunk.text(text=text)
|
|
541
|
+
|
|
542
|
+
case FunctionToolResultEvent(result=RetryPromptPart(tool_name=tool_name) as result):
|
|
543
|
+
error_msg = result.model_response()
|
|
544
|
+
text = f"{indent}ā [{source_name}] {tool_name}: {error_msg}\n"
|
|
545
|
+
yield AgentMessageChunk.text(text=text)
|
|
546
|
+
|
|
547
|
+
case StreamCompleteEvent():
|
|
548
|
+
header_key = f"{source_name}:{depth}"
|
|
549
|
+
self._subagent_headers.discard(header_key)
|
|
550
|
+
yield AgentMessageChunk.text(text=f"\n{indent}---\n")
|
|
551
|
+
|
|
552
|
+
case _:
|
|
553
|
+
pass
|
|
554
|
+
|
|
555
|
+
async def _convert_subagent_tool_box(
|
|
556
|
+
self,
|
|
557
|
+
source_name: str,
|
|
558
|
+
source_type: Literal["agent", "team_parallel", "team_sequential"],
|
|
559
|
+
inner_event: RichAgentStreamEvent[Any],
|
|
560
|
+
depth: int,
|
|
561
|
+
) -> AsyncIterator[ACPSessionUpdate]:
|
|
562
|
+
"""Convert subagent event to tool box notifications."""
|
|
563
|
+
state_key = f"subagent:{source_name}:{depth}"
|
|
564
|
+
icon = "š¤" if source_type == "agent" else "š„"
|
|
565
|
+
|
|
566
|
+
if state_key not in self._subagent_content:
|
|
567
|
+
self._subagent_content[state_key] = []
|
|
568
|
+
|
|
569
|
+
accumulated = self._subagent_content[state_key]
|
|
570
|
+
|
|
571
|
+
match inner_event:
|
|
572
|
+
case (
|
|
573
|
+
PartStartEvent(part=TextPart(content=delta))
|
|
574
|
+
| PartDeltaEvent(delta=TextPartDelta(content_delta=delta))
|
|
575
|
+
):
|
|
576
|
+
accumulated.append(delta)
|
|
577
|
+
async for n in self._emit_subagent_progress(state_key, f"{icon} {source_name}"):
|
|
578
|
+
yield n
|
|
579
|
+
|
|
580
|
+
case (
|
|
581
|
+
PartStartEvent(part=ThinkingPart(content=delta))
|
|
582
|
+
| PartDeltaEvent(delta=ThinkingPartDelta(content_delta=delta))
|
|
583
|
+
):
|
|
584
|
+
if delta:
|
|
585
|
+
accumulated.append(f"š {delta}")
|
|
586
|
+
async for n in self._emit_subagent_progress(state_key, f"{icon} {source_name}"):
|
|
587
|
+
yield n
|
|
588
|
+
|
|
589
|
+
case FunctionToolCallEvent(part=part):
|
|
590
|
+
accumulated.append(f"\nš§ Using tool: {part.tool_name}\n")
|
|
591
|
+
async for n in self._emit_subagent_progress(state_key, f"{icon} {source_name}"):
|
|
592
|
+
yield n
|
|
593
|
+
|
|
594
|
+
case FunctionToolResultEvent(
|
|
595
|
+
result=ToolReturnPart(content=content, tool_name=tool_name),
|
|
596
|
+
):
|
|
597
|
+
result_str = str(content)
|
|
598
|
+
if len(result_str) > 200: # noqa: PLR2004
|
|
599
|
+
result_str = result_str[:200] + "..."
|
|
600
|
+
accumulated.append(f"ā
{tool_name}: {result_str}\n")
|
|
601
|
+
async for n in self._emit_subagent_progress(state_key, f"{icon} {source_name}"):
|
|
602
|
+
yield n
|
|
603
|
+
|
|
604
|
+
case FunctionToolResultEvent(result=RetryPromptPart(tool_name=tool_name) as result):
|
|
605
|
+
error_msg = result.model_response()
|
|
606
|
+
accumulated.append(f"ā {tool_name}: {error_msg}\n")
|
|
607
|
+
async for n in self._emit_subagent_progress(state_key, f"{icon} {source_name}"):
|
|
608
|
+
yield n
|
|
609
|
+
|
|
610
|
+
case StreamCompleteEvent():
|
|
611
|
+
# Complete the tool call
|
|
612
|
+
if state_key in self._tool_states:
|
|
613
|
+
yield ToolCallProgress(tool_call_id=state_key, status="completed")
|
|
614
|
+
self._cleanup_tool_state(state_key)
|
|
615
|
+
self._subagent_content.pop(state_key, None)
|
|
616
|
+
|
|
617
|
+
case _:
|
|
618
|
+
pass
|
|
619
|
+
|
|
620
|
+
async def _emit_subagent_progress(
|
|
621
|
+
self, state_key: str, title: str
|
|
622
|
+
) -> AsyncIterator[ACPSessionUpdate]:
|
|
623
|
+
"""Emit tool call notifications for subagent content."""
|
|
624
|
+
accumulated = self._subagent_content.get(state_key, [])
|
|
625
|
+
content_text = "".join(accumulated)
|
|
626
|
+
|
|
627
|
+
if state_key not in self._tool_states:
|
|
628
|
+
# Create state and emit start
|
|
629
|
+
self._tool_states[state_key] = _ToolState(
|
|
630
|
+
tool_call_id=state_key,
|
|
631
|
+
tool_name="delegate",
|
|
632
|
+
title=title,
|
|
633
|
+
kind="other",
|
|
634
|
+
raw_input={},
|
|
635
|
+
started=True,
|
|
636
|
+
)
|
|
637
|
+
yield ToolCallStart(
|
|
638
|
+
tool_call_id=state_key,
|
|
639
|
+
title=title,
|
|
640
|
+
kind="other",
|
|
641
|
+
raw_input={},
|
|
642
|
+
status="pending",
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
# Emit progress update
|
|
646
|
+
yield ToolCallProgress(
|
|
647
|
+
tool_call_id=state_key,
|
|
648
|
+
title=title,
|
|
649
|
+
status="in_progress",
|
|
650
|
+
content=[ContentToolCallContent.text(text=content_text)],
|
|
651
|
+
)
|