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,244 @@
|
|
|
1
|
+
"""Slash command wrapper for Agent that injects command events into streams."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import re
|
|
7
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
8
|
+
|
|
9
|
+
import anyio
|
|
10
|
+
from slashed.events import (
|
|
11
|
+
CommandExecutedEvent,
|
|
12
|
+
CommandOutputEvent as SlashedCommandOutputEvent,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from agentpool.agents.events import CommandCompleteEvent, CommandOutputEvent
|
|
16
|
+
from agentpool.log import get_logger
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from collections.abc import AsyncGenerator, Callable
|
|
21
|
+
|
|
22
|
+
from slashed import CommandContext, CommandStore
|
|
23
|
+
from slashed.events import CommandStoreEvent
|
|
24
|
+
|
|
25
|
+
from agentpool.agents.base_agent import BaseAgent
|
|
26
|
+
from agentpool.agents.events import SlashedAgentStreamEvent
|
|
27
|
+
from agentpool.common_types import PromptCompatible
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
logger = get_logger(__name__)
|
|
31
|
+
SLASH_PATTERN = re.compile(r"^/([\w-]+)(?:\s+(.*))?$")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _parse_slash_command(command_text: str) -> tuple[str, str] | None:
|
|
35
|
+
"""Parse slash command into name and args.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
command_text: Full command text
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Tuple of (cmd_name, args) or None if invalid
|
|
42
|
+
"""
|
|
43
|
+
if match := SLASH_PATTERN.match(command_text.strip()):
|
|
44
|
+
cmd_name = match.group(1)
|
|
45
|
+
args = match.group(2) or ""
|
|
46
|
+
return cmd_name, args.strip()
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SlashedAgent[TDeps, OutputDataT]:
|
|
51
|
+
"""Wrapper around Agent that handles slash commands in streams.
|
|
52
|
+
|
|
53
|
+
Uses the "commands first" strategy from the ACP adapter:
|
|
54
|
+
1. Execute all slash commands first
|
|
55
|
+
2. Then process remaining content through wrapped agent
|
|
56
|
+
3. If only commands, end without LLM processing
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
agent: BaseAgent[TDeps, OutputDataT],
|
|
62
|
+
command_store: CommandStore | None = None,
|
|
63
|
+
*,
|
|
64
|
+
context_data_factory: Callable[[], Any] | None = None,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Initialize with wrapped agent and command store.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
agent: The agent to wrap
|
|
70
|
+
command_store: Command store for slash commands (creates default if None)
|
|
71
|
+
context_data_factory: Optional factory for creating command context data
|
|
72
|
+
"""
|
|
73
|
+
self.agent = agent
|
|
74
|
+
self._context_data_factory = context_data_factory
|
|
75
|
+
self._event_queue: asyncio.Queue[CommandStoreEvent] | None = None
|
|
76
|
+
|
|
77
|
+
# Create store with our streaming event handler
|
|
78
|
+
if command_store is None:
|
|
79
|
+
from slashed import CommandStore
|
|
80
|
+
|
|
81
|
+
from agentpool_commands import get_commands
|
|
82
|
+
|
|
83
|
+
cmds = get_commands()
|
|
84
|
+
self.command_store = CommandStore(event_handler=self._emit_event, commands=cmds)
|
|
85
|
+
else:
|
|
86
|
+
self.command_store = command_store
|
|
87
|
+
|
|
88
|
+
async def _emit_event(self, event: CommandStoreEvent) -> None:
|
|
89
|
+
"""Bridge store events to async queue during command execution."""
|
|
90
|
+
if self._event_queue:
|
|
91
|
+
await self._event_queue.put(event)
|
|
92
|
+
|
|
93
|
+
def _is_slash_command(self, text: str) -> bool:
|
|
94
|
+
"""Check if text starts with a slash command.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
text: Text to check
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
True if text is a slash command
|
|
101
|
+
"""
|
|
102
|
+
return bool(SLASH_PATTERN.match(text.strip()))
|
|
103
|
+
|
|
104
|
+
async def _execute_slash_command_streaming(
|
|
105
|
+
self, command_text: str
|
|
106
|
+
) -> AsyncGenerator[CommandOutputEvent | CommandCompleteEvent]:
|
|
107
|
+
"""Execute a single slash command and yield events as they happen.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
command_text: Full command text including slash
|
|
111
|
+
|
|
112
|
+
Yields:
|
|
113
|
+
Command output and completion events
|
|
114
|
+
"""
|
|
115
|
+
parsed = _parse_slash_command(command_text)
|
|
116
|
+
if not parsed:
|
|
117
|
+
logger.warning("Invalid slash command", command=command_text)
|
|
118
|
+
yield CommandCompleteEvent(command="unknown", success=False)
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
cmd_name, args = parsed
|
|
122
|
+
|
|
123
|
+
# Set up event queue for this command execution
|
|
124
|
+
self._event_queue = asyncio.Queue()
|
|
125
|
+
context_data = ( # Create command context
|
|
126
|
+
self._context_data_factory() if self._context_data_factory else self.agent.get_context()
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
cmd_ctx = self.command_store.create_context(data=context_data)
|
|
130
|
+
command_str = f"{cmd_name} {args}".strip()
|
|
131
|
+
execute_task = asyncio.create_task(self.command_store.execute_command(command_str, cmd_ctx))
|
|
132
|
+
|
|
133
|
+
success = True
|
|
134
|
+
try:
|
|
135
|
+
# Yield events from queue as command runs
|
|
136
|
+
while not execute_task.done():
|
|
137
|
+
try:
|
|
138
|
+
# Wait for events with short timeout to check task completion
|
|
139
|
+
event = await asyncio.wait_for(self._event_queue.get(), timeout=0.1)
|
|
140
|
+
# Convert store events to our stream events
|
|
141
|
+
match event:
|
|
142
|
+
case SlashedCommandOutputEvent(output=output):
|
|
143
|
+
yield CommandOutputEvent(command=cmd_name, output=output)
|
|
144
|
+
case CommandExecutedEvent(success=False, error=error) if error:
|
|
145
|
+
output = f"Command error: {error}"
|
|
146
|
+
yield CommandOutputEvent(command=cmd_name, output=output)
|
|
147
|
+
success = False
|
|
148
|
+
except TimeoutError:
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
# Ensure command task completes and handle any remaining events
|
|
152
|
+
try:
|
|
153
|
+
await execute_task
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.exception("Command execution failed", command=cmd_name)
|
|
156
|
+
success = False
|
|
157
|
+
yield CommandOutputEvent(command=cmd_name, output=f"Command error: {e}")
|
|
158
|
+
|
|
159
|
+
# Drain any remaining events from queue
|
|
160
|
+
while not self._event_queue.empty():
|
|
161
|
+
try:
|
|
162
|
+
match self._event_queue.get_nowait():
|
|
163
|
+
case SlashedCommandOutputEvent(output=output):
|
|
164
|
+
yield CommandOutputEvent(command=cmd_name, output=output)
|
|
165
|
+
except asyncio.QueueEmpty:
|
|
166
|
+
break
|
|
167
|
+
|
|
168
|
+
# Always yield completion event
|
|
169
|
+
yield CommandCompleteEvent(command=cmd_name, success=success)
|
|
170
|
+
|
|
171
|
+
finally:
|
|
172
|
+
# Clean up event queue
|
|
173
|
+
self._event_queue = None
|
|
174
|
+
|
|
175
|
+
async def run_stream(
|
|
176
|
+
self,
|
|
177
|
+
*prompts: PromptCompatible,
|
|
178
|
+
**kwargs: Any,
|
|
179
|
+
) -> AsyncGenerator[SlashedAgentStreamEvent[OutputDataT]]:
|
|
180
|
+
"""Run agent with slash command support.
|
|
181
|
+
|
|
182
|
+
Separates slash commands from regular prompts, executes commands first,
|
|
183
|
+
then processes remaining content through the wrapped agent.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
*prompts: Input prompts (may include slash commands)
|
|
187
|
+
**kwargs: Additional arguments passed to agent.run_stream
|
|
188
|
+
|
|
189
|
+
Yields:
|
|
190
|
+
Stream events from command execution and agent processing
|
|
191
|
+
"""
|
|
192
|
+
# Separate slash commands from regular content
|
|
193
|
+
commands: list[str] = []
|
|
194
|
+
regular_prompts: list[Any] = []
|
|
195
|
+
|
|
196
|
+
for prompt in prompts:
|
|
197
|
+
if isinstance(prompt, str) and self._is_slash_command(prompt):
|
|
198
|
+
logger.debug("Found slash command", command=prompt)
|
|
199
|
+
commands.append(prompt.strip())
|
|
200
|
+
else:
|
|
201
|
+
regular_prompts.append(prompt)
|
|
202
|
+
|
|
203
|
+
# Execute all commands first with streaming
|
|
204
|
+
if commands:
|
|
205
|
+
for command in commands:
|
|
206
|
+
logger.info("Processing slash command", command=command)
|
|
207
|
+
async for cmd_event in self._execute_slash_command_streaming(command):
|
|
208
|
+
yield cmd_event
|
|
209
|
+
|
|
210
|
+
# If we have regular content, process it through the agent
|
|
211
|
+
if regular_prompts:
|
|
212
|
+
logger.debug("Processing prompts through agent", num_prompts=len(regular_prompts))
|
|
213
|
+
async for event in self.agent.run_stream(*regular_prompts, **kwargs):
|
|
214
|
+
# ACPAgent always returns str, cast to match OutputDataT
|
|
215
|
+
yield cast("SlashedAgentStreamEvent[OutputDataT]", event)
|
|
216
|
+
|
|
217
|
+
# If we only had commands and no regular content, we're done
|
|
218
|
+
# (no additional events needed)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
if __name__ == "__main__":
|
|
222
|
+
import asyncio
|
|
223
|
+
|
|
224
|
+
from agentpool import Agent
|
|
225
|
+
|
|
226
|
+
async def main() -> None:
|
|
227
|
+
agent = Agent("test-agent", model="test", session=False)
|
|
228
|
+
slashed = SlashedAgent(agent) # Uses built-in commands by default
|
|
229
|
+
|
|
230
|
+
# Add a simple test command that outputs multiple lines
|
|
231
|
+
@slashed.command_store.command(name="test-streaming", category="test")
|
|
232
|
+
async def test_streaming(ctx: CommandContext[Any], *args: Any, **kwargs: Any) -> None:
|
|
233
|
+
"""Test command that outputs multiple lines."""
|
|
234
|
+
await ctx.print("Starting streaming test...")
|
|
235
|
+
for i in range(3):
|
|
236
|
+
await ctx.print(f"Output line {i + 1}")
|
|
237
|
+
await anyio.sleep(0.1) # Small delay to simulate work
|
|
238
|
+
await ctx.print("Streaming test complete!")
|
|
239
|
+
|
|
240
|
+
print("Testing SlashedAgent streaming:")
|
|
241
|
+
async for event in slashed.run_stream("/test-streaming"):
|
|
242
|
+
print(f"Event: {event}")
|
|
243
|
+
|
|
244
|
+
anyio.run(main)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""System prompt management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from contextlib import asynccontextmanager
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
7
|
+
|
|
8
|
+
from agentpool import text_templates
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import AsyncIterator
|
|
13
|
+
|
|
14
|
+
from toprompt import AnyPromptType
|
|
15
|
+
|
|
16
|
+
from agentpool.agents.base_agent import BaseAgent
|
|
17
|
+
from agentpool.prompts.manager import PromptManager
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
ToolInjectionMode = Literal["off", "all"]
|
|
21
|
+
ToolUsageStyle = Literal["suggestive", "strict"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SystemPrompts:
|
|
25
|
+
"""Manages system prompts for an agent."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
prompts: AnyPromptType | list[AnyPromptType] | None = None,
|
|
30
|
+
template: str | None = None,
|
|
31
|
+
dynamic: bool = True,
|
|
32
|
+
prompt_manager: PromptManager | None = None,
|
|
33
|
+
inject_agent_info: bool = True,
|
|
34
|
+
inject_tools: ToolInjectionMode = "off",
|
|
35
|
+
tool_usage_style: ToolUsageStyle = "suggestive",
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Initialize prompt manager."""
|
|
38
|
+
from jinjarope import Environment
|
|
39
|
+
from toprompt import to_prompt
|
|
40
|
+
|
|
41
|
+
match prompts:
|
|
42
|
+
case list():
|
|
43
|
+
self.prompts = prompts
|
|
44
|
+
case None:
|
|
45
|
+
self.prompts = []
|
|
46
|
+
case _:
|
|
47
|
+
self.prompts = [prompts]
|
|
48
|
+
self.prompt_manager = prompt_manager
|
|
49
|
+
self.template = template
|
|
50
|
+
self.dynamic = dynamic
|
|
51
|
+
self.inject_agent_info = inject_agent_info
|
|
52
|
+
self.inject_tools = inject_tools
|
|
53
|
+
self.tool_usage_style = tool_usage_style
|
|
54
|
+
self._cached = False
|
|
55
|
+
self._env = Environment(enable_async=True)
|
|
56
|
+
self._env.filters["to_prompt"] = to_prompt
|
|
57
|
+
|
|
58
|
+
def __repr__(self) -> str:
|
|
59
|
+
return (
|
|
60
|
+
f"SystemPrompts(prompts={len(self.prompts)}, "
|
|
61
|
+
f"dynamic={self.dynamic}, inject_agent_info={self.inject_agent_info}, "
|
|
62
|
+
f"inject_tools={self.inject_tools!r})"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def __len__(self) -> int:
|
|
66
|
+
return len(self.prompts)
|
|
67
|
+
|
|
68
|
+
def __getitem__(self, idx: int | slice) -> AnyPromptType | list[AnyPromptType]:
|
|
69
|
+
return self.prompts[idx]
|
|
70
|
+
|
|
71
|
+
async def add_by_reference(self, reference: str) -> None:
|
|
72
|
+
"""Add a system prompt using reference syntax.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
reference: [provider:]identifier[@version][?var1=val1,...]
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
await sys_prompts.add_by_reference("code_review?language=python")
|
|
79
|
+
await sys_prompts.add_by_reference("langfuse:expert@v2")
|
|
80
|
+
"""
|
|
81
|
+
if not self.prompt_manager:
|
|
82
|
+
msg = "No prompt_manager available to resolve prompts"
|
|
83
|
+
raise RuntimeError(msg)
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
content = await self.prompt_manager.get(reference)
|
|
87
|
+
self.prompts.append(content)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
msg = f"Failed to add prompt {reference!r}"
|
|
90
|
+
raise RuntimeError(msg) from e
|
|
91
|
+
|
|
92
|
+
async def add(
|
|
93
|
+
self,
|
|
94
|
+
identifier: str,
|
|
95
|
+
*,
|
|
96
|
+
provider: str | None = None,
|
|
97
|
+
version: str | None = None,
|
|
98
|
+
variables: dict[str, Any] | None = None,
|
|
99
|
+
) -> None:
|
|
100
|
+
"""Add a system prompt.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
identifier: Prompt identifier/name
|
|
104
|
+
provider: Provider name (None = builtin)
|
|
105
|
+
version: Optional version string
|
|
106
|
+
variables: Optional template variables
|
|
107
|
+
|
|
108
|
+
Examples:
|
|
109
|
+
await sys_prompts.add("code_review", variables={"language": "python"})
|
|
110
|
+
await sys_prompts.add("expert", provider="langfuse", version="v2")
|
|
111
|
+
"""
|
|
112
|
+
if not self.prompt_manager:
|
|
113
|
+
msg = "No prompt_manager available to resolve prompts"
|
|
114
|
+
raise RuntimeError(msg)
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
content = await self.prompt_manager.get_from(
|
|
118
|
+
identifier,
|
|
119
|
+
provider=provider,
|
|
120
|
+
version=version,
|
|
121
|
+
variables=variables,
|
|
122
|
+
)
|
|
123
|
+
self.prompts.append(content)
|
|
124
|
+
except Exception as e:
|
|
125
|
+
ref = f"{provider + ':' if provider else ''}{identifier}"
|
|
126
|
+
msg = f"Failed to add prompt {ref!r}"
|
|
127
|
+
raise RuntimeError(msg) from e
|
|
128
|
+
|
|
129
|
+
def clear(self) -> None:
|
|
130
|
+
"""Clear all system prompts."""
|
|
131
|
+
self.prompts = []
|
|
132
|
+
|
|
133
|
+
async def refresh_cache(self) -> None:
|
|
134
|
+
"""Force re-evaluation of prompts."""
|
|
135
|
+
from toprompt import to_prompt
|
|
136
|
+
|
|
137
|
+
evaluated = []
|
|
138
|
+
for prompt in self.prompts:
|
|
139
|
+
result = await to_prompt(prompt)
|
|
140
|
+
evaluated.append(result)
|
|
141
|
+
self.prompts = evaluated
|
|
142
|
+
self._cached = True
|
|
143
|
+
|
|
144
|
+
@asynccontextmanager
|
|
145
|
+
async def temporary_prompt(
|
|
146
|
+
self, prompt: AnyPromptType, exclusive: bool = False
|
|
147
|
+
) -> AsyncIterator[None]:
|
|
148
|
+
"""Temporarily override system prompts.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
prompt: Single prompt or sequence of prompts to use temporarily
|
|
152
|
+
exclusive: Whether to only use given prompt. If False, prompt will be
|
|
153
|
+
appended to the agents prompts temporarily.
|
|
154
|
+
"""
|
|
155
|
+
from toprompt import to_prompt
|
|
156
|
+
|
|
157
|
+
original_prompts = self.prompts.copy()
|
|
158
|
+
new_prompt = await to_prompt(prompt)
|
|
159
|
+
self.prompts = [new_prompt] if not exclusive else [*self.prompts, new_prompt]
|
|
160
|
+
try:
|
|
161
|
+
yield
|
|
162
|
+
finally:
|
|
163
|
+
self.prompts = original_prompts
|
|
164
|
+
|
|
165
|
+
async def format_system_prompt(self, agent: BaseAgent[Any, Any]) -> str:
|
|
166
|
+
"""Format complete system prompt."""
|
|
167
|
+
if not self.dynamic and not self._cached:
|
|
168
|
+
await self.refresh_cache()
|
|
169
|
+
template = self._env.from_string(self.template or text_templates.get_system_prompt())
|
|
170
|
+
result = await template.render_async(
|
|
171
|
+
agent=agent,
|
|
172
|
+
prompts=self.prompts,
|
|
173
|
+
dynamic=self.dynamic,
|
|
174
|
+
inject_agent_info=self.inject_agent_info,
|
|
175
|
+
inject_tools=self.inject_tools,
|
|
176
|
+
tool_usage_style=self.tool_usage_style,
|
|
177
|
+
)
|
|
178
|
+
return result.strip()
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Tool wrapping utilities for pydantic-ai integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import replace
|
|
6
|
+
from functools import wraps
|
|
7
|
+
import inspect
|
|
8
|
+
import time
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
from pydantic_ai import RunContext
|
|
12
|
+
|
|
13
|
+
from agentpool.agents.context import AgentContext
|
|
14
|
+
from agentpool.tasks import ChainAbortedError, RunAbortedError, ToolSkippedError
|
|
15
|
+
from agentpool.utils.inspection import execute, get_argument_key
|
|
16
|
+
from agentpool.utils.signatures import create_modified_signature, update_signature
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from collections.abc import Awaitable, Callable
|
|
21
|
+
|
|
22
|
+
from agentpool.hooks import AgentHooks
|
|
23
|
+
from agentpool.tools.base import Tool
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def wrap_tool[TReturn]( # noqa: PLR0915
|
|
27
|
+
tool: Tool[TReturn],
|
|
28
|
+
agent_ctx: AgentContext,
|
|
29
|
+
hooks: AgentHooks | None = None,
|
|
30
|
+
) -> Callable[..., Awaitable[TReturn | None]]:
|
|
31
|
+
"""Wrap tool with confirmation handling and hooks.
|
|
32
|
+
|
|
33
|
+
Strategy:
|
|
34
|
+
- Tools with RunContext only: Normal pydantic-ai handling
|
|
35
|
+
- Tools with AgentContext only: Treat as regular tools, inject AgentContext
|
|
36
|
+
- Tools with both contexts: Present as RunContext-only to pydantic-ai, inject AgentContext
|
|
37
|
+
- Tools with no context: Normal pydantic-ai handling
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
tool: The tool to wrap.
|
|
41
|
+
agent_ctx: Agent context for confirmation handling and dependency injection.
|
|
42
|
+
hooks: Optional AgentHooks for pre/post tool execution hooks.
|
|
43
|
+
"""
|
|
44
|
+
fn = tool.callable
|
|
45
|
+
run_ctx_key = get_argument_key(fn, RunContext)
|
|
46
|
+
agent_ctx_key = get_argument_key(fn, AgentContext)
|
|
47
|
+
|
|
48
|
+
# Validate parameter order if RunContext is present
|
|
49
|
+
if run_ctx_key:
|
|
50
|
+
param_names = list(inspect.signature(fn).parameters.keys())
|
|
51
|
+
run_ctx_index = param_names.index(run_ctx_key)
|
|
52
|
+
if run_ctx_index != 0:
|
|
53
|
+
msg = f"Tool {tool.name!r}: RunContext param {run_ctx_key!r} must come first."
|
|
54
|
+
raise ValueError(msg)
|
|
55
|
+
|
|
56
|
+
async def _execute_with_hooks(
|
|
57
|
+
execute_fn: Callable[..., Awaitable[TReturn]],
|
|
58
|
+
tool_input: dict[str, Any],
|
|
59
|
+
*args: Any,
|
|
60
|
+
**kwargs: Any,
|
|
61
|
+
) -> TReturn | None:
|
|
62
|
+
"""Execute tool with pre/post hooks."""
|
|
63
|
+
# Pre-tool hooks
|
|
64
|
+
if hooks:
|
|
65
|
+
pre_result = await hooks.run_pre_tool_hooks(
|
|
66
|
+
agent_name=agent_ctx.node_name,
|
|
67
|
+
tool_name=tool.name,
|
|
68
|
+
tool_input=tool_input,
|
|
69
|
+
conversation_id=None, # Could be passed through if needed
|
|
70
|
+
)
|
|
71
|
+
if pre_result.get("decision") == "deny":
|
|
72
|
+
reason = pre_result.get("reason", "Blocked by pre-tool hook")
|
|
73
|
+
msg = f"Tool {tool.name} blocked: {reason}"
|
|
74
|
+
raise ToolSkippedError(msg)
|
|
75
|
+
|
|
76
|
+
# Apply modified input if provided
|
|
77
|
+
if modified := pre_result.get("modified_input"):
|
|
78
|
+
kwargs.update(modified)
|
|
79
|
+
|
|
80
|
+
# Execute the tool
|
|
81
|
+
start_time = time.perf_counter()
|
|
82
|
+
result = await execute_fn(*args, **kwargs)
|
|
83
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
84
|
+
|
|
85
|
+
# Post-tool hooks
|
|
86
|
+
if hooks:
|
|
87
|
+
await hooks.run_post_tool_hooks(
|
|
88
|
+
agent_name=agent_ctx.node_name,
|
|
89
|
+
tool_name=tool.name,
|
|
90
|
+
tool_input=tool_input,
|
|
91
|
+
tool_output=result,
|
|
92
|
+
duration_ms=duration_ms,
|
|
93
|
+
conversation_id=None,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
if run_ctx_key or agent_ctx_key:
|
|
99
|
+
# Tool has RunContext and/or AgentContext
|
|
100
|
+
async def wrapped(ctx: RunContext, *args: Any, **kwargs: Any) -> TReturn | None: # pyright: ignore
|
|
101
|
+
result = await agent_ctx.handle_confirmation(tool, kwargs)
|
|
102
|
+
if result == "allow":
|
|
103
|
+
# Populate AgentContext with RunContext data if needed
|
|
104
|
+
if agent_ctx.data is None:
|
|
105
|
+
agent_ctx.data = ctx.deps
|
|
106
|
+
|
|
107
|
+
if agent_ctx_key: # inject AgentContext
|
|
108
|
+
# Create per-call copy with tool execution fields (avoids race condition)
|
|
109
|
+
call_ctx = replace(
|
|
110
|
+
agent_ctx,
|
|
111
|
+
tool_name=ctx.tool_name,
|
|
112
|
+
tool_call_id=ctx.tool_call_id,
|
|
113
|
+
tool_input=kwargs.copy(),
|
|
114
|
+
)
|
|
115
|
+
kwargs[agent_ctx_key] = call_ctx
|
|
116
|
+
|
|
117
|
+
tool_input = kwargs.copy()
|
|
118
|
+
if run_ctx_key:
|
|
119
|
+
# Pass RunContext to original function
|
|
120
|
+
return await _execute_with_hooks(
|
|
121
|
+
lambda *a, **kw: execute(fn, ctx, *a, **kw),
|
|
122
|
+
tool_input,
|
|
123
|
+
*args,
|
|
124
|
+
**kwargs,
|
|
125
|
+
)
|
|
126
|
+
# Don't pass RunContext to original function since it didn't expect it
|
|
127
|
+
return await _execute_with_hooks(
|
|
128
|
+
lambda *a, **kw: execute(fn, *a, **kw),
|
|
129
|
+
tool_input,
|
|
130
|
+
*args,
|
|
131
|
+
**kwargs,
|
|
132
|
+
)
|
|
133
|
+
await _handle_confirmation_result(result, tool.name)
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
else:
|
|
137
|
+
# Tool has no context - normal function call
|
|
138
|
+
async def wrapped(*args: Any, **kwargs: Any) -> TReturn | None: # type: ignore[misc]
|
|
139
|
+
result = await agent_ctx.handle_confirmation(tool, kwargs)
|
|
140
|
+
if result == "allow":
|
|
141
|
+
tool_input = kwargs.copy()
|
|
142
|
+
return await _execute_with_hooks(
|
|
143
|
+
lambda *a, **kw: execute(fn, *a, **kw),
|
|
144
|
+
tool_input,
|
|
145
|
+
*args,
|
|
146
|
+
**kwargs,
|
|
147
|
+
)
|
|
148
|
+
await _handle_confirmation_result(result, tool.name)
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
# Apply wraps first
|
|
152
|
+
wraps(fn)(wrapped) # pyright: ignore
|
|
153
|
+
# Python 3.14: functools.wraps copies __annotate__ but not __annotations__.
|
|
154
|
+
# Any subsequent assignment to __annotations__ destroys __annotate__ (PEP 649).
|
|
155
|
+
# Restore from original to preserve deferred annotation evaluation.
|
|
156
|
+
# TODO: probably review all wraps() calls in the codebase.
|
|
157
|
+
wrapped.__annotations__ = fn.__annotations__
|
|
158
|
+
wrapped.__doc__ = tool.description
|
|
159
|
+
wrapped.__name__ = tool.name
|
|
160
|
+
# Modify signature for pydantic-ai: hide AgentContext, add RunContext if needed
|
|
161
|
+
# Must be done AFTER wraps to prevent overwriting
|
|
162
|
+
if agent_ctx_key and not run_ctx_key:
|
|
163
|
+
# Tool has AgentContext only - make it appear to have RunContext to pydantic-ai
|
|
164
|
+
new_sig = create_modified_signature(fn, remove=agent_ctx_key, inject={"ctx": RunContext})
|
|
165
|
+
update_signature(wrapped, new_sig)
|
|
166
|
+
elif agent_ctx_key and run_ctx_key:
|
|
167
|
+
# Tool has both contexts - hide AgentContext from pydantic-ai
|
|
168
|
+
new_sig = create_modified_signature(fn, remove=agent_ctx_key)
|
|
169
|
+
update_signature(wrapped, new_sig)
|
|
170
|
+
return wrapped
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
async def _handle_confirmation_result(result: str, name: str) -> None:
|
|
174
|
+
"""Handle non-allow confirmation results."""
|
|
175
|
+
match result:
|
|
176
|
+
case "skip":
|
|
177
|
+
msg = f"Tool {name} execution skipped"
|
|
178
|
+
raise ToolSkippedError(msg)
|
|
179
|
+
case "abort_run":
|
|
180
|
+
msg = "Run aborted by user"
|
|
181
|
+
raise RunAbortedError(msg)
|
|
182
|
+
case "abort_chain":
|
|
183
|
+
msg = "Agent chain aborted by user"
|
|
184
|
+
raise ChainAbortedError(msg)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Base classes for providers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import abstractmethod
|
|
6
|
+
from typing import Any, Self
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseProvider[ConfigT: BaseModel]:
|
|
12
|
+
"""Base class for all providers."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, config: ConfigT) -> None:
|
|
15
|
+
"""Initialize provider with configuration."""
|
|
16
|
+
self.config = config
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def from_kwargs(cls, **kwargs: Any) -> Self:
|
|
21
|
+
"""Alternative constructor with explicit parameters."""
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
|
|
24
|
+
def __repr__(self) -> str:
|
|
25
|
+
"""Return string representation of provider with non-default config values."""
|
|
26
|
+
non_defaults = self.config.model_dump(exclude_defaults=True)
|
|
27
|
+
fields_str = ", ".join(f"{k}={v!r}" for k, v in non_defaults.items())
|
|
28
|
+
return f"{self.__class__.__name__}({fields_str})"
|