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