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.
- acp/README.md +64 -0
- acp/__init__.py +172 -0
- acp/__main__.py +10 -0
- acp/acp_requests.py +285 -0
- acp/agent/__init__.py +6 -0
- acp/agent/connection.py +256 -0
- acp/agent/implementations/__init__.py +6 -0
- acp/agent/implementations/debug_server/__init__.py +1 -0
- acp/agent/implementations/debug_server/cli.py +79 -0
- acp/agent/implementations/debug_server/debug.html +234 -0
- acp/agent/implementations/debug_server/debug_server.py +496 -0
- acp/agent/implementations/testing.py +91 -0
- acp/agent/protocol.py +65 -0
- acp/bridge/README.md +162 -0
- acp/bridge/__init__.py +6 -0
- acp/bridge/__main__.py +91 -0
- acp/bridge/bridge.py +246 -0
- acp/bridge/py.typed +0 -0
- acp/bridge/settings.py +15 -0
- acp/client/__init__.py +7 -0
- acp/client/connection.py +251 -0
- acp/client/implementations/__init__.py +7 -0
- acp/client/implementations/default_client.py +185 -0
- acp/client/implementations/headless_client.py +266 -0
- acp/client/implementations/noop_client.py +110 -0
- acp/client/protocol.py +61 -0
- acp/connection.py +280 -0
- acp/exceptions.py +46 -0
- acp/filesystem.py +524 -0
- acp/notifications.py +832 -0
- acp/py.typed +0 -0
- acp/schema/__init__.py +265 -0
- acp/schema/agent_plan.py +30 -0
- acp/schema/agent_requests.py +126 -0
- acp/schema/agent_responses.py +256 -0
- acp/schema/base.py +39 -0
- acp/schema/capabilities.py +230 -0
- acp/schema/client_requests.py +247 -0
- acp/schema/client_responses.py +96 -0
- acp/schema/common.py +81 -0
- acp/schema/content_blocks.py +188 -0
- acp/schema/mcp.py +82 -0
- acp/schema/messages.py +171 -0
- acp/schema/notifications.py +82 -0
- acp/schema/protocol_stuff.md +3 -0
- acp/schema/session_state.py +160 -0
- acp/schema/session_updates.py +419 -0
- acp/schema/slash_commands.py +51 -0
- acp/schema/terminal.py +15 -0
- acp/schema/tool_call.py +347 -0
- acp/stdio.py +250 -0
- acp/task/__init__.py +53 -0
- acp/task/debug.py +197 -0
- acp/task/dispatcher.py +93 -0
- acp/task/queue.py +69 -0
- acp/task/sender.py +82 -0
- acp/task/state.py +87 -0
- acp/task/supervisor.py +93 -0
- acp/terminal_handle.py +30 -0
- acp/tool_call_reporter.py +199 -0
- acp/tool_call_state.py +178 -0
- acp/transports.py +104 -0
- acp/utils.py +240 -0
- agentpool/__init__.py +63 -0
- agentpool/__main__.py +7 -0
- agentpool/agents/__init__.py +30 -0
- agentpool/agents/acp_agent/__init__.py +5 -0
- agentpool/agents/acp_agent/acp_agent.py +837 -0
- agentpool/agents/acp_agent/acp_converters.py +294 -0
- agentpool/agents/acp_agent/client_handler.py +317 -0
- agentpool/agents/acp_agent/session_state.py +44 -0
- agentpool/agents/agent.py +1264 -0
- agentpool/agents/agui_agent/__init__.py +19 -0
- agentpool/agents/agui_agent/agui_agent.py +677 -0
- agentpool/agents/agui_agent/agui_converters.py +423 -0
- agentpool/agents/agui_agent/chunk_transformer.py +204 -0
- agentpool/agents/agui_agent/event_types.py +83 -0
- agentpool/agents/agui_agent/helpers.py +192 -0
- agentpool/agents/architect.py +71 -0
- agentpool/agents/base_agent.py +177 -0
- agentpool/agents/claude_code_agent/__init__.py +11 -0
- agentpool/agents/claude_code_agent/claude_code_agent.py +1021 -0
- agentpool/agents/claude_code_agent/converters.py +243 -0
- agentpool/agents/context.py +105 -0
- agentpool/agents/events/__init__.py +61 -0
- agentpool/agents/events/builtin_handlers.py +129 -0
- agentpool/agents/events/event_emitter.py +320 -0
- agentpool/agents/events/events.py +561 -0
- agentpool/agents/events/tts_handlers.py +186 -0
- agentpool/agents/interactions.py +419 -0
- agentpool/agents/slashed_agent.py +244 -0
- agentpool/agents/sys_prompts.py +178 -0
- agentpool/agents/tool_wrapping.py +184 -0
- agentpool/base_provider.py +28 -0
- agentpool/common_types.py +226 -0
- agentpool/config_resources/__init__.py +16 -0
- agentpool/config_resources/acp_assistant.yml +24 -0
- agentpool/config_resources/agents.yml +109 -0
- agentpool/config_resources/agents_template.yml +18 -0
- agentpool/config_resources/agui_test.yml +18 -0
- agentpool/config_resources/claude_code_agent.yml +16 -0
- agentpool/config_resources/claude_style_subagent.md +30 -0
- agentpool/config_resources/external_acp_agents.yml +77 -0
- agentpool/config_resources/opencode_style_subagent.md +19 -0
- agentpool/config_resources/tts_test_agents.yml +78 -0
- agentpool/delegation/__init__.py +8 -0
- agentpool/delegation/base_team.py +504 -0
- agentpool/delegation/message_flow_tracker.py +39 -0
- agentpool/delegation/pool.py +1129 -0
- agentpool/delegation/team.py +325 -0
- agentpool/delegation/teamrun.py +343 -0
- agentpool/docs/__init__.py +5 -0
- agentpool/docs/gen_examples.py +42 -0
- agentpool/docs/utils.py +370 -0
- agentpool/functional/__init__.py +20 -0
- agentpool/functional/py.typed +0 -0
- agentpool/functional/run.py +80 -0
- agentpool/functional/structure.py +136 -0
- agentpool/hooks/__init__.py +20 -0
- agentpool/hooks/agent_hooks.py +247 -0
- agentpool/hooks/base.py +119 -0
- agentpool/hooks/callable.py +140 -0
- agentpool/hooks/command.py +180 -0
- agentpool/hooks/prompt.py +122 -0
- agentpool/jinja_filters.py +132 -0
- agentpool/log.py +224 -0
- agentpool/mcp_server/__init__.py +17 -0
- agentpool/mcp_server/client.py +429 -0
- agentpool/mcp_server/constants.py +32 -0
- agentpool/mcp_server/conversions.py +172 -0
- agentpool/mcp_server/helpers.py +47 -0
- agentpool/mcp_server/manager.py +232 -0
- agentpool/mcp_server/message_handler.py +164 -0
- agentpool/mcp_server/registries/__init__.py +1 -0
- agentpool/mcp_server/registries/official_registry_client.py +345 -0
- agentpool/mcp_server/registries/pulsemcp_client.py +88 -0
- agentpool/mcp_server/tool_bridge.py +548 -0
- agentpool/messaging/__init__.py +58 -0
- agentpool/messaging/compaction.py +928 -0
- agentpool/messaging/connection_manager.py +319 -0
- agentpool/messaging/context.py +66 -0
- agentpool/messaging/event_manager.py +426 -0
- agentpool/messaging/events.py +39 -0
- agentpool/messaging/message_container.py +209 -0
- agentpool/messaging/message_history.py +491 -0
- agentpool/messaging/messagenode.py +377 -0
- agentpool/messaging/messages.py +655 -0
- agentpool/messaging/processing.py +76 -0
- agentpool/mime_utils.py +95 -0
- agentpool/models/__init__.py +21 -0
- agentpool/models/acp_agents/__init__.py +22 -0
- agentpool/models/acp_agents/base.py +308 -0
- agentpool/models/acp_agents/mcp_capable.py +790 -0
- agentpool/models/acp_agents/non_mcp.py +842 -0
- agentpool/models/agents.py +450 -0
- agentpool/models/agui_agents.py +89 -0
- agentpool/models/claude_code_agents.py +238 -0
- agentpool/models/file_agents.py +116 -0
- agentpool/models/file_parsing.py +367 -0
- agentpool/models/manifest.py +658 -0
- agentpool/observability/__init__.py +9 -0
- agentpool/observability/observability_registry.py +97 -0
- agentpool/prompts/__init__.py +1 -0
- agentpool/prompts/base.py +27 -0
- agentpool/prompts/builtin_provider.py +75 -0
- agentpool/prompts/conversion_manager.py +95 -0
- agentpool/prompts/convert.py +96 -0
- agentpool/prompts/manager.py +204 -0
- agentpool/prompts/parts/zed.md +33 -0
- agentpool/prompts/prompts.py +581 -0
- agentpool/py.typed +0 -0
- agentpool/queries/tree-sitter-language-pack/README.md +7 -0
- agentpool/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- agentpool/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- agentpool/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- agentpool/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
- agentpool/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- agentpool/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- agentpool/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- agentpool/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- agentpool/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- agentpool/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- agentpool/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- agentpool/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- agentpool/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- agentpool/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- agentpool/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- agentpool/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- agentpool/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- agentpool/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
- agentpool/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- agentpool/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- agentpool/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- agentpool/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- agentpool/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- agentpool/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- agentpool/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- agentpool/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- agentpool/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- agentpool/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- agentpool/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- agentpool/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- agentpool/queries/tree-sitter-languages/README.md +24 -0
- agentpool/queries/tree-sitter-languages/c-tags.scm +9 -0
- agentpool/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- agentpool/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- agentpool/queries/tree-sitter-languages/dart-tags.scm +91 -0
- agentpool/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- agentpool/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- agentpool/queries/tree-sitter-languages/elm-tags.scm +19 -0
- agentpool/queries/tree-sitter-languages/fortran-tags.scm +15 -0
- agentpool/queries/tree-sitter-languages/go-tags.scm +30 -0
- agentpool/queries/tree-sitter-languages/haskell-tags.scm +3 -0
- agentpool/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- agentpool/queries/tree-sitter-languages/java-tags.scm +20 -0
- agentpool/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- agentpool/queries/tree-sitter-languages/julia-tags.scm +60 -0
- agentpool/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- agentpool/queries/tree-sitter-languages/matlab-tags.scm +10 -0
- agentpool/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- agentpool/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- agentpool/queries/tree-sitter-languages/php-tags.scm +26 -0
- agentpool/queries/tree-sitter-languages/python-tags.scm +12 -0
- agentpool/queries/tree-sitter-languages/ql-tags.scm +26 -0
- agentpool/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- agentpool/queries/tree-sitter-languages/rust-tags.scm +60 -0
- agentpool/queries/tree-sitter-languages/scala-tags.scm +65 -0
- agentpool/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- agentpool/queries/tree-sitter-languages/zig-tags.scm +3 -0
- agentpool/repomap.py +1231 -0
- agentpool/resource_providers/__init__.py +17 -0
- agentpool/resource_providers/aggregating.py +54 -0
- agentpool/resource_providers/base.py +172 -0
- agentpool/resource_providers/codemode/__init__.py +9 -0
- agentpool/resource_providers/codemode/code_executor.py +215 -0
- agentpool/resource_providers/codemode/default_prompt.py +19 -0
- agentpool/resource_providers/codemode/helpers.py +83 -0
- agentpool/resource_providers/codemode/progress_executor.py +212 -0
- agentpool/resource_providers/codemode/provider.py +150 -0
- agentpool/resource_providers/codemode/remote_mcp_execution.py +143 -0
- agentpool/resource_providers/codemode/remote_provider.py +171 -0
- agentpool/resource_providers/filtering.py +42 -0
- agentpool/resource_providers/mcp_provider.py +246 -0
- agentpool/resource_providers/plan_provider.py +196 -0
- agentpool/resource_providers/pool.py +69 -0
- agentpool/resource_providers/static.py +289 -0
- agentpool/running/__init__.py +20 -0
- agentpool/running/decorators.py +56 -0
- agentpool/running/discovery.py +101 -0
- agentpool/running/executor.py +284 -0
- agentpool/running/injection.py +111 -0
- agentpool/running/py.typed +0 -0
- agentpool/running/run_nodes.py +87 -0
- agentpool/server.py +122 -0
- agentpool/sessions/__init__.py +13 -0
- agentpool/sessions/manager.py +302 -0
- agentpool/sessions/models.py +71 -0
- agentpool/sessions/session.py +239 -0
- agentpool/sessions/store.py +163 -0
- agentpool/skills/__init__.py +5 -0
- agentpool/skills/manager.py +120 -0
- agentpool/skills/registry.py +210 -0
- agentpool/skills/skill.py +36 -0
- agentpool/storage/__init__.py +17 -0
- agentpool/storage/manager.py +419 -0
- agentpool/storage/serialization.py +136 -0
- agentpool/talk/__init__.py +13 -0
- agentpool/talk/registry.py +128 -0
- agentpool/talk/stats.py +159 -0
- agentpool/talk/talk.py +604 -0
- agentpool/tasks/__init__.py +20 -0
- agentpool/tasks/exceptions.py +25 -0
- agentpool/tasks/registry.py +33 -0
- agentpool/testing.py +129 -0
- agentpool/text_templates/__init__.py +39 -0
- agentpool/text_templates/system_prompt.jinja +30 -0
- agentpool/text_templates/tool_call_default.jinja +13 -0
- agentpool/text_templates/tool_call_markdown.jinja +25 -0
- agentpool/text_templates/tool_call_simple.jinja +5 -0
- agentpool/tools/__init__.py +16 -0
- agentpool/tools/base.py +269 -0
- agentpool/tools/exceptions.py +9 -0
- agentpool/tools/manager.py +255 -0
- agentpool/tools/tool_call_info.py +87 -0
- agentpool/ui/__init__.py +2 -0
- agentpool/ui/base.py +89 -0
- agentpool/ui/mock_provider.py +81 -0
- agentpool/ui/stdlib_provider.py +150 -0
- agentpool/utils/__init__.py +44 -0
- agentpool/utils/baseregistry.py +185 -0
- agentpool/utils/count_tokens.py +62 -0
- agentpool/utils/dag.py +184 -0
- agentpool/utils/importing.py +206 -0
- agentpool/utils/inspection.py +334 -0
- agentpool/utils/model_capabilities.py +25 -0
- agentpool/utils/network.py +28 -0
- agentpool/utils/now.py +22 -0
- agentpool/utils/parse_time.py +87 -0
- agentpool/utils/result_utils.py +35 -0
- agentpool/utils/signatures.py +305 -0
- agentpool/utils/streams.py +112 -0
- agentpool/utils/tasks.py +186 -0
- agentpool/vfs_registry.py +250 -0
- agentpool-2.1.9.dist-info/METADATA +336 -0
- agentpool-2.1.9.dist-info/RECORD +474 -0
- agentpool-2.1.9.dist-info/WHEEL +4 -0
- agentpool-2.1.9.dist-info/entry_points.txt +14 -0
- agentpool-2.1.9.dist-info/licenses/LICENSE +22 -0
- agentpool_cli/__init__.py +34 -0
- agentpool_cli/__main__.py +66 -0
- agentpool_cli/agent.py +175 -0
- agentpool_cli/cli_types.py +23 -0
- agentpool_cli/common.py +163 -0
- agentpool_cli/create.py +175 -0
- agentpool_cli/history.py +217 -0
- agentpool_cli/log.py +78 -0
- agentpool_cli/py.typed +0 -0
- agentpool_cli/run.py +84 -0
- agentpool_cli/serve_acp.py +177 -0
- agentpool_cli/serve_api.py +69 -0
- agentpool_cli/serve_mcp.py +74 -0
- agentpool_cli/serve_vercel.py +233 -0
- agentpool_cli/store.py +171 -0
- agentpool_cli/task.py +84 -0
- agentpool_cli/utils.py +104 -0
- agentpool_cli/watch.py +54 -0
- agentpool_commands/__init__.py +180 -0
- agentpool_commands/agents.py +199 -0
- agentpool_commands/base.py +45 -0
- agentpool_commands/commands.py +58 -0
- agentpool_commands/completers.py +110 -0
- agentpool_commands/connections.py +175 -0
- agentpool_commands/markdown_utils.py +31 -0
- agentpool_commands/models.py +62 -0
- agentpool_commands/prompts.py +78 -0
- agentpool_commands/py.typed +0 -0
- agentpool_commands/read.py +77 -0
- agentpool_commands/resources.py +210 -0
- agentpool_commands/session.py +48 -0
- agentpool_commands/tools.py +269 -0
- agentpool_commands/utils.py +189 -0
- agentpool_commands/workers.py +163 -0
- agentpool_config/__init__.py +53 -0
- agentpool_config/builtin_tools.py +265 -0
- agentpool_config/commands.py +237 -0
- agentpool_config/conditions.py +301 -0
- agentpool_config/converters.py +30 -0
- agentpool_config/durable.py +331 -0
- agentpool_config/event_handlers.py +600 -0
- agentpool_config/events.py +153 -0
- agentpool_config/forward_targets.py +251 -0
- agentpool_config/hook_conditions.py +331 -0
- agentpool_config/hooks.py +241 -0
- agentpool_config/jinja.py +206 -0
- agentpool_config/knowledge.py +41 -0
- agentpool_config/loaders.py +350 -0
- agentpool_config/mcp_server.py +243 -0
- agentpool_config/nodes.py +202 -0
- agentpool_config/observability.py +191 -0
- agentpool_config/output_types.py +55 -0
- agentpool_config/pool_server.py +267 -0
- agentpool_config/prompt_hubs.py +105 -0
- agentpool_config/prompts.py +185 -0
- agentpool_config/py.typed +0 -0
- agentpool_config/resources.py +33 -0
- agentpool_config/session.py +119 -0
- agentpool_config/skills.py +17 -0
- agentpool_config/storage.py +288 -0
- agentpool_config/system_prompts.py +190 -0
- agentpool_config/task.py +162 -0
- agentpool_config/teams.py +52 -0
- agentpool_config/tools.py +112 -0
- agentpool_config/toolsets.py +1033 -0
- agentpool_config/workers.py +86 -0
- agentpool_prompts/__init__.py +1 -0
- agentpool_prompts/braintrust_hub.py +235 -0
- agentpool_prompts/fabric.py +75 -0
- agentpool_prompts/langfuse_hub.py +79 -0
- agentpool_prompts/promptlayer_provider.py +59 -0
- agentpool_prompts/py.typed +0 -0
- agentpool_server/__init__.py +9 -0
- agentpool_server/a2a_server/__init__.py +5 -0
- agentpool_server/a2a_server/a2a_types.py +41 -0
- agentpool_server/a2a_server/server.py +190 -0
- agentpool_server/a2a_server/storage.py +81 -0
- agentpool_server/acp_server/__init__.py +22 -0
- agentpool_server/acp_server/acp_agent.py +786 -0
- agentpool_server/acp_server/acp_tools.py +43 -0
- agentpool_server/acp_server/commands/__init__.py +18 -0
- agentpool_server/acp_server/commands/acp_commands.py +594 -0
- agentpool_server/acp_server/commands/debug_commands.py +376 -0
- agentpool_server/acp_server/commands/docs_commands/__init__.py +39 -0
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +169 -0
- agentpool_server/acp_server/commands/docs_commands/get_schema.py +176 -0
- agentpool_server/acp_server/commands/docs_commands/get_source.py +110 -0
- agentpool_server/acp_server/commands/docs_commands/git_diff.py +111 -0
- agentpool_server/acp_server/commands/docs_commands/helpers.py +33 -0
- agentpool_server/acp_server/commands/docs_commands/url_to_markdown.py +90 -0
- agentpool_server/acp_server/commands/spawn.py +210 -0
- agentpool_server/acp_server/converters.py +235 -0
- agentpool_server/acp_server/input_provider.py +338 -0
- agentpool_server/acp_server/server.py +288 -0
- agentpool_server/acp_server/session.py +969 -0
- agentpool_server/acp_server/session_manager.py +313 -0
- agentpool_server/acp_server/syntax_detection.py +250 -0
- agentpool_server/acp_server/zed_tools.md +90 -0
- agentpool_server/aggregating_server.py +309 -0
- agentpool_server/agui_server/__init__.py +11 -0
- agentpool_server/agui_server/server.py +128 -0
- agentpool_server/base.py +189 -0
- agentpool_server/http_server.py +164 -0
- agentpool_server/mcp_server/__init__.py +6 -0
- agentpool_server/mcp_server/server.py +314 -0
- agentpool_server/mcp_server/zed_wrapper.py +110 -0
- agentpool_server/openai_api_server/__init__.py +5 -0
- agentpool_server/openai_api_server/completions/__init__.py +1 -0
- agentpool_server/openai_api_server/completions/helpers.py +81 -0
- agentpool_server/openai_api_server/completions/models.py +98 -0
- agentpool_server/openai_api_server/responses/__init__.py +1 -0
- agentpool_server/openai_api_server/responses/helpers.py +74 -0
- agentpool_server/openai_api_server/responses/models.py +96 -0
- agentpool_server/openai_api_server/server.py +242 -0
- agentpool_server/py.typed +0 -0
- agentpool_storage/__init__.py +9 -0
- agentpool_storage/base.py +310 -0
- agentpool_storage/file_provider.py +378 -0
- agentpool_storage/formatters.py +129 -0
- agentpool_storage/memory_provider.py +396 -0
- agentpool_storage/models.py +108 -0
- agentpool_storage/py.typed +0 -0
- agentpool_storage/session_store.py +262 -0
- agentpool_storage/sql_provider/__init__.py +21 -0
- agentpool_storage/sql_provider/cli.py +146 -0
- agentpool_storage/sql_provider/models.py +249 -0
- agentpool_storage/sql_provider/queries.py +15 -0
- agentpool_storage/sql_provider/sql_provider.py +444 -0
- agentpool_storage/sql_provider/utils.py +234 -0
- agentpool_storage/text_log_provider.py +275 -0
- agentpool_toolsets/__init__.py +15 -0
- agentpool_toolsets/builtin/__init__.py +33 -0
- agentpool_toolsets/builtin/agent_management.py +239 -0
- agentpool_toolsets/builtin/chain.py +288 -0
- agentpool_toolsets/builtin/code.py +398 -0
- agentpool_toolsets/builtin/debug.py +291 -0
- agentpool_toolsets/builtin/execution_environment.py +381 -0
- agentpool_toolsets/builtin/file_edit/__init__.py +11 -0
- agentpool_toolsets/builtin/file_edit/file_edit.py +747 -0
- agentpool_toolsets/builtin/file_edit/fuzzy_matcher/__init__.py +5 -0
- agentpool_toolsets/builtin/file_edit/fuzzy_matcher/example_usage.py +311 -0
- agentpool_toolsets/builtin/file_edit/fuzzy_matcher/streaming_fuzzy_matcher.py +443 -0
- agentpool_toolsets/builtin/history.py +36 -0
- agentpool_toolsets/builtin/integration.py +85 -0
- agentpool_toolsets/builtin/skills.py +77 -0
- agentpool_toolsets/builtin/subagent_tools.py +324 -0
- agentpool_toolsets/builtin/tool_management.py +90 -0
- agentpool_toolsets/builtin/user_interaction.py +52 -0
- agentpool_toolsets/builtin/workers.py +128 -0
- agentpool_toolsets/composio_toolset.py +96 -0
- agentpool_toolsets/config_creation.py +192 -0
- agentpool_toolsets/entry_points.py +47 -0
- agentpool_toolsets/fsspec_toolset/__init__.py +7 -0
- agentpool_toolsets/fsspec_toolset/diagnostics.py +115 -0
- agentpool_toolsets/fsspec_toolset/grep.py +450 -0
- agentpool_toolsets/fsspec_toolset/helpers.py +631 -0
- agentpool_toolsets/fsspec_toolset/streaming_diff_parser.py +249 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +1384 -0
- agentpool_toolsets/mcp_run_toolset.py +61 -0
- agentpool_toolsets/notifications.py +146 -0
- agentpool_toolsets/openapi.py +118 -0
- agentpool_toolsets/py.typed +0 -0
- agentpool_toolsets/search_toolset.py +202 -0
- agentpool_toolsets/semantic_memory_toolset.py +536 -0
- agentpool_toolsets/streaming_tools.py +265 -0
- 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)
|
agentpool/utils/tasks.py
ADDED
|
@@ -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)
|