agentpool 2.1.9__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.

Potentially problematic release.


This version of agentpool might be problematic. Click here for more details.

Files changed (474) hide show
  1. acp/README.md +64 -0
  2. acp/__init__.py +172 -0
  3. acp/__main__.py +10 -0
  4. acp/acp_requests.py +285 -0
  5. acp/agent/__init__.py +6 -0
  6. acp/agent/connection.py +256 -0
  7. acp/agent/implementations/__init__.py +6 -0
  8. acp/agent/implementations/debug_server/__init__.py +1 -0
  9. acp/agent/implementations/debug_server/cli.py +79 -0
  10. acp/agent/implementations/debug_server/debug.html +234 -0
  11. acp/agent/implementations/debug_server/debug_server.py +496 -0
  12. acp/agent/implementations/testing.py +91 -0
  13. acp/agent/protocol.py +65 -0
  14. acp/bridge/README.md +162 -0
  15. acp/bridge/__init__.py +6 -0
  16. acp/bridge/__main__.py +91 -0
  17. acp/bridge/bridge.py +246 -0
  18. acp/bridge/py.typed +0 -0
  19. acp/bridge/settings.py +15 -0
  20. acp/client/__init__.py +7 -0
  21. acp/client/connection.py +251 -0
  22. acp/client/implementations/__init__.py +7 -0
  23. acp/client/implementations/default_client.py +185 -0
  24. acp/client/implementations/headless_client.py +266 -0
  25. acp/client/implementations/noop_client.py +110 -0
  26. acp/client/protocol.py +61 -0
  27. acp/connection.py +280 -0
  28. acp/exceptions.py +46 -0
  29. acp/filesystem.py +524 -0
  30. acp/notifications.py +832 -0
  31. acp/py.typed +0 -0
  32. acp/schema/__init__.py +265 -0
  33. acp/schema/agent_plan.py +30 -0
  34. acp/schema/agent_requests.py +126 -0
  35. acp/schema/agent_responses.py +256 -0
  36. acp/schema/base.py +39 -0
  37. acp/schema/capabilities.py +230 -0
  38. acp/schema/client_requests.py +247 -0
  39. acp/schema/client_responses.py +96 -0
  40. acp/schema/common.py +81 -0
  41. acp/schema/content_blocks.py +188 -0
  42. acp/schema/mcp.py +82 -0
  43. acp/schema/messages.py +171 -0
  44. acp/schema/notifications.py +82 -0
  45. acp/schema/protocol_stuff.md +3 -0
  46. acp/schema/session_state.py +160 -0
  47. acp/schema/session_updates.py +419 -0
  48. acp/schema/slash_commands.py +51 -0
  49. acp/schema/terminal.py +15 -0
  50. acp/schema/tool_call.py +347 -0
  51. acp/stdio.py +250 -0
  52. acp/task/__init__.py +53 -0
  53. acp/task/debug.py +197 -0
  54. acp/task/dispatcher.py +93 -0
  55. acp/task/queue.py +69 -0
  56. acp/task/sender.py +82 -0
  57. acp/task/state.py +87 -0
  58. acp/task/supervisor.py +93 -0
  59. acp/terminal_handle.py +30 -0
  60. acp/tool_call_reporter.py +199 -0
  61. acp/tool_call_state.py +178 -0
  62. acp/transports.py +104 -0
  63. acp/utils.py +240 -0
  64. agentpool/__init__.py +63 -0
  65. agentpool/__main__.py +7 -0
  66. agentpool/agents/__init__.py +30 -0
  67. agentpool/agents/acp_agent/__init__.py +5 -0
  68. agentpool/agents/acp_agent/acp_agent.py +837 -0
  69. agentpool/agents/acp_agent/acp_converters.py +294 -0
  70. agentpool/agents/acp_agent/client_handler.py +317 -0
  71. agentpool/agents/acp_agent/session_state.py +44 -0
  72. agentpool/agents/agent.py +1264 -0
  73. agentpool/agents/agui_agent/__init__.py +19 -0
  74. agentpool/agents/agui_agent/agui_agent.py +677 -0
  75. agentpool/agents/agui_agent/agui_converters.py +423 -0
  76. agentpool/agents/agui_agent/chunk_transformer.py +204 -0
  77. agentpool/agents/agui_agent/event_types.py +83 -0
  78. agentpool/agents/agui_agent/helpers.py +192 -0
  79. agentpool/agents/architect.py +71 -0
  80. agentpool/agents/base_agent.py +177 -0
  81. agentpool/agents/claude_code_agent/__init__.py +11 -0
  82. agentpool/agents/claude_code_agent/claude_code_agent.py +1021 -0
  83. agentpool/agents/claude_code_agent/converters.py +243 -0
  84. agentpool/agents/context.py +105 -0
  85. agentpool/agents/events/__init__.py +61 -0
  86. agentpool/agents/events/builtin_handlers.py +129 -0
  87. agentpool/agents/events/event_emitter.py +320 -0
  88. agentpool/agents/events/events.py +561 -0
  89. agentpool/agents/events/tts_handlers.py +186 -0
  90. agentpool/agents/interactions.py +419 -0
  91. agentpool/agents/slashed_agent.py +244 -0
  92. agentpool/agents/sys_prompts.py +178 -0
  93. agentpool/agents/tool_wrapping.py +184 -0
  94. agentpool/base_provider.py +28 -0
  95. agentpool/common_types.py +226 -0
  96. agentpool/config_resources/__init__.py +16 -0
  97. agentpool/config_resources/acp_assistant.yml +24 -0
  98. agentpool/config_resources/agents.yml +109 -0
  99. agentpool/config_resources/agents_template.yml +18 -0
  100. agentpool/config_resources/agui_test.yml +18 -0
  101. agentpool/config_resources/claude_code_agent.yml +16 -0
  102. agentpool/config_resources/claude_style_subagent.md +30 -0
  103. agentpool/config_resources/external_acp_agents.yml +77 -0
  104. agentpool/config_resources/opencode_style_subagent.md +19 -0
  105. agentpool/config_resources/tts_test_agents.yml +78 -0
  106. agentpool/delegation/__init__.py +8 -0
  107. agentpool/delegation/base_team.py +504 -0
  108. agentpool/delegation/message_flow_tracker.py +39 -0
  109. agentpool/delegation/pool.py +1129 -0
  110. agentpool/delegation/team.py +325 -0
  111. agentpool/delegation/teamrun.py +343 -0
  112. agentpool/docs/__init__.py +5 -0
  113. agentpool/docs/gen_examples.py +42 -0
  114. agentpool/docs/utils.py +370 -0
  115. agentpool/functional/__init__.py +20 -0
  116. agentpool/functional/py.typed +0 -0
  117. agentpool/functional/run.py +80 -0
  118. agentpool/functional/structure.py +136 -0
  119. agentpool/hooks/__init__.py +20 -0
  120. agentpool/hooks/agent_hooks.py +247 -0
  121. agentpool/hooks/base.py +119 -0
  122. agentpool/hooks/callable.py +140 -0
  123. agentpool/hooks/command.py +180 -0
  124. agentpool/hooks/prompt.py +122 -0
  125. agentpool/jinja_filters.py +132 -0
  126. agentpool/log.py +224 -0
  127. agentpool/mcp_server/__init__.py +17 -0
  128. agentpool/mcp_server/client.py +429 -0
  129. agentpool/mcp_server/constants.py +32 -0
  130. agentpool/mcp_server/conversions.py +172 -0
  131. agentpool/mcp_server/helpers.py +47 -0
  132. agentpool/mcp_server/manager.py +232 -0
  133. agentpool/mcp_server/message_handler.py +164 -0
  134. agentpool/mcp_server/registries/__init__.py +1 -0
  135. agentpool/mcp_server/registries/official_registry_client.py +345 -0
  136. agentpool/mcp_server/registries/pulsemcp_client.py +88 -0
  137. agentpool/mcp_server/tool_bridge.py +548 -0
  138. agentpool/messaging/__init__.py +58 -0
  139. agentpool/messaging/compaction.py +928 -0
  140. agentpool/messaging/connection_manager.py +319 -0
  141. agentpool/messaging/context.py +66 -0
  142. agentpool/messaging/event_manager.py +426 -0
  143. agentpool/messaging/events.py +39 -0
  144. agentpool/messaging/message_container.py +209 -0
  145. agentpool/messaging/message_history.py +491 -0
  146. agentpool/messaging/messagenode.py +377 -0
  147. agentpool/messaging/messages.py +655 -0
  148. agentpool/messaging/processing.py +76 -0
  149. agentpool/mime_utils.py +95 -0
  150. agentpool/models/__init__.py +21 -0
  151. agentpool/models/acp_agents/__init__.py +22 -0
  152. agentpool/models/acp_agents/base.py +308 -0
  153. agentpool/models/acp_agents/mcp_capable.py +790 -0
  154. agentpool/models/acp_agents/non_mcp.py +842 -0
  155. agentpool/models/agents.py +450 -0
  156. agentpool/models/agui_agents.py +89 -0
  157. agentpool/models/claude_code_agents.py +238 -0
  158. agentpool/models/file_agents.py +116 -0
  159. agentpool/models/file_parsing.py +367 -0
  160. agentpool/models/manifest.py +658 -0
  161. agentpool/observability/__init__.py +9 -0
  162. agentpool/observability/observability_registry.py +97 -0
  163. agentpool/prompts/__init__.py +1 -0
  164. agentpool/prompts/base.py +27 -0
  165. agentpool/prompts/builtin_provider.py +75 -0
  166. agentpool/prompts/conversion_manager.py +95 -0
  167. agentpool/prompts/convert.py +96 -0
  168. agentpool/prompts/manager.py +204 -0
  169. agentpool/prompts/parts/zed.md +33 -0
  170. agentpool/prompts/prompts.py +581 -0
  171. agentpool/py.typed +0 -0
  172. agentpool/queries/tree-sitter-language-pack/README.md +7 -0
  173. agentpool/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  174. agentpool/queries/tree-sitter-language-pack/c-tags.scm +9 -0
  175. agentpool/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  176. agentpool/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
  177. agentpool/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
  178. agentpool/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
  179. agentpool/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
  180. agentpool/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  181. agentpool/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
  182. agentpool/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  183. agentpool/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
  184. agentpool/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
  185. agentpool/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  186. agentpool/queries/tree-sitter-language-pack/go-tags.scm +42 -0
  187. agentpool/queries/tree-sitter-language-pack/java-tags.scm +20 -0
  188. agentpool/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
  189. agentpool/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
  190. agentpool/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
  191. agentpool/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  192. agentpool/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
  193. agentpool/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  194. agentpool/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  195. agentpool/queries/tree-sitter-language-pack/python-tags.scm +14 -0
  196. agentpool/queries/tree-sitter-language-pack/r-tags.scm +21 -0
  197. agentpool/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  198. agentpool/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
  199. agentpool/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
  200. agentpool/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  201. agentpool/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
  202. agentpool/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  203. agentpool/queries/tree-sitter-languages/README.md +24 -0
  204. agentpool/queries/tree-sitter-languages/c-tags.scm +9 -0
  205. agentpool/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
  206. agentpool/queries/tree-sitter-languages/cpp-tags.scm +15 -0
  207. agentpool/queries/tree-sitter-languages/dart-tags.scm +91 -0
  208. agentpool/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  209. agentpool/queries/tree-sitter-languages/elixir-tags.scm +54 -0
  210. agentpool/queries/tree-sitter-languages/elm-tags.scm +19 -0
  211. agentpool/queries/tree-sitter-languages/fortran-tags.scm +15 -0
  212. agentpool/queries/tree-sitter-languages/go-tags.scm +30 -0
  213. agentpool/queries/tree-sitter-languages/haskell-tags.scm +3 -0
  214. agentpool/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  215. agentpool/queries/tree-sitter-languages/java-tags.scm +20 -0
  216. agentpool/queries/tree-sitter-languages/javascript-tags.scm +88 -0
  217. agentpool/queries/tree-sitter-languages/julia-tags.scm +60 -0
  218. agentpool/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
  219. agentpool/queries/tree-sitter-languages/matlab-tags.scm +10 -0
  220. agentpool/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  221. agentpool/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
  222. agentpool/queries/tree-sitter-languages/php-tags.scm +26 -0
  223. agentpool/queries/tree-sitter-languages/python-tags.scm +12 -0
  224. agentpool/queries/tree-sitter-languages/ql-tags.scm +26 -0
  225. agentpool/queries/tree-sitter-languages/ruby-tags.scm +64 -0
  226. agentpool/queries/tree-sitter-languages/rust-tags.scm +60 -0
  227. agentpool/queries/tree-sitter-languages/scala-tags.scm +65 -0
  228. agentpool/queries/tree-sitter-languages/typescript-tags.scm +41 -0
  229. agentpool/queries/tree-sitter-languages/zig-tags.scm +3 -0
  230. agentpool/repomap.py +1231 -0
  231. agentpool/resource_providers/__init__.py +17 -0
  232. agentpool/resource_providers/aggregating.py +54 -0
  233. agentpool/resource_providers/base.py +172 -0
  234. agentpool/resource_providers/codemode/__init__.py +9 -0
  235. agentpool/resource_providers/codemode/code_executor.py +215 -0
  236. agentpool/resource_providers/codemode/default_prompt.py +19 -0
  237. agentpool/resource_providers/codemode/helpers.py +83 -0
  238. agentpool/resource_providers/codemode/progress_executor.py +212 -0
  239. agentpool/resource_providers/codemode/provider.py +150 -0
  240. agentpool/resource_providers/codemode/remote_mcp_execution.py +143 -0
  241. agentpool/resource_providers/codemode/remote_provider.py +171 -0
  242. agentpool/resource_providers/filtering.py +42 -0
  243. agentpool/resource_providers/mcp_provider.py +246 -0
  244. agentpool/resource_providers/plan_provider.py +196 -0
  245. agentpool/resource_providers/pool.py +69 -0
  246. agentpool/resource_providers/static.py +289 -0
  247. agentpool/running/__init__.py +20 -0
  248. agentpool/running/decorators.py +56 -0
  249. agentpool/running/discovery.py +101 -0
  250. agentpool/running/executor.py +284 -0
  251. agentpool/running/injection.py +111 -0
  252. agentpool/running/py.typed +0 -0
  253. agentpool/running/run_nodes.py +87 -0
  254. agentpool/server.py +122 -0
  255. agentpool/sessions/__init__.py +13 -0
  256. agentpool/sessions/manager.py +302 -0
  257. agentpool/sessions/models.py +71 -0
  258. agentpool/sessions/session.py +239 -0
  259. agentpool/sessions/store.py +163 -0
  260. agentpool/skills/__init__.py +5 -0
  261. agentpool/skills/manager.py +120 -0
  262. agentpool/skills/registry.py +210 -0
  263. agentpool/skills/skill.py +36 -0
  264. agentpool/storage/__init__.py +17 -0
  265. agentpool/storage/manager.py +419 -0
  266. agentpool/storage/serialization.py +136 -0
  267. agentpool/talk/__init__.py +13 -0
  268. agentpool/talk/registry.py +128 -0
  269. agentpool/talk/stats.py +159 -0
  270. agentpool/talk/talk.py +604 -0
  271. agentpool/tasks/__init__.py +20 -0
  272. agentpool/tasks/exceptions.py +25 -0
  273. agentpool/tasks/registry.py +33 -0
  274. agentpool/testing.py +129 -0
  275. agentpool/text_templates/__init__.py +39 -0
  276. agentpool/text_templates/system_prompt.jinja +30 -0
  277. agentpool/text_templates/tool_call_default.jinja +13 -0
  278. agentpool/text_templates/tool_call_markdown.jinja +25 -0
  279. agentpool/text_templates/tool_call_simple.jinja +5 -0
  280. agentpool/tools/__init__.py +16 -0
  281. agentpool/tools/base.py +269 -0
  282. agentpool/tools/exceptions.py +9 -0
  283. agentpool/tools/manager.py +255 -0
  284. agentpool/tools/tool_call_info.py +87 -0
  285. agentpool/ui/__init__.py +2 -0
  286. agentpool/ui/base.py +89 -0
  287. agentpool/ui/mock_provider.py +81 -0
  288. agentpool/ui/stdlib_provider.py +150 -0
  289. agentpool/utils/__init__.py +44 -0
  290. agentpool/utils/baseregistry.py +185 -0
  291. agentpool/utils/count_tokens.py +62 -0
  292. agentpool/utils/dag.py +184 -0
  293. agentpool/utils/importing.py +206 -0
  294. agentpool/utils/inspection.py +334 -0
  295. agentpool/utils/model_capabilities.py +25 -0
  296. agentpool/utils/network.py +28 -0
  297. agentpool/utils/now.py +22 -0
  298. agentpool/utils/parse_time.py +87 -0
  299. agentpool/utils/result_utils.py +35 -0
  300. agentpool/utils/signatures.py +305 -0
  301. agentpool/utils/streams.py +112 -0
  302. agentpool/utils/tasks.py +186 -0
  303. agentpool/vfs_registry.py +250 -0
  304. agentpool-2.1.9.dist-info/METADATA +336 -0
  305. agentpool-2.1.9.dist-info/RECORD +474 -0
  306. agentpool-2.1.9.dist-info/WHEEL +4 -0
  307. agentpool-2.1.9.dist-info/entry_points.txt +14 -0
  308. agentpool-2.1.9.dist-info/licenses/LICENSE +22 -0
  309. agentpool_cli/__init__.py +34 -0
  310. agentpool_cli/__main__.py +66 -0
  311. agentpool_cli/agent.py +175 -0
  312. agentpool_cli/cli_types.py +23 -0
  313. agentpool_cli/common.py +163 -0
  314. agentpool_cli/create.py +175 -0
  315. agentpool_cli/history.py +217 -0
  316. agentpool_cli/log.py +78 -0
  317. agentpool_cli/py.typed +0 -0
  318. agentpool_cli/run.py +84 -0
  319. agentpool_cli/serve_acp.py +177 -0
  320. agentpool_cli/serve_api.py +69 -0
  321. agentpool_cli/serve_mcp.py +74 -0
  322. agentpool_cli/serve_vercel.py +233 -0
  323. agentpool_cli/store.py +171 -0
  324. agentpool_cli/task.py +84 -0
  325. agentpool_cli/utils.py +104 -0
  326. agentpool_cli/watch.py +54 -0
  327. agentpool_commands/__init__.py +180 -0
  328. agentpool_commands/agents.py +199 -0
  329. agentpool_commands/base.py +45 -0
  330. agentpool_commands/commands.py +58 -0
  331. agentpool_commands/completers.py +110 -0
  332. agentpool_commands/connections.py +175 -0
  333. agentpool_commands/markdown_utils.py +31 -0
  334. agentpool_commands/models.py +62 -0
  335. agentpool_commands/prompts.py +78 -0
  336. agentpool_commands/py.typed +0 -0
  337. agentpool_commands/read.py +77 -0
  338. agentpool_commands/resources.py +210 -0
  339. agentpool_commands/session.py +48 -0
  340. agentpool_commands/tools.py +269 -0
  341. agentpool_commands/utils.py +189 -0
  342. agentpool_commands/workers.py +163 -0
  343. agentpool_config/__init__.py +53 -0
  344. agentpool_config/builtin_tools.py +265 -0
  345. agentpool_config/commands.py +237 -0
  346. agentpool_config/conditions.py +301 -0
  347. agentpool_config/converters.py +30 -0
  348. agentpool_config/durable.py +331 -0
  349. agentpool_config/event_handlers.py +600 -0
  350. agentpool_config/events.py +153 -0
  351. agentpool_config/forward_targets.py +251 -0
  352. agentpool_config/hook_conditions.py +331 -0
  353. agentpool_config/hooks.py +241 -0
  354. agentpool_config/jinja.py +206 -0
  355. agentpool_config/knowledge.py +41 -0
  356. agentpool_config/loaders.py +350 -0
  357. agentpool_config/mcp_server.py +243 -0
  358. agentpool_config/nodes.py +202 -0
  359. agentpool_config/observability.py +191 -0
  360. agentpool_config/output_types.py +55 -0
  361. agentpool_config/pool_server.py +267 -0
  362. agentpool_config/prompt_hubs.py +105 -0
  363. agentpool_config/prompts.py +185 -0
  364. agentpool_config/py.typed +0 -0
  365. agentpool_config/resources.py +33 -0
  366. agentpool_config/session.py +119 -0
  367. agentpool_config/skills.py +17 -0
  368. agentpool_config/storage.py +288 -0
  369. agentpool_config/system_prompts.py +190 -0
  370. agentpool_config/task.py +162 -0
  371. agentpool_config/teams.py +52 -0
  372. agentpool_config/tools.py +112 -0
  373. agentpool_config/toolsets.py +1033 -0
  374. agentpool_config/workers.py +86 -0
  375. agentpool_prompts/__init__.py +1 -0
  376. agentpool_prompts/braintrust_hub.py +235 -0
  377. agentpool_prompts/fabric.py +75 -0
  378. agentpool_prompts/langfuse_hub.py +79 -0
  379. agentpool_prompts/promptlayer_provider.py +59 -0
  380. agentpool_prompts/py.typed +0 -0
  381. agentpool_server/__init__.py +9 -0
  382. agentpool_server/a2a_server/__init__.py +5 -0
  383. agentpool_server/a2a_server/a2a_types.py +41 -0
  384. agentpool_server/a2a_server/server.py +190 -0
  385. agentpool_server/a2a_server/storage.py +81 -0
  386. agentpool_server/acp_server/__init__.py +22 -0
  387. agentpool_server/acp_server/acp_agent.py +786 -0
  388. agentpool_server/acp_server/acp_tools.py +43 -0
  389. agentpool_server/acp_server/commands/__init__.py +18 -0
  390. agentpool_server/acp_server/commands/acp_commands.py +594 -0
  391. agentpool_server/acp_server/commands/debug_commands.py +376 -0
  392. agentpool_server/acp_server/commands/docs_commands/__init__.py +39 -0
  393. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +169 -0
  394. agentpool_server/acp_server/commands/docs_commands/get_schema.py +176 -0
  395. agentpool_server/acp_server/commands/docs_commands/get_source.py +110 -0
  396. agentpool_server/acp_server/commands/docs_commands/git_diff.py +111 -0
  397. agentpool_server/acp_server/commands/docs_commands/helpers.py +33 -0
  398. agentpool_server/acp_server/commands/docs_commands/url_to_markdown.py +90 -0
  399. agentpool_server/acp_server/commands/spawn.py +210 -0
  400. agentpool_server/acp_server/converters.py +235 -0
  401. agentpool_server/acp_server/input_provider.py +338 -0
  402. agentpool_server/acp_server/server.py +288 -0
  403. agentpool_server/acp_server/session.py +969 -0
  404. agentpool_server/acp_server/session_manager.py +313 -0
  405. agentpool_server/acp_server/syntax_detection.py +250 -0
  406. agentpool_server/acp_server/zed_tools.md +90 -0
  407. agentpool_server/aggregating_server.py +309 -0
  408. agentpool_server/agui_server/__init__.py +11 -0
  409. agentpool_server/agui_server/server.py +128 -0
  410. agentpool_server/base.py +189 -0
  411. agentpool_server/http_server.py +164 -0
  412. agentpool_server/mcp_server/__init__.py +6 -0
  413. agentpool_server/mcp_server/server.py +314 -0
  414. agentpool_server/mcp_server/zed_wrapper.py +110 -0
  415. agentpool_server/openai_api_server/__init__.py +5 -0
  416. agentpool_server/openai_api_server/completions/__init__.py +1 -0
  417. agentpool_server/openai_api_server/completions/helpers.py +81 -0
  418. agentpool_server/openai_api_server/completions/models.py +98 -0
  419. agentpool_server/openai_api_server/responses/__init__.py +1 -0
  420. agentpool_server/openai_api_server/responses/helpers.py +74 -0
  421. agentpool_server/openai_api_server/responses/models.py +96 -0
  422. agentpool_server/openai_api_server/server.py +242 -0
  423. agentpool_server/py.typed +0 -0
  424. agentpool_storage/__init__.py +9 -0
  425. agentpool_storage/base.py +310 -0
  426. agentpool_storage/file_provider.py +378 -0
  427. agentpool_storage/formatters.py +129 -0
  428. agentpool_storage/memory_provider.py +396 -0
  429. agentpool_storage/models.py +108 -0
  430. agentpool_storage/py.typed +0 -0
  431. agentpool_storage/session_store.py +262 -0
  432. agentpool_storage/sql_provider/__init__.py +21 -0
  433. agentpool_storage/sql_provider/cli.py +146 -0
  434. agentpool_storage/sql_provider/models.py +249 -0
  435. agentpool_storage/sql_provider/queries.py +15 -0
  436. agentpool_storage/sql_provider/sql_provider.py +444 -0
  437. agentpool_storage/sql_provider/utils.py +234 -0
  438. agentpool_storage/text_log_provider.py +275 -0
  439. agentpool_toolsets/__init__.py +15 -0
  440. agentpool_toolsets/builtin/__init__.py +33 -0
  441. agentpool_toolsets/builtin/agent_management.py +239 -0
  442. agentpool_toolsets/builtin/chain.py +288 -0
  443. agentpool_toolsets/builtin/code.py +398 -0
  444. agentpool_toolsets/builtin/debug.py +291 -0
  445. agentpool_toolsets/builtin/execution_environment.py +381 -0
  446. agentpool_toolsets/builtin/file_edit/__init__.py +11 -0
  447. agentpool_toolsets/builtin/file_edit/file_edit.py +747 -0
  448. agentpool_toolsets/builtin/file_edit/fuzzy_matcher/__init__.py +5 -0
  449. agentpool_toolsets/builtin/file_edit/fuzzy_matcher/example_usage.py +311 -0
  450. agentpool_toolsets/builtin/file_edit/fuzzy_matcher/streaming_fuzzy_matcher.py +443 -0
  451. agentpool_toolsets/builtin/history.py +36 -0
  452. agentpool_toolsets/builtin/integration.py +85 -0
  453. agentpool_toolsets/builtin/skills.py +77 -0
  454. agentpool_toolsets/builtin/subagent_tools.py +324 -0
  455. agentpool_toolsets/builtin/tool_management.py +90 -0
  456. agentpool_toolsets/builtin/user_interaction.py +52 -0
  457. agentpool_toolsets/builtin/workers.py +128 -0
  458. agentpool_toolsets/composio_toolset.py +96 -0
  459. agentpool_toolsets/config_creation.py +192 -0
  460. agentpool_toolsets/entry_points.py +47 -0
  461. agentpool_toolsets/fsspec_toolset/__init__.py +7 -0
  462. agentpool_toolsets/fsspec_toolset/diagnostics.py +115 -0
  463. agentpool_toolsets/fsspec_toolset/grep.py +450 -0
  464. agentpool_toolsets/fsspec_toolset/helpers.py +631 -0
  465. agentpool_toolsets/fsspec_toolset/streaming_diff_parser.py +249 -0
  466. agentpool_toolsets/fsspec_toolset/toolset.py +1384 -0
  467. agentpool_toolsets/mcp_run_toolset.py +61 -0
  468. agentpool_toolsets/notifications.py +146 -0
  469. agentpool_toolsets/openapi.py +118 -0
  470. agentpool_toolsets/py.typed +0 -0
  471. agentpool_toolsets/search_toolset.py +202 -0
  472. agentpool_toolsets/semantic_memory_toolset.py +536 -0
  473. agentpool_toolsets/streaming_tools.py +265 -0
  474. agentpool_toolsets/vfs_toolset.py +124 -0
@@ -0,0 +1,969 @@
1
+ """ACP (Agent Client Protocol) session management for agentpool.
2
+
3
+ This module provides session lifecycle management, state tracking, and coordination
4
+ between agents and ACP clients through the JSON-RPC protocol.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ from collections.abc import AsyncGenerator
11
+ from dataclasses import dataclass, field
12
+ import re
13
+ from typing import TYPE_CHECKING, Any
14
+
15
+ from exxec.acp_provider import ACPExecutionEnvironment
16
+ import logfire
17
+ from pydantic_ai import (
18
+ FinalResultEvent,
19
+ FunctionToolCallEvent,
20
+ FunctionToolResultEvent,
21
+ PartDeltaEvent,
22
+ PartStartEvent,
23
+ RetryPromptPart,
24
+ TextPart,
25
+ TextPartDelta,
26
+ ThinkingPart,
27
+ ThinkingPartDelta,
28
+ ToolCallPartDelta,
29
+ ToolReturnPart,
30
+ UsageLimitExceeded,
31
+ UserPromptPart,
32
+ )
33
+ from slashed import Command, CommandStore
34
+
35
+ from acp import RequestPermissionRequest
36
+ from acp.acp_requests import ACPRequests
37
+ from acp.filesystem import ACPFileSystem
38
+ from acp.notifications import ACPNotifications
39
+ from acp.schema import (
40
+ AvailableCommand,
41
+ ClientCapabilities,
42
+ ContentToolCallContent,
43
+ PlanEntry,
44
+ TerminalToolCallContent,
45
+ ToolCallLocation,
46
+ )
47
+ from acp.tool_call_state import ToolCallState
48
+ from acp.utils import generate_tool_title, infer_tool_kind, to_acp_content_blocks
49
+ from agentpool import Agent, AgentContext # noqa: TC001
50
+ from agentpool.agents import SlashedAgent
51
+ from agentpool.agents.acp_agent import ACPAgent
52
+ from agentpool.agents.events import (
53
+ PlanUpdateEvent,
54
+ StreamCompleteEvent,
55
+ ToolCallProgressEvent,
56
+ ToolCallStartEvent,
57
+ )
58
+ from agentpool.log import get_logger
59
+ from agentpool_commands import get_commands
60
+ from agentpool_commands.base import NodeCommand
61
+ from agentpool_server.acp_server.converters import (
62
+ convert_acp_mcp_server_to_config,
63
+ from_acp_content,
64
+ )
65
+ from agentpool_server.acp_server.input_provider import ACPInputProvider
66
+
67
+
68
+ if TYPE_CHECKING:
69
+ from collections.abc import Callable, Sequence
70
+
71
+ from pydantic_ai import (
72
+ SystemPromptPart,
73
+ UserContent,
74
+ )
75
+ from slashed import CommandContext
76
+
77
+ from acp import Client, RequestPermissionResponse
78
+ from acp.schema import (
79
+ ContentBlock,
80
+ Implementation,
81
+ McpServer,
82
+ StopReason,
83
+ )
84
+ from agentpool import AgentPool
85
+ from agentpool.agents import AGUIAgent
86
+ from agentpool.agents.claude_code_agent import ClaudeCodeAgent
87
+ from agentpool.agents.events import RichAgentStreamEvent
88
+ from agentpool.prompts.manager import PromptManager
89
+ from agentpool.prompts.prompts import MCPClientPrompt
90
+ from agentpool_server.acp_server.acp_agent import AgentPoolACPAgent
91
+ from agentpool_server.acp_server.session_manager import ACPSessionManager
92
+
93
+ logger = get_logger(__name__)
94
+ SLASH_PATTERN = re.compile(r"^/([\w-]+)(?:\s+(.*))?$")
95
+
96
+ # Zed-specific instructions for code references
97
+ ZED_CLIENT_PROMPT = """\
98
+ ## Code References
99
+
100
+ When referencing code locations in responses, use markdown links with `file://` URLs:
101
+
102
+ - **File**: `[filename](file:///absolute/path/to/file.py)`
103
+ - **Line range**: `[filename#L10-25](file:///absolute/path/to/file.py#L10:25)`
104
+ - **Single line**: `[filename#L10](file:///absolute/path/to/file.py#L10:10)`
105
+ - **Directory**: `[dirname/](file:///absolute/path/to/dir/)`
106
+
107
+ Line range format is `#L<start>:<end>` (1-based, inclusive).
108
+
109
+ Use these clickable references instead of inline code blocks when pointing to specific \
110
+ code locations. For showing actual code content, still use fenced code blocks.
111
+
112
+ ## Zed-specific URLs
113
+
114
+ In addition to `file://` URLs, these `zed://` URLs work in the agent context:
115
+
116
+ - **File reference**: `[text](zed:///agent/file?path=/absolute/path/to/file.py)`
117
+ - **Selection**: `[text](zed:///agent/selection?path=/absolute/path/to/file.py#L10:25)`
118
+ - **Symbol**: `[text](zed:///agent/symbol/function_name?path=/absolute/path/to/file.py#L10:25)`
119
+ - **Directory**: `[text](zed:///agent/directory?path=/absolute/path/to/dir)`
120
+
121
+ Query params must be URL-encoded (spaces → `%20`). Paths must be absolute.
122
+ """
123
+
124
+
125
+ def _is_slash_command(text: str) -> bool:
126
+ """Check if text starts with a slash command."""
127
+ return bool(SLASH_PATTERN.match(text.strip()))
128
+
129
+
130
+ def split_commands(
131
+ contents: Sequence[UserContent],
132
+ ) -> tuple[list[str], list[UserContent]]:
133
+ commands: list[str] = []
134
+ non_command_content: list[UserContent] = []
135
+ for item in contents:
136
+ if isinstance(item, str) and _is_slash_command(item):
137
+ commands.append(item.strip())
138
+ else:
139
+ non_command_content.append(item)
140
+ return commands, non_command_content
141
+
142
+
143
+ @dataclass
144
+ class StagedContent:
145
+ """Buffer for prompt parts to be injected into the next agent call.
146
+
147
+ This allows commands (like /fetch-repo, /git-diff) to stage content that will
148
+ be automatically included in the next prompt sent to the agent.
149
+ """
150
+
151
+ _parts: list[SystemPromptPart | UserPromptPart] = field(default_factory=list)
152
+
153
+ def add(self, parts: list[SystemPromptPart | UserPromptPart]) -> None:
154
+ """Add prompt parts to the staging area."""
155
+ self._parts.extend(parts)
156
+
157
+ def add_text(self, content: str) -> None:
158
+ """Add text content to the staging area as a UserPromptPart."""
159
+ self._parts.append(UserPromptPart(content=content))
160
+
161
+ def consume(self) -> list[SystemPromptPart | UserPromptPart]:
162
+ """Return all staged parts and clear the buffer."""
163
+ parts = self._parts.copy()
164
+ self._parts.clear()
165
+ return parts
166
+
167
+ def consume_as_text(self) -> str | None:
168
+ """Return all staged content as a single string and clear the buffer.
169
+
170
+ Returns:
171
+ Combined text content, or None if nothing staged.
172
+ """
173
+ if not self._parts:
174
+ return None
175
+ texts = [part.content for part in self._parts if isinstance(part.content, str)]
176
+ self._parts.clear()
177
+ return "\n\n".join(texts) if texts else None
178
+
179
+ def __len__(self) -> int:
180
+ """Return count of staged parts."""
181
+ return len(self._parts)
182
+
183
+
184
+ @dataclass
185
+ class ACPSession:
186
+ """Individual ACP session state and management.
187
+
188
+ Manages the lifecycle and state of a single ACP session, including:
189
+ - Agent instance and conversation state
190
+ - Working directory and environment
191
+ - MCP server connections
192
+ - File system bridge for client operations
193
+ - Tool execution and streaming updates
194
+ """
195
+
196
+ session_id: str
197
+ """Unique session identifier"""
198
+
199
+ agent_pool: AgentPool[Any]
200
+ """AgentPool containing available agents"""
201
+
202
+ current_agent_name: str
203
+ """Name of currently active agent"""
204
+
205
+ cwd: str
206
+ """Working directory for the session"""
207
+
208
+ client: Client
209
+ """External library Client interface for operations"""
210
+
211
+ acp_agent: AgentPoolACPAgent
212
+ """ACP agent instance for capability tools"""
213
+
214
+ mcp_servers: Sequence[McpServer] | None = None
215
+ """Optional MCP server configurations"""
216
+
217
+ client_capabilities: ClientCapabilities = field(default_factory=ClientCapabilities)
218
+ """Client capabilities for tool registration"""
219
+
220
+ client_info: Implementation | None = None
221
+ """Client implementation info (name, version, title)"""
222
+
223
+ manager: ACPSessionManager | None = None
224
+ """Session manager for managing sessions. Used for session management commands."""
225
+
226
+ def __post_init__(self) -> None:
227
+ """Initialize session state and set up providers."""
228
+ from agentpool_server.acp_server.commands import get_commands as get_acp_commands
229
+
230
+ self.mcp_servers = self.mcp_servers or []
231
+ self.log = logger.bind(session_id=self.session_id)
232
+ self._task_lock = asyncio.Lock()
233
+ self._cancelled = False
234
+ self._current_tool_inputs: dict[str, dict[str, Any]] = {}
235
+ self._tool_call_states: dict[str, ToolCallState] = {}
236
+ self.fs = ACPFileSystem(self.client, session_id=self.session_id)
237
+ cmds = [
238
+ *get_commands(
239
+ enable_set_model=False,
240
+ enable_list_resources=False,
241
+ enable_add_resource=False,
242
+ enable_show_resource=False,
243
+ ),
244
+ *get_acp_commands(),
245
+ ]
246
+ self.command_store = CommandStore(enable_system_commands=True, commands=cmds)
247
+ self.command_store._initialize_sync()
248
+ self._update_callbacks: list[Callable[[], None]] = []
249
+
250
+ self.staged_content = StagedContent()
251
+ # Inject Zed-specific instructions if client is Zed
252
+ if self.client_info and self.client_info.name and "zed" in self.client_info.name.lower():
253
+ self.staged_content.add_text(ZED_CLIENT_PROMPT)
254
+ self.notifications = ACPNotifications(client=self.client, session_id=self.session_id)
255
+ self.requests = ACPRequests(client=self.client, session_id=self.session_id)
256
+ self.input_provider = ACPInputProvider(self)
257
+ self.acp_env = ACPExecutionEnvironment(fs=self.fs, requests=self.requests, cwd=self.cwd)
258
+ for agent in self.agent_pool.all_agents.values():
259
+ agent.env = self.acp_env
260
+ if isinstance(agent, Agent):
261
+ # TODO: need to inject this info for ACP agents, too.
262
+ agent.sys_prompts.prompts.append(self.get_cwd_context) # pyright: ignore[reportArgumentType]
263
+ if isinstance(agent, ACPAgent):
264
+
265
+ async def permission_callback(
266
+ params: RequestPermissionRequest,
267
+ ) -> RequestPermissionResponse:
268
+ # Reconstruct request with our session_id (not nested agent's session_id)
269
+ self.log.debug(
270
+ "Forwarding permission request",
271
+ original_session_id=params.session_id,
272
+ our_session_id=self.session_id,
273
+ tool_call_id=params.tool_call.tool_call_id,
274
+ tool_call_title=params.tool_call.title,
275
+ options=[o.option_id for o in (params.options or [])],
276
+ )
277
+ forwarded_request = RequestPermissionRequest(
278
+ session_id=self.session_id, # Use agentpool ↔ Zed session_id
279
+ options=params.options,
280
+ tool_call=params.tool_call,
281
+ )
282
+ try:
283
+ response = await self.requests.client.request_permission(forwarded_request)
284
+ self.log.debug(
285
+ "Permission response received",
286
+ outcome_type=type(response.outcome).__name__,
287
+ outcome=response.outcome.outcome,
288
+ option_id=getattr(response.outcome, "option_id", None),
289
+ )
290
+ except Exception as exc:
291
+ self.log.exception("Permission forwarding failed", error=str(exc))
292
+ raise
293
+ else:
294
+ return response
295
+
296
+ agent.acp_permission_callback = permission_callback
297
+ self.log.info("Created ACP session", current_agent=self.current_agent_name)
298
+
299
+ async def initialize(self) -> None:
300
+ """Initialize async resources. Must be called after construction."""
301
+ await self.acp_env.__aenter__()
302
+
303
+ def _get_or_create_tool_state(
304
+ self,
305
+ tool_call_id: str,
306
+ tool_name: str,
307
+ tool_input: dict[str, Any],
308
+ ) -> ToolCallState:
309
+ """Get existing tool call state or create a new one.
310
+
311
+ Args:
312
+ tool_call_id: Unique identifier for the tool call
313
+ tool_name: Name of the tool being called
314
+ tool_input: Input parameters for the tool
315
+
316
+ Returns:
317
+ ToolCallState instance (existing or newly created)
318
+ """
319
+ if tool_call_id not in self._tool_call_states:
320
+ state = ToolCallState(
321
+ notifications=self.notifications,
322
+ tool_call_id=tool_call_id,
323
+ tool_name=tool_name,
324
+ title=generate_tool_title(tool_name, tool_input),
325
+ kind=infer_tool_kind(tool_name),
326
+ raw_input=tool_input,
327
+ )
328
+ self._tool_call_states[tool_call_id] = state
329
+ return self._tool_call_states[tool_call_id]
330
+
331
+ def _cleanup_tool_state(self, tool_call_id: str) -> None:
332
+ """Remove tool call state after completion."""
333
+ self._tool_call_states.pop(tool_call_id, None)
334
+ self._current_tool_inputs.pop(tool_call_id, None)
335
+
336
+ async def initialize_mcp_servers(self) -> None:
337
+ """Initialize MCP servers if any are configured."""
338
+ if not self.mcp_servers:
339
+ return
340
+ self.log.info("Initializing MCP servers", server_count=len(self.mcp_servers))
341
+ cfgs = [convert_acp_mcp_server_to_config(s) for s in self.mcp_servers]
342
+ # Define accessible roots for MCP servers
343
+ # root = Path(self.cwd).resolve().as_uri() if self.cwd else None
344
+ for _cfg in cfgs:
345
+ try:
346
+ # Server will be initialized when MCP manager enters context
347
+ self.log.info("Added MCP servers", server_count=len(cfgs))
348
+ await self._register_mcp_prompts_as_commands()
349
+ except Exception:
350
+ self.log.exception("Failed to initialize MCP manager")
351
+ # Don't fail session creation, just log the error
352
+
353
+ async def init_project_context(self) -> None:
354
+ """Load AGENTS.md file and inject project context into all agents.
355
+
356
+ TODO: Consider moving this to __aenter__
357
+ """
358
+ if info := await self.requests.read_agent_rules(self.cwd):
359
+ for agent in self.agent_pool.agents.values():
360
+ prompt = f"## Project Information\n\n{info}"
361
+ agent.sys_prompts.prompts.append(prompt)
362
+
363
+ async def init_client_skills(self) -> None:
364
+ """Discover and load skills from client-side .claude/skills directory.
365
+
366
+ Adds the client's .claude/skills directory to the pool's skills manager,
367
+ making those skills available to all agents via the SkillsTools toolset.
368
+
369
+ We pass the filesystem directly to avoid fsspec trying to create a new
370
+ ACPFileSystem instance without the required client/session_id parameters.
371
+ """
372
+ try:
373
+ await self.agent_pool.skills.add_skills_directory(".claude/skills", fs=self.fs)
374
+ skills = self.agent_pool.skills.list_skills()
375
+ self.log.info("Collected client-side skills", skill_count=len(skills))
376
+ except Exception as e:
377
+ self.log.exception("Failed to discover client-side skills", error=e)
378
+
379
+ @property
380
+ def agent(self) -> Agent[ACPSession, str] | ACPAgent | AGUIAgent | ClaudeCodeAgent:
381
+ """Get the currently active agent."""
382
+ if self.current_agent_name in self.agent_pool.agents:
383
+ return self.agent_pool.get_agent(self.current_agent_name, deps_type=ACPSession)
384
+ return self.agent_pool.all_agents[self.current_agent_name] # type: ignore[return-value]
385
+
386
+ @property
387
+ def slashed_agent(self) -> SlashedAgent[Any, str]:
388
+ """Get the wrapped slashed agent."""
389
+ return SlashedAgent(self.agent, command_store=self.command_store)
390
+
391
+ def get_cwd_context(self) -> str:
392
+ """Get current working directory context for prompts."""
393
+ return f"Working directory: {self.cwd}" if self.cwd else ""
394
+
395
+ async def switch_active_agent(self, agent_name: str) -> None:
396
+ """Switch to a different agent in the pool.
397
+
398
+ Args:
399
+ agent_name: Name of the agent to switch to
400
+
401
+ Raises:
402
+ ValueError: If agent not found in pool
403
+ """
404
+ if agent_name not in self.agent_pool.all_agents:
405
+ available = list(self.agent_pool.all_agents.keys())
406
+ raise ValueError(f"Agent {agent_name!r} not found. Available: {available}")
407
+
408
+ old_agent_name = self.current_agent_name
409
+ self.current_agent_name = agent_name
410
+ self.log.info("Switched agents", from_agent=old_agent_name, to_agent=agent_name)
411
+
412
+ # Persist the agent switch via session manager
413
+ if self.manager:
414
+ await self.manager.update_session_agent(self.session_id, agent_name)
415
+
416
+ await self.send_available_commands_update()
417
+
418
+ async def cancel(self) -> None:
419
+ """Cancel the current prompt turn.
420
+
421
+ This actively interrupts the running agent by calling its interrupt() method,
422
+ which handles protocol-specific cancellation (e.g., sending CancelNotification
423
+ for ACP agents, calling SDK interrupt for ClaudeCodeAgent, etc.).
424
+ """
425
+ self._cancelled = True
426
+ self.log.info("Session cancelled, interrupting agent")
427
+
428
+ # Actively interrupt the agent's stream
429
+ try:
430
+ await self.agent.interrupt()
431
+ except Exception:
432
+ self.log.exception("Failed to interrupt agent")
433
+
434
+ def is_cancelled(self) -> bool:
435
+ """Check if the session is cancelled."""
436
+ return self._cancelled
437
+
438
+ async def process_prompt(self, content_blocks: Sequence[ContentBlock]) -> StopReason: # noqa: PLR0911
439
+ """Process a prompt request and stream responses.
440
+
441
+ Args:
442
+ content_blocks: List of content blocks from the prompt request
443
+
444
+ Returns:
445
+ Stop reason
446
+ """
447
+ self._cancelled = False
448
+ contents = from_acp_content(content_blocks)
449
+ self.log.debug("Converted content", content=contents)
450
+ if not contents:
451
+ self.log.warning("Empty prompt received")
452
+ return "refusal"
453
+ commands, non_command_content = split_commands(contents)
454
+ async with self._task_lock:
455
+ if commands: # Process commands if found
456
+ for command in commands:
457
+ self.log.info("Processing slash command", command=command)
458
+ await self.execute_slash_command(command)
459
+
460
+ # If only commands, end turn
461
+ if not non_command_content:
462
+ return "end_turn"
463
+
464
+ # Consume any staged content and prepend to the prompt
465
+ staged = self.staged_content.consume_as_text()
466
+ all_content = [staged, *non_command_content] if staged else list(non_command_content)
467
+ self.log.debug(
468
+ "Processing prompt",
469
+ content_items=len(non_command_content),
470
+ has_staged=staged is not None,
471
+ )
472
+ event_count = 0
473
+ self._current_tool_inputs.clear() # Reset tool inputs for new stream
474
+
475
+ try: # Use the session's persistent input provider
476
+ async for event in self.agent.run_stream(
477
+ *all_content, input_provider=self.input_provider
478
+ ):
479
+ if self._cancelled:
480
+ return "cancelled"
481
+
482
+ event_count += 1
483
+ await self.handle_event(event)
484
+ self.log.info("Streaming finished", events_processed=event_count)
485
+
486
+ except UsageLimitExceeded as e:
487
+ self.log.info("Usage limit exceeded", error=str(e))
488
+ error_msg = str(e) # Determine which limit was hit based on error
489
+ if "request_limit" in error_msg:
490
+ return "max_turn_requests"
491
+ if any(limit in error_msg for limit in ["tokens_limit", "token_limit"]):
492
+ return "max_tokens"
493
+ # Tool call limits don't have a direct ACP stop reason, treat as refusal
494
+ if "tool_calls_limit" in error_msg or "tool call" in error_msg:
495
+ return "refusal"
496
+ return "max_tokens" # Default to max_tokens for other usage limits
497
+ except Exception as e:
498
+ self.log.exception("Error during streaming")
499
+ # Send error notification asynchronously to avoid blocking response
500
+ self.acp_agent.tasks.create_task(
501
+ self._send_error_notification(f"❌ Agent error: {e}"),
502
+ name=f"agent_error_notification_{self.session_id}",
503
+ )
504
+ return "end_turn"
505
+ else:
506
+ return "end_turn"
507
+
508
+ async def _send_error_notification(self, message: str) -> None:
509
+ """Send error notification, with exception handling."""
510
+ try:
511
+ await self.notifications.send_agent_text(message)
512
+ except Exception:
513
+ self.log.exception("Failed to send error notification")
514
+
515
+ async def handle_event(self, event: RichAgentStreamEvent[Any]) -> None: # noqa: PLR0915
516
+ match event:
517
+ case (
518
+ PartStartEvent(part=TextPart(content=delta))
519
+ | PartDeltaEvent(delta=TextPartDelta(content_delta=delta))
520
+ ):
521
+ await self.notifications.send_agent_text(delta)
522
+
523
+ case (
524
+ PartStartEvent(part=ThinkingPart(content=delta))
525
+ | PartDeltaEvent(delta=ThinkingPartDelta(content_delta=delta))
526
+ ):
527
+ await self.notifications.send_agent_thought(delta or "\n")
528
+
529
+ case PartStartEvent(part=part):
530
+ self.log.debug("Received unhandled PartStartEvent", part=part)
531
+
532
+ # Tool call streaming delta - create/update state and start notification
533
+ case PartDeltaEvent(delta=ToolCallPartDelta() as delta):
534
+ if delta_part := delta.as_part():
535
+ tool_call_id = delta_part.tool_call_id
536
+ try:
537
+ tool_input = delta_part.args_as_dict()
538
+ except ValueError:
539
+ # Args still streaming, not valid JSON yet - skip this delta
540
+ pass
541
+ else:
542
+ self._current_tool_inputs[tool_call_id] = tool_input
543
+ # Create state and send initial notification
544
+ state = self._get_or_create_tool_state(
545
+ tool_call_id=tool_call_id,
546
+ tool_name=delta_part.tool_name,
547
+ tool_input=tool_input,
548
+ )
549
+ await state.start()
550
+
551
+ # Tool call started - create/update state and start notification
552
+ case FunctionToolCallEvent(part=part):
553
+ tool_call_id = part.tool_call_id
554
+ try:
555
+ tool_input = part.args_as_dict()
556
+ except ValueError as e:
557
+ # Args might be malformed - use empty dict and log
558
+ self.log.warning(
559
+ "Failed to parse tool args", tool_name=part.tool_name, error=str(e)
560
+ )
561
+ tool_input = {}
562
+ self._current_tool_inputs[tool_call_id] = tool_input
563
+ # Create state and send initial notification
564
+ state = self._get_or_create_tool_state(
565
+ tool_call_id=tool_call_id,
566
+ tool_name=part.tool_name,
567
+ tool_input=tool_input,
568
+ )
569
+ await state.start()
570
+
571
+ # Tool completed successfully - update state and finalize
572
+ case FunctionToolResultEvent(
573
+ result=ToolReturnPart(content=content, tool_name=tool_name) as result,
574
+ tool_call_id=tool_call_id,
575
+ ):
576
+ if isinstance(content, AsyncGenerator):
577
+ full_content = ""
578
+ async for chunk in content:
579
+ full_content += str(chunk)
580
+ # Stream progress through state
581
+ if tool_state := self._tool_call_states.get(tool_call_id):
582
+ await tool_state.update(status="in_progress", raw_output=chunk)
583
+
584
+ # Replace the AsyncGenerator with the full content to prevent errors
585
+ result.content = full_content
586
+ final_output = full_content
587
+ else:
588
+ final_output = result.content
589
+
590
+ # Complete tool call through state (preserves accumulated content/locations)
591
+ if complete_state := self._tool_call_states.get(tool_call_id):
592
+ # Only add return value as content if no content was emitted during execution
593
+ if complete_state.has_content:
594
+ # Content already provided via progress events - just set raw_output
595
+ await complete_state.complete(raw_output=final_output)
596
+ else:
597
+ # No content yet - convert return value for display
598
+ converted_blocks = to_acp_content_blocks(final_output)
599
+ content_items = [
600
+ ContentToolCallContent(content=block) for block in converted_blocks
601
+ ]
602
+ await complete_state.complete(
603
+ raw_output=final_output,
604
+ content=content_items,
605
+ )
606
+ self._cleanup_tool_state(tool_call_id)
607
+
608
+ # Tool failed with retry - update state with error
609
+ case FunctionToolResultEvent(
610
+ result=RetryPromptPart(tool_name=tool_name) as result,
611
+ tool_call_id=tool_call_id,
612
+ ):
613
+ error_message = result.model_response()
614
+ if fail_state := self._tool_call_states.get(tool_call_id):
615
+ await fail_state.fail(error=error_message)
616
+ self._cleanup_tool_state(tool_call_id)
617
+
618
+ # Tool emits its own start event - update state with better title/content
619
+ case ToolCallStartEvent(
620
+ tool_call_id=tool_call_id,
621
+ tool_name=tool_name,
622
+ title=title,
623
+ kind=kind,
624
+ locations=loc_items,
625
+ raw_input=raw_input,
626
+ ):
627
+ self.log.debug(
628
+ "Tool call start event", tool_name=tool_name, tool_call_id=tool_call_id
629
+ )
630
+ # Get or create state (may already exist from FunctionToolCallEvent)
631
+ state = self._get_or_create_tool_state(
632
+ tool_call_id=tool_call_id,
633
+ tool_name=tool_name,
634
+ tool_input=raw_input or {},
635
+ )
636
+ # Convert LocationContentItem objects to ACP format
637
+ acp_locations = [
638
+ ToolCallLocation(path=loc.path, line=loc.line) for loc in loc_items
639
+ ]
640
+ # Update state with tool-provided details (better title, content, locations)
641
+ await state.update(title=title, kind=kind, locations=acp_locations or None)
642
+
643
+ # Tool progress event - update state with title and content
644
+ case ToolCallProgressEvent(
645
+ tool_call_id=tool_call_id,
646
+ title=title,
647
+ status=status,
648
+ items=items,
649
+ ) if tool_call_id and tool_call_id in self._tool_call_states:
650
+ progress_state = self._tool_call_states[tool_call_id]
651
+ self.log.debug("Progress event", tool_call_id=tool_call_id, title=title)
652
+
653
+ # Convert items to ACP content
654
+ from agentpool.agents.events import (
655
+ DiffContentItem,
656
+ FileContentItem,
657
+ LocationContentItem,
658
+ TerminalContentItem,
659
+ TextContentItem,
660
+ )
661
+ from agentpool_server.acp_server.syntax_detection import (
662
+ format_zed_code_block,
663
+ )
664
+
665
+ acp_content: list[Any] = []
666
+ location_paths: list[str] = []
667
+
668
+ for item in items:
669
+ match item:
670
+ case TerminalContentItem(terminal_id=tid):
671
+ acp_content.append(TerminalToolCallContent(terminal_id=tid))
672
+ case TextContentItem(text=text):
673
+ acp_content.append(ContentToolCallContent.text(text=text))
674
+ case FileContentItem(
675
+ content=file_content,
676
+ path=file_path,
677
+ start_line=start_line,
678
+ end_line=end_line,
679
+ ):
680
+ # Format as Zed-compatible code block with clickable path
681
+ formatted = format_zed_code_block(
682
+ file_content, file_path, start_line, end_line
683
+ )
684
+ acp_content.append(ContentToolCallContent.text(text=formatted))
685
+ # Also add path to locations for "follow along" feature
686
+ location_paths.append(file_path)
687
+ case DiffContentItem(path=diff_path, old_text=old, new_text=new):
688
+ # Send diff via direct notification
689
+ await self.notifications.file_edit_progress(
690
+ tool_call_id=tool_call_id,
691
+ path=diff_path,
692
+ old_text=old or "",
693
+ new_text=new,
694
+ status=status,
695
+ changed_lines=[],
696
+ )
697
+ case LocationContentItem(path=loc_path):
698
+ location_paths.append(loc_path)
699
+
700
+ await progress_state.update(
701
+ title=title,
702
+ status="in_progress",
703
+ content=acp_content if acp_content else None,
704
+ locations=location_paths if location_paths else None,
705
+ )
706
+
707
+ case FinalResultEvent():
708
+ self.log.debug("Final result received")
709
+
710
+ case StreamCompleteEvent():
711
+ pass
712
+
713
+ case PlanUpdateEvent(entries=entries, tool_call_id=tool_call_id):
714
+ acp_entries = [
715
+ PlanEntry(content=e.content, priority=e.priority, status=e.status)
716
+ for e in entries
717
+ ]
718
+ await self.notifications.update_plan(acp_entries)
719
+
720
+ case _:
721
+ self.log.debug("Unhandled event", event_type=type(event).__name__)
722
+
723
+ async def close(self) -> None:
724
+ """Close the session and cleanup resources."""
725
+ self._current_tool_inputs.clear()
726
+ self._tool_call_states.clear()
727
+
728
+ try:
729
+ await self.acp_env.__aexit__(None, None, None)
730
+ except Exception:
731
+ self.log.exception("Error closing acp_env")
732
+
733
+ try:
734
+ # Remove cwd context callable from all agents
735
+ for agent in self.agent_pool.agents.values():
736
+ if self.get_cwd_context in agent.sys_prompts.prompts:
737
+ agent.sys_prompts.prompts.remove(self.get_cwd_context) # pyright: ignore[reportArgumentType]
738
+
739
+ # Note: Individual agents are managed by the pool's lifecycle
740
+ # The pool will handle agent cleanup when it's closed
741
+ self.log.info("Closed ACP session")
742
+ except Exception:
743
+ self.log.exception("Error closing session")
744
+
745
+ async def send_available_commands_update(self) -> None:
746
+ """Send current available commands to client."""
747
+ try:
748
+ commands = self.get_acp_commands()
749
+ await self.notifications.update_commands(commands)
750
+ except Exception:
751
+ self.log.exception("Failed to send available commands update")
752
+
753
+ async def _register_mcp_prompts_as_commands(self) -> None:
754
+ """Register MCP prompts as slash commands."""
755
+ if not isinstance(self.agent, Agent):
756
+ return
757
+ try: # Get all prompts from the agent's ToolManager
758
+ if all_prompts := await self.agent.tools.list_prompts():
759
+ for prompt in all_prompts:
760
+ command = self.create_mcp_command(prompt)
761
+ self.command_store.register_command(command)
762
+ self._notify_command_update()
763
+ self.log.info("Registered MCP prompts as commands", prompt_count=len(all_prompts))
764
+ await self.send_available_commands_update() # Send updated command list to client
765
+ except Exception:
766
+ self.log.exception("Failed to register MCP prompts as commands")
767
+
768
+ async def _register_prompt_hub_commands(self) -> None:
769
+ """Register prompt hub prompts as slash commands."""
770
+ manager = self.agent_pool.manifest.prompt_manager
771
+ cmd_count = 0
772
+ try:
773
+ all_prompts = await manager.list_prompts()
774
+ for provider_name, prompt_names in all_prompts.items():
775
+ if not prompt_names: # Skip empty providers
776
+ continue
777
+ for prompt_name in prompt_names:
778
+ command = self.create_prompt_hub_command(provider_name, prompt_name, manager)
779
+ self.command_store.register_command(command)
780
+ cmd_count += 1
781
+
782
+ if cmd_count > 0:
783
+ self._notify_command_update()
784
+ self.log.info("Registered hub prompts as slash commands", cmd_count=cmd_count)
785
+ await self.send_available_commands_update() # Send updated command list to client
786
+ except Exception:
787
+ self.log.exception("Failed to register prompt hub prompts as commands")
788
+
789
+ def _notify_command_update(self) -> None:
790
+ """Notify all registered callbacks about command updates."""
791
+ for callback in self._update_callbacks:
792
+ try:
793
+ callback()
794
+ except Exception:
795
+ logger.exception("Command update callback failed")
796
+
797
+ def get_acp_commands(self) -> list[AvailableCommand]:
798
+ """Convert all slashed commands to ACP format.
799
+
800
+ Filters commands based on current agent's node type compatibility.
801
+
802
+ Returns:
803
+ List of ACP AvailableCommand objects compatible with current node
804
+ """
805
+ all_commands = self.command_store.list_commands()
806
+ current_node = self.agent
807
+ # Filter commands by node compatibility
808
+ compatible_commands = []
809
+ for cmd in all_commands:
810
+ cmd_cls = cmd if isinstance(cmd, type) else type(cmd)
811
+ # Check if command supports current node type
812
+ if issubclass(cmd_cls, NodeCommand) and not cmd_cls.supports_node(current_node): # type: ignore[union-attr]
813
+ continue
814
+ compatible_commands.append(cmd)
815
+
816
+ return [
817
+ AvailableCommand.create(name=i.name, description=i.description, input_hint=i.usage)
818
+ for i in compatible_commands
819
+ ]
820
+
821
+ @logfire.instrument(r"Execute Slash Command {command_text}")
822
+ async def execute_slash_command(self, command_text: str) -> None:
823
+ """Execute any slash command with unified handling.
824
+
825
+ Args:
826
+ command_text: Full command text (including slash)
827
+ session: ACP session context
828
+ """
829
+ if match := SLASH_PATTERN.match(command_text.strip()):
830
+ command_name = match.group(1)
831
+ args = match.group(2) or ""
832
+ else:
833
+ logger.warning("Invalid slash command", command=command_text)
834
+ return
835
+
836
+ # Check if command supports current node type
837
+ if cmd := self.command_store.get_command(command_name):
838
+ cmd_cls = cmd if isinstance(cmd, type) else type(cmd)
839
+ if issubclass(cmd_cls, NodeCommand) and not cmd_cls.supports_node(self.agent): # type: ignore[union-attr]
840
+ error_msg = f"❌ Command `/{command_name}` is not available for this node type"
841
+ await self.notifications.send_agent_text(error_msg)
842
+ return
843
+
844
+ # Create context with session data
845
+ agent_context = self.agent.get_context(data=self)
846
+ cmd_ctx = self.command_store.create_context(
847
+ data=agent_context,
848
+ output_writer=self.notifications.send_agent_text,
849
+ )
850
+
851
+ command_str = f"{command_name} {args}".strip()
852
+ try:
853
+ await self.command_store.execute_command(command_str, cmd_ctx)
854
+ except Exception as e:
855
+ logger.exception("Command execution failed")
856
+ # Send error notification asynchronously to avoid blocking
857
+ self.acp_agent.tasks.create_task(
858
+ self._send_error_notification(f"❌ Command error: {e}"),
859
+ name=f"command_error_notification_{self.session_id}",
860
+ )
861
+
862
+ def register_update_callback(self, callback: Callable[[], None]) -> None:
863
+ """Register callback for command updates.
864
+
865
+ Args:
866
+ callback: Function to call when commands are updated
867
+ """
868
+ self._update_callbacks.append(callback)
869
+
870
+ def create_mcp_command(self, prompt: MCPClientPrompt) -> Command:
871
+ """Convert MCP prompt to slashed Command.
872
+
873
+ Args:
874
+ prompt: MCP prompt to wrap
875
+ session: ACP session for execution context
876
+
877
+ Returns:
878
+ Slashed Command that executes the prompt
879
+ """
880
+
881
+ async def execute_prompt(
882
+ ctx: CommandContext[AgentContext],
883
+ args: list[str],
884
+ kwargs: dict[str, str],
885
+ ) -> None:
886
+ """Execute the MCP prompt with parsed arguments."""
887
+ # Map parsed args to prompt parameters
888
+
889
+ result = {}
890
+ # Map positional args to prompt parameter names
891
+ for i, arg_value in enumerate(args):
892
+ if i < len(prompt.arguments):
893
+ param_name = prompt.arguments[i]["name"]
894
+ result[param_name] = arg_value
895
+ result.update(kwargs)
896
+ try:
897
+ # Get prompt components
898
+ components = await prompt.get_components(result or None)
899
+ self.staged_content.add(components)
900
+ # Send confirmation
901
+ staged_count = len(self.staged_content)
902
+ await ctx.print(f"✅ Prompt {prompt.name!r} staged ({staged_count} total parts)")
903
+
904
+ except Exception as e:
905
+ logger.exception("MCP prompt execution failed", prompt=prompt.name)
906
+ await ctx.print(f"❌ Prompt error: {e}")
907
+
908
+ usage_hint = (
909
+ " ".join(f"<{arg['name']}>" for arg in prompt.arguments) if prompt.arguments else None
910
+ )
911
+ return Command(
912
+ execute_func=execute_prompt,
913
+ name=prompt.name,
914
+ description=prompt.description or f"MCP prompt: {prompt.name}",
915
+ category="mcp",
916
+ usage=usage_hint,
917
+ )
918
+
919
+ def create_prompt_hub_command(
920
+ self, provider: str, name: str, manager: PromptManager
921
+ ) -> Command:
922
+ """Convert prompt hub prompt to slash command.
923
+
924
+ Args:
925
+ provider: Provider name (e.g., 'langfuse', 'builtin')
926
+ name: Prompt name
927
+ manager: PromptManager instance
928
+
929
+ Returns:
930
+ Command that executes the prompt hub prompt
931
+ """
932
+
933
+ async def execute_prompt(
934
+ ctx: CommandContext[Any],
935
+ args: list[str],
936
+ kwargs: dict[str, str],
937
+ ) -> None:
938
+ """Execute the prompt hub prompt with parsed arguments."""
939
+ try:
940
+ # Build reference string
941
+ reference = f"{provider}:{name}" if provider != "builtin" else name
942
+
943
+ # Add variables as query parameters if provided
944
+ if kwargs:
945
+ params = "&".join(f"{k}={v}" for k, v in kwargs.items())
946
+ reference = f"{reference}?{params}"
947
+ # Get the rendered prompt
948
+ result = await manager.get(reference)
949
+ self.staged_content.add([UserPromptPart(content=result)])
950
+ # Send confirmation
951
+ staged_count = len(self.staged_content)
952
+ await ctx.print(
953
+ f"✅ Prompt {name!r} from {provider} staged ({staged_count} total parts)"
954
+ )
955
+
956
+ except Exception as e:
957
+ logger.exception("Prompt hub execution failed", prompt=name, provider=provider)
958
+ await ctx.print(f"❌ Prompt error: {e}")
959
+
960
+ # Create command name - prefix with provider if not builtin
961
+ command_name = f"{provider}_{name}" if provider != "builtin" else name
962
+
963
+ return Command(
964
+ execute_func=execute_prompt,
965
+ name=command_name,
966
+ description=f"Prompt hub: {provider}:{name}",
967
+ category="prompts",
968
+ usage="[key=value ...]", # Generic since we don't have parameter schemas
969
+ )