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
@@ -1,33 +0,0 @@
1
- """Models for resource information."""
2
-
3
- from __future__ import annotations
4
-
5
- from dataclasses import dataclass
6
- from typing import TYPE_CHECKING, Self
7
-
8
-
9
- if TYPE_CHECKING:
10
- from mcp.types import Resource as MCPResource
11
-
12
-
13
- @dataclass
14
- class ResourceInfo:
15
- """Information about an available resource.
16
-
17
- This class provides essential information about a resource that can be loaded.
18
- Use the resource name with load_resource() to access the actual content.
19
- """
20
-
21
- name: str
22
- """Name of the resource, use this with load_resource()"""
23
-
24
- uri: str
25
- """URI identifying the resource location"""
26
-
27
- description: str | None = None
28
- """Optional description of the resource's content or purpose"""
29
-
30
- @classmethod
31
- async def from_mcp_resource(cls, resource: MCPResource) -> Self:
32
- """Create ResourceInfo from MCP resource."""
33
- return cls(name=resource.name, uri=str(resource.uri), description=resource.description)
@@ -1,43 +0,0 @@
1
- """ACP resource providers."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import TYPE_CHECKING
6
-
7
- from exxec.acp_provider import ACPExecutionEnvironment
8
-
9
- from agentpool.resource_providers import PlanProvider
10
- from agentpool_toolsets.builtin import CodeTools, ExecutionEnvironmentTools
11
- from agentpool_toolsets.fsspec_toolset import FSSpecTools
12
-
13
-
14
- if TYPE_CHECKING:
15
- from agentpool.resource_providers.aggregating import AggregatingResourceProvider
16
- from agentpool_server.acp_server.session import ACPSession
17
-
18
-
19
- def get_acp_provider(session: ACPSession) -> AggregatingResourceProvider:
20
- """Create aggregated resource provider with ACP-specific toolsets.
21
-
22
- Args:
23
- session: The ACP session to create providers for
24
-
25
- Returns:
26
- AggregatingResourceProvider with execution, filesystem, and code tools
27
- """
28
- from agentpool.resource_providers.aggregating import AggregatingResourceProvider
29
-
30
- execution_env = ACPExecutionEnvironment(
31
- fs=session.fs, requests=session.requests, cwd=session.cwd
32
- )
33
-
34
- providers = [
35
- PlanProvider(),
36
- ExecutionEnvironmentTools(env=execution_env, name=f"acp_execution_{session.session_id}"),
37
- FSSpecTools(execution_env, name=f"acp_fs_{session.session_id}", cwd=session.cwd),
38
- CodeTools(execution_env, name=f"acp_code_{session.session_id}", cwd=session.cwd),
39
- ]
40
- return AggregatingResourceProvider(providers=providers, name=f"acp_{session.session_id}")
41
-
42
-
43
- __all__ = ["get_acp_provider"]
@@ -1,210 +0,0 @@
1
- """Spawn subagent slash command."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import TYPE_CHECKING, Any
6
- import uuid
7
-
8
- from pydantic_ai import (
9
- FinalResultEvent,
10
- FunctionToolCallEvent,
11
- FunctionToolResultEvent,
12
- PartDeltaEvent,
13
- PartStartEvent,
14
- RetryPromptPart,
15
- TextPart,
16
- TextPartDelta,
17
- ThinkingPart,
18
- ThinkingPartDelta,
19
- ToolCallPartDelta,
20
- ToolReturnPart,
21
- )
22
- from slashed import CommandContext, CommandError # noqa: TC002
23
-
24
- from agentpool.agents.events import StreamCompleteEvent, ToolCallProgressEvent
25
- from agentpool.log import get_logger
26
- from agentpool.messaging.context import NodeContext # noqa: TC001
27
- from agentpool_commands.base import NodeCommand
28
- from agentpool_server.acp_server.session import ACPSession # noqa: TC001
29
-
30
-
31
- if TYPE_CHECKING:
32
- from agentpool.agents.events import RichAgentStreamEvent
33
-
34
-
35
- logger = get_logger(__name__)
36
-
37
-
38
- class SpawnSubagentCommand(NodeCommand):
39
- """Spawn a subagent to execute a specific task.
40
-
41
- The subagent runs concurrently and reports progress in a dedicated tool call box.
42
-
43
- Usage:
44
- /spawn "agent-name" "prompt for the subagent"
45
- /spawn "code-reviewer" "Review the main.py file for potential bugs"
46
- """
47
-
48
- name = "spawn"
49
- category = "agents"
50
-
51
- async def execute_command(
52
- self,
53
- ctx: CommandContext[NodeContext[ACPSession]],
54
- agent_name: str,
55
- task_prompt: str,
56
- ) -> None:
57
- """Spawn a subagent to execute a task.
58
-
59
- Args:
60
- ctx: Command context with ACP session
61
- agent_name: Name of the agent to spawn
62
- task_prompt: Task prompt for the subagent
63
- """
64
- session = ctx.context.data
65
- assert session, "ACP session required for spawn command"
66
- # Generate unique tool call ID
67
- tool_call_id = f"spawn-{agent_name}-{uuid.uuid4().hex[:8]}"
68
- try:
69
- # Check if agent exists in pool
70
- if not session.agent_pool or agent_name not in session.agent_pool.agents:
71
- available = list(session.agent_pool.agents.keys())
72
- error_msg = f"Agent {agent_name!r} not found. Available agents: {available}"
73
- await ctx.print(f"❌ {error_msg}")
74
- return
75
-
76
- target_agent = session.agent_pool.get_agent(agent_name)
77
- await session.notifications.tool_call_start(
78
- tool_call_id=tool_call_id,
79
- title=f"Spawning agent: {agent_name}",
80
- kind="execute",
81
- raw_input={
82
- "agent_name": agent_name,
83
- "task_prompt": task_prompt,
84
- },
85
- )
86
-
87
- aggregated_content: list[str] = [] # Aggregate output as we stream
88
- try:
89
- # Run the subagent and handle events
90
- async for event in target_agent.run_stream(task_prompt):
91
- await _handle_subagent_event(event, tool_call_id, aggregated_content, session)
92
-
93
- final_content = "".join(aggregated_content).strip()
94
- await session.notifications.tool_call_progress(
95
- tool_call_id=tool_call_id,
96
- status="completed",
97
- content=[final_content] if final_content else None,
98
- )
99
- except Exception as e:
100
- error_msg = f"Subagent execution failed: {e}"
101
- logger.exception("Subagent execution error", error=str(e))
102
- await session.notifications.tool_call_progress(
103
- tool_call_id=tool_call_id,
104
- status="failed",
105
- raw_output=error_msg,
106
- )
107
-
108
- except Exception as e:
109
- error_msg = f"Failed to spawn agent '{agent_name}': {e}"
110
- logger.exception("Spawn command error", error=str(e))
111
- raise CommandError(error_msg) from e
112
-
113
-
114
- async def _handle_subagent_event(
115
- event: RichAgentStreamEvent[Any],
116
- tool_call_id: str,
117
- aggregated_content: list[str],
118
- session: ACPSession,
119
- ) -> None:
120
- """Handle events from spawned subagent and convert to tool_call_progress.
121
-
122
- Args:
123
- event: Event from the subagent stream
124
- tool_call_id: ID of the tool call box
125
- aggregated_content: List to accumulate content for final display
126
- session: ACP session for notifications
127
- """
128
- match event:
129
- case (
130
- PartStartEvent(part=TextPart(content=delta))
131
- | PartDeltaEvent(delta=TextPartDelta(content_delta=delta))
132
- ):
133
- # Subagent text output → accumulate and update progress
134
- aggregated_content.append(delta)
135
- await session.notifications.tool_call_progress(
136
- tool_call_id=tool_call_id,
137
- status="in_progress",
138
- content=["".join(aggregated_content)],
139
- )
140
-
141
- case (
142
- PartStartEvent(part=ThinkingPart(content=delta))
143
- | PartDeltaEvent(delta=ThinkingPartDelta(content_delta=delta))
144
- ):
145
- # Subagent thinking → show thinking indicator
146
- if delta:
147
- thinking_text = f"💭 {delta}"
148
- aggregated_content.append(thinking_text)
149
- await session.notifications.tool_call_progress(
150
- tool_call_id=tool_call_id,
151
- status="in_progress",
152
- content=["".join(aggregated_content)],
153
- )
154
-
155
- case FunctionToolCallEvent(part=part):
156
- # Subagent calls a tool → show nested tool call
157
- tool_text = f"\n🔧 Using tool: {part.tool_name}\n"
158
- aggregated_content.append(tool_text)
159
- await session.notifications.tool_call_progress(
160
- tool_call_id=tool_call_id,
161
- status="in_progress",
162
- content=["".join(aggregated_content)],
163
- )
164
-
165
- case FunctionToolResultEvent(
166
- result=ToolReturnPart(content=content, tool_name=tool_name),
167
- ):
168
- # Subagent tool completes → show tool result
169
- result_text = f"✅ {tool_name}: {content}\n"
170
- aggregated_content.append(result_text)
171
- await session.notifications.tool_call_progress(
172
- tool_call_id=tool_call_id,
173
- status="in_progress",
174
- content=["".join(aggregated_content)],
175
- )
176
-
177
- case FunctionToolResultEvent(
178
- result=RetryPromptPart(tool_name=tool_name) as result,
179
- ):
180
- # Tool call failed and needs retry
181
- error_message = result.model_response()
182
- error_text = f"❌ {tool_name or 'unknown'}: Error: {error_message}\n"
183
- aggregated_content.append(error_text)
184
- await session.notifications.tool_call_progress(
185
- tool_call_id=tool_call_id,
186
- status="in_progress",
187
- content=["".join(aggregated_content)],
188
- )
189
-
190
- case ToolCallProgressEvent(message=message, tool_name=tool_name):
191
- # Progress event from tools
192
- if message:
193
- progress_text = f"🔄 {tool_name}: {message}\n"
194
- aggregated_content.append(progress_text)
195
- await session.notifications.tool_call_progress(
196
- tool_call_id=tool_call_id,
197
- status="in_progress",
198
- content=["".join(aggregated_content)],
199
- )
200
-
201
- case (
202
- PartStartEvent()
203
- | PartDeltaEvent(delta=ToolCallPartDelta())
204
- | FinalResultEvent()
205
- | StreamCompleteEvent()
206
- ):
207
- pass # These events don't need special handling
208
-
209
- case _:
210
- logger.debug("Unhandled subagent event", event_type=type(event).__name__)
@@ -1,275 +0,0 @@
1
- """Text-based storage provider with dynamic paths."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import TYPE_CHECKING, Any, ClassVar
6
-
7
- from upathtools import to_upath
8
-
9
- from agentpool.log import get_logger
10
- from agentpool.utils.now import get_now
11
- from agentpool_storage.base import StorageProvider
12
-
13
-
14
- if TYPE_CHECKING:
15
- from datetime import datetime
16
-
17
- from jinja2 import Template
18
- from upathtools import JoinablePathLike, UPath
19
-
20
- from agentpool.common_types import JsonValue
21
- from agentpool_config.storage import LogFormat, TextLogConfig
22
-
23
-
24
- logger = get_logger(__name__)
25
-
26
-
27
- CONVERSATIONS_TEMPLATE = """\
28
- === AgentPool Log ===
29
-
30
- {%- for conv_id, conv in conversations.items() %}
31
- === Conversation {{ conv_id }} (agent: {{ conv.agent_name }}, started: {{ conv.start_time.strftime('%Y-%m-%d %H:%M:%S') }}) ===
32
-
33
- {%- for msg in messages if msg.conversation_id == conv_id %}
34
- [{{ msg.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}] {{ msg.sender }}{% if msg.model %} ({{ msg.model }}){% endif %}: {{ msg.content }}
35
- {%- if msg.cost_info %}
36
- Tokens: {{ msg.cost_info.token_usage.total }} (prompt: {{ msg.cost_info.token_usage.prompt }}, completion: {{ msg.cost_info.token_usage.completion }})
37
- Cost: ${{ "%.4f"|format(msg.cost_info.total_cost) }}
38
- {%- endif %}
39
- {%- if msg.response_time %}
40
- Response time: {{ "%.1f"|format(msg.response_time) }}s
41
- {%- endif %}
42
- {%- if msg.forwarded_from %}
43
- Forwarded via: {{ msg.forwarded_from|join(' -> ') }}
44
- {%- endif %}
45
-
46
- {%- for tool in tool_calls if tool.message_id == msg.id %}
47
- Tool Call: {{ tool.tool_name }}
48
- Args: {{ tool.args|pprint }}
49
- Result: {{ tool.result }}
50
- {%- endfor %}
51
- {%- endfor %}
52
- {%- endfor %}
53
-
54
- === Commands ===
55
- {%- for cmd in commands %}
56
- [{{ cmd.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}] {{ cmd.agent_name }} ({{ cmd.session_id }}): {{ cmd.command }}
57
- {%- endfor %}
58
- """ # noqa: E501
59
-
60
- CHRONOLOGICAL_TEMPLATE = """\
61
- === AgentPool Log ===
62
-
63
- {%- for entry in entries|sort(attribute="timestamp") %}
64
- {%- if entry.type == "conversation_start" %}
65
- === Conversation {{ entry.conversation_id }} (agent: {{ entry.agent_name }}) started ===
66
-
67
- {%- elif entry.type == "message" %}
68
- [{{ entry.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}] {{ entry.sender }}{% if entry.model %} ({{ entry.model }}){% endif %}: {{ entry.content }}
69
- {%- if entry.cost_info %}
70
- Tokens: {{ entry.cost_info.token_usage.total }} (prompt: {{ entry.cost_info.token_usage.prompt }}, completion: {{ entry.cost_info.token_usage.completion }})
71
- Cost: ${{ "%.4f"|format(entry.cost_info.total_cost) }}
72
- {%- endif %}
73
- {%- if entry.response_time %}
74
- Response time: {{ "%.1f"|format(entry.response_time) }}s
75
- {%- endif %}
76
- {%- if entry.forwarded_from %}
77
- Forwarded via: {{ entry.forwarded_from|join(' -> ') }}
78
- {%- endif %}
79
-
80
- {%- elif entry.type == "tool_call" %}
81
- [{{ entry.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}] Tool Call: {{ entry.tool_name }}
82
- Args: {{ entry.args|pprint }}
83
- Result: {{ entry.result }}
84
-
85
- {%- elif entry.type == "command" %}
86
- [{{ entry.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}] Command by {{ entry.agent_name }}: {{ entry.command }}
87
-
88
- {%- endif %}
89
- {%- endfor %}
90
- """ # noqa: E501
91
-
92
-
93
- class TextLogProvider(StorageProvider):
94
- """Human-readable text log provider with dynamic paths.
95
-
96
- Available template variables:
97
- - now: datetime - Current timestamp
98
- - date: date - Current date
99
- - operation: str - Type of operation (message/conversation/tool_call/command)
100
- - conversation_id: str - ID of current conversation
101
- - agent_name: str - Name of the agent
102
- - content: str - Message content
103
- - role: str - Message role
104
- - model: str - Model name
105
- - session_id: str - Session ID
106
- - tool_name: str - Name of tool being called
107
- - command: str - Command being executed
108
-
109
- All variables default to empty string if not available for current operation.
110
- """
111
-
112
- TEMPLATES: ClassVar[dict[LogFormat, str]] = {
113
- "chronological": CHRONOLOGICAL_TEMPLATE,
114
- "conversations": CONVERSATIONS_TEMPLATE,
115
- }
116
- can_load_history = False
117
-
118
- def __init__(self, config: TextLogConfig) -> None:
119
- """Initialize text log provider."""
120
- from jinja2 import Environment, Undefined
121
-
122
- class EmptyStringUndefined(Undefined):
123
- """Return empty string for undefined variables."""
124
-
125
- def __str__(self) -> str:
126
- return ""
127
-
128
- super().__init__(config)
129
- self.encoding = config.encoding
130
- self.content_template = self._load_template(config.template)
131
- # Configure Jinja env with empty string for undefined
132
- env = Environment(undefined=EmptyStringUndefined, enable_async=True)
133
- self.path_template = env.from_string(config.path)
134
-
135
- self._entries: list[dict[str, Any]] = []
136
-
137
- def _load_template(
138
- self,
139
- template: LogFormat | JoinablePathLike | None,
140
- ) -> Template:
141
- """Load template from predefined or file."""
142
- from jinja2 import Template
143
-
144
- if template is None:
145
- template_str = self.TEMPLATES["chronological"]
146
- elif template in self.TEMPLATES:
147
- template_str = self.TEMPLATES[template] # type: ignore
148
- else: # Assume it's a path
149
- template_str = to_upath(template).read_text()
150
- return Template(template_str)
151
-
152
- def _get_base_context(self, operation: str) -> dict[str, Any]:
153
- """Get base context with defaults.
154
-
155
- Args:
156
- operation: Type of operation being logged
157
-
158
- Returns:
159
- Base context dict with defaults
160
- """
161
- # All other variables will default to empty string via EmptyStringUndefined
162
- return {"now": get_now(), "date": get_now().date(), "operation": operation}
163
-
164
- async def _get_path(self, operation: str, **context: Any) -> UPath:
165
- """Render path template with context.
166
-
167
- Args:
168
- operation: Type of operation being logged
169
- **context: Additional context variables
170
-
171
- Returns:
172
- Concrete path for current operation
173
- """
174
- # Combine base context with provided values
175
- path_context = self._get_base_context(operation)
176
- path_context.update(context)
177
-
178
- path = await self.path_template.render_async(**path_context)
179
- resolved_path = to_upath(path)
180
- resolved_path.parent.mkdir(parents=True, exist_ok=True)
181
- return resolved_path
182
-
183
- async def log_message(
184
- self,
185
- *,
186
- conversation_id: str,
187
- message_id: str,
188
- content: str,
189
- role: str,
190
- name: str | None = None,
191
- cost_info: Any | None = None,
192
- model: str | None = None,
193
- response_time: float | None = None,
194
- forwarded_from: list[str] | None = None,
195
- provider_name: str | None = None,
196
- provider_response_id: str | None = None,
197
- messages: str | None = None,
198
- finish_reason: str | None = None,
199
- ) -> None:
200
- """Store message and update log."""
201
- entry = {
202
- "type": "message",
203
- "timestamp": get_now(),
204
- "conversation_id": conversation_id,
205
- "message_id": message_id,
206
- "content": content,
207
- "role": role,
208
- "agent_name": name,
209
- "model": model,
210
- "cost_info": cost_info,
211
- "response_time": response_time,
212
- "forwarded_from": forwarded_from,
213
- "provider_name": provider_name,
214
- "provider_response_id": provider_response_id,
215
- "messages": messages,
216
- "finish_reason": finish_reason,
217
- }
218
- self._entries.append(entry)
219
-
220
- path = await self._get_path("message", **entry)
221
- self._write(path)
222
-
223
- async def log_conversation(
224
- self,
225
- *,
226
- conversation_id: str,
227
- node_name: str,
228
- start_time: datetime | None = None,
229
- ) -> None:
230
- """Store conversation start."""
231
- entry = {
232
- "type": "conversation",
233
- "timestamp": start_time or get_now(),
234
- "conversation_id": conversation_id,
235
- "agent_name": node_name,
236
- }
237
- self._entries.append(entry)
238
-
239
- path = await self._get_path("conversation", **entry)
240
- self._write(path)
241
-
242
- async def log_command(
243
- self,
244
- *,
245
- agent_name: str,
246
- session_id: str,
247
- command: str,
248
- context_type: type | None = None,
249
- metadata: dict[str, JsonValue] | None = None,
250
- ) -> None:
251
- """Store command."""
252
- entry = {
253
- "type": "command",
254
- "timestamp": get_now(),
255
- "agent_name": agent_name,
256
- "session_id": session_id,
257
- "command": command,
258
- "context_type": context_type.__name__ if context_type else "",
259
- "metadata": metadata or {},
260
- }
261
- self._entries.append(entry)
262
-
263
- path = await self._get_path("command", **entry)
264
- self._write(path)
265
-
266
- def _write(self, path: UPath) -> None:
267
- """Write current state to file at given path."""
268
- context = {"entries": self._entries}
269
- try:
270
- text = self.content_template.render(context)
271
- path.write_text(text, encoding=self.encoding)
272
- except Exception as e:
273
- logger.exception("Failed to write to log file", path=path)
274
- msg = f"Failed to write to log file: {e}"
275
- raise RuntimeError(msg) from e