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,305 @@
1
+ """Signature utils."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from typing import TYPE_CHECKING, Any, get_args, get_origin
7
+
8
+ from agentpool.log import get_logger
9
+
10
+
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Awaitable, Callable
13
+
14
+
15
+ logger = get_logger(__name__)
16
+
17
+
18
+ _CONTEXT_TYPE_NAMES = frozenset({
19
+ "NodeContext",
20
+ "AgentContext",
21
+ "RunContext",
22
+ })
23
+
24
+
25
+ def is_context_type(annotation: Any) -> bool:
26
+ """Check if an annotation is a context type (for auto-injection/hiding from agent).
27
+
28
+ This detects context types that should be hidden from the agent's view of tool
29
+ parameters. Both our own context types (AgentContext/NodeContext) and pydantic-ai's
30
+ RunContext are detected.
31
+
32
+ Uses name-based detection to avoid importing context classes, which would trigger
33
+ Pydantic schema generation for their fields.
34
+
35
+ Handles:
36
+ - Direct AgentContext/NodeContext/RunContext references
37
+ - String annotations (forward references from `from __future__ import annotations`)
38
+ - Generic forms like AgentContext[SomeDeps], RunContext[SomeDeps]
39
+ - Union types containing context types
40
+ """
41
+ if annotation is None or annotation is inspect.Parameter.empty:
42
+ return False
43
+
44
+ # Handle string annotations (forward references from `from __future__ import annotations`)
45
+ # Check if the string matches or starts with a known context type name
46
+ if isinstance(annotation, str):
47
+ # Handle plain names like "AgentContext" and generics like "AgentContext[Deps]"
48
+ base_name = annotation.split("[")[0].strip()
49
+ return base_name in _CONTEXT_TYPE_NAMES
50
+
51
+ # Check direct class match by name
52
+ if isinstance(annotation, type) and annotation.__name__ in _CONTEXT_TYPE_NAMES:
53
+ return True
54
+
55
+ # Check generic origin (e.g., AgentContext[SomeDeps], RunContext[SomeDeps])
56
+ origin = get_origin(annotation)
57
+ if origin is not None:
58
+ if isinstance(origin, type) and origin.__name__ in _CONTEXT_TYPE_NAMES:
59
+ return True
60
+ # Handle Union types
61
+ if origin is type(None) or str(origin) in ("typing.Union", "types.UnionType"):
62
+ args = get_args(annotation)
63
+ return any(is_context_type(arg) for arg in args)
64
+
65
+ return False
66
+
67
+
68
+ def get_params_matching_predicate(
69
+ fn: Callable[..., Any],
70
+ predicate: Callable[[inspect.Parameter], bool],
71
+ ) -> set[str]:
72
+ """Get names of function parameters matching a predicate.
73
+
74
+ Args:
75
+ fn: Function to inspect
76
+ predicate: Function that takes a Parameter and returns True if it matches
77
+
78
+ Returns:
79
+ Set of parameter names that match the predicate
80
+ """
81
+ sig = inspect.signature(fn)
82
+ return {name for name, param in sig.parameters.items() if predicate(param)}
83
+
84
+
85
+ def filter_schema_params(schema: dict[str, Any], params_to_remove: set[str]) -> dict[str, Any]:
86
+ """Filter parameters from a JSON schema.
87
+
88
+ Creates a copy of the schema with specified parameters removed from
89
+ 'properties' and 'required' fields.
90
+
91
+ Args:
92
+ schema: JSON schema with 'properties' dict
93
+ params_to_remove: Set of parameter names to remove
94
+
95
+ Returns:
96
+ New schema dict with parameters filtered out
97
+ """
98
+ if not params_to_remove:
99
+ return schema
100
+
101
+ result = schema.copy()
102
+ if "properties" in result:
103
+ result["properties"] = {
104
+ k: v for k, v in result["properties"].items() if k not in params_to_remove
105
+ }
106
+ if "required" in result:
107
+ result["required"] = [r for r in result["required"] if r not in params_to_remove]
108
+ return result
109
+
110
+
111
+ def create_modified_signature(
112
+ fn_or_sig: Callable[..., Any] | inspect.Signature,
113
+ *,
114
+ remove: str | list[str] | None = None,
115
+ inject: dict[str, type] | None = None,
116
+ ) -> inspect.Signature:
117
+ """Create a modified signature by removing specified parameters / injecting new ones.
118
+
119
+ Args:
120
+ fn_or_sig: The function or signature to modify.
121
+ remove: The parameter(s) to remove.
122
+ inject: The parameter(s) to inject.
123
+
124
+ Returns:
125
+ The modified signature.
126
+ """
127
+ sig = fn_or_sig if isinstance(fn_or_sig, inspect.Signature) else inspect.signature(fn_or_sig)
128
+ rem_keys = [remove] if isinstance(remove, str) else remove or []
129
+ new_params = [p for p in sig.parameters.values() if p.name not in rem_keys]
130
+ if inject:
131
+ injected_params = []
132
+ for k, v in inject.items():
133
+ injected_params.append(
134
+ inspect.Parameter(k, inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=v)
135
+ )
136
+ new_params = injected_params + new_params
137
+ return sig.replace(parameters=new_params)
138
+
139
+
140
+ def modify_signature(
141
+ fn: Callable[..., Any],
142
+ *,
143
+ remove: str | list[str] | None = None,
144
+ inject: dict[str, type] | None = None,
145
+ ) -> None:
146
+ new_sig = create_modified_signature(fn, remove=remove, inject=inject)
147
+ update_signature(fn, new_sig)
148
+
149
+
150
+ def update_signature(fn: Callable[..., Any], signature: inspect.Signature) -> None:
151
+ """Update function signature and annotations.
152
+
153
+ Note: Setting __annotations__ destroys __annotate__ in Python 3.14+ (PEP 649).
154
+ Callers using functools.wraps should restore __annotations__ from the original
155
+ function after calling this function.
156
+ """
157
+ fn.__signature__ = signature # type: ignore
158
+ fn.__annotations__ = {
159
+ name: param.annotation for name, param in signature.parameters.items()
160
+ } | {"return": signature.return_annotation}
161
+
162
+
163
+ def create_bound_callable( # noqa: PLR0915
164
+ original_callable: Callable[..., Any],
165
+ by_name: dict[str, Any] | None = None,
166
+ by_type: dict[type, Any] | None = None,
167
+ bind_kwargs: bool = False,
168
+ ) -> Callable[..., Awaitable[Any]]:
169
+ """Create a wrapper that pre-binds parameters by name or type.
170
+
171
+ Parameters are bound by their position in the function signature. Only
172
+ positional and positional-or-keyword parameters can be bound by default.
173
+ If bind_kwargs=True, keyword-only parameters can also be bound using the
174
+ same by_name/by_type logic. Binding by name takes priority over binding by type.
175
+
176
+ Args:
177
+ original_callable: The original callable that may need parameter binding
178
+ by_name: Parameters to bind by exact parameter name
179
+ by_type: Parameters to bind by parameter type annotation
180
+ bind_kwargs: Whether to also bind keyword-only parameters
181
+
182
+ Returns:
183
+ New callable with parameters pre-bound and proper introspection
184
+
185
+ Raises:
186
+ ValueError: If the callable's signature cannot be inspected
187
+ """
188
+ try:
189
+ sig = inspect.signature(original_callable)
190
+ except (ValueError, TypeError) as e:
191
+ msg = f"Cannot inspect signature of {original_callable}. Ensure callable is inspectable."
192
+ raise ValueError(msg) from e
193
+
194
+ # Build position-to-value mapping for positional binding
195
+ context_values = {}
196
+ # Build name-to-value mapping for keyword-only binding
197
+ kwarg_bindings = {}
198
+
199
+ for i, param in enumerate(sig.parameters.values()):
200
+ # Bind positional and positional-or-keyword parameters
201
+ if param.kind in (
202
+ inspect.Parameter.POSITIONAL_ONLY,
203
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
204
+ ):
205
+ # Bind by name first (higher priority)
206
+ if by_name and param.name in by_name:
207
+ context_values[i] = by_name[param.name]
208
+ # Then bind by type if not already bound
209
+ elif by_type and _find_matching_type(param.annotation, by_type) is not None:
210
+ context_values[i] = _find_matching_type(param.annotation, by_type)
211
+ # Bind keyword-only parameters if enabled
212
+ elif bind_kwargs and param.kind == inspect.Parameter.KEYWORD_ONLY:
213
+ # Bind by name first (higher priority)
214
+ if by_name and param.name in by_name:
215
+ kwarg_bindings[param.name] = by_name[param.name]
216
+ # Then bind by type if not already bound
217
+ elif by_type and _find_matching_type(param.annotation, by_type) is not None:
218
+ kwarg_bindings[param.name] = _find_matching_type(param.annotation, by_type)
219
+
220
+ async def wrapper(*args: Any, **kwargs: Any) -> Any:
221
+ # Filter out kwargs that would conflict with bound parameters
222
+ param_names = list(sig.parameters.keys())
223
+ bound_param_names = {param_names[i] for i in context_values}
224
+ bound_kwarg_names = set(kwarg_bindings.keys())
225
+ filtered_kwargs = {
226
+ k: v
227
+ for k, v in kwargs.items()
228
+ if k not in bound_param_names and k not in bound_kwarg_names
229
+ }
230
+
231
+ # Add bound keyword-only parameters
232
+ filtered_kwargs.update(kwarg_bindings)
233
+
234
+ # Build new_args with context values at correct positions
235
+ new_args = []
236
+ arg_index = 0
237
+ for param_index in range(len(sig.parameters)):
238
+ if param_index in context_values:
239
+ new_args.append(context_values[param_index])
240
+ elif arg_index < len(args):
241
+ new_args.append(args[arg_index])
242
+ arg_index += 1
243
+
244
+ # Add any remaining positional args
245
+ if arg_index < len(args):
246
+ new_args.extend(args[arg_index:])
247
+
248
+ if inspect.iscoroutinefunction(original_callable):
249
+ return await original_callable(*new_args, **filtered_kwargs)
250
+ return original_callable(*new_args, **filtered_kwargs)
251
+
252
+ # Preserve introspection attributes
253
+ wrapper.__name__ = getattr(original_callable, "__name__", "wrapper")
254
+ wrapper.__doc__ = getattr(original_callable, "__doc__", None)
255
+ wrapper.__module__ = getattr(original_callable, "__module__", None) # type: ignore[assignment]
256
+ wrapper.__wrapped__ = original_callable # type: ignore[attr-defined]
257
+ wrapper.__agentpool_wrapped__ = original_callable # type: ignore[attr-defined]
258
+
259
+ # Create modified signature without context parameters
260
+ try:
261
+ params = list(sig.parameters.values())
262
+ # Remove parameters at context positions and bound kwargs
263
+ context_positions = set(context_values.keys())
264
+ bound_kwarg_names = set(kwarg_bindings.keys())
265
+ new_params = [
266
+ param
267
+ for i, param in enumerate(params)
268
+ if i not in context_positions and param.name not in bound_kwarg_names
269
+ ]
270
+ new_sig = sig.replace(parameters=new_params)
271
+ wrapper.__signature__ = new_sig # type: ignore[attr-defined]
272
+ wrapper.__annotations__ = {
273
+ name: param.annotation for name, param in new_sig.parameters.items()
274
+ }
275
+ if sig.return_annotation != inspect.Signature.empty:
276
+ wrapper.__annotations__["return"] = sig.return_annotation
277
+
278
+ except (ValueError, TypeError):
279
+ logger.debug("Failed to update wrapper signature", original=original_callable)
280
+
281
+ return wrapper
282
+
283
+
284
+ def _find_matching_type(param_annotation: Any, by_type: dict[type, Any]) -> Any | None:
285
+ """Find a matching type binding for the given parameter annotation.
286
+
287
+ Supports exact matching and generic origin matching.
288
+
289
+ Args:
290
+ param_annotation: The parameter's type annotation
291
+ by_type: Dictionary of type bindings
292
+
293
+ Returns:
294
+ The bound value if a match is found, None otherwise
295
+ """
296
+ # First try exact match
297
+ if param_annotation in by_type:
298
+ return by_type[param_annotation]
299
+
300
+ # Then try origin type matching for generics
301
+ param_origin = get_origin(param_annotation)
302
+ if param_origin is not None and param_origin in by_type:
303
+ return by_type[param_origin]
304
+
305
+ return None
@@ -0,0 +1,112 @@
1
+ """Stream utilities for merging async iterators."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from contextlib import asynccontextmanager
7
+ from typing import TYPE_CHECKING
8
+
9
+
10
+ if TYPE_CHECKING:
11
+ from collections.abc import AsyncIterator
12
+
13
+
14
+ @asynccontextmanager
15
+ async def merge_queue_into_iterator[T, V]( # noqa: PLR0915
16
+ primary_stream: AsyncIterator[T],
17
+ secondary_queue: asyncio.Queue[V],
18
+ ) -> AsyncIterator[AsyncIterator[T | V]]:
19
+ """Merge a primary async stream with events from a secondary queue.
20
+
21
+ Args:
22
+ primary_stream: The main async iterator (e.g., provider events)
23
+ secondary_queue: Queue containing secondary events (e.g., progress events)
24
+
25
+ Yields:
26
+ Async iterator that yields events from both sources in real-time.
27
+ Secondary queue is fully drained before the iterator completes.
28
+
29
+ Example:
30
+ ```python
31
+ progress_queue: asyncio.Queue[ProgressEvent] = asyncio.Queue()
32
+
33
+ async with merge_queue_into_iterator(provider_stream, progress_queue) as events:
34
+ async for event in events:
35
+ print(f"Got event: {event}")
36
+ ```
37
+ """
38
+ # Create a queue for all merged events
39
+ event_queue: asyncio.Queue[V | T | None] = asyncio.Queue()
40
+ primary_done = asyncio.Event()
41
+ primary_exception: BaseException | None = None
42
+ # Track if we've signaled the end of streams
43
+ end_signaled = False
44
+
45
+ # Task to read from primary stream and put into merged queue
46
+ async def primary_task() -> None:
47
+ nonlocal primary_exception, end_signaled
48
+ try:
49
+ async for event in primary_stream:
50
+ await event_queue.put(event)
51
+ except asyncio.CancelledError:
52
+ # Signal completion and unblock merged_events before re-raising
53
+ primary_done.set()
54
+ if not end_signaled:
55
+ end_signaled = True
56
+ await event_queue.put(None)
57
+ raise
58
+ except BaseException as e: # noqa: BLE001
59
+ primary_exception = e
60
+ finally:
61
+ primary_done.set()
62
+
63
+ # Task to read from secondary queue and put into merged queue
64
+ async def secondary_task() -> None:
65
+ nonlocal end_signaled
66
+ try:
67
+ while not primary_done.is_set():
68
+ try:
69
+ secondary_event = await asyncio.wait_for(secondary_queue.get(), timeout=0.1)
70
+ await event_queue.put(secondary_event)
71
+ except TimeoutError:
72
+ continue
73
+ # Drain any remaining events after primary completes
74
+ while not secondary_queue.empty():
75
+ try:
76
+ secondary_event = secondary_queue.get_nowait()
77
+ await event_queue.put(secondary_event)
78
+ except asyncio.QueueEmpty:
79
+ break
80
+ # Now signal end of all events (only if not already signaled)
81
+ if not end_signaled:
82
+ end_signaled = True
83
+ await event_queue.put(None)
84
+ except asyncio.CancelledError:
85
+ # Still need to signal completion on cancel (only if not already signaled)
86
+ if not end_signaled:
87
+ end_signaled = True
88
+ await event_queue.put(None)
89
+
90
+ # Start both tasks
91
+ primary_task_obj = asyncio.create_task(primary_task())
92
+ secondary_task_obj = asyncio.create_task(secondary_task())
93
+
94
+ try:
95
+ # Create async iterator that drains the merged queue
96
+ async def merged_events() -> AsyncIterator[V | T]:
97
+ while True:
98
+ event = await event_queue.get()
99
+ if event is None: # End of all streams
100
+ break
101
+ yield event
102
+ # Re-raise any exception from primary stream after draining
103
+ if primary_exception is not None:
104
+ raise primary_exception
105
+
106
+ yield merged_events()
107
+
108
+ finally:
109
+ # Clean up tasks - cancel BOTH tasks
110
+ primary_task_obj.cancel()
111
+ secondary_task_obj.cancel()
112
+ await asyncio.gather(primary_task_obj, secondary_task_obj, return_exceptions=True)
@@ -0,0 +1,186 @@
1
+ """Task management mixin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from dataclasses import dataclass, field
7
+ import heapq
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ import anyio
11
+
12
+ from agentpool.log import get_logger
13
+ from agentpool.utils.inspection import get_fn_name
14
+ from agentpool.utils.now import get_now
15
+
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Coroutine
19
+ from datetime import datetime, timedelta
20
+
21
+
22
+ logger = get_logger(__name__)
23
+
24
+
25
+ @dataclass(order=True)
26
+ class PrioritizedTask:
27
+ """Task with priority and optional delay."""
28
+
29
+ priority: int
30
+ execute_at: datetime
31
+ coroutine: Coroutine[Any, Any, Any] = field(compare=False)
32
+ name: str | None = field(default=None, compare=False)
33
+
34
+
35
+ class TaskManager:
36
+ """Mixin for managing async tasks.
37
+
38
+ Provides utilities for:
39
+ - Creating and tracking tasks
40
+ - Fire-and-forget task execution
41
+ - Running coroutines in sync context
42
+ - Cleanup of pending tasks
43
+ """
44
+
45
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
46
+ self._pending_tasks: set[asyncio.Task[Any]] = set()
47
+ self._task_queue: list[PrioritizedTask] = [] # heap queue
48
+ self._scheduler_task: asyncio.Task[Any] | None = None
49
+
50
+ def create_task[T](
51
+ self,
52
+ coro: Coroutine[Any, Any, T],
53
+ *,
54
+ name: str | None = None,
55
+ priority: int = 0,
56
+ delay: timedelta | None = None,
57
+ ) -> asyncio.Task[T]:
58
+ """Create and track a new task with optional priority and delay.
59
+
60
+ Args:
61
+ coro: Coroutine to run
62
+ name: Optional name for the task (defaults to coroutine function name)
63
+ priority: Priority (lower = higher priority, default 0)
64
+ delay: Optional delay before execution
65
+ """
66
+ task_name = name or get_fn_name(coro)
67
+ task = asyncio.create_task(coro, name=task_name)
68
+ logger.debug("Created task", name=task.get_name(), priority=priority, delay=delay)
69
+
70
+ def _done_callback(t: asyncio.Task[Any]) -> None:
71
+ logger.debug("Task completed", name=t.get_name())
72
+ self._pending_tasks.discard(t)
73
+ if t.exception():
74
+ logger.error("Task failed", error=t.exception(), name=t.get_name())
75
+
76
+ task.add_done_callback(_done_callback)
77
+ self._pending_tasks.add(task)
78
+
79
+ if delay is not None:
80
+ execute_at = get_now() + delay
81
+ prio_task = PrioritizedTask(priority, execute_at, coro, name)
82
+ heapq.heappush(self._task_queue, prio_task) # Store the coro instead of task
83
+ if not self._scheduler_task: # Start scheduler if not running
84
+ self._scheduler_task = asyncio.create_task(self._run_scheduler())
85
+ task.cancel() # Cancel the original task since we'll run it later
86
+ return task
87
+
88
+ return task
89
+
90
+ async def _run_scheduler(self) -> None:
91
+ """Run scheduled tasks when their time comes."""
92
+ try:
93
+ while self._task_queue:
94
+ # Get next task without removing
95
+ next_task = self._task_queue[0]
96
+ now = get_now()
97
+
98
+ if now >= next_task.execute_at:
99
+ # Remove and execute
100
+ heapq.heappop(self._task_queue)
101
+ # Create new task from stored coroutine
102
+ new_task = asyncio.create_task(
103
+ next_task.coroutine,
104
+ name=next_task.name,
105
+ )
106
+ self._pending_tasks.add(new_task)
107
+ new_task.add_done_callback(self._pending_tasks.discard)
108
+ else:
109
+ # Wait until next task is due
110
+ await anyio.sleep((next_task.execute_at - now).total_seconds())
111
+
112
+ except Exception:
113
+ logger.exception("Task scheduler error")
114
+ finally:
115
+ self._scheduler_task = None
116
+
117
+ def fire_and_forget(self, coro: Coroutine[Any, Any, Any]) -> None:
118
+ """Run coroutine without waiting for result."""
119
+ try:
120
+ loop = asyncio.get_running_loop()
121
+ task = loop.create_task(coro)
122
+ self._pending_tasks.add(task)
123
+ task.add_done_callback(self._pending_tasks.discard)
124
+ except RuntimeError:
125
+ # No running loop - use new loop
126
+ loop = asyncio.new_event_loop()
127
+ try:
128
+ loop.run_until_complete(coro)
129
+ finally:
130
+ loop.close()
131
+
132
+ def run_task_sync[T](self, coro: Coroutine[Any, Any, T]) -> T:
133
+ """Run coroutine synchronously."""
134
+ try:
135
+ loop = asyncio.get_running_loop()
136
+ if loop.is_running():
137
+ # Running loop - use thread pool
138
+ import concurrent.futures
139
+
140
+ msg = "Running coroutine in Executor due to active event loop"
141
+ logger.debug(msg, name=coro.__name__)
142
+ with concurrent.futures.ThreadPoolExecutor() as pool:
143
+ future = pool.submit(lambda: asyncio.run(coro))
144
+ return future.result()
145
+
146
+ # Existing but not running loop - use task tracking
147
+ task = loop.create_task(coro)
148
+ self._pending_tasks.add(task)
149
+ task.add_done_callback(self._pending_tasks.discard)
150
+ return loop.run_until_complete(task)
151
+ except RuntimeError:
152
+ # No loop - create new one
153
+ return asyncio.run(coro)
154
+
155
+ def run_background(
156
+ self,
157
+ coro: Coroutine[Any, Any, Any],
158
+ name: str | None = None,
159
+ priority: int = 0,
160
+ delay: timedelta | None = None,
161
+ ) -> None:
162
+ """Run a coroutine in the background and track it."""
163
+ try:
164
+ self.create_task(coro, name=name, priority=priority, delay=delay)
165
+
166
+ except RuntimeError:
167
+ # No running loop - use fire_and_forget
168
+ self.fire_and_forget(coro)
169
+
170
+ def is_busy(self) -> bool:
171
+ """Check if we have any tasks pending."""
172
+ return bool(self._pending_tasks)
173
+
174
+ async def cleanup_tasks(self) -> None:
175
+ """Wait for all pending tasks to complete."""
176
+ if self._pending_tasks:
177
+ await asyncio.gather(*self._pending_tasks, return_exceptions=True)
178
+ self._pending_tasks.clear()
179
+
180
+ async def complete_tasks(self, cancel: bool = False) -> None:
181
+ """Wait for all pending tasks to complete."""
182
+ if cancel:
183
+ for task in self._pending_tasks:
184
+ task.cancel()
185
+ if self._pending_tasks:
186
+ await asyncio.wait(self._pending_tasks)