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,1264 @@
1
+ """The main Agent. Can do all sort of crazy things."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from collections.abc import Awaitable, Callable
7
+ from contextlib import AsyncExitStack, asynccontextmanager, suppress
8
+ from dataclasses import dataclass, field, replace
9
+ import time
10
+ from typing import TYPE_CHECKING, Any, Self, TypedDict, TypeVar, overload
11
+ from uuid import uuid4
12
+
13
+ from anyenv import method_spawner
14
+ import anyio
15
+ from llmling_models import function_to_model, infer_model
16
+ import logfire
17
+ from psygnal import Signal
18
+ from pydantic import ValidationError
19
+ from pydantic._internal import _typing_extra
20
+ from pydantic_ai import (
21
+ Agent as PydanticAgent,
22
+ AgentRunResultEvent,
23
+ BaseToolCallPart,
24
+ FunctionToolCallEvent,
25
+ FunctionToolResultEvent,
26
+ PartDeltaEvent,
27
+ PartStartEvent,
28
+ RunContext,
29
+ TextPart,
30
+ TextPartDelta,
31
+ ToolReturnPart,
32
+ )
33
+
34
+ from agentpool.agents.base_agent import BaseAgent
35
+ from agentpool.agents.events import RunStartedEvent, StreamCompleteEvent, ToolCallCompleteEvent
36
+ from agentpool.log import get_logger
37
+ from agentpool.messaging import ChatMessage, MessageHistory, MessageNode
38
+ from agentpool.messaging.processing import prepare_prompts
39
+ from agentpool.prompts.convert import convert_prompts
40
+ from agentpool.storage import StorageManager
41
+ from agentpool.talk.stats import MessageStats
42
+ from agentpool.tools import Tool, ToolManager
43
+ from agentpool.tools.exceptions import ToolError
44
+ from agentpool.utils.inspection import call_with_context, get_argument_key
45
+ from agentpool.utils.now import get_now
46
+ from agentpool.utils.result_utils import to_type
47
+ from agentpool.utils.streams import merge_queue_into_iterator
48
+
49
+
50
+ TResult = TypeVar("TResult")
51
+
52
+
53
+ if TYPE_CHECKING:
54
+ from collections.abc import AsyncIterator, Coroutine, Sequence
55
+ from datetime import datetime
56
+ from types import TracebackType
57
+
58
+ from exxec import ExecutionEnvironment
59
+ from pydantic_ai import UsageLimits
60
+ from pydantic_ai.output import OutputSpec
61
+ from pydantic_ai.settings import ModelSettings
62
+ from toprompt import AnyPromptType
63
+ from upathtools import JoinablePathLike
64
+
65
+ from agentpool.agents import AgentContext
66
+ from agentpool.agents.events import RichAgentStreamEvent
67
+ from agentpool.common_types import (
68
+ AgentName,
69
+ BuiltinEventHandlerType,
70
+ EndStrategy,
71
+ IndividualEventHandler,
72
+ ModelType,
73
+ ProcessorCallback,
74
+ PromptCompatible,
75
+ SessionIdType,
76
+ ToolType,
77
+ )
78
+ from agentpool.delegation import AgentPool, Team, TeamRun
79
+ from agentpool.hooks import AgentHooks
80
+ from agentpool.models.agents import AutoCache, NativeAgentConfig, ToolMode
81
+ from agentpool.prompts.prompts import PromptType
82
+ from agentpool.resource_providers import ResourceProvider
83
+ from agentpool.ui.base import InputProvider
84
+ from agentpool_config.knowledge import Knowledge
85
+ from agentpool_config.mcp_server import MCPServerConfig
86
+ from agentpool_config.nodes import ToolConfirmationMode
87
+ from agentpool_config.session import MemoryConfig, SessionQuery
88
+ from agentpool_config.task import Job
89
+
90
+
91
+ logger = get_logger(__name__)
92
+ # OutputDataT = TypeVar('OutputDataT', default=str, covariant=True)
93
+ NoneType = type(None)
94
+
95
+
96
+ class AgentKwargs(TypedDict, total=False):
97
+ """Keyword arguments for configuring an Agent instance."""
98
+
99
+ description: str | None
100
+ model: ModelType
101
+ system_prompt: str | Sequence[str]
102
+ tools: Sequence[ToolType] | None
103
+ toolsets: Sequence[ResourceProvider] | None
104
+ mcp_servers: Sequence[str | MCPServerConfig] | None
105
+ skills_paths: Sequence[JoinablePathLike] | None
106
+ retries: int
107
+ output_retries: int | None
108
+ end_strategy: EndStrategy
109
+ # context: AgentContext[Any] | None # x
110
+ session: SessionIdType | SessionQuery | MemoryConfig | bool | int
111
+ input_provider: InputProvider | None
112
+ event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None
113
+ env: ExecutionEnvironment | None
114
+ auto_cache: AutoCache
115
+ hooks: AgentHooks | None
116
+ model_settings: ModelSettings | None
117
+
118
+
119
+ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
120
+ """The main agent class.
121
+
122
+ Generically typed with: Agent[Type of Dependencies, Type of Result]
123
+ """
124
+
125
+ @dataclass(frozen=True)
126
+ class AgentReset:
127
+ """Emitted when agent is reset."""
128
+
129
+ agent_name: AgentName
130
+ previous_tools: dict[str, bool]
131
+ new_tools: dict[str, bool]
132
+ timestamp: datetime = field(default_factory=get_now)
133
+
134
+ run_failed = Signal(str, Exception)
135
+ agent_reset = Signal(AgentReset)
136
+
137
+ def __init__(
138
+ # we dont use AgentKwargs here so that we can work with explicit ones in the ctor
139
+ self,
140
+ name: str = "agentpool",
141
+ *,
142
+ deps_type: type[TDeps] | None = None,
143
+ model: ModelType = None,
144
+ output_type: OutputSpec[OutputDataT] = str, # type: ignore[assignment]
145
+ # context: AgentContext[TDeps] | None = None,
146
+ session: SessionIdType | SessionQuery | MemoryConfig | bool | int = None,
147
+ system_prompt: AnyPromptType | Sequence[AnyPromptType] = (),
148
+ description: str | None = None,
149
+ display_name: str | None = None,
150
+ tools: Sequence[ToolType] | None = None,
151
+ toolsets: Sequence[ResourceProvider] | None = None,
152
+ mcp_servers: Sequence[str | MCPServerConfig] | None = None,
153
+ resources: Sequence[PromptType | str] = (),
154
+ skills_paths: Sequence[JoinablePathLike] | None = None,
155
+ retries: int = 1,
156
+ output_retries: int | None = None,
157
+ end_strategy: EndStrategy = "early",
158
+ input_provider: InputProvider | None = None,
159
+ parallel_init: bool = True,
160
+ model_settings: ModelSettings | None = None,
161
+ event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
162
+ agent_pool: AgentPool[Any] | None = None,
163
+ tool_mode: ToolMode | None = None,
164
+ knowledge: Knowledge | None = None,
165
+ agent_config: NativeAgentConfig | None = None,
166
+ env: ExecutionEnvironment | None = None,
167
+ auto_cache: AutoCache = "off",
168
+ hooks: AgentHooks | None = None,
169
+ tool_confirmation_mode: ToolConfirmationMode = "per_tool",
170
+ ) -> None:
171
+ """Initialize agent.
172
+
173
+ Args:
174
+ name: Identifier for the agent (used for logging and lookups)
175
+ deps_type: Type of dependencies to use
176
+ model: The default model to use (defaults to GPT-5)
177
+ output_type: The default output type to use (defaults to str)
178
+ context: Agent context with configuration
179
+ session: Memory configuration.
180
+ - None: Default memory config
181
+ - False: Disable message history (max_messages=0)
182
+ - int: Max tokens for memory
183
+ - str/UUID: Session identifier
184
+ - MemoryConfig: Full memory configuration
185
+ - MemoryProvider: Custom memory provider
186
+ - SessionQuery: Session query
187
+
188
+ system_prompt: System prompts for the agent
189
+ description: Description of the Agent ("what it can do")
190
+ display_name: Human-readable display name (falls back to name)
191
+ tools: List of tools to register with the agent
192
+ toolsets: List of toolset resource providers for the agent
193
+ mcp_servers: MCP servers to connect to
194
+ resources: Additional resources to load
195
+ skills_paths: Local directories to search for agent-specific skills
196
+ retries: Default number of retries for failed operations
197
+ output_retries: Max retries for result validation (defaults to retries)
198
+ end_strategy: Strategy for handling tool calls that are requested alongside
199
+ a final result
200
+ input_provider: Provider for human input (tool confirmation / HumanProviders)
201
+ parallel_init: Whether to initialize resources in parallel
202
+ model_settings: Settings for the AI model
203
+ event_handlers: Sequence of event handlers to register with the agent
204
+ agent_pool: AgentPool instance for managing agent resources
205
+ tool_mode: Tool execution mode (None or "codemode")
206
+ knowledge: Knowledge sources for this agent
207
+ agent_config: Agent configuration
208
+ env: Execution environment for code/command execution and filesystem access
209
+ auto_cache: Automatic caching configuration ("off", "5m", or "1h")
210
+ hooks: AgentHooks instance for intercepting agent behavior at run and tool events
211
+ tool_confirmation_mode: Tool confirmation mode
212
+ """
213
+ from agentpool.agents.interactions import Interactions
214
+ from agentpool.agents.sys_prompts import SystemPrompts
215
+ from agentpool.models.agents import NativeAgentConfig
216
+ from agentpool.prompts.conversion_manager import ConversionManager
217
+ from agentpool_config.session import MemoryConfig
218
+
219
+ self._infinite = False
220
+ self.deps_type = deps_type
221
+ self.model_settings = model_settings
222
+ memory_cfg = (
223
+ session if isinstance(session, MemoryConfig) else MemoryConfig.from_value(session)
224
+ )
225
+ # Collect MCP servers from config
226
+ all_mcp_servers = list(mcp_servers) if mcp_servers else []
227
+ if agent_config and agent_config.mcp_servers:
228
+ all_mcp_servers.extend(agent_config.get_mcp_servers())
229
+
230
+ # Call base class with shared parameters
231
+ super().__init__(
232
+ name=name,
233
+ description=description,
234
+ display_name=display_name,
235
+ enable_logging=memory_cfg.enable,
236
+ mcp_servers=all_mcp_servers,
237
+ agent_pool=agent_pool,
238
+ event_configs=agent_config.triggers if agent_config else [],
239
+ env=env,
240
+ input_provider=input_provider,
241
+ output_type=to_type(output_type), # type: ignore[arg-type]
242
+ tool_confirmation_mode=tool_confirmation_mode,
243
+ event_handlers=event_handlers,
244
+ )
245
+
246
+ # Store config for context creation
247
+ self._agent_config = agent_config or NativeAgentConfig(name=name)
248
+
249
+ # Override tools with Agent-specific ToolManager (with tools and tool_mode)
250
+ all_tools = list(tools or [])
251
+ self.tools = ToolManager(all_tools, tool_mode=tool_mode)
252
+ for toolset_provider in toolsets or []:
253
+ self.tools.add_provider(toolset_provider)
254
+ aggregating_provider = self.mcp.get_aggregating_provider()
255
+ self.tools.add_provider(aggregating_provider)
256
+
257
+ # Override conversation with Agent-specific MessageHistory (with storage, etc.)
258
+ resources = list(resources)
259
+ if knowledge:
260
+ resources.extend(knowledge.get_resources())
261
+ storage = agent_pool.storage if agent_pool else StorageManager(self._manifest.storage)
262
+ self.conversation = MessageHistory(
263
+ storage=storage,
264
+ converter=ConversionManager(config=self._manifest.conversion),
265
+ session_config=memory_cfg,
266
+ resources=resources,
267
+ )
268
+ self._model = infer_model(model) if isinstance(model, str) else model
269
+ self._retries = retries
270
+ self._end_strategy: EndStrategy = end_strategy
271
+ self._output_retries = output_retries
272
+ self.parallel_init = parallel_init
273
+ self._background_task: asyncio.Task[ChatMessage[Any]] | None = None
274
+ self.talk = Interactions(self)
275
+
276
+ # Set up system prompts
277
+ all_prompts: list[AnyPromptType] = []
278
+ if isinstance(system_prompt, (list, tuple)):
279
+ all_prompts.extend(system_prompt)
280
+ elif system_prompt:
281
+ all_prompts.append(system_prompt)
282
+ self.sys_prompts = SystemPrompts(all_prompts, prompt_manager=self._manifest.prompt_manager)
283
+
284
+ # Store hooks
285
+ self.hooks = hooks
286
+
287
+ # Store auto_cache setting
288
+ self._auto_cache: AutoCache = auto_cache
289
+
290
+ def __repr__(self) -> str:
291
+ desc = f", {self.description!r}" if self.description else ""
292
+ return f"Agent({self.name!r}, model={self._model!r}{desc})"
293
+
294
+ async def __prompt__(self) -> str:
295
+ typ = self.__class__.__name__
296
+ model = self.model_name or "default"
297
+ parts = [f"Agent: {self.name}", f"Type: {typ}", f"Model: {model}"]
298
+ if self.description:
299
+ parts.append(f"Description: {self.description}")
300
+ parts.extend([await self.tools.__prompt__(), self.conversation.__prompt__()])
301
+ return "\n".join(parts)
302
+
303
+ async def __aenter__(self) -> Self:
304
+ """Enter async context and set up MCP servers."""
305
+ try:
306
+ # Collect all coroutines that need to be run
307
+ coros: list[Coroutine[Any, Any, Any]] = []
308
+ coros.append(super().__aenter__())
309
+ coros.extend(self.conversation.get_initialization_tasks())
310
+ if self.parallel_init and coros:
311
+ await asyncio.gather(*coros)
312
+ else:
313
+ for coro in coros:
314
+ await coro
315
+ except Exception as e:
316
+ msg = "Failed to initialize agent"
317
+ raise RuntimeError(msg) from e
318
+ else:
319
+ return self
320
+
321
+ async def __aexit__(
322
+ self,
323
+ exc_type: type[BaseException] | None,
324
+ exc_val: BaseException | None,
325
+ exc_tb: TracebackType | None,
326
+ ) -> None:
327
+ """Exit async context."""
328
+ await super().__aexit__(exc_type, exc_val, exc_tb)
329
+
330
+ @overload
331
+ def __and__( # if other doesnt define deps, we take the agents one
332
+ self, other: ProcessorCallback[Any] | Team[TDeps] | Agent[TDeps, Any]
333
+ ) -> Team[TDeps]: ...
334
+
335
+ @overload
336
+ def __and__( # otherwise, we dont know and deps is Any
337
+ self, other: ProcessorCallback[Any] | Team[Any] | Agent[Any, Any]
338
+ ) -> Team[Any]: ...
339
+
340
+ def __and__(self, other: MessageNode[Any, Any] | ProcessorCallback[Any]) -> Team[Any]:
341
+ """Create sequential team using & operator.
342
+
343
+ Example:
344
+ group = analyzer & planner & executor # Create group of 3
345
+ group = analyzer & existing_group # Add to existing group
346
+ """
347
+ from agentpool.delegation.team import Team
348
+
349
+ match other:
350
+ case Team():
351
+ return Team([self, *other.nodes])
352
+ case Callable():
353
+ agent_2 = Agent.from_callback(other)
354
+ agent_2.agent_pool = self.agent_pool
355
+ return Team([self, agent_2])
356
+ case MessageNode():
357
+ return Team([self, other])
358
+ case _:
359
+ msg = f"Invalid agent type: {type(other)}"
360
+ raise ValueError(msg)
361
+
362
+ @overload
363
+ def __or__(self, other: MessageNode[TDeps, Any]) -> TeamRun[TDeps, Any]: ...
364
+
365
+ @overload
366
+ def __or__[TOtherDeps](self, other: MessageNode[TOtherDeps, Any]) -> TeamRun[Any, Any]: ...
367
+
368
+ @overload
369
+ def __or__(self, other: ProcessorCallback[Any]) -> TeamRun[Any, Any]: ...
370
+
371
+ def __or__(self, other: MessageNode[Any, Any] | ProcessorCallback[Any]) -> TeamRun[Any, Any]:
372
+ # Create new execution with sequential mode (for piping)
373
+ from agentpool import TeamRun
374
+
375
+ if callable(other):
376
+ other = Agent.from_callback(other)
377
+ other.agent_pool = self.agent_pool
378
+
379
+ return TeamRun([self, other])
380
+
381
+ @overload
382
+ @classmethod
383
+ def from_callback(
384
+ cls,
385
+ callback: Callable[..., Awaitable[TResult]],
386
+ *,
387
+ name: str | None = None,
388
+ **kwargs: Any,
389
+ ) -> Agent[None, TResult]: ...
390
+
391
+ @overload
392
+ @classmethod
393
+ def from_callback(
394
+ cls,
395
+ callback: Callable[..., TResult],
396
+ *,
397
+ name: str | None = None,
398
+ **kwargs: Any,
399
+ ) -> Agent[None, TResult]: ...
400
+
401
+ @classmethod
402
+ def from_callback(
403
+ cls,
404
+ callback: ProcessorCallback[Any],
405
+ *,
406
+ name: str | None = None,
407
+ **kwargs: Any,
408
+ ) -> Agent[None, Any]:
409
+ """Create an agent from a processing callback.
410
+
411
+ Args:
412
+ callback: Function to process messages. Can be:
413
+ - sync or async
414
+ - with or without context
415
+ - must return str for pipeline compatibility
416
+ name: Optional name for the agent
417
+ kwargs: Additional arguments for agent
418
+ """
419
+ name = name or callback.__name__ or "processor"
420
+ model = function_to_model(callback)
421
+ return_type = _typing_extra.get_function_type_hints(callback).get("return")
422
+ if ( # If async, unwrap from Awaitable
423
+ return_type
424
+ and hasattr(return_type, "__origin__")
425
+ and return_type.__origin__ is Awaitable
426
+ ):
427
+ return_type = return_type.__args__[0]
428
+ return Agent(model=model, name=name, output_type=return_type or str, **kwargs)
429
+
430
+ @property
431
+ def name(self) -> str:
432
+ """Get agent name."""
433
+ return self._name or "agentpool"
434
+
435
+ @name.setter
436
+ def name(self, value: str) -> None:
437
+ """Set agent name."""
438
+ self._name = value
439
+
440
+ def get_context(self, data: TDeps | None = None) -> AgentContext[TDeps]: # type: ignore[override]
441
+ """Create a new context for this agent.
442
+
443
+ Args:
444
+ data: Optional custom data to attach to the context
445
+
446
+ Returns:
447
+ A new AgentContext instance
448
+ """
449
+ from agentpool.agents import AgentContext
450
+
451
+ return AgentContext(
452
+ node=self,
453
+ definition=self._manifest,
454
+ config=self._agent_config,
455
+ input_provider=self._input_provider,
456
+ pool=self.agent_pool,
457
+ data=data,
458
+ )
459
+
460
+ def to_structured[NewOutputDataT](
461
+ self,
462
+ output_type: type[NewOutputDataT],
463
+ *,
464
+ tool_name: str | None = None,
465
+ tool_description: str | None = None,
466
+ ) -> Agent[TDeps, NewOutputDataT]:
467
+ """Convert this agent to a structured agent.
468
+
469
+ Args:
470
+ output_type: Type for structured responses. Can be:
471
+ - A Python type (Pydantic model)
472
+ tool_name: Optional override for result tool name
473
+ tool_description: Optional override for result tool description
474
+
475
+ Returns:
476
+ Typed Agent
477
+ """
478
+ self.log.debug("Setting result type", output_type=output_type)
479
+ self._output_type = to_type(output_type) # type: ignore[assignment]
480
+ return self # type: ignore
481
+
482
+ def is_busy(self) -> bool:
483
+ """Check if agent is currently processing tasks."""
484
+ return bool(self.task_manager._pending_tasks or self._background_task)
485
+
486
+ @property
487
+ def model_name(self) -> str | None:
488
+ """Get the model name in a consistent format (provider:model_name)."""
489
+ if self._model:
490
+ # Construct full model ID with provider prefix (e.g., "anthropic:claude-haiku-4-5")
491
+ return f"{self._model.system}:{self._model.model_name}"
492
+ return None
493
+
494
+ def to_tool(
495
+ self,
496
+ *,
497
+ name: str | None = None,
498
+ description: str | None = None,
499
+ reset_history_on_run: bool = True,
500
+ pass_message_history: bool = False,
501
+ parent: Agent[Any, Any] | None = None,
502
+ **_kwargs: Any,
503
+ ) -> Tool[OutputDataT]:
504
+ """Create a tool from this agent.
505
+
506
+ Args:
507
+ name: Optional tool name override
508
+ description: Optional tool description override
509
+ reset_history_on_run: Clear agent's history before each run
510
+ pass_message_history: Pass parent's message history to agent
511
+ parent: Optional parent agent for history/context sharing
512
+ """
513
+
514
+ async def wrapped_tool(prompt: str) -> Any:
515
+ if pass_message_history and not parent:
516
+ msg = "Parent agent required for message history sharing"
517
+ raise ToolError(msg)
518
+
519
+ if reset_history_on_run:
520
+ self.conversation.clear()
521
+
522
+ history = None
523
+ if pass_message_history and parent:
524
+ history = parent.conversation.get_history()
525
+ old = self.conversation.get_history()
526
+ self.conversation.set_history(history)
527
+ result = await self.run(prompt)
528
+ if history:
529
+ self.conversation.set_history(old)
530
+ return result.data
531
+
532
+ # Set the correct return annotation dynamically
533
+ wrapped_tool.__annotations__ = {"prompt": str, "return": self._output_type or Any}
534
+
535
+ normalized_name = self.name.replace("_", " ").title()
536
+ docstring = f"Get expert answer from specialized agent: {normalized_name}"
537
+ description = description or self.description
538
+ if description:
539
+ docstring = f"{docstring}\n\n{description}"
540
+ tool_name = name or f"ask_{self.name}"
541
+ wrapped_tool.__doc__ = docstring
542
+ wrapped_tool.__name__ = tool_name
543
+
544
+ return Tool.from_callable(
545
+ wrapped_tool,
546
+ name_override=tool_name,
547
+ description_override=docstring,
548
+ source="agent",
549
+ )
550
+
551
+ async def get_agentlet[AgentOutputType](
552
+ self,
553
+ tool_choice: str | list[str] | None,
554
+ model: ModelType,
555
+ output_type: type[AgentOutputType] | None,
556
+ input_provider: InputProvider | None = None,
557
+ ) -> PydanticAgent[TDeps, AgentOutputType]:
558
+ """Create pydantic-ai agent from current state."""
559
+ # Monkey patch pydantic-ai to recognize AgentContext
560
+
561
+ from agentpool.agents.tool_wrapping import wrap_tool
562
+
563
+ tools = await self.tools.get_tools(state="enabled", names=tool_choice)
564
+ final_type = to_type(output_type) if output_type not in [None, str] else self._output_type
565
+ actual_model = model or self._model
566
+ model_ = infer_model(actual_model) if isinstance(actual_model, str) else actual_model
567
+ agent = PydanticAgent(
568
+ name=self.name,
569
+ model=model_,
570
+ model_settings=self.model_settings,
571
+ instructions=await self.sys_prompts.format_system_prompt(self),
572
+ retries=self._retries,
573
+ end_strategy=self._end_strategy,
574
+ output_retries=self._output_retries,
575
+ deps_type=self.deps_type or NoneType,
576
+ output_type=final_type,
577
+ )
578
+
579
+ base_context = self.get_context()
580
+ context_for_tools = (
581
+ base_context
582
+ if input_provider is None
583
+ else replace(base_context, input_provider=input_provider)
584
+ )
585
+
586
+ for tool in tools:
587
+ wrapped = wrap_tool(tool, context_for_tools, hooks=self.hooks)
588
+ if get_argument_key(wrapped, RunContext):
589
+ logger.info("Registering tool: with context", tool_name=tool.name)
590
+ agent.tool(wrapped)
591
+ else:
592
+ logger.info("Registering tool: no context", tool_name=tool.name)
593
+ agent.tool_plain(wrapped)
594
+
595
+ return agent # type: ignore[return-value]
596
+
597
+ @overload
598
+ async def run(
599
+ self,
600
+ *prompts: PromptCompatible | ChatMessage[Any],
601
+ output_type: None = None,
602
+ model: ModelType = None,
603
+ store_history: bool = True,
604
+ tool_choice: str | list[str] | None = None,
605
+ usage_limits: UsageLimits | None = None,
606
+ message_id: str | None = None,
607
+ conversation_id: str | None = None,
608
+ message_history: MessageHistory | None = None,
609
+ deps: TDeps | None = None,
610
+ input_provider: InputProvider | None = None,
611
+ wait_for_connections: bool | None = None,
612
+ instructions: str | None = None,
613
+ ) -> ChatMessage[OutputDataT]: ...
614
+
615
+ @overload
616
+ async def run[OutputTypeT](
617
+ self,
618
+ *prompts: PromptCompatible | ChatMessage[Any],
619
+ output_type: type[OutputTypeT],
620
+ model: ModelType = None,
621
+ store_history: bool = True,
622
+ tool_choice: str | list[str] | None = None,
623
+ usage_limits: UsageLimits | None = None,
624
+ message_id: str | None = None,
625
+ conversation_id: str | None = None,
626
+ message_history: MessageHistory | None = None,
627
+ deps: TDeps | None = None,
628
+ input_provider: InputProvider | None = None,
629
+ wait_for_connections: bool | None = None,
630
+ instructions: str | None = None,
631
+ ) -> ChatMessage[OutputTypeT]: ...
632
+
633
+ @method_spawner # type: ignore[misc]
634
+ async def run(
635
+ self,
636
+ *prompts: PromptCompatible | ChatMessage[Any],
637
+ output_type: type[Any] | None = None,
638
+ model: ModelType = None,
639
+ store_history: bool = True,
640
+ tool_choice: str | list[str] | None = None,
641
+ usage_limits: UsageLimits | None = None,
642
+ message_id: str | None = None,
643
+ conversation_id: str | None = None,
644
+ message_history: MessageHistory | None = None,
645
+ deps: TDeps | None = None,
646
+ input_provider: InputProvider | None = None,
647
+ wait_for_connections: bool | None = None,
648
+ instructions: str | None = None,
649
+ ) -> ChatMessage[Any]:
650
+ """Run agent with prompt and get response.
651
+
652
+ Args:
653
+ prompts: User query or instruction
654
+ output_type: Optional type for structured responses
655
+ model: Optional model override
656
+ store_history: Whether the message exchange should be added to the
657
+ context window
658
+ tool_choice: Filter tool choice by name
659
+ usage_limits: Optional usage limits for the model
660
+ message_id: Optional message id for the returned message.
661
+ Automatically generated if not provided.
662
+ conversation_id: Optional conversation id for the returned message.
663
+ message_history: Optional MessageHistory object to
664
+ use instead of agent's own conversation
665
+ deps: Optional dependencies for the agent
666
+ input_provider: Optional input provider for the agent
667
+ wait_for_connections: Whether to wait for connected agents to complete
668
+ instructions: Optional instructions to override the agent's system prompt
669
+
670
+ Returns:
671
+ Result containing response and run information
672
+
673
+ Raises:
674
+ UnexpectedModelBehavior: If the model fails or behaves unexpectedly
675
+ """
676
+ # Collect all events through run_stream
677
+ final_message: ChatMessage[Any] | None = None
678
+ async for event in self.run_stream(
679
+ *prompts,
680
+ output_type=output_type,
681
+ model=model,
682
+ store_history=store_history,
683
+ tool_choice=tool_choice,
684
+ usage_limits=usage_limits,
685
+ message_id=message_id,
686
+ conversation_id=conversation_id,
687
+ message_history=message_history,
688
+ deps=deps,
689
+ input_provider=input_provider,
690
+ wait_for_connections=wait_for_connections,
691
+ instructions=instructions,
692
+ ):
693
+ if isinstance(event, StreamCompleteEvent):
694
+ final_message = event.message
695
+
696
+ if final_message is None:
697
+ msg = "No final message received from stream"
698
+ raise RuntimeError(msg)
699
+
700
+ return final_message
701
+
702
+ @method_spawner
703
+ async def run_stream( # noqa: PLR0915
704
+ self,
705
+ *prompt: PromptCompatible,
706
+ output_type: type[OutputDataT] | None = None,
707
+ model: ModelType = None,
708
+ tool_choice: str | list[str] | None = None,
709
+ store_history: bool = True,
710
+ usage_limits: UsageLimits | None = None,
711
+ message_id: str | None = None,
712
+ conversation_id: str | None = None,
713
+ message_history: MessageHistory | None = None,
714
+ input_provider: InputProvider | None = None,
715
+ wait_for_connections: bool | None = None,
716
+ deps: TDeps | None = None,
717
+ instructions: str | None = None,
718
+ ) -> AsyncIterator[RichAgentStreamEvent[OutputDataT]]:
719
+ """Run agent with prompt and get a streaming response.
720
+
721
+ Args:
722
+ prompt: User query or instruction
723
+ output_type: Optional type for structured responses
724
+ model: Optional model override
725
+ tool_choice: Filter tool choice by name
726
+ store_history: Whether the message exchange should be added to the
727
+ context window
728
+ usage_limits: Optional usage limits for the model
729
+ message_id: Optional message id for the returned message.
730
+ Automatically generated if not provided.
731
+ conversation_id: Optional conversation id for the returned message.
732
+ message_history: Optional MessageHistory to use instead of agent's own
733
+ input_provider: Optional input provider for the agent
734
+ wait_for_connections: Whether to wait for connected agents to complete
735
+ deps: Optional dependencies for the agent
736
+ instructions: Optional instructions to override the agent's system prompt
737
+ Returns:
738
+ An async iterator yielding streaming events with final message embedded.
739
+
740
+ Raises:
741
+ UnexpectedModelBehavior: If the model fails or behaves unexpectedly
742
+ """
743
+ conversation = message_history if message_history is not None else self.conversation
744
+ message_id = message_id or str(uuid4())
745
+ run_id = str(uuid4())
746
+ user_msg, prompts, original_message = await prepare_prompts(*prompt)
747
+ self.message_received.emit(user_msg)
748
+ start_time = time.perf_counter()
749
+ history_list = conversation.get_history()
750
+ pending_parts = conversation.get_pending_parts()
751
+
752
+ # Reset cancellation state and track current task
753
+ self._cancelled = False
754
+ self._current_stream_task = asyncio.current_task()
755
+
756
+ # Track accumulated content for partial message on cancellation
757
+ accumulated_text: list[str] = []
758
+
759
+ # Execute pre-run hooks
760
+ if self.hooks:
761
+ pre_run_result = await self.hooks.run_pre_run_hooks(
762
+ agent_name=self.name,
763
+ prompt=user_msg.content
764
+ if isinstance(user_msg.content, str)
765
+ else str(user_msg.content),
766
+ conversation_id=conversation_id,
767
+ )
768
+ if pre_run_result.get("decision") == "deny":
769
+ reason = pre_run_result.get("reason", "Blocked by pre-run hook")
770
+ msg = f"Run blocked: {reason}"
771
+ raise RuntimeError(msg)
772
+
773
+ yield RunStartedEvent(thread_id=self.conversation_id, run_id=run_id, agent_name=self.name)
774
+ try:
775
+ agentlet = await self.get_agentlet(tool_choice, model, output_type, input_provider)
776
+ content = await convert_prompts(prompts)
777
+ response_msg: ChatMessage[Any] | None = None
778
+ # Prepend pending context parts (content is already pydantic-ai format)
779
+ converted = [*pending_parts, *content]
780
+
781
+ # Add CachePoint if auto_cache is enabled
782
+ if self._auto_cache != "off":
783
+ from pydantic_ai.messages import CachePoint
784
+
785
+ cache_point = CachePoint(ttl=self._auto_cache)
786
+ converted.append(cache_point)
787
+ stream_events = agentlet.run_stream_events(
788
+ converted,
789
+ deps=deps, # type: ignore[arg-type]
790
+ message_history=[m for run in history_list for m in run.to_pydantic_ai()],
791
+ usage_limits=usage_limits,
792
+ instructions=instructions,
793
+ )
794
+
795
+ # Stream events through merge_queue for progress events
796
+ async with merge_queue_into_iterator(stream_events, self._event_queue) as events:
797
+ # Track tool call starts to combine with results later
798
+ pending_tcs: dict[str, BaseToolCallPart] = {}
799
+ try:
800
+ async for event in events:
801
+ # Check for cancellation
802
+ if self._cancelled:
803
+ self.log.info("Stream cancelled by user")
804
+ break
805
+
806
+ # Call event handlers for all events
807
+ for handler in self.event_handler._wrapped_handlers:
808
+ await handler(None, event)
809
+
810
+ yield event # type: ignore[misc]
811
+
812
+ # Accumulate text content for partial message
813
+ match event:
814
+ case PartDeltaEvent(delta=TextPartDelta(content_delta=delta)):
815
+ accumulated_text.append(delta)
816
+ case PartStartEvent(part=TextPart(content=text)) if text:
817
+ accumulated_text.append(text)
818
+
819
+ match event:
820
+ case (
821
+ PartStartEvent(part=BaseToolCallPart() as tool_part)
822
+ | FunctionToolCallEvent(part=tool_part)
823
+ ):
824
+ # Store tool call start info for later combination with result
825
+ pending_tcs[tool_part.tool_call_id] = tool_part
826
+ case FunctionToolResultEvent(tool_call_id=call_id) as result_event:
827
+ # Check if we have a pending tool call to combine with
828
+ if call_info := pending_tcs.pop(call_id, None):
829
+ # Create and yield combined event
830
+ combined_event = ToolCallCompleteEvent(
831
+ tool_name=call_info.tool_name,
832
+ tool_call_id=call_id,
833
+ tool_input=call_info.args_as_dict(),
834
+ tool_result=result_event.result.content
835
+ if isinstance(result_event.result, ToolReturnPart)
836
+ else result_event.result,
837
+ agent_name=self.name,
838
+ message_id=message_id,
839
+ )
840
+ yield combined_event
841
+ case AgentRunResultEvent():
842
+ # Capture final result data, Build final response message
843
+ response_time = time.perf_counter() - start_time
844
+ response_msg = await ChatMessage.from_run_result(
845
+ event.result,
846
+ agent_name=self.name,
847
+ message_id=message_id,
848
+ conversation_id=conversation_id or user_msg.conversation_id,
849
+ response_time=response_time,
850
+ )
851
+ except asyncio.CancelledError:
852
+ self.log.info("Stream cancelled via task cancellation")
853
+ self._cancelled = True
854
+
855
+ # Handle cancellation - emit partial message
856
+ if self._cancelled:
857
+ response_time = time.perf_counter() - start_time
858
+ partial_content = "".join(accumulated_text)
859
+ if partial_content:
860
+ partial_content += "\n\n"
861
+ partial_content += "[Request interrupted by user]"
862
+ response_msg = ChatMessage(
863
+ content=partial_content,
864
+ role="assistant",
865
+ name=self.name,
866
+ message_id=message_id,
867
+ conversation_id=conversation_id or user_msg.conversation_id,
868
+ response_time=response_time,
869
+ finish_reason="stop",
870
+ )
871
+ yield StreamCompleteEvent(message=response_msg)
872
+ self._current_stream_task = None
873
+ return
874
+
875
+ # Only finalize if we got a result (stream may exit early on error)
876
+ if response_msg is None:
877
+ msg = "Stream completed without producing a result"
878
+ raise RuntimeError(msg) # noqa: TRY301
879
+
880
+ # Execute post-run hooks
881
+ if self.hooks:
882
+ prompt_str = (
883
+ user_msg.content if isinstance(user_msg.content, str) else str(user_msg.content)
884
+ )
885
+ await self.hooks.run_post_run_hooks(
886
+ agent_name=self.name,
887
+ prompt=prompt_str,
888
+ result=response_msg.content,
889
+ conversation_id=conversation_id,
890
+ )
891
+
892
+ # Apply forwarding logic if needed
893
+ if original_message:
894
+ response_msg = response_msg.forwarded(original_message)
895
+ # Send additional enriched completion event
896
+ yield StreamCompleteEvent(message=response_msg)
897
+ self.message_sent.emit(response_msg)
898
+ await self.log_message(response_msg)
899
+ if store_history:
900
+ conversation.add_chat_messages([user_msg, response_msg])
901
+ await self.connections.route_message(response_msg, wait=wait_for_connections)
902
+
903
+ except Exception as e:
904
+ self.log.exception("Agent stream failed")
905
+ self.run_failed.emit("Agent stream failed", e)
906
+ raise
907
+ finally:
908
+ self._current_stream_task = None
909
+
910
+ async def run_iter(
911
+ self,
912
+ *prompt_groups: Sequence[PromptCompatible],
913
+ output_type: type[OutputDataT] | None = None,
914
+ model: ModelType = None,
915
+ store_history: bool = True,
916
+ wait_for_connections: bool | None = None,
917
+ ) -> AsyncIterator[ChatMessage[OutputDataT]]:
918
+ """Run agent sequentially on multiple prompt groups.
919
+
920
+ Args:
921
+ prompt_groups: Groups of prompts to process sequentially
922
+ output_type: Optional type for structured responses
923
+ model: Optional model override
924
+ store_history: Whether to store in conversation history
925
+ wait_for_connections: Whether to wait for connected agents
926
+
927
+ Yields:
928
+ Response messages in sequence
929
+
930
+ Example:
931
+ questions = [
932
+ ["What is your name?"],
933
+ ["How old are you?", image1],
934
+ ["Describe this image", image2],
935
+ ]
936
+ async for response in agent.run_iter(*questions):
937
+ print(response.content)
938
+ """
939
+ for prompts in prompt_groups:
940
+ response = await self.run(
941
+ *prompts,
942
+ output_type=output_type,
943
+ model=model,
944
+ store_history=store_history,
945
+ wait_for_connections=wait_for_connections,
946
+ )
947
+ yield response # pyright: ignore
948
+
949
+ @method_spawner
950
+ async def run_job(
951
+ self,
952
+ job: Job[TDeps, str | None],
953
+ *,
954
+ store_history: bool = True,
955
+ include_agent_tools: bool = True,
956
+ ) -> ChatMessage[OutputDataT]:
957
+ """Execute a pre-defined task.
958
+
959
+ Args:
960
+ job: Job configuration to execute
961
+ store_history: Whether the message exchange should be added to the
962
+ context window
963
+ include_agent_tools: Whether to include agent tools
964
+ Returns:
965
+ Job execution result
966
+
967
+ Raises:
968
+ JobError: If task execution fails
969
+ ValueError: If task configuration is invalid
970
+ """
971
+ from agentpool.tasks import JobError
972
+
973
+ if job.required_dependency is not None:
974
+ agent_ctx = self.get_context()
975
+ if not isinstance(agent_ctx.data, job.required_dependency):
976
+ msg = (
977
+ f"Agent dependencies ({type(agent_ctx.data)}) "
978
+ f"don't match job requirement ({job.required_dependency})"
979
+ )
980
+ raise JobError(msg)
981
+
982
+ # Load task knowledge
983
+ if job.knowledge:
984
+ # Add knowledge sources to context
985
+ for source in list(job.knowledge.paths):
986
+ await self.conversation.load_context_source(source)
987
+ for prompt in job.knowledge.prompts:
988
+ await self.conversation.load_context_source(prompt)
989
+ try:
990
+ # Register task tools temporarily
991
+ tools = job.get_tools()
992
+ async with self.tools.temporary_tools(tools, exclusive=not include_agent_tools):
993
+ # Execute job with job-specific tools
994
+ return await self.run(await job.get_prompt(), store_history=store_history)
995
+
996
+ except Exception as e:
997
+ self.log.exception("Task execution failed", error=str(e))
998
+ msg = f"Task execution failed: {e}"
999
+ raise JobError(msg) from e
1000
+
1001
+ async def run_in_background(
1002
+ self,
1003
+ *prompt: PromptCompatible,
1004
+ max_count: int | None = None,
1005
+ interval: float = 1.0,
1006
+ **kwargs: Any,
1007
+ ) -> asyncio.Task[ChatMessage[OutputDataT] | None]:
1008
+ """Run agent continuously in background with prompt or dynamic prompt function.
1009
+
1010
+ Args:
1011
+ prompt: Static prompt or function that generates prompts
1012
+ max_count: Maximum number of runs (None = infinite)
1013
+ interval: Seconds between runs
1014
+ **kwargs: Arguments passed to run()
1015
+ """
1016
+ self._infinite = max_count is None
1017
+
1018
+ async def _continuous() -> ChatMessage[Any]:
1019
+ count = 0
1020
+ self.log.debug("Starting continuous run", max_count=max_count, interval=interval)
1021
+ latest = None
1022
+ while (max_count is None or count < max_count) and not self._cancelled:
1023
+ try:
1024
+ agent_ctx = self.get_context()
1025
+ current_prompts = [
1026
+ call_with_context(p, agent_ctx, **kwargs) if callable(p) else p
1027
+ for p in prompt
1028
+ ]
1029
+ self.log.debug("Generated prompt", iteration=count)
1030
+ latest = await self.run(current_prompts, **kwargs)
1031
+ self.log.debug("Run continuous result", iteration=count)
1032
+
1033
+ count += 1
1034
+ await anyio.sleep(interval)
1035
+ except asyncio.CancelledError:
1036
+ self.log.debug("Continuous run cancelled")
1037
+ break
1038
+ except Exception:
1039
+ # Check if we were cancelled (may surface as other exceptions)
1040
+ if self._cancelled:
1041
+ self.log.debug("Continuous run cancelled via flag")
1042
+ break
1043
+ count += 1
1044
+ self.log.exception("Background run failed")
1045
+ await anyio.sleep(interval)
1046
+ self.log.debug("Continuous run completed", iterations=count)
1047
+ return latest # type: ignore[return-value]
1048
+
1049
+ await self.stop() # Cancel any existing background task
1050
+ self._cancelled = False # Reset cancellation flag for new run
1051
+ task = asyncio.create_task(_continuous(), name=f"background_{self.name}")
1052
+ self.log.debug("Started background task", task_name=task.get_name())
1053
+ self._background_task = task
1054
+ return task
1055
+
1056
+ async def stop(self) -> None:
1057
+ """Stop continuous execution if running."""
1058
+ self._cancelled = True # Signal cancellation via flag
1059
+ if self._background_task and not self._background_task.done():
1060
+ self._background_task.cancel()
1061
+ with suppress(asyncio.CancelledError): # Expected when we cancel the task
1062
+ await self._background_task
1063
+ self._background_task = None
1064
+
1065
+ async def wait(self) -> ChatMessage[OutputDataT]:
1066
+ """Wait for background execution to complete."""
1067
+ if not self._background_task:
1068
+ msg = "No background task running"
1069
+ raise RuntimeError(msg)
1070
+ if self._infinite:
1071
+ msg = "Cannot wait on infinite execution"
1072
+ raise RuntimeError(msg)
1073
+ try:
1074
+ return await self._background_task
1075
+ finally:
1076
+ self._background_task = None
1077
+
1078
+ async def share(
1079
+ self,
1080
+ target: Agent[TDeps, Any],
1081
+ *,
1082
+ tools: list[str] | None = None,
1083
+ history: bool | int | None = None, # bool or number of messages
1084
+ token_limit: int | None = None,
1085
+ ) -> None:
1086
+ """Share capabilities and knowledge with another agent.
1087
+
1088
+ Args:
1089
+ target: Agent to share with
1090
+ tools: List of tool names to share
1091
+ history: Share conversation history:
1092
+ - True: Share full history
1093
+ - int: Number of most recent messages to share
1094
+ - None: Don't share history
1095
+ token_limit: Optional max tokens for history
1096
+
1097
+ Raises:
1098
+ ValueError: If requested items don't exist
1099
+ RuntimeError: If runtime not available for resources
1100
+ """
1101
+ # Share tools if requested
1102
+ for name in tools or []:
1103
+ tool = await self.tools.get_tool(name)
1104
+ meta = {"shared_from": self.name}
1105
+ target.tools.register_tool(tool.callable, metadata=meta)
1106
+
1107
+ # Share history if requested
1108
+ if history:
1109
+ history_text = await self.conversation.format_history(
1110
+ max_tokens=token_limit,
1111
+ num_messages=history if isinstance(history, int) else None,
1112
+ )
1113
+ target.conversation.add_context_message(
1114
+ history_text, source=self.name, metadata={"type": "shared_history"}
1115
+ )
1116
+
1117
+ def register_worker(
1118
+ self,
1119
+ worker: MessageNode[Any, Any],
1120
+ *,
1121
+ name: str | None = None,
1122
+ reset_history_on_run: bool = True,
1123
+ pass_message_history: bool = False,
1124
+ ) -> Tool:
1125
+ """Register another agent as a worker tool."""
1126
+ return self.tools.register_worker(
1127
+ worker,
1128
+ name=name,
1129
+ reset_history_on_run=reset_history_on_run,
1130
+ pass_message_history=pass_message_history,
1131
+ parent=self if pass_message_history else None,
1132
+ )
1133
+
1134
+ def set_model(self, model: ModelType) -> None:
1135
+ """Set the model for this agent.
1136
+
1137
+ Args:
1138
+ model: New model to use (name or instance)
1139
+
1140
+ """
1141
+ self._model = infer_model(model) if isinstance(model, str) else model
1142
+
1143
+ async def set_tool_confirmation_mode(self, mode: ToolConfirmationMode) -> None:
1144
+ """Set the tool confirmation mode for this agent.
1145
+
1146
+ Args:
1147
+ mode: Tool confirmation mode:
1148
+ - "always": Always require confirmation for all tools
1149
+ - "never": Never require confirmation
1150
+ - "per_tool": Use individual tool settings
1151
+ """
1152
+ self.tool_confirmation_mode = mode
1153
+ self.log.info("Tool confirmation mode changed", mode=mode)
1154
+
1155
+ async def reset(self) -> None:
1156
+ """Reset agent state (conversation history and tool states)."""
1157
+ old_tools = await self.tools.list_tools()
1158
+ self.conversation.clear()
1159
+ await self.tools.reset_states()
1160
+ new_tools = await self.tools.list_tools()
1161
+
1162
+ event = self.AgentReset(
1163
+ agent_name=self.name,
1164
+ previous_tools=old_tools,
1165
+ new_tools=new_tools,
1166
+ )
1167
+ self.agent_reset.emit(event)
1168
+
1169
+ async def get_stats(self) -> MessageStats:
1170
+ """Get message statistics (async version)."""
1171
+ messages = await self.get_message_history()
1172
+ return MessageStats(messages=messages)
1173
+
1174
+ @asynccontextmanager
1175
+ async def temporary_state[T](
1176
+ self,
1177
+ *,
1178
+ system_prompts: list[AnyPromptType] | None = None,
1179
+ output_type: type[T] | None = None,
1180
+ replace_prompts: bool = False,
1181
+ tools: list[ToolType] | None = None,
1182
+ replace_tools: bool = False,
1183
+ history: list[AnyPromptType] | SessionQuery | None = None,
1184
+ replace_history: bool = False,
1185
+ pause_routing: bool = False,
1186
+ model: ModelType | None = None,
1187
+ ) -> AsyncIterator[Self | Agent[T]]:
1188
+ """Temporarily modify agent state.
1189
+
1190
+ Args:
1191
+ system_prompts: Temporary system prompts to use
1192
+ output_type: Temporary output type to use
1193
+ replace_prompts: Whether to replace existing prompts
1194
+ tools: Temporary tools to make available
1195
+ replace_tools: Whether to replace existing tools
1196
+ history: Conversation history (prompts or query)
1197
+ replace_history: Whether to replace existing history
1198
+ pause_routing: Whether to pause message routing
1199
+ model: Temporary model override
1200
+ """
1201
+ old_model = self._model
1202
+ if output_type:
1203
+ old_type = self._output_type
1204
+ self.to_structured(output_type)
1205
+ async with AsyncExitStack() as stack:
1206
+ if system_prompts is not None: # System prompts
1207
+ await stack.enter_async_context(
1208
+ self.sys_prompts.temporary_prompt(system_prompts, exclusive=replace_prompts)
1209
+ )
1210
+
1211
+ if tools is not None: # Tools
1212
+ await stack.enter_async_context(
1213
+ self.tools.temporary_tools(tools, exclusive=replace_tools)
1214
+ )
1215
+
1216
+ if history is not None: # History
1217
+ await stack.enter_async_context(
1218
+ self.conversation.temporary_state(history, replace_history=replace_history)
1219
+ )
1220
+
1221
+ if pause_routing: # Routing
1222
+ await stack.enter_async_context(self.connections.paused_routing())
1223
+
1224
+ elif model is not None: # Model
1225
+ self._model = infer_model(model) if isinstance(model, str) else model
1226
+
1227
+ try:
1228
+ yield self
1229
+ finally: # Restore model
1230
+ if model is not None and old_model:
1231
+ self._model = old_model
1232
+ if output_type:
1233
+ self.to_structured(old_type)
1234
+
1235
+ async def validate_against(
1236
+ self,
1237
+ prompt: str,
1238
+ criteria: type[OutputDataT],
1239
+ **kwargs: Any,
1240
+ ) -> bool:
1241
+ """Check if agent's response satisfies stricter criteria."""
1242
+ result = await self.run(prompt, **kwargs)
1243
+ try:
1244
+ criteria.model_validate(result.content.model_dump()) # type: ignore
1245
+ except ValidationError:
1246
+ return False
1247
+ else:
1248
+ return True
1249
+
1250
+
1251
+ if __name__ == "__main__":
1252
+ import logging
1253
+
1254
+ logfire.configure()
1255
+ logfire.instrument_pydantic_ai()
1256
+ logging.basicConfig(handlers=[logfire.LogfireLoggingHandler()])
1257
+ sys_prompt = "Open browser with google,"
1258
+ _model = "openai:gpt-5-nano"
1259
+
1260
+ async def handle_events(ctx: RunContext, event: Any) -> None:
1261
+ print(f"[EVENT] {type(event).__name__}: {event}")
1262
+
1263
+ agent = Agent(model=_model, tools=["webbrowser.open"], event_handlers=[handle_events])
1264
+ result = agent.run.sync(sys_prompt) # type: ignore[attr-defined]