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,969 @@
|
|
|
1
|
+
"""ACP (Agent Client Protocol) session management for agentpool.
|
|
2
|
+
|
|
3
|
+
This module provides session lifecycle management, state tracking, and coordination
|
|
4
|
+
between agents and ACP clients through the JSON-RPC protocol.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
from collections.abc import AsyncGenerator
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
import re
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
from exxec.acp_provider import ACPExecutionEnvironment
|
|
16
|
+
import logfire
|
|
17
|
+
from pydantic_ai import (
|
|
18
|
+
FinalResultEvent,
|
|
19
|
+
FunctionToolCallEvent,
|
|
20
|
+
FunctionToolResultEvent,
|
|
21
|
+
PartDeltaEvent,
|
|
22
|
+
PartStartEvent,
|
|
23
|
+
RetryPromptPart,
|
|
24
|
+
TextPart,
|
|
25
|
+
TextPartDelta,
|
|
26
|
+
ThinkingPart,
|
|
27
|
+
ThinkingPartDelta,
|
|
28
|
+
ToolCallPartDelta,
|
|
29
|
+
ToolReturnPart,
|
|
30
|
+
UsageLimitExceeded,
|
|
31
|
+
UserPromptPart,
|
|
32
|
+
)
|
|
33
|
+
from slashed import Command, CommandStore
|
|
34
|
+
|
|
35
|
+
from acp import RequestPermissionRequest
|
|
36
|
+
from acp.acp_requests import ACPRequests
|
|
37
|
+
from acp.filesystem import ACPFileSystem
|
|
38
|
+
from acp.notifications import ACPNotifications
|
|
39
|
+
from acp.schema import (
|
|
40
|
+
AvailableCommand,
|
|
41
|
+
ClientCapabilities,
|
|
42
|
+
ContentToolCallContent,
|
|
43
|
+
PlanEntry,
|
|
44
|
+
TerminalToolCallContent,
|
|
45
|
+
ToolCallLocation,
|
|
46
|
+
)
|
|
47
|
+
from acp.tool_call_state import ToolCallState
|
|
48
|
+
from acp.utils import generate_tool_title, infer_tool_kind, to_acp_content_blocks
|
|
49
|
+
from agentpool import Agent, AgentContext # noqa: TC001
|
|
50
|
+
from agentpool.agents import SlashedAgent
|
|
51
|
+
from agentpool.agents.acp_agent import ACPAgent
|
|
52
|
+
from agentpool.agents.events import (
|
|
53
|
+
PlanUpdateEvent,
|
|
54
|
+
StreamCompleteEvent,
|
|
55
|
+
ToolCallProgressEvent,
|
|
56
|
+
ToolCallStartEvent,
|
|
57
|
+
)
|
|
58
|
+
from agentpool.log import get_logger
|
|
59
|
+
from agentpool_commands import get_commands
|
|
60
|
+
from agentpool_commands.base import NodeCommand
|
|
61
|
+
from agentpool_server.acp_server.converters import (
|
|
62
|
+
convert_acp_mcp_server_to_config,
|
|
63
|
+
from_acp_content,
|
|
64
|
+
)
|
|
65
|
+
from agentpool_server.acp_server.input_provider import ACPInputProvider
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if TYPE_CHECKING:
|
|
69
|
+
from collections.abc import Callable, Sequence
|
|
70
|
+
|
|
71
|
+
from pydantic_ai import (
|
|
72
|
+
SystemPromptPart,
|
|
73
|
+
UserContent,
|
|
74
|
+
)
|
|
75
|
+
from slashed import CommandContext
|
|
76
|
+
|
|
77
|
+
from acp import Client, RequestPermissionResponse
|
|
78
|
+
from acp.schema import (
|
|
79
|
+
ContentBlock,
|
|
80
|
+
Implementation,
|
|
81
|
+
McpServer,
|
|
82
|
+
StopReason,
|
|
83
|
+
)
|
|
84
|
+
from agentpool import AgentPool
|
|
85
|
+
from agentpool.agents import AGUIAgent
|
|
86
|
+
from agentpool.agents.claude_code_agent import ClaudeCodeAgent
|
|
87
|
+
from agentpool.agents.events import RichAgentStreamEvent
|
|
88
|
+
from agentpool.prompts.manager import PromptManager
|
|
89
|
+
from agentpool.prompts.prompts import MCPClientPrompt
|
|
90
|
+
from agentpool_server.acp_server.acp_agent import AgentPoolACPAgent
|
|
91
|
+
from agentpool_server.acp_server.session_manager import ACPSessionManager
|
|
92
|
+
|
|
93
|
+
logger = get_logger(__name__)
|
|
94
|
+
SLASH_PATTERN = re.compile(r"^/([\w-]+)(?:\s+(.*))?$")
|
|
95
|
+
|
|
96
|
+
# Zed-specific instructions for code references
|
|
97
|
+
ZED_CLIENT_PROMPT = """\
|
|
98
|
+
## Code References
|
|
99
|
+
|
|
100
|
+
When referencing code locations in responses, use markdown links with `file://` URLs:
|
|
101
|
+
|
|
102
|
+
- **File**: `[filename](file:///absolute/path/to/file.py)`
|
|
103
|
+
- **Line range**: `[filename#L10-25](file:///absolute/path/to/file.py#L10:25)`
|
|
104
|
+
- **Single line**: `[filename#L10](file:///absolute/path/to/file.py#L10:10)`
|
|
105
|
+
- **Directory**: `[dirname/](file:///absolute/path/to/dir/)`
|
|
106
|
+
|
|
107
|
+
Line range format is `#L<start>:<end>` (1-based, inclusive).
|
|
108
|
+
|
|
109
|
+
Use these clickable references instead of inline code blocks when pointing to specific \
|
|
110
|
+
code locations. For showing actual code content, still use fenced code blocks.
|
|
111
|
+
|
|
112
|
+
## Zed-specific URLs
|
|
113
|
+
|
|
114
|
+
In addition to `file://` URLs, these `zed://` URLs work in the agent context:
|
|
115
|
+
|
|
116
|
+
- **File reference**: `[text](zed:///agent/file?path=/absolute/path/to/file.py)`
|
|
117
|
+
- **Selection**: `[text](zed:///agent/selection?path=/absolute/path/to/file.py#L10:25)`
|
|
118
|
+
- **Symbol**: `[text](zed:///agent/symbol/function_name?path=/absolute/path/to/file.py#L10:25)`
|
|
119
|
+
- **Directory**: `[text](zed:///agent/directory?path=/absolute/path/to/dir)`
|
|
120
|
+
|
|
121
|
+
Query params must be URL-encoded (spaces → `%20`). Paths must be absolute.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _is_slash_command(text: str) -> bool:
|
|
126
|
+
"""Check if text starts with a slash command."""
|
|
127
|
+
return bool(SLASH_PATTERN.match(text.strip()))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def split_commands(
|
|
131
|
+
contents: Sequence[UserContent],
|
|
132
|
+
) -> tuple[list[str], list[UserContent]]:
|
|
133
|
+
commands: list[str] = []
|
|
134
|
+
non_command_content: list[UserContent] = []
|
|
135
|
+
for item in contents:
|
|
136
|
+
if isinstance(item, str) and _is_slash_command(item):
|
|
137
|
+
commands.append(item.strip())
|
|
138
|
+
else:
|
|
139
|
+
non_command_content.append(item)
|
|
140
|
+
return commands, non_command_content
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass
|
|
144
|
+
class StagedContent:
|
|
145
|
+
"""Buffer for prompt parts to be injected into the next agent call.
|
|
146
|
+
|
|
147
|
+
This allows commands (like /fetch-repo, /git-diff) to stage content that will
|
|
148
|
+
be automatically included in the next prompt sent to the agent.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
_parts: list[SystemPromptPart | UserPromptPart] = field(default_factory=list)
|
|
152
|
+
|
|
153
|
+
def add(self, parts: list[SystemPromptPart | UserPromptPart]) -> None:
|
|
154
|
+
"""Add prompt parts to the staging area."""
|
|
155
|
+
self._parts.extend(parts)
|
|
156
|
+
|
|
157
|
+
def add_text(self, content: str) -> None:
|
|
158
|
+
"""Add text content to the staging area as a UserPromptPart."""
|
|
159
|
+
self._parts.append(UserPromptPart(content=content))
|
|
160
|
+
|
|
161
|
+
def consume(self) -> list[SystemPromptPart | UserPromptPart]:
|
|
162
|
+
"""Return all staged parts and clear the buffer."""
|
|
163
|
+
parts = self._parts.copy()
|
|
164
|
+
self._parts.clear()
|
|
165
|
+
return parts
|
|
166
|
+
|
|
167
|
+
def consume_as_text(self) -> str | None:
|
|
168
|
+
"""Return all staged content as a single string and clear the buffer.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Combined text content, or None if nothing staged.
|
|
172
|
+
"""
|
|
173
|
+
if not self._parts:
|
|
174
|
+
return None
|
|
175
|
+
texts = [part.content for part in self._parts if isinstance(part.content, str)]
|
|
176
|
+
self._parts.clear()
|
|
177
|
+
return "\n\n".join(texts) if texts else None
|
|
178
|
+
|
|
179
|
+
def __len__(self) -> int:
|
|
180
|
+
"""Return count of staged parts."""
|
|
181
|
+
return len(self._parts)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@dataclass
|
|
185
|
+
class ACPSession:
|
|
186
|
+
"""Individual ACP session state and management.
|
|
187
|
+
|
|
188
|
+
Manages the lifecycle and state of a single ACP session, including:
|
|
189
|
+
- Agent instance and conversation state
|
|
190
|
+
- Working directory and environment
|
|
191
|
+
- MCP server connections
|
|
192
|
+
- File system bridge for client operations
|
|
193
|
+
- Tool execution and streaming updates
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
session_id: str
|
|
197
|
+
"""Unique session identifier"""
|
|
198
|
+
|
|
199
|
+
agent_pool: AgentPool[Any]
|
|
200
|
+
"""AgentPool containing available agents"""
|
|
201
|
+
|
|
202
|
+
current_agent_name: str
|
|
203
|
+
"""Name of currently active agent"""
|
|
204
|
+
|
|
205
|
+
cwd: str
|
|
206
|
+
"""Working directory for the session"""
|
|
207
|
+
|
|
208
|
+
client: Client
|
|
209
|
+
"""External library Client interface for operations"""
|
|
210
|
+
|
|
211
|
+
acp_agent: AgentPoolACPAgent
|
|
212
|
+
"""ACP agent instance for capability tools"""
|
|
213
|
+
|
|
214
|
+
mcp_servers: Sequence[McpServer] | None = None
|
|
215
|
+
"""Optional MCP server configurations"""
|
|
216
|
+
|
|
217
|
+
client_capabilities: ClientCapabilities = field(default_factory=ClientCapabilities)
|
|
218
|
+
"""Client capabilities for tool registration"""
|
|
219
|
+
|
|
220
|
+
client_info: Implementation | None = None
|
|
221
|
+
"""Client implementation info (name, version, title)"""
|
|
222
|
+
|
|
223
|
+
manager: ACPSessionManager | None = None
|
|
224
|
+
"""Session manager for managing sessions. Used for session management commands."""
|
|
225
|
+
|
|
226
|
+
def __post_init__(self) -> None:
|
|
227
|
+
"""Initialize session state and set up providers."""
|
|
228
|
+
from agentpool_server.acp_server.commands import get_commands as get_acp_commands
|
|
229
|
+
|
|
230
|
+
self.mcp_servers = self.mcp_servers or []
|
|
231
|
+
self.log = logger.bind(session_id=self.session_id)
|
|
232
|
+
self._task_lock = asyncio.Lock()
|
|
233
|
+
self._cancelled = False
|
|
234
|
+
self._current_tool_inputs: dict[str, dict[str, Any]] = {}
|
|
235
|
+
self._tool_call_states: dict[str, ToolCallState] = {}
|
|
236
|
+
self.fs = ACPFileSystem(self.client, session_id=self.session_id)
|
|
237
|
+
cmds = [
|
|
238
|
+
*get_commands(
|
|
239
|
+
enable_set_model=False,
|
|
240
|
+
enable_list_resources=False,
|
|
241
|
+
enable_add_resource=False,
|
|
242
|
+
enable_show_resource=False,
|
|
243
|
+
),
|
|
244
|
+
*get_acp_commands(),
|
|
245
|
+
]
|
|
246
|
+
self.command_store = CommandStore(enable_system_commands=True, commands=cmds)
|
|
247
|
+
self.command_store._initialize_sync()
|
|
248
|
+
self._update_callbacks: list[Callable[[], None]] = []
|
|
249
|
+
|
|
250
|
+
self.staged_content = StagedContent()
|
|
251
|
+
# Inject Zed-specific instructions if client is Zed
|
|
252
|
+
if self.client_info and self.client_info.name and "zed" in self.client_info.name.lower():
|
|
253
|
+
self.staged_content.add_text(ZED_CLIENT_PROMPT)
|
|
254
|
+
self.notifications = ACPNotifications(client=self.client, session_id=self.session_id)
|
|
255
|
+
self.requests = ACPRequests(client=self.client, session_id=self.session_id)
|
|
256
|
+
self.input_provider = ACPInputProvider(self)
|
|
257
|
+
self.acp_env = ACPExecutionEnvironment(fs=self.fs, requests=self.requests, cwd=self.cwd)
|
|
258
|
+
for agent in self.agent_pool.all_agents.values():
|
|
259
|
+
agent.env = self.acp_env
|
|
260
|
+
if isinstance(agent, Agent):
|
|
261
|
+
# TODO: need to inject this info for ACP agents, too.
|
|
262
|
+
agent.sys_prompts.prompts.append(self.get_cwd_context) # pyright: ignore[reportArgumentType]
|
|
263
|
+
if isinstance(agent, ACPAgent):
|
|
264
|
+
|
|
265
|
+
async def permission_callback(
|
|
266
|
+
params: RequestPermissionRequest,
|
|
267
|
+
) -> RequestPermissionResponse:
|
|
268
|
+
# Reconstruct request with our session_id (not nested agent's session_id)
|
|
269
|
+
self.log.debug(
|
|
270
|
+
"Forwarding permission request",
|
|
271
|
+
original_session_id=params.session_id,
|
|
272
|
+
our_session_id=self.session_id,
|
|
273
|
+
tool_call_id=params.tool_call.tool_call_id,
|
|
274
|
+
tool_call_title=params.tool_call.title,
|
|
275
|
+
options=[o.option_id for o in (params.options or [])],
|
|
276
|
+
)
|
|
277
|
+
forwarded_request = RequestPermissionRequest(
|
|
278
|
+
session_id=self.session_id, # Use agentpool ↔ Zed session_id
|
|
279
|
+
options=params.options,
|
|
280
|
+
tool_call=params.tool_call,
|
|
281
|
+
)
|
|
282
|
+
try:
|
|
283
|
+
response = await self.requests.client.request_permission(forwarded_request)
|
|
284
|
+
self.log.debug(
|
|
285
|
+
"Permission response received",
|
|
286
|
+
outcome_type=type(response.outcome).__name__,
|
|
287
|
+
outcome=response.outcome.outcome,
|
|
288
|
+
option_id=getattr(response.outcome, "option_id", None),
|
|
289
|
+
)
|
|
290
|
+
except Exception as exc:
|
|
291
|
+
self.log.exception("Permission forwarding failed", error=str(exc))
|
|
292
|
+
raise
|
|
293
|
+
else:
|
|
294
|
+
return response
|
|
295
|
+
|
|
296
|
+
agent.acp_permission_callback = permission_callback
|
|
297
|
+
self.log.info("Created ACP session", current_agent=self.current_agent_name)
|
|
298
|
+
|
|
299
|
+
async def initialize(self) -> None:
|
|
300
|
+
"""Initialize async resources. Must be called after construction."""
|
|
301
|
+
await self.acp_env.__aenter__()
|
|
302
|
+
|
|
303
|
+
def _get_or_create_tool_state(
|
|
304
|
+
self,
|
|
305
|
+
tool_call_id: str,
|
|
306
|
+
tool_name: str,
|
|
307
|
+
tool_input: dict[str, Any],
|
|
308
|
+
) -> ToolCallState:
|
|
309
|
+
"""Get existing tool call state or create a new one.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
tool_call_id: Unique identifier for the tool call
|
|
313
|
+
tool_name: Name of the tool being called
|
|
314
|
+
tool_input: Input parameters for the tool
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
ToolCallState instance (existing or newly created)
|
|
318
|
+
"""
|
|
319
|
+
if tool_call_id not in self._tool_call_states:
|
|
320
|
+
state = ToolCallState(
|
|
321
|
+
notifications=self.notifications,
|
|
322
|
+
tool_call_id=tool_call_id,
|
|
323
|
+
tool_name=tool_name,
|
|
324
|
+
title=generate_tool_title(tool_name, tool_input),
|
|
325
|
+
kind=infer_tool_kind(tool_name),
|
|
326
|
+
raw_input=tool_input,
|
|
327
|
+
)
|
|
328
|
+
self._tool_call_states[tool_call_id] = state
|
|
329
|
+
return self._tool_call_states[tool_call_id]
|
|
330
|
+
|
|
331
|
+
def _cleanup_tool_state(self, tool_call_id: str) -> None:
|
|
332
|
+
"""Remove tool call state after completion."""
|
|
333
|
+
self._tool_call_states.pop(tool_call_id, None)
|
|
334
|
+
self._current_tool_inputs.pop(tool_call_id, None)
|
|
335
|
+
|
|
336
|
+
async def initialize_mcp_servers(self) -> None:
|
|
337
|
+
"""Initialize MCP servers if any are configured."""
|
|
338
|
+
if not self.mcp_servers:
|
|
339
|
+
return
|
|
340
|
+
self.log.info("Initializing MCP servers", server_count=len(self.mcp_servers))
|
|
341
|
+
cfgs = [convert_acp_mcp_server_to_config(s) for s in self.mcp_servers]
|
|
342
|
+
# Define accessible roots for MCP servers
|
|
343
|
+
# root = Path(self.cwd).resolve().as_uri() if self.cwd else None
|
|
344
|
+
for _cfg in cfgs:
|
|
345
|
+
try:
|
|
346
|
+
# Server will be initialized when MCP manager enters context
|
|
347
|
+
self.log.info("Added MCP servers", server_count=len(cfgs))
|
|
348
|
+
await self._register_mcp_prompts_as_commands()
|
|
349
|
+
except Exception:
|
|
350
|
+
self.log.exception("Failed to initialize MCP manager")
|
|
351
|
+
# Don't fail session creation, just log the error
|
|
352
|
+
|
|
353
|
+
async def init_project_context(self) -> None:
|
|
354
|
+
"""Load AGENTS.md file and inject project context into all agents.
|
|
355
|
+
|
|
356
|
+
TODO: Consider moving this to __aenter__
|
|
357
|
+
"""
|
|
358
|
+
if info := await self.requests.read_agent_rules(self.cwd):
|
|
359
|
+
for agent in self.agent_pool.agents.values():
|
|
360
|
+
prompt = f"## Project Information\n\n{info}"
|
|
361
|
+
agent.sys_prompts.prompts.append(prompt)
|
|
362
|
+
|
|
363
|
+
async def init_client_skills(self) -> None:
|
|
364
|
+
"""Discover and load skills from client-side .claude/skills directory.
|
|
365
|
+
|
|
366
|
+
Adds the client's .claude/skills directory to the pool's skills manager,
|
|
367
|
+
making those skills available to all agents via the SkillsTools toolset.
|
|
368
|
+
|
|
369
|
+
We pass the filesystem directly to avoid fsspec trying to create a new
|
|
370
|
+
ACPFileSystem instance without the required client/session_id parameters.
|
|
371
|
+
"""
|
|
372
|
+
try:
|
|
373
|
+
await self.agent_pool.skills.add_skills_directory(".claude/skills", fs=self.fs)
|
|
374
|
+
skills = self.agent_pool.skills.list_skills()
|
|
375
|
+
self.log.info("Collected client-side skills", skill_count=len(skills))
|
|
376
|
+
except Exception as e:
|
|
377
|
+
self.log.exception("Failed to discover client-side skills", error=e)
|
|
378
|
+
|
|
379
|
+
@property
|
|
380
|
+
def agent(self) -> Agent[ACPSession, str] | ACPAgent | AGUIAgent | ClaudeCodeAgent:
|
|
381
|
+
"""Get the currently active agent."""
|
|
382
|
+
if self.current_agent_name in self.agent_pool.agents:
|
|
383
|
+
return self.agent_pool.get_agent(self.current_agent_name, deps_type=ACPSession)
|
|
384
|
+
return self.agent_pool.all_agents[self.current_agent_name] # type: ignore[return-value]
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def slashed_agent(self) -> SlashedAgent[Any, str]:
|
|
388
|
+
"""Get the wrapped slashed agent."""
|
|
389
|
+
return SlashedAgent(self.agent, command_store=self.command_store)
|
|
390
|
+
|
|
391
|
+
def get_cwd_context(self) -> str:
|
|
392
|
+
"""Get current working directory context for prompts."""
|
|
393
|
+
return f"Working directory: {self.cwd}" if self.cwd else ""
|
|
394
|
+
|
|
395
|
+
async def switch_active_agent(self, agent_name: str) -> None:
|
|
396
|
+
"""Switch to a different agent in the pool.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
agent_name: Name of the agent to switch to
|
|
400
|
+
|
|
401
|
+
Raises:
|
|
402
|
+
ValueError: If agent not found in pool
|
|
403
|
+
"""
|
|
404
|
+
if agent_name not in self.agent_pool.all_agents:
|
|
405
|
+
available = list(self.agent_pool.all_agents.keys())
|
|
406
|
+
raise ValueError(f"Agent {agent_name!r} not found. Available: {available}")
|
|
407
|
+
|
|
408
|
+
old_agent_name = self.current_agent_name
|
|
409
|
+
self.current_agent_name = agent_name
|
|
410
|
+
self.log.info("Switched agents", from_agent=old_agent_name, to_agent=agent_name)
|
|
411
|
+
|
|
412
|
+
# Persist the agent switch via session manager
|
|
413
|
+
if self.manager:
|
|
414
|
+
await self.manager.update_session_agent(self.session_id, agent_name)
|
|
415
|
+
|
|
416
|
+
await self.send_available_commands_update()
|
|
417
|
+
|
|
418
|
+
async def cancel(self) -> None:
|
|
419
|
+
"""Cancel the current prompt turn.
|
|
420
|
+
|
|
421
|
+
This actively interrupts the running agent by calling its interrupt() method,
|
|
422
|
+
which handles protocol-specific cancellation (e.g., sending CancelNotification
|
|
423
|
+
for ACP agents, calling SDK interrupt for ClaudeCodeAgent, etc.).
|
|
424
|
+
"""
|
|
425
|
+
self._cancelled = True
|
|
426
|
+
self.log.info("Session cancelled, interrupting agent")
|
|
427
|
+
|
|
428
|
+
# Actively interrupt the agent's stream
|
|
429
|
+
try:
|
|
430
|
+
await self.agent.interrupt()
|
|
431
|
+
except Exception:
|
|
432
|
+
self.log.exception("Failed to interrupt agent")
|
|
433
|
+
|
|
434
|
+
def is_cancelled(self) -> bool:
|
|
435
|
+
"""Check if the session is cancelled."""
|
|
436
|
+
return self._cancelled
|
|
437
|
+
|
|
438
|
+
async def process_prompt(self, content_blocks: Sequence[ContentBlock]) -> StopReason: # noqa: PLR0911
|
|
439
|
+
"""Process a prompt request and stream responses.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
content_blocks: List of content blocks from the prompt request
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
Stop reason
|
|
446
|
+
"""
|
|
447
|
+
self._cancelled = False
|
|
448
|
+
contents = from_acp_content(content_blocks)
|
|
449
|
+
self.log.debug("Converted content", content=contents)
|
|
450
|
+
if not contents:
|
|
451
|
+
self.log.warning("Empty prompt received")
|
|
452
|
+
return "refusal"
|
|
453
|
+
commands, non_command_content = split_commands(contents)
|
|
454
|
+
async with self._task_lock:
|
|
455
|
+
if commands: # Process commands if found
|
|
456
|
+
for command in commands:
|
|
457
|
+
self.log.info("Processing slash command", command=command)
|
|
458
|
+
await self.execute_slash_command(command)
|
|
459
|
+
|
|
460
|
+
# If only commands, end turn
|
|
461
|
+
if not non_command_content:
|
|
462
|
+
return "end_turn"
|
|
463
|
+
|
|
464
|
+
# Consume any staged content and prepend to the prompt
|
|
465
|
+
staged = self.staged_content.consume_as_text()
|
|
466
|
+
all_content = [staged, *non_command_content] if staged else list(non_command_content)
|
|
467
|
+
self.log.debug(
|
|
468
|
+
"Processing prompt",
|
|
469
|
+
content_items=len(non_command_content),
|
|
470
|
+
has_staged=staged is not None,
|
|
471
|
+
)
|
|
472
|
+
event_count = 0
|
|
473
|
+
self._current_tool_inputs.clear() # Reset tool inputs for new stream
|
|
474
|
+
|
|
475
|
+
try: # Use the session's persistent input provider
|
|
476
|
+
async for event in self.agent.run_stream(
|
|
477
|
+
*all_content, input_provider=self.input_provider
|
|
478
|
+
):
|
|
479
|
+
if self._cancelled:
|
|
480
|
+
return "cancelled"
|
|
481
|
+
|
|
482
|
+
event_count += 1
|
|
483
|
+
await self.handle_event(event)
|
|
484
|
+
self.log.info("Streaming finished", events_processed=event_count)
|
|
485
|
+
|
|
486
|
+
except UsageLimitExceeded as e:
|
|
487
|
+
self.log.info("Usage limit exceeded", error=str(e))
|
|
488
|
+
error_msg = str(e) # Determine which limit was hit based on error
|
|
489
|
+
if "request_limit" in error_msg:
|
|
490
|
+
return "max_turn_requests"
|
|
491
|
+
if any(limit in error_msg for limit in ["tokens_limit", "token_limit"]):
|
|
492
|
+
return "max_tokens"
|
|
493
|
+
# Tool call limits don't have a direct ACP stop reason, treat as refusal
|
|
494
|
+
if "tool_calls_limit" in error_msg or "tool call" in error_msg:
|
|
495
|
+
return "refusal"
|
|
496
|
+
return "max_tokens" # Default to max_tokens for other usage limits
|
|
497
|
+
except Exception as e:
|
|
498
|
+
self.log.exception("Error during streaming")
|
|
499
|
+
# Send error notification asynchronously to avoid blocking response
|
|
500
|
+
self.acp_agent.tasks.create_task(
|
|
501
|
+
self._send_error_notification(f"❌ Agent error: {e}"),
|
|
502
|
+
name=f"agent_error_notification_{self.session_id}",
|
|
503
|
+
)
|
|
504
|
+
return "end_turn"
|
|
505
|
+
else:
|
|
506
|
+
return "end_turn"
|
|
507
|
+
|
|
508
|
+
async def _send_error_notification(self, message: str) -> None:
|
|
509
|
+
"""Send error notification, with exception handling."""
|
|
510
|
+
try:
|
|
511
|
+
await self.notifications.send_agent_text(message)
|
|
512
|
+
except Exception:
|
|
513
|
+
self.log.exception("Failed to send error notification")
|
|
514
|
+
|
|
515
|
+
async def handle_event(self, event: RichAgentStreamEvent[Any]) -> None: # noqa: PLR0915
|
|
516
|
+
match event:
|
|
517
|
+
case (
|
|
518
|
+
PartStartEvent(part=TextPart(content=delta))
|
|
519
|
+
| PartDeltaEvent(delta=TextPartDelta(content_delta=delta))
|
|
520
|
+
):
|
|
521
|
+
await self.notifications.send_agent_text(delta)
|
|
522
|
+
|
|
523
|
+
case (
|
|
524
|
+
PartStartEvent(part=ThinkingPart(content=delta))
|
|
525
|
+
| PartDeltaEvent(delta=ThinkingPartDelta(content_delta=delta))
|
|
526
|
+
):
|
|
527
|
+
await self.notifications.send_agent_thought(delta or "\n")
|
|
528
|
+
|
|
529
|
+
case PartStartEvent(part=part):
|
|
530
|
+
self.log.debug("Received unhandled PartStartEvent", part=part)
|
|
531
|
+
|
|
532
|
+
# Tool call streaming delta - create/update state and start notification
|
|
533
|
+
case PartDeltaEvent(delta=ToolCallPartDelta() as delta):
|
|
534
|
+
if delta_part := delta.as_part():
|
|
535
|
+
tool_call_id = delta_part.tool_call_id
|
|
536
|
+
try:
|
|
537
|
+
tool_input = delta_part.args_as_dict()
|
|
538
|
+
except ValueError:
|
|
539
|
+
# Args still streaming, not valid JSON yet - skip this delta
|
|
540
|
+
pass
|
|
541
|
+
else:
|
|
542
|
+
self._current_tool_inputs[tool_call_id] = tool_input
|
|
543
|
+
# Create state and send initial notification
|
|
544
|
+
state = self._get_or_create_tool_state(
|
|
545
|
+
tool_call_id=tool_call_id,
|
|
546
|
+
tool_name=delta_part.tool_name,
|
|
547
|
+
tool_input=tool_input,
|
|
548
|
+
)
|
|
549
|
+
await state.start()
|
|
550
|
+
|
|
551
|
+
# Tool call started - create/update state and start notification
|
|
552
|
+
case FunctionToolCallEvent(part=part):
|
|
553
|
+
tool_call_id = part.tool_call_id
|
|
554
|
+
try:
|
|
555
|
+
tool_input = part.args_as_dict()
|
|
556
|
+
except ValueError as e:
|
|
557
|
+
# Args might be malformed - use empty dict and log
|
|
558
|
+
self.log.warning(
|
|
559
|
+
"Failed to parse tool args", tool_name=part.tool_name, error=str(e)
|
|
560
|
+
)
|
|
561
|
+
tool_input = {}
|
|
562
|
+
self._current_tool_inputs[tool_call_id] = tool_input
|
|
563
|
+
# Create state and send initial notification
|
|
564
|
+
state = self._get_or_create_tool_state(
|
|
565
|
+
tool_call_id=tool_call_id,
|
|
566
|
+
tool_name=part.tool_name,
|
|
567
|
+
tool_input=tool_input,
|
|
568
|
+
)
|
|
569
|
+
await state.start()
|
|
570
|
+
|
|
571
|
+
# Tool completed successfully - update state and finalize
|
|
572
|
+
case FunctionToolResultEvent(
|
|
573
|
+
result=ToolReturnPart(content=content, tool_name=tool_name) as result,
|
|
574
|
+
tool_call_id=tool_call_id,
|
|
575
|
+
):
|
|
576
|
+
if isinstance(content, AsyncGenerator):
|
|
577
|
+
full_content = ""
|
|
578
|
+
async for chunk in content:
|
|
579
|
+
full_content += str(chunk)
|
|
580
|
+
# Stream progress through state
|
|
581
|
+
if tool_state := self._tool_call_states.get(tool_call_id):
|
|
582
|
+
await tool_state.update(status="in_progress", raw_output=chunk)
|
|
583
|
+
|
|
584
|
+
# Replace the AsyncGenerator with the full content to prevent errors
|
|
585
|
+
result.content = full_content
|
|
586
|
+
final_output = full_content
|
|
587
|
+
else:
|
|
588
|
+
final_output = result.content
|
|
589
|
+
|
|
590
|
+
# Complete tool call through state (preserves accumulated content/locations)
|
|
591
|
+
if complete_state := self._tool_call_states.get(tool_call_id):
|
|
592
|
+
# Only add return value as content if no content was emitted during execution
|
|
593
|
+
if complete_state.has_content:
|
|
594
|
+
# Content already provided via progress events - just set raw_output
|
|
595
|
+
await complete_state.complete(raw_output=final_output)
|
|
596
|
+
else:
|
|
597
|
+
# No content yet - convert return value for display
|
|
598
|
+
converted_blocks = to_acp_content_blocks(final_output)
|
|
599
|
+
content_items = [
|
|
600
|
+
ContentToolCallContent(content=block) for block in converted_blocks
|
|
601
|
+
]
|
|
602
|
+
await complete_state.complete(
|
|
603
|
+
raw_output=final_output,
|
|
604
|
+
content=content_items,
|
|
605
|
+
)
|
|
606
|
+
self._cleanup_tool_state(tool_call_id)
|
|
607
|
+
|
|
608
|
+
# Tool failed with retry - update state with error
|
|
609
|
+
case FunctionToolResultEvent(
|
|
610
|
+
result=RetryPromptPart(tool_name=tool_name) as result,
|
|
611
|
+
tool_call_id=tool_call_id,
|
|
612
|
+
):
|
|
613
|
+
error_message = result.model_response()
|
|
614
|
+
if fail_state := self._tool_call_states.get(tool_call_id):
|
|
615
|
+
await fail_state.fail(error=error_message)
|
|
616
|
+
self._cleanup_tool_state(tool_call_id)
|
|
617
|
+
|
|
618
|
+
# Tool emits its own start event - update state with better title/content
|
|
619
|
+
case ToolCallStartEvent(
|
|
620
|
+
tool_call_id=tool_call_id,
|
|
621
|
+
tool_name=tool_name,
|
|
622
|
+
title=title,
|
|
623
|
+
kind=kind,
|
|
624
|
+
locations=loc_items,
|
|
625
|
+
raw_input=raw_input,
|
|
626
|
+
):
|
|
627
|
+
self.log.debug(
|
|
628
|
+
"Tool call start event", tool_name=tool_name, tool_call_id=tool_call_id
|
|
629
|
+
)
|
|
630
|
+
# Get or create state (may already exist from FunctionToolCallEvent)
|
|
631
|
+
state = self._get_or_create_tool_state(
|
|
632
|
+
tool_call_id=tool_call_id,
|
|
633
|
+
tool_name=tool_name,
|
|
634
|
+
tool_input=raw_input or {},
|
|
635
|
+
)
|
|
636
|
+
# Convert LocationContentItem objects to ACP format
|
|
637
|
+
acp_locations = [
|
|
638
|
+
ToolCallLocation(path=loc.path, line=loc.line) for loc in loc_items
|
|
639
|
+
]
|
|
640
|
+
# Update state with tool-provided details (better title, content, locations)
|
|
641
|
+
await state.update(title=title, kind=kind, locations=acp_locations or None)
|
|
642
|
+
|
|
643
|
+
# Tool progress event - update state with title and content
|
|
644
|
+
case ToolCallProgressEvent(
|
|
645
|
+
tool_call_id=tool_call_id,
|
|
646
|
+
title=title,
|
|
647
|
+
status=status,
|
|
648
|
+
items=items,
|
|
649
|
+
) if tool_call_id and tool_call_id in self._tool_call_states:
|
|
650
|
+
progress_state = self._tool_call_states[tool_call_id]
|
|
651
|
+
self.log.debug("Progress event", tool_call_id=tool_call_id, title=title)
|
|
652
|
+
|
|
653
|
+
# Convert items to ACP content
|
|
654
|
+
from agentpool.agents.events import (
|
|
655
|
+
DiffContentItem,
|
|
656
|
+
FileContentItem,
|
|
657
|
+
LocationContentItem,
|
|
658
|
+
TerminalContentItem,
|
|
659
|
+
TextContentItem,
|
|
660
|
+
)
|
|
661
|
+
from agentpool_server.acp_server.syntax_detection import (
|
|
662
|
+
format_zed_code_block,
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
acp_content: list[Any] = []
|
|
666
|
+
location_paths: list[str] = []
|
|
667
|
+
|
|
668
|
+
for item in items:
|
|
669
|
+
match item:
|
|
670
|
+
case TerminalContentItem(terminal_id=tid):
|
|
671
|
+
acp_content.append(TerminalToolCallContent(terminal_id=tid))
|
|
672
|
+
case TextContentItem(text=text):
|
|
673
|
+
acp_content.append(ContentToolCallContent.text(text=text))
|
|
674
|
+
case FileContentItem(
|
|
675
|
+
content=file_content,
|
|
676
|
+
path=file_path,
|
|
677
|
+
start_line=start_line,
|
|
678
|
+
end_line=end_line,
|
|
679
|
+
):
|
|
680
|
+
# Format as Zed-compatible code block with clickable path
|
|
681
|
+
formatted = format_zed_code_block(
|
|
682
|
+
file_content, file_path, start_line, end_line
|
|
683
|
+
)
|
|
684
|
+
acp_content.append(ContentToolCallContent.text(text=formatted))
|
|
685
|
+
# Also add path to locations for "follow along" feature
|
|
686
|
+
location_paths.append(file_path)
|
|
687
|
+
case DiffContentItem(path=diff_path, old_text=old, new_text=new):
|
|
688
|
+
# Send diff via direct notification
|
|
689
|
+
await self.notifications.file_edit_progress(
|
|
690
|
+
tool_call_id=tool_call_id,
|
|
691
|
+
path=diff_path,
|
|
692
|
+
old_text=old or "",
|
|
693
|
+
new_text=new,
|
|
694
|
+
status=status,
|
|
695
|
+
changed_lines=[],
|
|
696
|
+
)
|
|
697
|
+
case LocationContentItem(path=loc_path):
|
|
698
|
+
location_paths.append(loc_path)
|
|
699
|
+
|
|
700
|
+
await progress_state.update(
|
|
701
|
+
title=title,
|
|
702
|
+
status="in_progress",
|
|
703
|
+
content=acp_content if acp_content else None,
|
|
704
|
+
locations=location_paths if location_paths else None,
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
case FinalResultEvent():
|
|
708
|
+
self.log.debug("Final result received")
|
|
709
|
+
|
|
710
|
+
case StreamCompleteEvent():
|
|
711
|
+
pass
|
|
712
|
+
|
|
713
|
+
case PlanUpdateEvent(entries=entries, tool_call_id=tool_call_id):
|
|
714
|
+
acp_entries = [
|
|
715
|
+
PlanEntry(content=e.content, priority=e.priority, status=e.status)
|
|
716
|
+
for e in entries
|
|
717
|
+
]
|
|
718
|
+
await self.notifications.update_plan(acp_entries)
|
|
719
|
+
|
|
720
|
+
case _:
|
|
721
|
+
self.log.debug("Unhandled event", event_type=type(event).__name__)
|
|
722
|
+
|
|
723
|
+
async def close(self) -> None:
|
|
724
|
+
"""Close the session and cleanup resources."""
|
|
725
|
+
self._current_tool_inputs.clear()
|
|
726
|
+
self._tool_call_states.clear()
|
|
727
|
+
|
|
728
|
+
try:
|
|
729
|
+
await self.acp_env.__aexit__(None, None, None)
|
|
730
|
+
except Exception:
|
|
731
|
+
self.log.exception("Error closing acp_env")
|
|
732
|
+
|
|
733
|
+
try:
|
|
734
|
+
# Remove cwd context callable from all agents
|
|
735
|
+
for agent in self.agent_pool.agents.values():
|
|
736
|
+
if self.get_cwd_context in agent.sys_prompts.prompts:
|
|
737
|
+
agent.sys_prompts.prompts.remove(self.get_cwd_context) # pyright: ignore[reportArgumentType]
|
|
738
|
+
|
|
739
|
+
# Note: Individual agents are managed by the pool's lifecycle
|
|
740
|
+
# The pool will handle agent cleanup when it's closed
|
|
741
|
+
self.log.info("Closed ACP session")
|
|
742
|
+
except Exception:
|
|
743
|
+
self.log.exception("Error closing session")
|
|
744
|
+
|
|
745
|
+
async def send_available_commands_update(self) -> None:
|
|
746
|
+
"""Send current available commands to client."""
|
|
747
|
+
try:
|
|
748
|
+
commands = self.get_acp_commands()
|
|
749
|
+
await self.notifications.update_commands(commands)
|
|
750
|
+
except Exception:
|
|
751
|
+
self.log.exception("Failed to send available commands update")
|
|
752
|
+
|
|
753
|
+
async def _register_mcp_prompts_as_commands(self) -> None:
|
|
754
|
+
"""Register MCP prompts as slash commands."""
|
|
755
|
+
if not isinstance(self.agent, Agent):
|
|
756
|
+
return
|
|
757
|
+
try: # Get all prompts from the agent's ToolManager
|
|
758
|
+
if all_prompts := await self.agent.tools.list_prompts():
|
|
759
|
+
for prompt in all_prompts:
|
|
760
|
+
command = self.create_mcp_command(prompt)
|
|
761
|
+
self.command_store.register_command(command)
|
|
762
|
+
self._notify_command_update()
|
|
763
|
+
self.log.info("Registered MCP prompts as commands", prompt_count=len(all_prompts))
|
|
764
|
+
await self.send_available_commands_update() # Send updated command list to client
|
|
765
|
+
except Exception:
|
|
766
|
+
self.log.exception("Failed to register MCP prompts as commands")
|
|
767
|
+
|
|
768
|
+
async def _register_prompt_hub_commands(self) -> None:
|
|
769
|
+
"""Register prompt hub prompts as slash commands."""
|
|
770
|
+
manager = self.agent_pool.manifest.prompt_manager
|
|
771
|
+
cmd_count = 0
|
|
772
|
+
try:
|
|
773
|
+
all_prompts = await manager.list_prompts()
|
|
774
|
+
for provider_name, prompt_names in all_prompts.items():
|
|
775
|
+
if not prompt_names: # Skip empty providers
|
|
776
|
+
continue
|
|
777
|
+
for prompt_name in prompt_names:
|
|
778
|
+
command = self.create_prompt_hub_command(provider_name, prompt_name, manager)
|
|
779
|
+
self.command_store.register_command(command)
|
|
780
|
+
cmd_count += 1
|
|
781
|
+
|
|
782
|
+
if cmd_count > 0:
|
|
783
|
+
self._notify_command_update()
|
|
784
|
+
self.log.info("Registered hub prompts as slash commands", cmd_count=cmd_count)
|
|
785
|
+
await self.send_available_commands_update() # Send updated command list to client
|
|
786
|
+
except Exception:
|
|
787
|
+
self.log.exception("Failed to register prompt hub prompts as commands")
|
|
788
|
+
|
|
789
|
+
def _notify_command_update(self) -> None:
|
|
790
|
+
"""Notify all registered callbacks about command updates."""
|
|
791
|
+
for callback in self._update_callbacks:
|
|
792
|
+
try:
|
|
793
|
+
callback()
|
|
794
|
+
except Exception:
|
|
795
|
+
logger.exception("Command update callback failed")
|
|
796
|
+
|
|
797
|
+
def get_acp_commands(self) -> list[AvailableCommand]:
|
|
798
|
+
"""Convert all slashed commands to ACP format.
|
|
799
|
+
|
|
800
|
+
Filters commands based on current agent's node type compatibility.
|
|
801
|
+
|
|
802
|
+
Returns:
|
|
803
|
+
List of ACP AvailableCommand objects compatible with current node
|
|
804
|
+
"""
|
|
805
|
+
all_commands = self.command_store.list_commands()
|
|
806
|
+
current_node = self.agent
|
|
807
|
+
# Filter commands by node compatibility
|
|
808
|
+
compatible_commands = []
|
|
809
|
+
for cmd in all_commands:
|
|
810
|
+
cmd_cls = cmd if isinstance(cmd, type) else type(cmd)
|
|
811
|
+
# Check if command supports current node type
|
|
812
|
+
if issubclass(cmd_cls, NodeCommand) and not cmd_cls.supports_node(current_node): # type: ignore[union-attr]
|
|
813
|
+
continue
|
|
814
|
+
compatible_commands.append(cmd)
|
|
815
|
+
|
|
816
|
+
return [
|
|
817
|
+
AvailableCommand.create(name=i.name, description=i.description, input_hint=i.usage)
|
|
818
|
+
for i in compatible_commands
|
|
819
|
+
]
|
|
820
|
+
|
|
821
|
+
@logfire.instrument(r"Execute Slash Command {command_text}")
|
|
822
|
+
async def execute_slash_command(self, command_text: str) -> None:
|
|
823
|
+
"""Execute any slash command with unified handling.
|
|
824
|
+
|
|
825
|
+
Args:
|
|
826
|
+
command_text: Full command text (including slash)
|
|
827
|
+
session: ACP session context
|
|
828
|
+
"""
|
|
829
|
+
if match := SLASH_PATTERN.match(command_text.strip()):
|
|
830
|
+
command_name = match.group(1)
|
|
831
|
+
args = match.group(2) or ""
|
|
832
|
+
else:
|
|
833
|
+
logger.warning("Invalid slash command", command=command_text)
|
|
834
|
+
return
|
|
835
|
+
|
|
836
|
+
# Check if command supports current node type
|
|
837
|
+
if cmd := self.command_store.get_command(command_name):
|
|
838
|
+
cmd_cls = cmd if isinstance(cmd, type) else type(cmd)
|
|
839
|
+
if issubclass(cmd_cls, NodeCommand) and not cmd_cls.supports_node(self.agent): # type: ignore[union-attr]
|
|
840
|
+
error_msg = f"❌ Command `/{command_name}` is not available for this node type"
|
|
841
|
+
await self.notifications.send_agent_text(error_msg)
|
|
842
|
+
return
|
|
843
|
+
|
|
844
|
+
# Create context with session data
|
|
845
|
+
agent_context = self.agent.get_context(data=self)
|
|
846
|
+
cmd_ctx = self.command_store.create_context(
|
|
847
|
+
data=agent_context,
|
|
848
|
+
output_writer=self.notifications.send_agent_text,
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
command_str = f"{command_name} {args}".strip()
|
|
852
|
+
try:
|
|
853
|
+
await self.command_store.execute_command(command_str, cmd_ctx)
|
|
854
|
+
except Exception as e:
|
|
855
|
+
logger.exception("Command execution failed")
|
|
856
|
+
# Send error notification asynchronously to avoid blocking
|
|
857
|
+
self.acp_agent.tasks.create_task(
|
|
858
|
+
self._send_error_notification(f"❌ Command error: {e}"),
|
|
859
|
+
name=f"command_error_notification_{self.session_id}",
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
def register_update_callback(self, callback: Callable[[], None]) -> None:
|
|
863
|
+
"""Register callback for command updates.
|
|
864
|
+
|
|
865
|
+
Args:
|
|
866
|
+
callback: Function to call when commands are updated
|
|
867
|
+
"""
|
|
868
|
+
self._update_callbacks.append(callback)
|
|
869
|
+
|
|
870
|
+
def create_mcp_command(self, prompt: MCPClientPrompt) -> Command:
|
|
871
|
+
"""Convert MCP prompt to slashed Command.
|
|
872
|
+
|
|
873
|
+
Args:
|
|
874
|
+
prompt: MCP prompt to wrap
|
|
875
|
+
session: ACP session for execution context
|
|
876
|
+
|
|
877
|
+
Returns:
|
|
878
|
+
Slashed Command that executes the prompt
|
|
879
|
+
"""
|
|
880
|
+
|
|
881
|
+
async def execute_prompt(
|
|
882
|
+
ctx: CommandContext[AgentContext],
|
|
883
|
+
args: list[str],
|
|
884
|
+
kwargs: dict[str, str],
|
|
885
|
+
) -> None:
|
|
886
|
+
"""Execute the MCP prompt with parsed arguments."""
|
|
887
|
+
# Map parsed args to prompt parameters
|
|
888
|
+
|
|
889
|
+
result = {}
|
|
890
|
+
# Map positional args to prompt parameter names
|
|
891
|
+
for i, arg_value in enumerate(args):
|
|
892
|
+
if i < len(prompt.arguments):
|
|
893
|
+
param_name = prompt.arguments[i]["name"]
|
|
894
|
+
result[param_name] = arg_value
|
|
895
|
+
result.update(kwargs)
|
|
896
|
+
try:
|
|
897
|
+
# Get prompt components
|
|
898
|
+
components = await prompt.get_components(result or None)
|
|
899
|
+
self.staged_content.add(components)
|
|
900
|
+
# Send confirmation
|
|
901
|
+
staged_count = len(self.staged_content)
|
|
902
|
+
await ctx.print(f"✅ Prompt {prompt.name!r} staged ({staged_count} total parts)")
|
|
903
|
+
|
|
904
|
+
except Exception as e:
|
|
905
|
+
logger.exception("MCP prompt execution failed", prompt=prompt.name)
|
|
906
|
+
await ctx.print(f"❌ Prompt error: {e}")
|
|
907
|
+
|
|
908
|
+
usage_hint = (
|
|
909
|
+
" ".join(f"<{arg['name']}>" for arg in prompt.arguments) if prompt.arguments else None
|
|
910
|
+
)
|
|
911
|
+
return Command(
|
|
912
|
+
execute_func=execute_prompt,
|
|
913
|
+
name=prompt.name,
|
|
914
|
+
description=prompt.description or f"MCP prompt: {prompt.name}",
|
|
915
|
+
category="mcp",
|
|
916
|
+
usage=usage_hint,
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
def create_prompt_hub_command(
|
|
920
|
+
self, provider: str, name: str, manager: PromptManager
|
|
921
|
+
) -> Command:
|
|
922
|
+
"""Convert prompt hub prompt to slash command.
|
|
923
|
+
|
|
924
|
+
Args:
|
|
925
|
+
provider: Provider name (e.g., 'langfuse', 'builtin')
|
|
926
|
+
name: Prompt name
|
|
927
|
+
manager: PromptManager instance
|
|
928
|
+
|
|
929
|
+
Returns:
|
|
930
|
+
Command that executes the prompt hub prompt
|
|
931
|
+
"""
|
|
932
|
+
|
|
933
|
+
async def execute_prompt(
|
|
934
|
+
ctx: CommandContext[Any],
|
|
935
|
+
args: list[str],
|
|
936
|
+
kwargs: dict[str, str],
|
|
937
|
+
) -> None:
|
|
938
|
+
"""Execute the prompt hub prompt with parsed arguments."""
|
|
939
|
+
try:
|
|
940
|
+
# Build reference string
|
|
941
|
+
reference = f"{provider}:{name}" if provider != "builtin" else name
|
|
942
|
+
|
|
943
|
+
# Add variables as query parameters if provided
|
|
944
|
+
if kwargs:
|
|
945
|
+
params = "&".join(f"{k}={v}" for k, v in kwargs.items())
|
|
946
|
+
reference = f"{reference}?{params}"
|
|
947
|
+
# Get the rendered prompt
|
|
948
|
+
result = await manager.get(reference)
|
|
949
|
+
self.staged_content.add([UserPromptPart(content=result)])
|
|
950
|
+
# Send confirmation
|
|
951
|
+
staged_count = len(self.staged_content)
|
|
952
|
+
await ctx.print(
|
|
953
|
+
f"✅ Prompt {name!r} from {provider} staged ({staged_count} total parts)"
|
|
954
|
+
)
|
|
955
|
+
|
|
956
|
+
except Exception as e:
|
|
957
|
+
logger.exception("Prompt hub execution failed", prompt=name, provider=provider)
|
|
958
|
+
await ctx.print(f"❌ Prompt error: {e}")
|
|
959
|
+
|
|
960
|
+
# Create command name - prefix with provider if not builtin
|
|
961
|
+
command_name = f"{provider}_{name}" if provider != "builtin" else name
|
|
962
|
+
|
|
963
|
+
return Command(
|
|
964
|
+
execute_func=execute_prompt,
|
|
965
|
+
name=command_name,
|
|
966
|
+
description=f"Prompt hub: {provider}:{name}",
|
|
967
|
+
category="prompts",
|
|
968
|
+
usage="[key=value ...]", # Generic since we don't have parameter schemas
|
|
969
|
+
)
|