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,1021 @@
|
|
|
1
|
+
"""ClaudeCodeAgent - Native Claude Agent SDK integration.
|
|
2
|
+
|
|
3
|
+
This module provides an agent implementation that wraps the Claude Agent SDK's
|
|
4
|
+
ClaudeSDKClient for native integration with agentpool.
|
|
5
|
+
|
|
6
|
+
The ClaudeCodeAgent acts as a client to the Claude Code CLI, enabling:
|
|
7
|
+
- Bidirectional streaming communication
|
|
8
|
+
- Tool permission handling via callbacks
|
|
9
|
+
- Integration with agentpool's event system
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
```python
|
|
13
|
+
async with ClaudeCodeAgent(
|
|
14
|
+
name="claude_coder",
|
|
15
|
+
cwd="/path/to/project",
|
|
16
|
+
allowed_tools=["Read", "Write", "Bash"],
|
|
17
|
+
) as agent:
|
|
18
|
+
async for event in agent.run_stream("Write a hello world program"):
|
|
19
|
+
print(event)
|
|
20
|
+
```
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import asyncio
|
|
26
|
+
from decimal import Decimal
|
|
27
|
+
from typing import TYPE_CHECKING, Any, Self, cast
|
|
28
|
+
import uuid
|
|
29
|
+
|
|
30
|
+
import anyio
|
|
31
|
+
from pydantic import TypeAdapter
|
|
32
|
+
from pydantic_ai import (
|
|
33
|
+
ModelRequest,
|
|
34
|
+
ModelResponse,
|
|
35
|
+
PartDeltaEvent,
|
|
36
|
+
PartEndEvent,
|
|
37
|
+
PartStartEvent,
|
|
38
|
+
RunUsage,
|
|
39
|
+
TextPart,
|
|
40
|
+
TextPartDelta,
|
|
41
|
+
ThinkingPart,
|
|
42
|
+
ThinkingPartDelta,
|
|
43
|
+
ToolCallPart,
|
|
44
|
+
ToolReturnPart,
|
|
45
|
+
UserPromptPart,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
from agentpool.agents.base_agent import BaseAgent
|
|
49
|
+
from agentpool.agents.claude_code_agent.converters import claude_message_to_events
|
|
50
|
+
from agentpool.agents.events import (
|
|
51
|
+
RunErrorEvent,
|
|
52
|
+
RunStartedEvent,
|
|
53
|
+
StreamCompleteEvent,
|
|
54
|
+
ToolCallCompleteEvent,
|
|
55
|
+
ToolCallStartEvent,
|
|
56
|
+
)
|
|
57
|
+
from agentpool.log import get_logger
|
|
58
|
+
from agentpool.messaging import ChatMessage
|
|
59
|
+
from agentpool.messaging.messages import TokenCost
|
|
60
|
+
from agentpool.messaging.processing import prepare_prompts
|
|
61
|
+
from agentpool.models.claude_code_agents import ClaudeCodeAgentConfig
|
|
62
|
+
from agentpool.utils.streams import merge_queue_into_iterator
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if TYPE_CHECKING:
|
|
66
|
+
from collections.abc import AsyncIterator, Sequence
|
|
67
|
+
from types import TracebackType
|
|
68
|
+
|
|
69
|
+
from claude_agent_sdk import (
|
|
70
|
+
ClaudeAgentOptions,
|
|
71
|
+
ClaudeSDKClient,
|
|
72
|
+
McpServerConfig,
|
|
73
|
+
PermissionMode,
|
|
74
|
+
PermissionResult,
|
|
75
|
+
ToolPermissionContext,
|
|
76
|
+
ToolUseBlock,
|
|
77
|
+
)
|
|
78
|
+
from evented.configs import EventConfig
|
|
79
|
+
from exxec import ExecutionEnvironment
|
|
80
|
+
from toprompt import AnyPromptType
|
|
81
|
+
|
|
82
|
+
from agentpool.agents.context import AgentContext
|
|
83
|
+
from agentpool.agents.events import RichAgentStreamEvent
|
|
84
|
+
from agentpool.common_types import (
|
|
85
|
+
BuiltinEventHandlerType,
|
|
86
|
+
IndividualEventHandler,
|
|
87
|
+
PromptCompatible,
|
|
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.talk.stats import MessageStats
|
|
93
|
+
from agentpool.ui.base import InputProvider
|
|
94
|
+
from agentpool_config.mcp_server import MCPServerConfig
|
|
95
|
+
from agentpool_config.nodes import ToolConfirmationMode
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
logger = get_logger(__name__)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ClaudeCodeAgent[TDeps = None, TResult = str](BaseAgent[TDeps, TResult]):
|
|
102
|
+
"""Agent wrapping Claude Agent SDK's ClaudeSDKClient.
|
|
103
|
+
|
|
104
|
+
This provides native integration with Claude Code, enabling:
|
|
105
|
+
- Bidirectional streaming for interactive conversations
|
|
106
|
+
- Tool permission handling via can_use_tool callback
|
|
107
|
+
- Full access to Claude Code's capabilities (file ops, terminals, etc.)
|
|
108
|
+
|
|
109
|
+
The agent manages:
|
|
110
|
+
- ClaudeSDKClient lifecycle (connect on enter, disconnect on exit)
|
|
111
|
+
- Event conversion from Claude SDK to agentpool events
|
|
112
|
+
- Tool confirmation via input provider
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
*,
|
|
118
|
+
config: ClaudeCodeAgentConfig | None = None,
|
|
119
|
+
name: str | None = None,
|
|
120
|
+
description: str | None = None,
|
|
121
|
+
display_name: str | None = None,
|
|
122
|
+
cwd: str | None = None,
|
|
123
|
+
allowed_tools: list[str] | None = None,
|
|
124
|
+
disallowed_tools: list[str] | None = None,
|
|
125
|
+
system_prompt: str | Sequence[str] | None = None,
|
|
126
|
+
include_builtin_system_prompt: bool = True,
|
|
127
|
+
model: str | None = None,
|
|
128
|
+
max_turns: int | None = None,
|
|
129
|
+
max_thinking_tokens: int | None = None,
|
|
130
|
+
permission_mode: PermissionMode | None = None,
|
|
131
|
+
mcp_servers: Sequence[MCPServerConfig] | None = None,
|
|
132
|
+
environment: dict[str, str] | None = None,
|
|
133
|
+
add_dir: list[str] | None = None,
|
|
134
|
+
builtin_tools: list[str] | None = None,
|
|
135
|
+
fallback_model: str | None = None,
|
|
136
|
+
dangerously_skip_permissions: bool = False,
|
|
137
|
+
env: ExecutionEnvironment | None = None,
|
|
138
|
+
input_provider: InputProvider | None = None,
|
|
139
|
+
agent_pool: AgentPool[Any] | None = None,
|
|
140
|
+
enable_logging: bool = True,
|
|
141
|
+
event_configs: Sequence[EventConfig] | None = None,
|
|
142
|
+
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
|
|
143
|
+
tool_confirmation_mode: ToolConfirmationMode = "always",
|
|
144
|
+
output_type: type[TResult] | None = None,
|
|
145
|
+
) -> None:
|
|
146
|
+
"""Initialize ClaudeCodeAgent.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
config: Configuration object (alternative to individual kwargs)
|
|
150
|
+
name: Agent name
|
|
151
|
+
description: Agent description
|
|
152
|
+
display_name: Display name for UI
|
|
153
|
+
cwd: Working directory for Claude Code
|
|
154
|
+
allowed_tools: List of allowed tool names
|
|
155
|
+
disallowed_tools: List of disallowed tool names
|
|
156
|
+
system_prompt: System prompt - string or list (appended to builtin by default)
|
|
157
|
+
include_builtin_system_prompt: If True, the builtin system prompt is included.
|
|
158
|
+
model: Model to use (e.g., "claude-sonnet-4-5")
|
|
159
|
+
max_turns: Maximum conversation turns
|
|
160
|
+
max_thinking_tokens: Max tokens for extended thinking
|
|
161
|
+
permission_mode: Permission mode ("default", "acceptEdits", "plan", "bypassPermissions")
|
|
162
|
+
mcp_servers: External MCP servers to connect to (internal format, converted at runtime)
|
|
163
|
+
environment: Environment variables for the agent process
|
|
164
|
+
add_dir: Additional directories to allow tool access to
|
|
165
|
+
builtin_tools: Available tools from Claude Code's built-in set (empty list disables all)
|
|
166
|
+
fallback_model: Fallback model when default is overloaded
|
|
167
|
+
dangerously_skip_permissions: Bypass all permission checks (sandboxed only)
|
|
168
|
+
env: Execution environment
|
|
169
|
+
input_provider: Provider for user input/confirmations
|
|
170
|
+
agent_pool: Agent pool for multi-agent coordination
|
|
171
|
+
enable_logging: Whether to enable logging
|
|
172
|
+
event_configs: Event configuration
|
|
173
|
+
event_handlers: Event handlers for streaming events
|
|
174
|
+
tool_confirmation_mode: Tool confirmation behavior
|
|
175
|
+
output_type: Type for structured output (uses JSON schema)
|
|
176
|
+
"""
|
|
177
|
+
from agentpool.agents.sys_prompts import SystemPrompts
|
|
178
|
+
|
|
179
|
+
# Build config from kwargs if not provided
|
|
180
|
+
if config is None:
|
|
181
|
+
config = ClaudeCodeAgentConfig(
|
|
182
|
+
name=name or "claude_code",
|
|
183
|
+
description=description,
|
|
184
|
+
display_name=display_name,
|
|
185
|
+
cwd=cwd,
|
|
186
|
+
model=model,
|
|
187
|
+
allowed_tools=allowed_tools,
|
|
188
|
+
disallowed_tools=disallowed_tools,
|
|
189
|
+
system_prompt=system_prompt,
|
|
190
|
+
include_builtin_system_prompt=include_builtin_system_prompt,
|
|
191
|
+
max_turns=max_turns,
|
|
192
|
+
max_thinking_tokens=max_thinking_tokens,
|
|
193
|
+
permission_mode=permission_mode,
|
|
194
|
+
mcp_servers=list(mcp_servers) if mcp_servers else [],
|
|
195
|
+
env=environment,
|
|
196
|
+
add_dir=add_dir,
|
|
197
|
+
builtin_tools=builtin_tools,
|
|
198
|
+
fallback_model=fallback_model,
|
|
199
|
+
dangerously_skip_permissions=dangerously_skip_permissions,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
super().__init__(
|
|
203
|
+
name=name or config.name or "claude_code",
|
|
204
|
+
description=description or config.description,
|
|
205
|
+
display_name=display_name or config.display_name,
|
|
206
|
+
agent_pool=agent_pool,
|
|
207
|
+
enable_logging=enable_logging,
|
|
208
|
+
event_configs=event_configs or list(config.triggers),
|
|
209
|
+
env=env,
|
|
210
|
+
input_provider=input_provider,
|
|
211
|
+
output_type=output_type or str, # type: ignore[arg-type]
|
|
212
|
+
tool_confirmation_mode=tool_confirmation_mode,
|
|
213
|
+
event_handlers=event_handlers,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
self._config = config
|
|
217
|
+
self._cwd = cwd or config.cwd
|
|
218
|
+
self._allowed_tools = allowed_tools or config.allowed_tools
|
|
219
|
+
self._disallowed_tools = disallowed_tools or config.disallowed_tools
|
|
220
|
+
self._include_builtin_system_prompt = (
|
|
221
|
+
include_builtin_system_prompt and config.include_builtin_system_prompt
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Initialize SystemPrompts manager
|
|
225
|
+
# Normalize system_prompt to a list
|
|
226
|
+
all_prompts: list[AnyPromptType] = []
|
|
227
|
+
prompt_source = system_prompt if system_prompt is not None else config.system_prompt
|
|
228
|
+
if prompt_source is not None:
|
|
229
|
+
if isinstance(prompt_source, str):
|
|
230
|
+
all_prompts.append(prompt_source)
|
|
231
|
+
else:
|
|
232
|
+
all_prompts.extend(prompt_source)
|
|
233
|
+
prompt_manager = agent_pool.manifest.prompt_manager if agent_pool else None
|
|
234
|
+
self.sys_prompts = SystemPrompts(all_prompts, prompt_manager=prompt_manager)
|
|
235
|
+
self._model = model or config.model
|
|
236
|
+
self._max_turns = max_turns or config.max_turns
|
|
237
|
+
self._max_thinking_tokens = max_thinking_tokens or config.max_thinking_tokens
|
|
238
|
+
self._permission_mode: PermissionMode | None = permission_mode or config.permission_mode
|
|
239
|
+
self._external_mcp_servers = list(mcp_servers) if mcp_servers else config.get_mcp_servers()
|
|
240
|
+
self._environment = environment or config.env
|
|
241
|
+
self._add_dir = add_dir or config.add_dir
|
|
242
|
+
self._builtin_tools = builtin_tools if builtin_tools is not None else config.builtin_tools
|
|
243
|
+
self._fallback_model = fallback_model or config.fallback_model
|
|
244
|
+
self._dangerously_skip_permissions = (
|
|
245
|
+
dangerously_skip_permissions or config.dangerously_skip_permissions
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Client state
|
|
249
|
+
self._client: ClaudeSDKClient | None = None
|
|
250
|
+
self._current_model: str | None = self._model
|
|
251
|
+
self.deps_type = type(None)
|
|
252
|
+
|
|
253
|
+
# ToolBridge state for exposing toolsets via MCP
|
|
254
|
+
self._tool_bridge: ToolManagerBridge | None = None
|
|
255
|
+
self._owns_bridge = False # Track if we created the bridge (for cleanup)
|
|
256
|
+
self._mcp_servers: dict[str, McpServerConfig] = {} # Claude SDK MCP server configs
|
|
257
|
+
|
|
258
|
+
def get_context(self, data: Any = None) -> AgentContext:
|
|
259
|
+
"""Create a new context for this agent.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
data: Optional custom data to attach to the context
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
A new AgentContext instance
|
|
266
|
+
"""
|
|
267
|
+
from agentpool.agents import AgentContext
|
|
268
|
+
from agentpool.models import AgentsManifest
|
|
269
|
+
|
|
270
|
+
defn = self.agent_pool.manifest if self.agent_pool else AgentsManifest()
|
|
271
|
+
return AgentContext(
|
|
272
|
+
node=self, pool=self.agent_pool, config=self._config, definition=defn, data=data
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def _convert_mcp_servers_to_sdk_format(self) -> dict[str, McpServerConfig]:
|
|
276
|
+
"""Convert internal MCPServerConfig to Claude SDK format.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Dict mapping server names to SDK-compatible config dicts
|
|
280
|
+
"""
|
|
281
|
+
from claude_agent_sdk import McpServerConfig
|
|
282
|
+
|
|
283
|
+
from agentpool_config.mcp_server import (
|
|
284
|
+
SSEMCPServerConfig,
|
|
285
|
+
StdioMCPServerConfig,
|
|
286
|
+
StreamableHTTPMCPServerConfig,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
result: dict[str, McpServerConfig] = {}
|
|
290
|
+
|
|
291
|
+
for idx, server in enumerate(self._external_mcp_servers):
|
|
292
|
+
# Determine server name
|
|
293
|
+
if server.name:
|
|
294
|
+
name = server.name
|
|
295
|
+
elif isinstance(server, StdioMCPServerConfig) and server.args:
|
|
296
|
+
name = server.args[-1].split("/")[-1].split("@")[0]
|
|
297
|
+
elif isinstance(server, StdioMCPServerConfig):
|
|
298
|
+
name = server.command
|
|
299
|
+
elif isinstance(server, SSEMCPServerConfig | StreamableHTTPMCPServerConfig):
|
|
300
|
+
from urllib.parse import urlparse
|
|
301
|
+
|
|
302
|
+
name = urlparse(str(server.url)).hostname or f"server_{idx}"
|
|
303
|
+
else:
|
|
304
|
+
name = f"server_{idx}"
|
|
305
|
+
|
|
306
|
+
# Build SDK-compatible config
|
|
307
|
+
config: dict[str, Any]
|
|
308
|
+
match server:
|
|
309
|
+
case StdioMCPServerConfig(command=command, args=args):
|
|
310
|
+
config = {"type": "stdio", "command": command, "args": args}
|
|
311
|
+
if server.env:
|
|
312
|
+
config["env"] = server.get_env_vars()
|
|
313
|
+
case SSEMCPServerConfig(url=url):
|
|
314
|
+
config = {"type": "sse", "url": str(url)}
|
|
315
|
+
if server.headers:
|
|
316
|
+
config["headers"] = server.headers
|
|
317
|
+
case StreamableHTTPMCPServerConfig(url=url):
|
|
318
|
+
config = {"type": "http", "url": str(url)}
|
|
319
|
+
if server.headers:
|
|
320
|
+
config["headers"] = server.headers
|
|
321
|
+
|
|
322
|
+
result[name] = cast(McpServerConfig, config)
|
|
323
|
+
|
|
324
|
+
return result
|
|
325
|
+
|
|
326
|
+
async def _setup_toolsets(self) -> None:
|
|
327
|
+
"""Initialize toolsets from config and create bridge if needed.
|
|
328
|
+
|
|
329
|
+
Creates providers from toolset configs, adds them to the tool manager,
|
|
330
|
+
and starts an MCP bridge to expose them to Claude Code via the SDK's
|
|
331
|
+
native MCP support. Also converts external MCP servers to SDK format.
|
|
332
|
+
"""
|
|
333
|
+
from agentpool.mcp_server.tool_bridge import BridgeConfig, ToolManagerBridge
|
|
334
|
+
|
|
335
|
+
# Convert external MCP servers to SDK format first
|
|
336
|
+
if self._external_mcp_servers:
|
|
337
|
+
external_configs = self._convert_mcp_servers_to_sdk_format()
|
|
338
|
+
self._mcp_servers.update(external_configs)
|
|
339
|
+
self.log.info("External MCP servers configured", server_count=len(external_configs))
|
|
340
|
+
|
|
341
|
+
if not self._config.toolsets:
|
|
342
|
+
return
|
|
343
|
+
|
|
344
|
+
# Create providers from toolset configs and add to tool manager
|
|
345
|
+
for toolset_config in self._config.toolsets:
|
|
346
|
+
provider = toolset_config.get_provider()
|
|
347
|
+
self.tools.add_provider(provider)
|
|
348
|
+
|
|
349
|
+
# Auto-create bridge to expose tools via MCP
|
|
350
|
+
config = BridgeConfig(
|
|
351
|
+
transport="streamable-http", server_name=f"agentpool-{self.name}-tools"
|
|
352
|
+
)
|
|
353
|
+
self._tool_bridge = ToolManagerBridge(node=self, config=config)
|
|
354
|
+
await self._tool_bridge.start()
|
|
355
|
+
self._owns_bridge = True
|
|
356
|
+
|
|
357
|
+
# Get Claude SDK-compatible MCP config and merge into our servers dict
|
|
358
|
+
mcp_config = self._tool_bridge.get_claude_mcp_server_config()
|
|
359
|
+
self._mcp_servers.update(mcp_config)
|
|
360
|
+
self.log.info("Toolsets initialized", toolset_count=len(self._config.toolsets))
|
|
361
|
+
|
|
362
|
+
async def add_tool_bridge(self, bridge: ToolManagerBridge) -> None:
|
|
363
|
+
"""Add an external tool bridge to expose its tools via MCP.
|
|
364
|
+
|
|
365
|
+
The bridge must already be started. Its MCP server config will be
|
|
366
|
+
added to the Claude SDK options. Use this for bridges created externally
|
|
367
|
+
(e.g., from AgentPool). For toolsets defined in config, bridges
|
|
368
|
+
are created automatically.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
bridge: Started ToolManagerBridge instance
|
|
372
|
+
"""
|
|
373
|
+
if self._tool_bridge is None: # Don't replace our own bridge
|
|
374
|
+
self._tool_bridge = bridge
|
|
375
|
+
|
|
376
|
+
# Get Claude SDK-compatible config and merge
|
|
377
|
+
mcp_config = bridge.get_claude_mcp_server_config()
|
|
378
|
+
self._mcp_servers.update(mcp_config)
|
|
379
|
+
self.log.info("Added external tool bridge", server_name=bridge.config.server_name)
|
|
380
|
+
|
|
381
|
+
async def _cleanup_bridge(self) -> None:
|
|
382
|
+
"""Clean up tool bridge resources."""
|
|
383
|
+
if self._tool_bridge and self._owns_bridge:
|
|
384
|
+
await self._tool_bridge.stop()
|
|
385
|
+
self._tool_bridge = None
|
|
386
|
+
self._owns_bridge = False
|
|
387
|
+
self._mcp_servers.clear()
|
|
388
|
+
|
|
389
|
+
@property
|
|
390
|
+
def model_name(self) -> str | None:
|
|
391
|
+
"""Get the model name."""
|
|
392
|
+
return self._current_model
|
|
393
|
+
|
|
394
|
+
def _build_options(self, *, formatted_system_prompt: str | None = None) -> ClaudeAgentOptions:
|
|
395
|
+
"""Build ClaudeAgentOptions from runtime state.
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
formatted_system_prompt: Pre-formatted system prompt from SystemPrompts manager
|
|
399
|
+
"""
|
|
400
|
+
from claude_agent_sdk import ClaudeAgentOptions
|
|
401
|
+
from claude_agent_sdk.types import SystemPromptPreset
|
|
402
|
+
|
|
403
|
+
# Build system prompt value
|
|
404
|
+
system_prompt: str | SystemPromptPreset | None = None
|
|
405
|
+
if formatted_system_prompt:
|
|
406
|
+
if self._include_builtin_system_prompt:
|
|
407
|
+
# Use SystemPromptPreset to append to builtin prompt
|
|
408
|
+
system_prompt = SystemPromptPreset(
|
|
409
|
+
type="preset",
|
|
410
|
+
preset="claude_code",
|
|
411
|
+
append=formatted_system_prompt,
|
|
412
|
+
)
|
|
413
|
+
else:
|
|
414
|
+
system_prompt = formatted_system_prompt
|
|
415
|
+
|
|
416
|
+
# Determine effective permission mode
|
|
417
|
+
permission_mode = self._permission_mode
|
|
418
|
+
if self._dangerously_skip_permissions and not permission_mode:
|
|
419
|
+
permission_mode = "bypassPermissions"
|
|
420
|
+
|
|
421
|
+
# Determine can_use_tool callback
|
|
422
|
+
bypass = permission_mode == "bypassPermissions" or self._dangerously_skip_permissions
|
|
423
|
+
can_use_tool = (
|
|
424
|
+
self._can_use_tool if self.tool_confirmation_mode != "never" and not bypass else None
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
# Build structured output format if needed
|
|
428
|
+
output_format: dict[str, Any] | None = None
|
|
429
|
+
if self._output_type is not str:
|
|
430
|
+
adapter = TypeAdapter(self._output_type)
|
|
431
|
+
schema = adapter.json_schema()
|
|
432
|
+
output_format = {"type": "json_schema", "schema": schema}
|
|
433
|
+
|
|
434
|
+
return ClaudeAgentOptions(
|
|
435
|
+
cwd=self._cwd,
|
|
436
|
+
allowed_tools=self._allowed_tools or [],
|
|
437
|
+
disallowed_tools=self._disallowed_tools or [],
|
|
438
|
+
system_prompt=system_prompt,
|
|
439
|
+
model=self._model,
|
|
440
|
+
max_turns=self._max_turns,
|
|
441
|
+
max_thinking_tokens=self._max_thinking_tokens,
|
|
442
|
+
permission_mode=permission_mode,
|
|
443
|
+
env=self._environment or {},
|
|
444
|
+
add_dirs=self._add_dir or [], # type: ignore[arg-type] # SDK uses list not Sequence
|
|
445
|
+
tools=self._builtin_tools,
|
|
446
|
+
fallback_model=self._fallback_model,
|
|
447
|
+
can_use_tool=can_use_tool,
|
|
448
|
+
output_format=output_format,
|
|
449
|
+
mcp_servers=self._mcp_servers or {},
|
|
450
|
+
include_partial_messages=True,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
async def _can_use_tool( # noqa: PLR0911
|
|
454
|
+
self,
|
|
455
|
+
tool_name: str,
|
|
456
|
+
input_data: dict[str, Any],
|
|
457
|
+
context: ToolPermissionContext,
|
|
458
|
+
) -> PermissionResult:
|
|
459
|
+
"""Handle tool permission requests.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
tool_name: Name of the tool being called
|
|
463
|
+
input_data: Tool input arguments
|
|
464
|
+
context: Permission context with suggestions
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
PermissionResult indicating allow or deny
|
|
468
|
+
"""
|
|
469
|
+
from claude_agent_sdk import PermissionResultAllow, PermissionResultDeny
|
|
470
|
+
|
|
471
|
+
from agentpool.tools.base import Tool
|
|
472
|
+
|
|
473
|
+
# Auto-grant if confirmation mode is "never"
|
|
474
|
+
if self.tool_confirmation_mode == "never":
|
|
475
|
+
return PermissionResultAllow()
|
|
476
|
+
|
|
477
|
+
# Auto-grant tools from our own bridge - they already show ToolCallStartEvent in UI
|
|
478
|
+
# Bridge tools are named like: mcp__agentpool-{agent_name}-tools__{tool}
|
|
479
|
+
if self._tool_bridge:
|
|
480
|
+
bridge_prefix = f"mcp__{self._tool_bridge.config.server_name}__"
|
|
481
|
+
if tool_name.startswith(bridge_prefix):
|
|
482
|
+
return PermissionResultAllow()
|
|
483
|
+
|
|
484
|
+
# Use input provider if available
|
|
485
|
+
if self._input_provider:
|
|
486
|
+
# Create a dummy Tool for the confirmation dialog
|
|
487
|
+
desc = f"Claude Code tool: {tool_name}"
|
|
488
|
+
tool = Tool(callable=lambda: None, name=tool_name, description=desc)
|
|
489
|
+
result = await self._input_provider.get_tool_confirmation(
|
|
490
|
+
context=self.get_context(),
|
|
491
|
+
tool=tool,
|
|
492
|
+
args=input_data,
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
match result:
|
|
496
|
+
case "allow":
|
|
497
|
+
return PermissionResultAllow()
|
|
498
|
+
case "skip":
|
|
499
|
+
return PermissionResultDeny(message="User skipped tool execution")
|
|
500
|
+
case "abort_run" | "abort_chain":
|
|
501
|
+
return PermissionResultDeny(message="User aborted execution", interrupt=True)
|
|
502
|
+
case _:
|
|
503
|
+
return PermissionResultDeny(message="Unknown confirmation result")
|
|
504
|
+
|
|
505
|
+
# Default: deny if no input provider
|
|
506
|
+
return PermissionResultDeny(message="No input provider configured")
|
|
507
|
+
|
|
508
|
+
async def __aenter__(self) -> Self:
|
|
509
|
+
"""Connect to Claude Code."""
|
|
510
|
+
from claude_agent_sdk import ClaudeSDKClient
|
|
511
|
+
|
|
512
|
+
await super().__aenter__()
|
|
513
|
+
await self._setup_toolsets() # Setup toolsets before building opts (they add MCP servers)
|
|
514
|
+
formatted_prompt = await self.sys_prompts.format_system_prompt(self)
|
|
515
|
+
options = self._build_options(formatted_system_prompt=formatted_prompt)
|
|
516
|
+
self._client = ClaudeSDKClient(options=options)
|
|
517
|
+
await self._client.connect()
|
|
518
|
+
self.log.info("Claude Code client connected")
|
|
519
|
+
return self
|
|
520
|
+
|
|
521
|
+
async def __aexit__(
|
|
522
|
+
self,
|
|
523
|
+
exc_type: type[BaseException] | None,
|
|
524
|
+
exc_val: BaseException | None,
|
|
525
|
+
exc_tb: TracebackType | None,
|
|
526
|
+
) -> None:
|
|
527
|
+
"""Disconnect from Claude Code."""
|
|
528
|
+
# Clean up tool bridge first
|
|
529
|
+
await self._cleanup_bridge()
|
|
530
|
+
if self._client:
|
|
531
|
+
try:
|
|
532
|
+
await self._client.disconnect()
|
|
533
|
+
self.log.info("Claude Code client disconnected")
|
|
534
|
+
except Exception:
|
|
535
|
+
self.log.exception("Error disconnecting Claude Code client")
|
|
536
|
+
self._client = None
|
|
537
|
+
await super().__aexit__(exc_type, exc_val, exc_tb)
|
|
538
|
+
|
|
539
|
+
async def run(
|
|
540
|
+
self,
|
|
541
|
+
*prompts: PromptCompatible,
|
|
542
|
+
message_id: str | None = None,
|
|
543
|
+
input_provider: InputProvider | None = None,
|
|
544
|
+
message_history: MessageHistory | None = None,
|
|
545
|
+
) -> ChatMessage[TResult]:
|
|
546
|
+
"""Execute prompt against Claude Code.
|
|
547
|
+
|
|
548
|
+
Args:
|
|
549
|
+
prompts: Prompts to send
|
|
550
|
+
message_id: Optional message ID for the returned message
|
|
551
|
+
input_provider: Optional input provider for permission requests
|
|
552
|
+
message_history: Optional MessageHistory to use instead of agent's own
|
|
553
|
+
|
|
554
|
+
Returns:
|
|
555
|
+
ChatMessage containing the agent's response
|
|
556
|
+
"""
|
|
557
|
+
final_message: ChatMessage[TResult] | None = None
|
|
558
|
+
async for event in self.run_stream(
|
|
559
|
+
*prompts,
|
|
560
|
+
message_id=message_id,
|
|
561
|
+
input_provider=input_provider,
|
|
562
|
+
message_history=message_history,
|
|
563
|
+
):
|
|
564
|
+
if isinstance(event, StreamCompleteEvent):
|
|
565
|
+
final_message = event.message
|
|
566
|
+
|
|
567
|
+
if final_message is None:
|
|
568
|
+
msg = "No final message received from stream"
|
|
569
|
+
raise RuntimeError(msg)
|
|
570
|
+
|
|
571
|
+
return final_message
|
|
572
|
+
|
|
573
|
+
async def run_stream( # noqa: PLR0915
|
|
574
|
+
self,
|
|
575
|
+
*prompts: PromptCompatible,
|
|
576
|
+
message_id: str | None = None,
|
|
577
|
+
input_provider: InputProvider | None = None,
|
|
578
|
+
message_history: MessageHistory | None = None,
|
|
579
|
+
) -> AsyncIterator[RichAgentStreamEvent[TResult]]:
|
|
580
|
+
"""Stream events from Claude Code execution.
|
|
581
|
+
|
|
582
|
+
Args:
|
|
583
|
+
prompts: Prompts to send
|
|
584
|
+
message_id: Optional message ID for the final message
|
|
585
|
+
input_provider: Optional input provider for permission requests
|
|
586
|
+
message_history: Optional MessageHistory to use instead of agent's own
|
|
587
|
+
|
|
588
|
+
Yields:
|
|
589
|
+
RichAgentStreamEvent instances during execution
|
|
590
|
+
"""
|
|
591
|
+
from claude_agent_sdk import (
|
|
592
|
+
AssistantMessage,
|
|
593
|
+
Message,
|
|
594
|
+
ResultMessage,
|
|
595
|
+
TextBlock,
|
|
596
|
+
ThinkingBlock,
|
|
597
|
+
ToolResultBlock,
|
|
598
|
+
ToolUseBlock as ToolUseBlockType,
|
|
599
|
+
UserMessage,
|
|
600
|
+
)
|
|
601
|
+
from claude_agent_sdk.types import StreamEvent
|
|
602
|
+
|
|
603
|
+
# Reset cancellation state
|
|
604
|
+
self._cancelled = False
|
|
605
|
+
self._current_stream_task = asyncio.current_task()
|
|
606
|
+
|
|
607
|
+
# Update input provider if provided
|
|
608
|
+
if input_provider is not None:
|
|
609
|
+
self._input_provider = input_provider
|
|
610
|
+
|
|
611
|
+
if not self._client:
|
|
612
|
+
msg = "Agent not initialized - use async context manager"
|
|
613
|
+
raise RuntimeError(msg)
|
|
614
|
+
|
|
615
|
+
conversation = message_history if message_history is not None else self.conversation
|
|
616
|
+
# Prepare prompts
|
|
617
|
+
user_msg, processed_prompts, _original_message = await prepare_prompts(*prompts)
|
|
618
|
+
# Get pending parts from conversation (staged content)
|
|
619
|
+
pending_parts = conversation.get_pending_parts()
|
|
620
|
+
# Combine pending parts with new prompts, then join into single string for Claude SDK
|
|
621
|
+
all_parts = [*pending_parts, *processed_prompts]
|
|
622
|
+
prompt_text = " ".join(str(p) for p in all_parts)
|
|
623
|
+
run_id = str(uuid.uuid4())
|
|
624
|
+
# Emit run started
|
|
625
|
+
run_started = RunStartedEvent(
|
|
626
|
+
thread_id=self.conversation_id,
|
|
627
|
+
run_id=run_id,
|
|
628
|
+
agent_name=self.name,
|
|
629
|
+
)
|
|
630
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
631
|
+
await handler(None, run_started)
|
|
632
|
+
yield run_started
|
|
633
|
+
request = ModelRequest(parts=[UserPromptPart(content=prompt_text)])
|
|
634
|
+
model_messages: list[ModelResponse | ModelRequest] = [request]
|
|
635
|
+
current_response_parts: list[TextPart | ThinkingPart | ToolCallPart] = []
|
|
636
|
+
text_chunks: list[str] = []
|
|
637
|
+
pending_tool_calls: dict[str, ToolUseBlock] = {}
|
|
638
|
+
|
|
639
|
+
try:
|
|
640
|
+
await self._client.query(prompt_text)
|
|
641
|
+
# Merge SDK messages with event queue for real-time tool event streaming
|
|
642
|
+
async with merge_queue_into_iterator(
|
|
643
|
+
self._client.receive_response(), self._event_queue
|
|
644
|
+
) as merged_events:
|
|
645
|
+
async for event_or_message in merged_events:
|
|
646
|
+
# Check if it's a queued event (from tools via EventEmitter)
|
|
647
|
+
if not isinstance(event_or_message, Message):
|
|
648
|
+
# It's an event from the queue - yield it immediately
|
|
649
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
650
|
+
await handler(None, event_or_message)
|
|
651
|
+
yield event_or_message
|
|
652
|
+
continue
|
|
653
|
+
|
|
654
|
+
message = event_or_message
|
|
655
|
+
# Process assistant messages - extract parts incrementally
|
|
656
|
+
if isinstance(message, AssistantMessage):
|
|
657
|
+
# Update model name from first assistant message
|
|
658
|
+
if message.model:
|
|
659
|
+
self._current_model = message.model
|
|
660
|
+
for block in message.content:
|
|
661
|
+
match block:
|
|
662
|
+
case TextBlock(text=text):
|
|
663
|
+
text_chunks.append(text)
|
|
664
|
+
current_response_parts.append(TextPart(content=text))
|
|
665
|
+
case ThinkingBlock(thinking=thinking):
|
|
666
|
+
current_response_parts.append(ThinkingPart(content=thinking))
|
|
667
|
+
case ToolUseBlockType(id=tc_id, name=name, input=input_data):
|
|
668
|
+
pending_tool_calls[tc_id] = block
|
|
669
|
+
current_response_parts.append(
|
|
670
|
+
ToolCallPart(
|
|
671
|
+
tool_name=name, args=input_data, tool_call_id=tc_id
|
|
672
|
+
)
|
|
673
|
+
)
|
|
674
|
+
# Emit ToolCallStartEvent with rich display info
|
|
675
|
+
from agentpool.agents.claude_code_agent.converters import (
|
|
676
|
+
derive_rich_tool_info,
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
rich_info = derive_rich_tool_info(name, input_data)
|
|
680
|
+
tool_start_event = ToolCallStartEvent(
|
|
681
|
+
tool_call_id=tc_id,
|
|
682
|
+
tool_name=name,
|
|
683
|
+
title=rich_info.title,
|
|
684
|
+
kind=rich_info.kind,
|
|
685
|
+
locations=rich_info.locations,
|
|
686
|
+
content=rich_info.content,
|
|
687
|
+
raw_input=input_data,
|
|
688
|
+
)
|
|
689
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
690
|
+
await handler(None, tool_start_event)
|
|
691
|
+
yield tool_start_event
|
|
692
|
+
case ToolResultBlock(tool_use_id=tc_id, content=content):
|
|
693
|
+
# Tool result received - flush response parts and add request
|
|
694
|
+
if current_response_parts:
|
|
695
|
+
response = ModelResponse(parts=current_response_parts)
|
|
696
|
+
model_messages.append(response)
|
|
697
|
+
current_response_parts = []
|
|
698
|
+
|
|
699
|
+
# Get tool name from pending calls
|
|
700
|
+
tool_use = pending_tool_calls.pop(tc_id, None)
|
|
701
|
+
tool_name = tool_use.name if tool_use else "unknown"
|
|
702
|
+
tool_input = tool_use.input if tool_use else {}
|
|
703
|
+
tool_done_event = ToolCallCompleteEvent(
|
|
704
|
+
tool_name=tool_name,
|
|
705
|
+
tool_call_id=tc_id,
|
|
706
|
+
tool_input=tool_input,
|
|
707
|
+
tool_result=content,
|
|
708
|
+
agent_name=self.name,
|
|
709
|
+
message_id="",
|
|
710
|
+
)
|
|
711
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
712
|
+
await handler(None, tool_done_event)
|
|
713
|
+
yield tool_done_event
|
|
714
|
+
|
|
715
|
+
# Add tool return as ModelRequest
|
|
716
|
+
part = ToolReturnPart(
|
|
717
|
+
tool_name=tool_name, content=content, tool_call_id=tc_id
|
|
718
|
+
)
|
|
719
|
+
model_messages.append(ModelRequest(parts=[part]))
|
|
720
|
+
|
|
721
|
+
# Process user messages - may contain tool results
|
|
722
|
+
elif isinstance(message, UserMessage):
|
|
723
|
+
user_content = message.content
|
|
724
|
+
user_blocks = (
|
|
725
|
+
[user_content] if isinstance(user_content, str) else user_content
|
|
726
|
+
)
|
|
727
|
+
for user_block in user_blocks:
|
|
728
|
+
if isinstance(user_block, ToolResultBlock):
|
|
729
|
+
tc_id = user_block.tool_use_id
|
|
730
|
+
result_content = user_block.content
|
|
731
|
+
|
|
732
|
+
# Flush response parts
|
|
733
|
+
if current_response_parts:
|
|
734
|
+
model_messages.append(
|
|
735
|
+
ModelResponse(parts=current_response_parts)
|
|
736
|
+
)
|
|
737
|
+
current_response_parts = []
|
|
738
|
+
|
|
739
|
+
# Get tool name from pending calls
|
|
740
|
+
tool_use = pending_tool_calls.pop(tc_id, None)
|
|
741
|
+
tool_name = tool_use.name if tool_use else "unknown"
|
|
742
|
+
tool_input = tool_use.input if tool_use else {}
|
|
743
|
+
# Emit ToolCallCompleteEvent
|
|
744
|
+
tool_complete_event = ToolCallCompleteEvent(
|
|
745
|
+
tool_name=tool_name,
|
|
746
|
+
tool_call_id=tc_id,
|
|
747
|
+
tool_input=tool_input,
|
|
748
|
+
tool_result=result_content,
|
|
749
|
+
agent_name=self.name,
|
|
750
|
+
message_id="",
|
|
751
|
+
)
|
|
752
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
753
|
+
await handler(None, tool_complete_event)
|
|
754
|
+
yield tool_complete_event
|
|
755
|
+
# Add tool return as ModelRequest
|
|
756
|
+
part = ToolReturnPart(
|
|
757
|
+
tool_name=tool_name,
|
|
758
|
+
content=result_content,
|
|
759
|
+
tool_call_id=tc_id,
|
|
760
|
+
)
|
|
761
|
+
model_messages.append(ModelRequest(parts=[part]))
|
|
762
|
+
|
|
763
|
+
# Handle StreamEvent for real-time streaming
|
|
764
|
+
elif isinstance(message, StreamEvent):
|
|
765
|
+
event_data = message.event
|
|
766
|
+
event_type = event_data.get("type")
|
|
767
|
+
index = event_data.get("index", 0)
|
|
768
|
+
|
|
769
|
+
# Handle content_block_start events
|
|
770
|
+
if event_type == "content_block_start":
|
|
771
|
+
content_block = event_data.get("content_block", {})
|
|
772
|
+
block_type = content_block.get("type")
|
|
773
|
+
|
|
774
|
+
if block_type == "text":
|
|
775
|
+
start_event = PartStartEvent(index=index, part=TextPart(content=""))
|
|
776
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
777
|
+
await handler(None, start_event)
|
|
778
|
+
yield start_event
|
|
779
|
+
|
|
780
|
+
elif block_type == "thinking":
|
|
781
|
+
thinking_part = ThinkingPart(content="")
|
|
782
|
+
start_event = PartStartEvent(index=index, part=thinking_part)
|
|
783
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
784
|
+
await handler(None, start_event)
|
|
785
|
+
yield start_event
|
|
786
|
+
|
|
787
|
+
elif block_type == "tool_use":
|
|
788
|
+
# Tool use start is handled via AssistantMessage ToolUseBlock
|
|
789
|
+
pass
|
|
790
|
+
|
|
791
|
+
# Handle content_block_delta events (text streaming)
|
|
792
|
+
elif event_type == "content_block_delta":
|
|
793
|
+
delta = event_data.get("delta", {})
|
|
794
|
+
delta_type = delta.get("type")
|
|
795
|
+
|
|
796
|
+
if delta_type == "text_delta":
|
|
797
|
+
text_delta = delta.get("text", "")
|
|
798
|
+
if text_delta:
|
|
799
|
+
text_part = TextPartDelta(content_delta=text_delta)
|
|
800
|
+
delta_event = PartDeltaEvent(index=index, delta=text_part)
|
|
801
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
802
|
+
await handler(None, delta_event)
|
|
803
|
+
yield delta_event
|
|
804
|
+
|
|
805
|
+
elif delta_type == "thinking_delta":
|
|
806
|
+
thinking_delta = delta.get("thinking", "")
|
|
807
|
+
if thinking_delta:
|
|
808
|
+
delta = ThinkingPartDelta(content_delta=thinking_delta)
|
|
809
|
+
delta_event = PartDeltaEvent(index=index, delta=delta)
|
|
810
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
811
|
+
await handler(None, delta_event)
|
|
812
|
+
yield delta_event
|
|
813
|
+
|
|
814
|
+
# Handle content_block_stop events
|
|
815
|
+
elif event_type == "content_block_stop":
|
|
816
|
+
# We don't have the full part content here, emit with empty part
|
|
817
|
+
# The actual content was accumulated via deltas
|
|
818
|
+
end_event = PartEndEvent(index=index, part=TextPart(content=""))
|
|
819
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
820
|
+
await handler(None, end_event)
|
|
821
|
+
yield end_event
|
|
822
|
+
|
|
823
|
+
# Skip further processing for StreamEvent - don't duplicate
|
|
824
|
+
continue
|
|
825
|
+
|
|
826
|
+
# Convert to events and yield
|
|
827
|
+
# (skip AssistantMessage - already streamed via StreamEvent)
|
|
828
|
+
if not isinstance(message, AssistantMessage):
|
|
829
|
+
events = claude_message_to_events(
|
|
830
|
+
message,
|
|
831
|
+
agent_name=self.name,
|
|
832
|
+
pending_tool_calls={}, # Already handled above
|
|
833
|
+
)
|
|
834
|
+
for event in events:
|
|
835
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
836
|
+
await handler(None, event)
|
|
837
|
+
yield event
|
|
838
|
+
|
|
839
|
+
# Check for result (end of response) and capture usage info
|
|
840
|
+
if isinstance(message, ResultMessage):
|
|
841
|
+
result_message = message
|
|
842
|
+
break
|
|
843
|
+
|
|
844
|
+
# Check for cancellation
|
|
845
|
+
if self._cancelled:
|
|
846
|
+
self.log.info("Stream cancelled by user")
|
|
847
|
+
# Emit partial response
|
|
848
|
+
response_msg = ChatMessage[TResult](
|
|
849
|
+
content="".join(text_chunks), # type: ignore[arg-type]
|
|
850
|
+
role="assistant",
|
|
851
|
+
name=self.name,
|
|
852
|
+
message_id=message_id or str(uuid.uuid4()),
|
|
853
|
+
conversation_id=self.conversation_id,
|
|
854
|
+
model_name=self.model_name,
|
|
855
|
+
messages=model_messages,
|
|
856
|
+
finish_reason="stop",
|
|
857
|
+
)
|
|
858
|
+
complete_event = StreamCompleteEvent(message=response_msg)
|
|
859
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
860
|
+
await handler(None, complete_event)
|
|
861
|
+
yield complete_event
|
|
862
|
+
return
|
|
863
|
+
else:
|
|
864
|
+
result_message = None
|
|
865
|
+
|
|
866
|
+
except asyncio.CancelledError:
|
|
867
|
+
self.log.info("Stream cancelled via CancelledError")
|
|
868
|
+
# Emit partial response on cancellation
|
|
869
|
+
response_msg = ChatMessage[TResult](
|
|
870
|
+
content="".join(text_chunks), # type: ignore[arg-type]
|
|
871
|
+
role="assistant",
|
|
872
|
+
name=self.name,
|
|
873
|
+
message_id=message_id or str(uuid.uuid4()),
|
|
874
|
+
conversation_id=self.conversation_id,
|
|
875
|
+
model_name=self.model_name,
|
|
876
|
+
messages=model_messages,
|
|
877
|
+
finish_reason="stop",
|
|
878
|
+
)
|
|
879
|
+
complete_event = StreamCompleteEvent(message=response_msg)
|
|
880
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
881
|
+
await handler(None, complete_event)
|
|
882
|
+
yield complete_event
|
|
883
|
+
return
|
|
884
|
+
|
|
885
|
+
except Exception as e:
|
|
886
|
+
error_event = RunErrorEvent(message=str(e), run_id=run_id, agent_name=self.name)
|
|
887
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
888
|
+
await handler(None, error_event)
|
|
889
|
+
yield error_event
|
|
890
|
+
raise
|
|
891
|
+
|
|
892
|
+
# Flush any remaining response parts
|
|
893
|
+
if current_response_parts:
|
|
894
|
+
model_messages.append(ModelResponse(parts=current_response_parts))
|
|
895
|
+
|
|
896
|
+
# Determine final content - use structured output if available
|
|
897
|
+
final_content: TResult = (
|
|
898
|
+
result_message.structured_output # type: ignore[assignment]
|
|
899
|
+
if self._output_type is not str and result_message and result_message.structured_output
|
|
900
|
+
else "".join(text_chunks)
|
|
901
|
+
)
|
|
902
|
+
|
|
903
|
+
# Build cost_info from ResultMessage if available
|
|
904
|
+
cost_info: TokenCost | None = None
|
|
905
|
+
if result_message and result_message.usage:
|
|
906
|
+
usage = result_message.usage
|
|
907
|
+
run_usage = RunUsage(
|
|
908
|
+
input_tokens=usage.get("input_tokens", 0),
|
|
909
|
+
output_tokens=usage.get("output_tokens", 0),
|
|
910
|
+
cache_read_tokens=usage.get("cache_read_input_tokens", 0),
|
|
911
|
+
cache_write_tokens=usage.get("cache_creation_input_tokens", 0),
|
|
912
|
+
)
|
|
913
|
+
total_cost = Decimal(str(result_message.total_cost_usd or 0))
|
|
914
|
+
cost_info = TokenCost(token_usage=run_usage, total_cost=total_cost)
|
|
915
|
+
|
|
916
|
+
chat_message = ChatMessage[TResult](
|
|
917
|
+
content=final_content,
|
|
918
|
+
role="assistant",
|
|
919
|
+
name=self.name,
|
|
920
|
+
message_id=message_id or str(uuid.uuid4()),
|
|
921
|
+
conversation_id=self.conversation_id,
|
|
922
|
+
model_name=self.model_name,
|
|
923
|
+
messages=model_messages,
|
|
924
|
+
cost_info=cost_info,
|
|
925
|
+
response_time=result_message.duration_ms / 1000 if result_message else None,
|
|
926
|
+
)
|
|
927
|
+
|
|
928
|
+
# Emit stream complete
|
|
929
|
+
complete_event = StreamCompleteEvent[TResult](message=chat_message)
|
|
930
|
+
for handler in self.event_handler._wrapped_handlers:
|
|
931
|
+
await handler(None, complete_event)
|
|
932
|
+
yield complete_event
|
|
933
|
+
# Record to history
|
|
934
|
+
self.message_sent.emit(chat_message)
|
|
935
|
+
conversation.add_chat_messages([user_msg, chat_message])
|
|
936
|
+
|
|
937
|
+
async def run_iter(
|
|
938
|
+
self,
|
|
939
|
+
*prompt_groups: Sequence[PromptCompatible],
|
|
940
|
+
) -> AsyncIterator[ChatMessage[TResult]]:
|
|
941
|
+
"""Run agent sequentially on multiple prompt groups.
|
|
942
|
+
|
|
943
|
+
Args:
|
|
944
|
+
prompt_groups: Groups of prompts to process sequentially
|
|
945
|
+
|
|
946
|
+
Yields:
|
|
947
|
+
Response messages in sequence
|
|
948
|
+
"""
|
|
949
|
+
for prompts in prompt_groups:
|
|
950
|
+
response = await self.run(*prompts)
|
|
951
|
+
yield response
|
|
952
|
+
|
|
953
|
+
async def interrupt(self) -> None:
|
|
954
|
+
"""Interrupt the currently running stream.
|
|
955
|
+
|
|
956
|
+
Calls the Claude SDK's native interrupt() method to stop the query,
|
|
957
|
+
then cancels the local stream task.
|
|
958
|
+
"""
|
|
959
|
+
self._cancelled = True
|
|
960
|
+
|
|
961
|
+
# Use Claude SDK's native interrupt
|
|
962
|
+
if self._client:
|
|
963
|
+
try:
|
|
964
|
+
await self._client.interrupt()
|
|
965
|
+
self.log.info("Claude Code client interrupted")
|
|
966
|
+
except Exception:
|
|
967
|
+
self.log.exception("Failed to interrupt Claude Code client")
|
|
968
|
+
|
|
969
|
+
# Also cancel the current stream task
|
|
970
|
+
if self._current_stream_task and not self._current_stream_task.done():
|
|
971
|
+
self._current_stream_task.cancel()
|
|
972
|
+
|
|
973
|
+
async def set_model(self, model: str) -> None:
|
|
974
|
+
"""Set the model for future requests.
|
|
975
|
+
|
|
976
|
+
Note: This updates the model for the next query. The client
|
|
977
|
+
maintains the connection, so this takes effect on the next query().
|
|
978
|
+
|
|
979
|
+
Args:
|
|
980
|
+
model: Model name to use
|
|
981
|
+
"""
|
|
982
|
+
self._model = model
|
|
983
|
+
self._current_model = model
|
|
984
|
+
|
|
985
|
+
if self._client:
|
|
986
|
+
await self._client.set_model(model)
|
|
987
|
+
self.log.info("Model changed", model=model)
|
|
988
|
+
|
|
989
|
+
async def set_tool_confirmation_mode(self, mode: ToolConfirmationMode) -> None:
|
|
990
|
+
"""Set tool confirmation mode.
|
|
991
|
+
|
|
992
|
+
Args:
|
|
993
|
+
mode: Confirmation mode - "always", "never", or "per_tool"
|
|
994
|
+
"""
|
|
995
|
+
self.tool_confirmation_mode = mode
|
|
996
|
+
# Update permission mode on client if connected
|
|
997
|
+
if self._client and mode == "never":
|
|
998
|
+
await self._client.set_permission_mode("bypassPermissions")
|
|
999
|
+
elif self._client and mode == "always":
|
|
1000
|
+
await self._client.set_permission_mode("default")
|
|
1001
|
+
|
|
1002
|
+
async def get_stats(self) -> MessageStats:
|
|
1003
|
+
"""Get message statistics."""
|
|
1004
|
+
from agentpool.talk.stats import MessageStats
|
|
1005
|
+
|
|
1006
|
+
return MessageStats(messages=list(self.conversation.chat_messages))
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
if __name__ == "__main__":
|
|
1010
|
+
import os
|
|
1011
|
+
|
|
1012
|
+
os.environ["ANTHROPIC_API_KEY"] = ""
|
|
1013
|
+
|
|
1014
|
+
async def main() -> None:
|
|
1015
|
+
"""Demo: Basic call to Claude Code."""
|
|
1016
|
+
async with ClaudeCodeAgent(name="demo", event_handlers=["detailed"]) as agent:
|
|
1017
|
+
print("Response (streaming): ", end="", flush=True)
|
|
1018
|
+
async for _ in agent.run_stream("What files are in the current directory?"):
|
|
1019
|
+
pass
|
|
1020
|
+
|
|
1021
|
+
anyio.run(main)
|