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,837 @@
1
+ """ACP Agent - MessageNode wrapping an external ACP subprocess.
2
+
3
+ This module provides an agent implementation that communicates with external
4
+ ACP (Agent Client Protocol) servers via stdio, enabling integration of any
5
+ ACP-compatible agent into the agentpool pool.
6
+
7
+ The ACPAgent class acts as an ACP client, spawning an ACP server subprocess
8
+ and communicating with it via JSON-RPC over stdio. This allows:
9
+ - Integration of external ACP-compatible agents (like claude-code-acp)
10
+ - Composition with native agents via connections, teams, etc.
11
+ - Full ACP protocol support including file operations and terminals
12
+
13
+ Example:
14
+ ```python
15
+ config = ACPAgentConfig(
16
+ command="claude-code-acp",
17
+ name="claude_coder",
18
+ cwd="/path/to/project",
19
+ )
20
+ async with ACPAgent(config) as agent:
21
+ result = await agent.run("Write a hello world program")
22
+ print(result.content)
23
+ ```
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import asyncio
29
+ import os
30
+ from pathlib import Path
31
+ import subprocess
32
+ from typing import TYPE_CHECKING, Any, Self, overload
33
+ import uuid
34
+
35
+ import anyio
36
+ from pydantic_ai import (
37
+ ModelRequest,
38
+ ModelResponse,
39
+ PartDeltaEvent,
40
+ TextPart,
41
+ TextPartDelta,
42
+ ThinkingPart,
43
+ ThinkingPartDelta,
44
+ ToolCallPart,
45
+ UserPromptPart,
46
+ )
47
+
48
+ from agentpool.agents.acp_agent.acp_converters import convert_to_acp_content, mcp_configs_to_acp
49
+ from agentpool.agents.acp_agent.client_handler import ACPClientHandler
50
+ from agentpool.agents.acp_agent.session_state import ACPSessionState
51
+ from agentpool.agents.base_agent import BaseAgent
52
+ from agentpool.agents.events import RunStartedEvent, StreamCompleteEvent, ToolCallStartEvent
53
+ from agentpool.log import get_logger
54
+ from agentpool.messaging import ChatMessage
55
+ from agentpool.messaging.processing import prepare_prompts
56
+ from agentpool.models.acp_agents import ACPAgentConfig, MCPCapableACPAgentConfig
57
+ from agentpool.talk.stats import MessageStats
58
+ from agentpool.utils.streams import merge_queue_into_iterator
59
+
60
+
61
+ if TYPE_CHECKING:
62
+ from collections.abc import AsyncIterator, Awaitable, Callable, Sequence
63
+ from types import TracebackType
64
+
65
+ from anyio.abc import Process
66
+ from evented.configs import EventConfig
67
+ from exxec import ExecutionEnvironment
68
+ from pydantic_ai import FinishReason
69
+ from tokonomics.model_discovery import ProviderType
70
+
71
+ from acp.agent.protocol import Agent as ACPAgentProtocol
72
+ from acp.client.connection import ClientSideConnection
73
+ from acp.client.protocol import Client
74
+ from acp.schema import (
75
+ InitializeResponse,
76
+ RequestPermissionRequest,
77
+ RequestPermissionResponse,
78
+ StopReason,
79
+ )
80
+ from acp.schema.mcp import McpServer
81
+ from agentpool.agents import AgentContext
82
+ from agentpool.agents.events import RichAgentStreamEvent
83
+ from agentpool.common_types import (
84
+ BuiltinEventHandlerType,
85
+ IndividualEventHandler,
86
+ PromptCompatible,
87
+ SimpleJsonType,
88
+ )
89
+ from agentpool.delegation import AgentPool
90
+ from agentpool.mcp_server.tool_bridge import ToolManagerBridge
91
+ from agentpool.messaging import MessageHistory
92
+ from agentpool.models.acp_agents import BaseACPAgentConfig
93
+ from agentpool.ui.base import InputProvider
94
+ from agentpool_config.nodes import ToolConfirmationMode
95
+
96
+ logger = get_logger(__name__)
97
+
98
+ PROTOCOL_VERSION = 1
99
+
100
+ STOP_REASON_MAP: dict[StopReason, FinishReason] = {
101
+ "end_turn": "stop",
102
+ "max_tokens": "length",
103
+ "max_turn_requests": "length",
104
+ "refusal": "content_filter",
105
+ "cancelled": "error",
106
+ }
107
+
108
+
109
+ def extract_file_path_from_tool_call(tool_name: str, raw_input: dict[str, Any]) -> str | None:
110
+ """Extract file path from a tool call if it's a file-writing tool.
111
+
112
+ Uses simple heuristics by default:
113
+ - Tool name contains 'write' or 'edit' (case-insensitive)
114
+ - Input contains 'path' or 'file_path' key
115
+
116
+ Override in subclasses for agent-specific tool naming conventions.
117
+
118
+ Args:
119
+ tool_name: Name of the tool being called
120
+ raw_input: Tool call arguments
121
+
122
+ Returns:
123
+ File path if this is a file-writing tool, None otherwise
124
+ """
125
+ name_lower = tool_name.lower()
126
+ if "write" not in name_lower and "edit" not in name_lower:
127
+ return None
128
+
129
+ # Try common path argument names
130
+ for key in ("file_path", "path", "filepath", "filename", "file"):
131
+ if key in raw_input and isinstance(val := raw_input[key], str):
132
+ return val
133
+
134
+ return None
135
+
136
+
137
+ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
138
+ """MessageNode that wraps an external ACP agent subprocess.
139
+
140
+ This allows integrating any ACP-compatible agent into the agentpool
141
+ pool, enabling composition with native agents via connections, teams, etc.
142
+
143
+ The agent manages:
144
+ - Subprocess lifecycle (spawn on enter, terminate on exit)
145
+ - ACP protocol initialization and session creation
146
+ - Prompt execution with session update collection
147
+ - Client-side operations (filesystem, terminals, permissions)
148
+
149
+ Supports both blocking `run()` and streaming `run_iter()` execution modes.
150
+
151
+ Example with config:
152
+ ```python
153
+ config = ClaudeACPAgentConfig(cwd="/project", model="sonnet")
154
+ agent = ACPAgent(config, agent_pool=pool)
155
+ ```
156
+
157
+ Example with kwargs:
158
+ ```python
159
+ agent = ACPAgent(
160
+ command="claude-code-acp",
161
+ cwd="/project",
162
+ providers=["anthropic"],
163
+ )
164
+ ```
165
+ """
166
+
167
+ @overload
168
+ def __init__(
169
+ self,
170
+ *,
171
+ config: BaseACPAgentConfig,
172
+ input_provider: InputProvider | None = None,
173
+ agent_pool: AgentPool[Any] | None = None,
174
+ enable_logging: bool = True,
175
+ event_configs: Sequence[EventConfig] | None = None,
176
+ event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
177
+ ) -> None: ...
178
+
179
+ @overload
180
+ def __init__(
181
+ self,
182
+ *,
183
+ command: str,
184
+ name: str | None = None,
185
+ description: str | None = None,
186
+ display_name: str | None = None,
187
+ args: list[str] | None = None,
188
+ cwd: str | None = None,
189
+ env_vars: dict[str, str] | None = None,
190
+ env: ExecutionEnvironment | None = None,
191
+ allow_file_operations: bool = True,
192
+ allow_terminal: bool = True,
193
+ providers: list[ProviderType] | None = None,
194
+ input_provider: InputProvider | None = None,
195
+ agent_pool: AgentPool[Any] | None = None,
196
+ enable_logging: bool = True,
197
+ event_configs: Sequence[EventConfig] | None = None,
198
+ event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
199
+ tool_confirmation_mode: ToolConfirmationMode = "always",
200
+ ) -> None: ...
201
+
202
+ def __init__(
203
+ self,
204
+ *,
205
+ config: BaseACPAgentConfig | None = None,
206
+ command: str | None = None,
207
+ name: str | None = None,
208
+ description: str | None = None,
209
+ display_name: str | None = None,
210
+ args: list[str] | None = None,
211
+ cwd: str | None = None,
212
+ env_vars: dict[str, str] | None = None,
213
+ env: ExecutionEnvironment | None = None,
214
+ allow_file_operations: bool = True,
215
+ allow_terminal: bool = True,
216
+ providers: list[ProviderType] | None = None,
217
+ input_provider: InputProvider | None = None,
218
+ agent_pool: AgentPool[Any] | None = None,
219
+ enable_logging: bool = True,
220
+ event_configs: Sequence[EventConfig] | None = None,
221
+ event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
222
+ tool_confirmation_mode: ToolConfirmationMode = "always",
223
+ ) -> None:
224
+ # Build config from kwargs if not provided
225
+ if config is None:
226
+ if command is None:
227
+ msg = "Either config or command must be provided"
228
+ raise ValueError(msg)
229
+ config = ACPAgentConfig(
230
+ name=name,
231
+ description=description,
232
+ display_name=display_name,
233
+ command=command,
234
+ args=args or [],
235
+ cwd=cwd,
236
+ env=env_vars or {},
237
+ allow_file_operations=allow_file_operations,
238
+ allow_terminal=allow_terminal,
239
+ requires_tool_confirmation=tool_confirmation_mode,
240
+ providers=list(providers) if providers else [],
241
+ )
242
+
243
+ super().__init__(
244
+ name=name or config.name or config.get_command(),
245
+ description=description or config.description,
246
+ display_name=display_name,
247
+ mcp_servers=config.mcp_servers,
248
+ agent_pool=agent_pool,
249
+ enable_logging=enable_logging,
250
+ event_configs=event_configs or list(config.triggers),
251
+ env=env or config.get_execution_environment(),
252
+ input_provider=input_provider,
253
+ tool_confirmation_mode=tool_confirmation_mode,
254
+ event_handlers=event_handlers,
255
+ )
256
+
257
+ # ACP-specific state
258
+ self.acp_permission_callback: (
259
+ Callable[[RequestPermissionRequest], Awaitable[RequestPermissionResponse]] | None
260
+ ) = None
261
+ self.config = config
262
+ self._process: Process | None = None
263
+ self._connection: ClientSideConnection | None = None
264
+ self._client_handler: ACPClientHandler | None = None
265
+ self._init_response: InitializeResponse | None = None
266
+ self._session_id: str | None = None
267
+ self._state: ACPSessionState | None = None
268
+ self.deps_type = type(None)
269
+ self._extra_mcp_servers: list[McpServer] = []
270
+ self._tool_bridge: ToolManagerBridge | None = None
271
+ self._owns_bridge = False # Track if we created the bridge (for cleanup)
272
+ # Client execution environment (for subprocess requests) - falls back to env
273
+ self._client_env: ExecutionEnvironment | None = config.get_client_execution_environment()
274
+ # Track the prompt task for cancellation
275
+ self._prompt_task: asyncio.Task[Any] | None = None
276
+
277
+ @property
278
+ def client_env(self) -> ExecutionEnvironment:
279
+ """Execution environment for handling subprocess requests.
280
+
281
+ This is used by ACPClientHandler for file/terminal operations requested
282
+ by the subprocess. Falls back to the agent's main env if not explicitly set.
283
+
284
+ Use cases:
285
+ - Default (None): Subprocess requests use same env as toolsets
286
+ - Explicit: Subprocess operates in a different environment than toolsets
287
+ """
288
+ return self._client_env if self._client_env is not None else self.env
289
+
290
+ def get_context(self, data: Any = None) -> AgentContext:
291
+ """Create a new context for this agent.
292
+
293
+ Args:
294
+ data: Optional custom data to attach to the context
295
+
296
+ Returns:
297
+ A new AgentContext instance
298
+ """
299
+ from agentpool.agents.context import AgentContext
300
+ from agentpool.models.manifest import AgentsManifest
301
+
302
+ defn = self.agent_pool.manifest if self.agent_pool else AgentsManifest()
303
+ return AgentContext(
304
+ node=self, pool=self.agent_pool, config=self.config, definition=defn, data=data
305
+ )
306
+
307
+ async def _setup_toolsets(self) -> None:
308
+ """Initialize toolsets from config and create bridge if needed."""
309
+ from agentpool.mcp_server.tool_bridge import BridgeConfig, ToolManagerBridge
310
+
311
+ if not isinstance(self.config, MCPCapableACPAgentConfig) or not self.config.toolsets:
312
+ return
313
+ # Create providers from toolset configs and add to tool manager
314
+ for toolset_config in self.config.toolsets:
315
+ provider = toolset_config.get_provider()
316
+ self.tools.add_provider(provider)
317
+ # Auto-create bridge to expose tools via MCP
318
+ config = BridgeConfig(transport="sse", server_name=f"agentpool-{self.name}-tools")
319
+ self._tool_bridge = ToolManagerBridge(node=self, config=config)
320
+ await self._tool_bridge.start()
321
+ self._owns_bridge = True
322
+ # Add bridge's MCP server to session
323
+ mcp_config = self._tool_bridge.get_mcp_server_config()
324
+ self._extra_mcp_servers.append(mcp_config)
325
+
326
+ async def __aenter__(self) -> Self:
327
+ """Start subprocess and initialize ACP connection."""
328
+ await super().__aenter__()
329
+ await self._setup_toolsets() # Setup toolsets before session creation
330
+ await self._start_process()
331
+ await self._initialize()
332
+ await self._create_session()
333
+ await anyio.sleep(0.3) # Small delay to let subprocess fully initialize
334
+ return self
335
+
336
+ async def __aexit__(
337
+ self,
338
+ exc_type: type[BaseException] | None,
339
+ exc_val: BaseException | None,
340
+ exc_tb: TracebackType | None,
341
+ ) -> None:
342
+ """Clean up subprocess and connection."""
343
+ await self._cleanup()
344
+ await super().__aexit__(exc_type, exc_val, exc_tb)
345
+
346
+ async def _start_process(self) -> None:
347
+ """Start the ACP server subprocess."""
348
+ prompt_manager = self.agent_pool.manifest.prompt_manager if self.agent_pool else None
349
+ args = await self.config.get_args(prompt_manager)
350
+ cmd = [self.config.get_command(), *args]
351
+ self.log.info("Starting ACP subprocess", command=cmd)
352
+
353
+ self._process = await anyio.open_process(
354
+ cmd,
355
+ stdin=subprocess.PIPE,
356
+ stdout=subprocess.PIPE,
357
+ stderr=subprocess.PIPE,
358
+ env={**os.environ, **self.config.env},
359
+ cwd=str(self.config.cwd) if self.config.cwd else None,
360
+ )
361
+ if not self._process.stdin or not self._process.stdout:
362
+ msg = "Failed to create subprocess pipes"
363
+ raise RuntimeError(msg)
364
+
365
+ async def _initialize(self) -> None:
366
+ """Initialize the ACP connection."""
367
+ from acp.client.connection import ClientSideConnection
368
+ from acp.schema import InitializeRequest
369
+
370
+ if not self._process or not self._process.stdin or not self._process.stdout:
371
+ msg = "Process not started"
372
+ raise RuntimeError(msg)
373
+
374
+ self._state = ACPSessionState(session_id="")
375
+ self._client_handler = ACPClientHandler(self, self._state, self._input_provider)
376
+
377
+ def client_factory(agent: ACPAgentProtocol) -> Client:
378
+ return self._client_handler # type: ignore[return-value]
379
+
380
+ self._connection = ClientSideConnection(
381
+ to_client=client_factory,
382
+ input_stream=self._process.stdin,
383
+ output_stream=self._process.stdout,
384
+ )
385
+ init_request = InitializeRequest.create(
386
+ title="AgentPool",
387
+ version="0.1.0",
388
+ name="agentpool",
389
+ protocol_version=PROTOCOL_VERSION,
390
+ terminal=self.config.allow_terminal,
391
+ read_text_file=self.config.allow_file_operations,
392
+ write_text_file=self.config.allow_file_operations,
393
+ )
394
+ self._init_response = await self._connection.initialize(init_request)
395
+ self.log.info("ACP connection initialized", agent_info=self._init_response.agent_info)
396
+
397
+ async def _create_session(self) -> None:
398
+ """Create a new ACP session with configured MCP servers."""
399
+ from acp.schema import NewSessionRequest
400
+
401
+ if not self._connection:
402
+ msg = "Connection not initialized"
403
+ raise RuntimeError(msg)
404
+
405
+ mcp_servers: list[McpServer] = [] # Collect MCP servers from config
406
+ # Add servers from config (converted to ACP format)
407
+ config_servers = self.config.get_mcp_servers()
408
+ if config_servers:
409
+ mcp_servers.extend(mcp_configs_to_acp(config_servers))
410
+ # Add any extra MCP servers (e.g., from tool bridges)
411
+ mcp_servers.extend(self._extra_mcp_servers)
412
+ cwd = self.config.cwd or str(Path.cwd())
413
+ session_request = NewSessionRequest(cwd=cwd, mcp_servers=mcp_servers)
414
+ response = await self._connection.new_session(session_request)
415
+ self._session_id = response.session_id
416
+ if self._state:
417
+ self._state.session_id = self._session_id
418
+ if response.models: # Store full model info from session response
419
+ self._state.models = response.models
420
+ self._state.current_model_id = response.models.current_model_id
421
+ self._state.modes = response.modes
422
+ model = self._state.current_model_id if self._state else None
423
+ self.log.info("ACP session created", session_id=self._session_id, model=model)
424
+
425
+ def add_mcp_server(self, server: McpServer) -> None:
426
+ """Add an MCP server to be passed to the next session."""
427
+ self._extra_mcp_servers.append(server)
428
+
429
+ async def add_tool_bridge(self, bridge: ToolManagerBridge) -> None:
430
+ """Add an external tool bridge to expose its tools via MCP.
431
+
432
+ The bridge must already be started. Its MCP server config will be
433
+ added to the session. Use this for bridges created externally
434
+ (e.g., from AgentPool). For toolsets defined in config, bridges
435
+ are created automatically.
436
+
437
+ Args:
438
+ bridge: Started ToolManagerBridge instance
439
+ """
440
+ if self._tool_bridge is None: # Don't replace our own bridge
441
+ self._tool_bridge = bridge
442
+ mcp_config = bridge.get_mcp_server_config()
443
+ self._extra_mcp_servers.append(mcp_config)
444
+ self.log.info("Added external tool bridge", url=bridge.url)
445
+
446
+ async def _cleanup(self) -> None:
447
+ """Clean up resources."""
448
+ if self._tool_bridge and self._owns_bridge: # Stop our own bridge if we created it
449
+ await self._tool_bridge.stop()
450
+ self._tool_bridge = None
451
+ self._owns_bridge = False
452
+ self._extra_mcp_servers.clear()
453
+
454
+ if self._client_handler:
455
+ try:
456
+ await self._client_handler.cleanup()
457
+ except Exception:
458
+ self.log.exception("Error cleaning up client handler")
459
+ self._client_handler = None
460
+
461
+ if self._connection:
462
+ try:
463
+ await self._connection.close()
464
+ except Exception:
465
+ self.log.exception("Error closing ACP connection")
466
+ self._connection = None
467
+
468
+ if self._process:
469
+ try:
470
+ self._process.terminate()
471
+ await asyncio.wait_for(self._process.wait(), timeout=5.0)
472
+ except TimeoutError:
473
+ self._process.kill()
474
+ await self._process.wait()
475
+ except Exception:
476
+ self.log.exception("Error terminating ACP process")
477
+ self._process = None
478
+
479
+ async def run(
480
+ self,
481
+ *prompts: PromptCompatible,
482
+ message_id: str | None = None,
483
+ input_provider: InputProvider | None = None,
484
+ message_history: MessageHistory | None = None,
485
+ ) -> ChatMessage[str]:
486
+ """Execute prompt against ACP agent.
487
+
488
+ Args:
489
+ prompts: Prompts to send (will be joined with spaces)
490
+ message_id: Optional message id for the returned message
491
+ input_provider: Optional input provider for permission requests
492
+ message_history: Optional MessageHistory to use instead of agent's own
493
+
494
+ Returns:
495
+ ChatMessage containing the agent's aggregated text response
496
+ """
497
+ # Collect all events through run_stream
498
+ final_message: ChatMessage[str] | None = None
499
+ async for event in self.run_stream(
500
+ *prompts,
501
+ message_id=message_id,
502
+ input_provider=input_provider,
503
+ message_history=message_history,
504
+ ):
505
+ if isinstance(event, StreamCompleteEvent):
506
+ final_message = event.message
507
+
508
+ if final_message is None:
509
+ msg = "No final message received from stream"
510
+ raise RuntimeError(msg)
511
+
512
+ return final_message
513
+
514
+ async def run_stream( # noqa: PLR0915
515
+ self,
516
+ *prompts: PromptCompatible,
517
+ message_id: str | None = None,
518
+ input_provider: InputProvider | None = None,
519
+ message_history: MessageHistory | None = None,
520
+ ) -> AsyncIterator[RichAgentStreamEvent[str]]:
521
+ """Stream native events as they arrive from ACP agent.
522
+
523
+ Args:
524
+ prompts: Prompts to send (will be joined with spaces)
525
+ message_id: Optional message id for the final message
526
+ input_provider: Optional input provider for permission requests
527
+ message_history: Optional MessageHistory to use instead of agent's own
528
+
529
+ Yields:
530
+ RichAgentStreamEvent instances converted from ACP session updates
531
+ """
532
+ from acp.schema import PromptRequest
533
+ from acp.utils import to_acp_content_blocks
534
+
535
+ # Update input provider if provided
536
+ if input_provider is not None:
537
+ self._input_provider = input_provider
538
+ if self._client_handler:
539
+ self._client_handler._input_provider = input_provider
540
+ if not self._connection or not self._session_id or not self._state:
541
+ msg = "Agent not initialized - use async context manager"
542
+ raise RuntimeError(msg)
543
+
544
+ # Capture state for use in nested function (avoids type narrowing issues)
545
+ state = self._state
546
+
547
+ conversation = message_history if message_history is not None else self.conversation
548
+ # Prepare user message for history and convert to ACP content blocks
549
+ user_msg, processed_prompts, _original_message = await prepare_prompts(*prompts)
550
+ run_id = str(uuid.uuid4())
551
+ state.clear() # Reset state
552
+ # Track messages in pydantic-ai format: ModelRequest -> ModelResponse -> ...
553
+ # This mirrors pydantic-ai's new_messages() which includes the initial user request.
554
+ model_messages: list[ModelResponse | ModelRequest] = []
555
+ # Start with the user's request (same as pydantic-ai's new_messages())
556
+ initial_request = ModelRequest(parts=[UserPromptPart(content=processed_prompts)])
557
+ model_messages.append(initial_request)
558
+ current_response_parts: list[TextPart | ThinkingPart | ToolCallPart] = []
559
+ text_chunks: list[str] = [] # For final content string
560
+ touched_files: set[str] = set() # Track files modified by tool calls
561
+ run_started = RunStartedEvent(
562
+ thread_id=self.conversation_id,
563
+ run_id=run_id,
564
+ agent_name=self.name,
565
+ )
566
+ for handler in self.event_handler._wrapped_handlers:
567
+ await handler(None, run_started)
568
+ yield run_started
569
+ content_blocks = convert_to_acp_content(processed_prompts)
570
+ pending_parts = conversation.get_pending_parts()
571
+ final_blocks = [*to_acp_content_blocks(pending_parts), *content_blocks]
572
+ prompt_request = PromptRequest(session_id=self._session_id, prompt=final_blocks)
573
+ self.log.debug("Starting streaming prompt", num_blocks=len(final_blocks))
574
+
575
+ # Reset cancellation state
576
+ self._cancelled = False
577
+ self._current_stream_task = asyncio.current_task()
578
+
579
+ # Run prompt in background
580
+ prompt_task = asyncio.create_task(self._connection.prompt(prompt_request))
581
+ self._prompt_task = prompt_task
582
+
583
+ # Create async generator that polls ACP events
584
+ async def poll_acp_events() -> AsyncIterator[RichAgentStreamEvent[str]]:
585
+ """Poll events from ACP state until prompt completes."""
586
+ last_idx = 0
587
+ while not prompt_task.done():
588
+ if self._client_handler:
589
+ try:
590
+ await asyncio.wait_for(
591
+ self._client_handler._update_event.wait(), timeout=0.05
592
+ )
593
+ self._client_handler._update_event.clear()
594
+ except TimeoutError:
595
+ pass
596
+
597
+ # Yield new events from state
598
+ while last_idx < len(state.events):
599
+ yield state.events[last_idx]
600
+ last_idx += 1
601
+
602
+ # Yield remaining events after prompt completes
603
+ while last_idx < len(state.events):
604
+ yield state.events[last_idx]
605
+ last_idx += 1
606
+
607
+ # Merge ACP events with custom events from queue
608
+ try:
609
+ async with merge_queue_into_iterator(
610
+ poll_acp_events(), self._event_queue
611
+ ) as merged_events:
612
+ async for event in merged_events:
613
+ # Check for cancellation
614
+ if self._cancelled:
615
+ self.log.info("Stream cancelled by user")
616
+ break
617
+
618
+ # Extract content from events and build parts in arrival order
619
+ match event:
620
+ case PartDeltaEvent(delta=TextPartDelta(content_delta=delta)):
621
+ text_chunks.append(delta)
622
+ current_response_parts.append(TextPart(content=delta))
623
+ case PartDeltaEvent(delta=ThinkingPartDelta(content_delta=delta)) if delta:
624
+ current_response_parts.append(ThinkingPart(content=delta))
625
+ case ToolCallStartEvent(
626
+ tool_call_id=tc_id, tool_name=tc_name, raw_input=tc_input
627
+ ):
628
+ current_response_parts.append(
629
+ ToolCallPart(tool_name=tc_name, args=tc_input, tool_call_id=tc_id)
630
+ )
631
+ # Track files modified by write/edit tools
632
+ if file_path := extract_file_path_from_tool_call(
633
+ tc_name or "", tc_input or {}
634
+ ):
635
+ touched_files.add(file_path)
636
+
637
+ # Distribute to handlers
638
+ for handler in self.event_handler._wrapped_handlers:
639
+ await handler(None, event)
640
+ yield event
641
+ except asyncio.CancelledError:
642
+ self.log.info("Stream cancelled via task cancellation")
643
+ self._cancelled = True
644
+
645
+ # Handle cancellation - emit partial message
646
+ if self._cancelled:
647
+ text_content = "".join(text_chunks)
648
+ metadata: SimpleJsonType = {}
649
+ if touched_files:
650
+ metadata["touched_files"] = sorted(touched_files)
651
+ message = ChatMessage[str](
652
+ content=text_content,
653
+ role="assistant",
654
+ name=self.name,
655
+ message_id=message_id or str(uuid.uuid4()),
656
+ conversation_id=self.conversation_id,
657
+ model_name=self.model_name,
658
+ messages=model_messages,
659
+ metadata=metadata,
660
+ finish_reason="stop",
661
+ )
662
+ complete_event = StreamCompleteEvent(message=message)
663
+ for handler in self.event_handler._wrapped_handlers:
664
+ await handler(None, complete_event)
665
+ yield complete_event
666
+ self._current_stream_task = None
667
+ self._prompt_task = None
668
+ return
669
+
670
+ # Ensure we catch any exceptions from the prompt task
671
+ response = await prompt_task
672
+ finish_reason: FinishReason = STOP_REASON_MAP.get(response.stop_reason, "stop")
673
+ # Flush response parts to model_messages
674
+ if current_response_parts:
675
+ model_messages.append(ModelResponse(parts=current_response_parts))
676
+
677
+ text_content = "".join(text_chunks)
678
+ # Build metadata with touched files if any
679
+ metadata = {}
680
+ if touched_files:
681
+ metadata["touched_files"] = sorted(touched_files)
682
+ message = ChatMessage[str](
683
+ content=text_content,
684
+ role="assistant",
685
+ name=self.name,
686
+ message_id=message_id or str(uuid.uuid4()),
687
+ conversation_id=self.conversation_id,
688
+ model_name=self.model_name,
689
+ messages=model_messages,
690
+ metadata=metadata,
691
+ finish_reason=finish_reason,
692
+ )
693
+ complete_event = StreamCompleteEvent(message=message)
694
+ for handler in self.event_handler._wrapped_handlers:
695
+ await handler(None, complete_event)
696
+ yield complete_event # Emit final StreamCompleteEvent with aggregated message
697
+ self.message_sent.emit(message)
698
+ conversation.add_chat_messages([user_msg, message]) # Record to conversation history
699
+
700
+ async def run_iter(
701
+ self,
702
+ *prompt_groups: Sequence[PromptCompatible],
703
+ ) -> AsyncIterator[ChatMessage[str]]:
704
+ """Run agent sequentially on multiple prompt groups.
705
+
706
+ Args:
707
+ prompt_groups: Groups of prompts to process sequentially
708
+
709
+ Yields:
710
+ Response messages in sequence
711
+ """
712
+ for prompts in prompt_groups:
713
+ response = await self.run(*prompts)
714
+ yield response
715
+
716
+ @property
717
+ def model_name(self) -> str | None:
718
+ """Get the model name in a consistent format."""
719
+ if self._state and self._state.current_model_id:
720
+ return self._state.current_model_id
721
+ if self._init_response and self._init_response.agent_info:
722
+ return self._init_response.agent_info.name
723
+ return None
724
+
725
+ async def set_model(self, model: str) -> None:
726
+ """Update the model and restart the ACP agent process.
727
+
728
+ Args:
729
+ model: New model name to use
730
+
731
+ Raises:
732
+ ValueError: If the config doesn't have a model field
733
+ RuntimeError: If agent is currently processing (has active process but no session)
734
+ """
735
+ # TODO: Once ACP protocol stabilizes, use set_session_model instead of restart
736
+ # from acp.schema import SetSessionModelRequest # UNSTABLE
737
+ # if self._connection and self._session_id:
738
+ # request = SetSessionModelRequest(session_id=self._session_id, model_id=model)
739
+ # await self._connection.set_session_model(request)
740
+ # if self._state:
741
+ # self._state.current_model_id = model
742
+ # self.log.info("Model changed via ACP protocol", model=model)
743
+ # return
744
+
745
+ if not hasattr(self.config, "model"):
746
+ msg = f"Config type {type(self.config).__name__} doesn't support model changes"
747
+ raise ValueError(msg)
748
+ # Prevent changes during active processing
749
+ if self._process and not self._session_id:
750
+ msg = "Cannot change model while agent is initializing"
751
+ raise RuntimeError(msg)
752
+ # Create new config with updated model
753
+ new_config = self.config.model_copy(update={"model": model})
754
+ if self._process: # Clean up existing process if any
755
+ await self._cleanup()
756
+ self.config = new_config # Update config and restart
757
+ await self._start_process()
758
+ await self._initialize()
759
+
760
+ async def set_tool_confirmation_mode(self, mode: ToolConfirmationMode) -> None:
761
+ """Set the tool confirmation mode for this agent.
762
+
763
+ For ACPAgent, this sends a set_session_mode request to the remote ACP server
764
+ to change its mode. The mode is also stored locally for the client handler.
765
+
766
+ Note: "per_tool" behaves like "always" since we don't have per-tool metadata
767
+ from the ACP server.
768
+
769
+ Args:
770
+ mode: Tool confirmation mode
771
+ """
772
+ from acp.schema import SetSessionModeRequest
773
+ from agentpool_server.acp_server.converters import confirmation_mode_to_mode_id
774
+
775
+ self.tool_confirmation_mode = mode
776
+ # Update client handler if it exists
777
+ if self._client_handler:
778
+ self._client_handler.tool_confirmation_mode = mode
779
+
780
+ # Forward mode change to remote ACP server if connected
781
+ if self._connection and self._session_id:
782
+ mode_id = confirmation_mode_to_mode_id(mode)
783
+ request = SetSessionModeRequest(session_id=self._session_id, mode_id=mode_id)
784
+ try:
785
+ await self._connection.set_session_mode(request)
786
+ msg = "Forwarded mode change to remote ACP server"
787
+ self.log.info(msg, mode=mode, mode_id=mode_id)
788
+ except Exception:
789
+ self.log.exception("Failed to forward mode change to remote ACP server")
790
+ else:
791
+ self.log.info("Tool confirmation mode changed (local only)", mode=mode)
792
+
793
+ async def get_stats(self) -> MessageStats:
794
+ """Get message statistics."""
795
+ return MessageStats(messages=list(self.conversation.chat_messages))
796
+
797
+ async def interrupt(self) -> None:
798
+ """Interrupt the currently running stream.
799
+
800
+ Sends a CancelNotification to the remote ACP server and cancels
801
+ the local prompt task.
802
+ """
803
+ from acp.schema import CancelNotification
804
+
805
+ self._cancelled = True
806
+
807
+ # Send cancel notification to the remote ACP server
808
+ if self._connection and self._session_id:
809
+ try:
810
+ cancel_notification = CancelNotification(session_id=self._session_id)
811
+ await self._connection.cancel(cancel_notification)
812
+ self.log.info("Sent cancel notification to ACP server")
813
+ except Exception:
814
+ self.log.exception("Failed to send cancel notification to ACP server")
815
+
816
+ # Cancel the local prompt task
817
+ if self._prompt_task and not self._prompt_task.done():
818
+ self._prompt_task.cancel()
819
+ self.log.info("Cancelled prompt task")
820
+
821
+ # Also cancel current stream task (from base class)
822
+ if self._current_stream_task and not self._current_stream_task.done():
823
+ self._current_stream_task.cancel()
824
+
825
+
826
+ if __name__ == "__main__":
827
+
828
+ async def main() -> None:
829
+ """Demo: Basic call to an ACP agent."""
830
+ args = ["run", "agentpool", "serve-acp", "--model-provider", "openai"]
831
+ cwd = str(Path.cwd())
832
+ async with ACPAgent(command="uv", args=args, cwd=cwd, event_handlers=["detailed"]) as agent:
833
+ print("Response (streaming): ", end="", flush=True)
834
+ async for chunk in agent.run_stream("Say hello briefly."):
835
+ print(chunk, end="", flush=True)
836
+
837
+ anyio.run(main)