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.
Files changed (311) hide show
  1. acp/__init__.py +13 -4
  2. acp/acp_requests.py +20 -77
  3. acp/agent/connection.py +8 -0
  4. acp/agent/implementations/debug_server/debug_server.py +6 -2
  5. acp/agent/protocol.py +6 -0
  6. acp/bridge/README.md +15 -2
  7. acp/bridge/__init__.py +3 -2
  8. acp/bridge/__main__.py +60 -19
  9. acp/bridge/ws_server.py +173 -0
  10. acp/bridge/ws_server_cli.py +89 -0
  11. acp/client/connection.py +38 -29
  12. acp/client/implementations/default_client.py +3 -2
  13. acp/client/implementations/headless_client.py +2 -2
  14. acp/connection.py +2 -2
  15. acp/notifications.py +20 -50
  16. acp/schema/__init__.py +2 -0
  17. acp/schema/agent_responses.py +21 -0
  18. acp/schema/client_requests.py +3 -3
  19. acp/schema/session_state.py +63 -29
  20. acp/stdio.py +39 -9
  21. acp/task/supervisor.py +2 -2
  22. acp/transports.py +362 -2
  23. acp/utils.py +17 -4
  24. agentpool/__init__.py +6 -1
  25. agentpool/agents/__init__.py +2 -0
  26. agentpool/agents/acp_agent/acp_agent.py +407 -277
  27. agentpool/agents/acp_agent/acp_converters.py +196 -38
  28. agentpool/agents/acp_agent/client_handler.py +191 -26
  29. agentpool/agents/acp_agent/session_state.py +17 -6
  30. agentpool/agents/agent.py +607 -572
  31. agentpool/agents/agui_agent/__init__.py +0 -2
  32. agentpool/agents/agui_agent/agui_agent.py +176 -110
  33. agentpool/agents/agui_agent/agui_converters.py +0 -131
  34. agentpool/agents/agui_agent/helpers.py +3 -4
  35. agentpool/agents/base_agent.py +632 -17
  36. agentpool/agents/claude_code_agent/FORKING.md +191 -0
  37. agentpool/agents/claude_code_agent/__init__.py +13 -1
  38. agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
  39. agentpool/agents/claude_code_agent/converters.py +74 -143
  40. agentpool/agents/claude_code_agent/history.py +474 -0
  41. agentpool/agents/claude_code_agent/models.py +77 -0
  42. agentpool/agents/claude_code_agent/static_info.py +100 -0
  43. agentpool/agents/claude_code_agent/usage.py +242 -0
  44. agentpool/agents/context.py +40 -0
  45. agentpool/agents/events/__init__.py +24 -0
  46. agentpool/agents/events/builtin_handlers.py +67 -1
  47. agentpool/agents/events/event_emitter.py +32 -2
  48. agentpool/agents/events/events.py +104 -3
  49. agentpool/agents/events/infer_info.py +145 -0
  50. agentpool/agents/events/processors.py +254 -0
  51. agentpool/agents/interactions.py +41 -6
  52. agentpool/agents/modes.py +67 -0
  53. agentpool/agents/slashed_agent.py +5 -4
  54. agentpool/agents/tool_call_accumulator.py +213 -0
  55. agentpool/agents/tool_wrapping.py +18 -6
  56. agentpool/common_types.py +56 -21
  57. agentpool/config_resources/__init__.py +38 -1
  58. agentpool/config_resources/acp_assistant.yml +2 -2
  59. agentpool/config_resources/agents.yml +3 -0
  60. agentpool/config_resources/agents_template.yml +1 -0
  61. agentpool/config_resources/claude_code_agent.yml +10 -6
  62. agentpool/config_resources/external_acp_agents.yml +2 -1
  63. agentpool/delegation/base_team.py +4 -30
  64. agentpool/delegation/pool.py +136 -289
  65. agentpool/delegation/team.py +58 -57
  66. agentpool/delegation/teamrun.py +51 -55
  67. agentpool/diagnostics/__init__.py +53 -0
  68. agentpool/diagnostics/lsp_manager.py +1593 -0
  69. agentpool/diagnostics/lsp_proxy.py +41 -0
  70. agentpool/diagnostics/lsp_proxy_script.py +229 -0
  71. agentpool/diagnostics/models.py +398 -0
  72. agentpool/functional/run.py +10 -4
  73. agentpool/mcp_server/__init__.py +0 -2
  74. agentpool/mcp_server/client.py +76 -32
  75. agentpool/mcp_server/conversions.py +54 -13
  76. agentpool/mcp_server/manager.py +34 -54
  77. agentpool/mcp_server/registries/official_registry_client.py +35 -1
  78. agentpool/mcp_server/tool_bridge.py +186 -139
  79. agentpool/messaging/__init__.py +0 -2
  80. agentpool/messaging/compaction.py +72 -197
  81. agentpool/messaging/connection_manager.py +11 -10
  82. agentpool/messaging/event_manager.py +5 -5
  83. agentpool/messaging/message_container.py +6 -30
  84. agentpool/messaging/message_history.py +99 -8
  85. agentpool/messaging/messagenode.py +52 -14
  86. agentpool/messaging/messages.py +54 -35
  87. agentpool/messaging/processing.py +12 -22
  88. agentpool/models/__init__.py +1 -1
  89. agentpool/models/acp_agents/base.py +6 -24
  90. agentpool/models/acp_agents/mcp_capable.py +126 -157
  91. agentpool/models/acp_agents/non_mcp.py +129 -95
  92. agentpool/models/agents.py +98 -76
  93. agentpool/models/agui_agents.py +1 -1
  94. agentpool/models/claude_code_agents.py +144 -19
  95. agentpool/models/file_parsing.py +0 -1
  96. agentpool/models/manifest.py +113 -50
  97. agentpool/prompts/conversion_manager.py +1 -1
  98. agentpool/prompts/prompts.py +5 -2
  99. agentpool/repomap.py +1 -1
  100. agentpool/resource_providers/__init__.py +11 -1
  101. agentpool/resource_providers/aggregating.py +56 -5
  102. agentpool/resource_providers/base.py +70 -4
  103. agentpool/resource_providers/codemode/code_executor.py +72 -5
  104. agentpool/resource_providers/codemode/helpers.py +2 -2
  105. agentpool/resource_providers/codemode/provider.py +64 -12
  106. agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
  107. agentpool/resource_providers/codemode/remote_provider.py +9 -12
  108. agentpool/resource_providers/filtering.py +3 -1
  109. agentpool/resource_providers/mcp_provider.py +89 -12
  110. agentpool/resource_providers/plan_provider.py +228 -46
  111. agentpool/resource_providers/pool.py +7 -3
  112. agentpool/resource_providers/resource_info.py +111 -0
  113. agentpool/resource_providers/static.py +4 -2
  114. agentpool/sessions/__init__.py +4 -1
  115. agentpool/sessions/manager.py +33 -5
  116. agentpool/sessions/models.py +59 -6
  117. agentpool/sessions/protocol.py +28 -0
  118. agentpool/sessions/session.py +11 -55
  119. agentpool/skills/registry.py +13 -8
  120. agentpool/storage/manager.py +572 -49
  121. agentpool/talk/registry.py +4 -4
  122. agentpool/talk/talk.py +9 -10
  123. agentpool/testing.py +538 -20
  124. agentpool/tool_impls/__init__.py +6 -0
  125. agentpool/tool_impls/agent_cli/__init__.py +42 -0
  126. agentpool/tool_impls/agent_cli/tool.py +95 -0
  127. agentpool/tool_impls/bash/__init__.py +64 -0
  128. agentpool/tool_impls/bash/helpers.py +35 -0
  129. agentpool/tool_impls/bash/tool.py +171 -0
  130. agentpool/tool_impls/delete_path/__init__.py +70 -0
  131. agentpool/tool_impls/delete_path/tool.py +142 -0
  132. agentpool/tool_impls/download_file/__init__.py +80 -0
  133. agentpool/tool_impls/download_file/tool.py +183 -0
  134. agentpool/tool_impls/execute_code/__init__.py +55 -0
  135. agentpool/tool_impls/execute_code/tool.py +163 -0
  136. agentpool/tool_impls/grep/__init__.py +80 -0
  137. agentpool/tool_impls/grep/tool.py +200 -0
  138. agentpool/tool_impls/list_directory/__init__.py +73 -0
  139. agentpool/tool_impls/list_directory/tool.py +197 -0
  140. agentpool/tool_impls/question/__init__.py +42 -0
  141. agentpool/tool_impls/question/tool.py +127 -0
  142. agentpool/tool_impls/read/__init__.py +104 -0
  143. agentpool/tool_impls/read/tool.py +305 -0
  144. agentpool/tools/__init__.py +2 -1
  145. agentpool/tools/base.py +114 -34
  146. agentpool/tools/manager.py +57 -1
  147. agentpool/ui/base.py +2 -2
  148. agentpool/ui/mock_provider.py +2 -2
  149. agentpool/ui/stdlib_provider.py +2 -2
  150. agentpool/utils/file_watcher.py +269 -0
  151. agentpool/utils/identifiers.py +121 -0
  152. agentpool/utils/pydantic_ai_helpers.py +46 -0
  153. agentpool/utils/streams.py +616 -2
  154. agentpool/utils/subprocess_utils.py +155 -0
  155. agentpool/utils/token_breakdown.py +461 -0
  156. agentpool/vfs_registry.py +7 -2
  157. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
  158. agentpool-2.5.0.dist-info/RECORD +579 -0
  159. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
  160. agentpool_cli/__main__.py +24 -0
  161. agentpool_cli/create.py +1 -1
  162. agentpool_cli/serve_acp.py +100 -21
  163. agentpool_cli/serve_agui.py +87 -0
  164. agentpool_cli/serve_opencode.py +119 -0
  165. agentpool_cli/ui.py +557 -0
  166. agentpool_commands/__init__.py +42 -5
  167. agentpool_commands/agents.py +75 -2
  168. agentpool_commands/history.py +62 -0
  169. agentpool_commands/mcp.py +176 -0
  170. agentpool_commands/models.py +56 -3
  171. agentpool_commands/pool.py +260 -0
  172. agentpool_commands/session.py +1 -1
  173. agentpool_commands/text_sharing/__init__.py +119 -0
  174. agentpool_commands/text_sharing/base.py +123 -0
  175. agentpool_commands/text_sharing/github_gist.py +80 -0
  176. agentpool_commands/text_sharing/opencode.py +462 -0
  177. agentpool_commands/text_sharing/paste_rs.py +59 -0
  178. agentpool_commands/text_sharing/pastebin.py +116 -0
  179. agentpool_commands/text_sharing/shittycodingagent.py +112 -0
  180. agentpool_commands/tools.py +57 -0
  181. agentpool_commands/utils.py +80 -30
  182. agentpool_config/__init__.py +30 -2
  183. agentpool_config/agentpool_tools.py +498 -0
  184. agentpool_config/builtin_tools.py +77 -22
  185. agentpool_config/commands.py +24 -1
  186. agentpool_config/compaction.py +258 -0
  187. agentpool_config/converters.py +1 -1
  188. agentpool_config/event_handlers.py +42 -0
  189. agentpool_config/events.py +1 -1
  190. agentpool_config/forward_targets.py +1 -4
  191. agentpool_config/jinja.py +3 -3
  192. agentpool_config/mcp_server.py +132 -6
  193. agentpool_config/nodes.py +1 -1
  194. agentpool_config/observability.py +44 -0
  195. agentpool_config/session.py +0 -3
  196. agentpool_config/storage.py +82 -38
  197. agentpool_config/task.py +3 -3
  198. agentpool_config/tools.py +11 -22
  199. agentpool_config/toolsets.py +109 -233
  200. agentpool_server/a2a_server/agent_worker.py +307 -0
  201. agentpool_server/a2a_server/server.py +23 -18
  202. agentpool_server/acp_server/acp_agent.py +234 -181
  203. agentpool_server/acp_server/commands/acp_commands.py +151 -156
  204. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
  205. agentpool_server/acp_server/event_converter.py +651 -0
  206. agentpool_server/acp_server/input_provider.py +53 -10
  207. agentpool_server/acp_server/server.py +24 -90
  208. agentpool_server/acp_server/session.py +173 -331
  209. agentpool_server/acp_server/session_manager.py +8 -34
  210. agentpool_server/agui_server/server.py +3 -1
  211. agentpool_server/mcp_server/server.py +5 -2
  212. agentpool_server/opencode_server/.rules +95 -0
  213. agentpool_server/opencode_server/ENDPOINTS.md +401 -0
  214. agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
  215. agentpool_server/opencode_server/__init__.py +19 -0
  216. agentpool_server/opencode_server/command_validation.py +172 -0
  217. agentpool_server/opencode_server/converters.py +975 -0
  218. agentpool_server/opencode_server/dependencies.py +24 -0
  219. agentpool_server/opencode_server/input_provider.py +421 -0
  220. agentpool_server/opencode_server/models/__init__.py +250 -0
  221. agentpool_server/opencode_server/models/agent.py +53 -0
  222. agentpool_server/opencode_server/models/app.py +72 -0
  223. agentpool_server/opencode_server/models/base.py +26 -0
  224. agentpool_server/opencode_server/models/common.py +23 -0
  225. agentpool_server/opencode_server/models/config.py +37 -0
  226. agentpool_server/opencode_server/models/events.py +821 -0
  227. agentpool_server/opencode_server/models/file.py +88 -0
  228. agentpool_server/opencode_server/models/mcp.py +44 -0
  229. agentpool_server/opencode_server/models/message.py +179 -0
  230. agentpool_server/opencode_server/models/parts.py +323 -0
  231. agentpool_server/opencode_server/models/provider.py +81 -0
  232. agentpool_server/opencode_server/models/pty.py +43 -0
  233. agentpool_server/opencode_server/models/question.py +56 -0
  234. agentpool_server/opencode_server/models/session.py +111 -0
  235. agentpool_server/opencode_server/routes/__init__.py +29 -0
  236. agentpool_server/opencode_server/routes/agent_routes.py +473 -0
  237. agentpool_server/opencode_server/routes/app_routes.py +202 -0
  238. agentpool_server/opencode_server/routes/config_routes.py +302 -0
  239. agentpool_server/opencode_server/routes/file_routes.py +571 -0
  240. agentpool_server/opencode_server/routes/global_routes.py +94 -0
  241. agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
  242. agentpool_server/opencode_server/routes/message_routes.py +761 -0
  243. agentpool_server/opencode_server/routes/permission_routes.py +63 -0
  244. agentpool_server/opencode_server/routes/pty_routes.py +300 -0
  245. agentpool_server/opencode_server/routes/question_routes.py +128 -0
  246. agentpool_server/opencode_server/routes/session_routes.py +1276 -0
  247. agentpool_server/opencode_server/routes/tui_routes.py +139 -0
  248. agentpool_server/opencode_server/server.py +475 -0
  249. agentpool_server/opencode_server/state.py +151 -0
  250. agentpool_server/opencode_server/time_utils.py +8 -0
  251. agentpool_storage/__init__.py +12 -0
  252. agentpool_storage/base.py +184 -2
  253. agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
  254. agentpool_storage/claude_provider/__init__.py +42 -0
  255. agentpool_storage/claude_provider/provider.py +1089 -0
  256. agentpool_storage/file_provider.py +278 -15
  257. agentpool_storage/memory_provider.py +193 -12
  258. agentpool_storage/models.py +3 -0
  259. agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
  260. agentpool_storage/opencode_provider/__init__.py +16 -0
  261. agentpool_storage/opencode_provider/helpers.py +414 -0
  262. agentpool_storage/opencode_provider/provider.py +895 -0
  263. agentpool_storage/project_store.py +325 -0
  264. agentpool_storage/session_store.py +26 -6
  265. agentpool_storage/sql_provider/__init__.py +4 -2
  266. agentpool_storage/sql_provider/models.py +48 -0
  267. agentpool_storage/sql_provider/sql_provider.py +269 -3
  268. agentpool_storage/sql_provider/utils.py +12 -13
  269. agentpool_storage/zed_provider/__init__.py +16 -0
  270. agentpool_storage/zed_provider/helpers.py +281 -0
  271. agentpool_storage/zed_provider/models.py +130 -0
  272. agentpool_storage/zed_provider/provider.py +442 -0
  273. agentpool_storage/zed_provider.py +803 -0
  274. agentpool_toolsets/__init__.py +0 -2
  275. agentpool_toolsets/builtin/__init__.py +2 -12
  276. agentpool_toolsets/builtin/code.py +96 -57
  277. agentpool_toolsets/builtin/debug.py +118 -48
  278. agentpool_toolsets/builtin/execution_environment.py +115 -230
  279. agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
  280. agentpool_toolsets/builtin/skills.py +9 -4
  281. agentpool_toolsets/builtin/subagent_tools.py +64 -51
  282. agentpool_toolsets/builtin/workers.py +4 -2
  283. agentpool_toolsets/composio_toolset.py +2 -2
  284. agentpool_toolsets/entry_points.py +3 -1
  285. agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
  286. agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
  287. agentpool_toolsets/fsspec_toolset/grep.py +99 -7
  288. agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
  289. agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
  290. agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
  291. agentpool_toolsets/mcp_discovery/__init__.py +5 -0
  292. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  293. agentpool_toolsets/mcp_discovery/toolset.py +511 -0
  294. agentpool_toolsets/mcp_run_toolset.py +87 -12
  295. agentpool_toolsets/notifications.py +33 -33
  296. agentpool_toolsets/openapi.py +3 -1
  297. agentpool_toolsets/search_toolset.py +3 -1
  298. agentpool-2.1.9.dist-info/RECORD +0 -474
  299. agentpool_config/resources.py +0 -33
  300. agentpool_server/acp_server/acp_tools.py +0 -43
  301. agentpool_server/acp_server/commands/spawn.py +0 -210
  302. agentpool_storage/text_log_provider.py +0 -275
  303. agentpool_toolsets/builtin/agent_management.py +0 -239
  304. agentpool_toolsets/builtin/chain.py +0 -288
  305. agentpool_toolsets/builtin/history.py +0 -36
  306. agentpool_toolsets/builtin/integration.py +0 -85
  307. agentpool_toolsets/builtin/tool_management.py +0 -90
  308. agentpool_toolsets/builtin/user_interaction.py +0 -52
  309. agentpool_toolsets/semantic_memory_toolset.py +0 -536
  310. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
  311. {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
+ )