agentpool 2.1.9__py3-none-any.whl → 2.5.0__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.
- acp/__init__.py +13 -4
- acp/acp_requests.py +20 -77
- acp/agent/connection.py +8 -0
- acp/agent/implementations/debug_server/debug_server.py +6 -2
- acp/agent/protocol.py +6 -0
- acp/bridge/README.md +15 -2
- acp/bridge/__init__.py +3 -2
- acp/bridge/__main__.py +60 -19
- acp/bridge/ws_server.py +173 -0
- acp/bridge/ws_server_cli.py +89 -0
- acp/client/connection.py +38 -29
- acp/client/implementations/default_client.py +3 -2
- acp/client/implementations/headless_client.py +2 -2
- acp/connection.py +2 -2
- acp/notifications.py +20 -50
- acp/schema/__init__.py +2 -0
- acp/schema/agent_responses.py +21 -0
- acp/schema/client_requests.py +3 -3
- acp/schema/session_state.py +63 -29
- acp/stdio.py +39 -9
- acp/task/supervisor.py +2 -2
- acp/transports.py +362 -2
- acp/utils.py +17 -4
- agentpool/__init__.py +6 -1
- agentpool/agents/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +407 -277
- agentpool/agents/acp_agent/acp_converters.py +196 -38
- agentpool/agents/acp_agent/client_handler.py +191 -26
- agentpool/agents/acp_agent/session_state.py +17 -6
- agentpool/agents/agent.py +607 -572
- agentpool/agents/agui_agent/__init__.py +0 -2
- agentpool/agents/agui_agent/agui_agent.py +176 -110
- agentpool/agents/agui_agent/agui_converters.py +0 -131
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +632 -17
- agentpool/agents/claude_code_agent/FORKING.md +191 -0
- agentpool/agents/claude_code_agent/__init__.py +13 -1
- agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
- agentpool/agents/claude_code_agent/converters.py +74 -143
- agentpool/agents/claude_code_agent/history.py +474 -0
- agentpool/agents/claude_code_agent/models.py +77 -0
- agentpool/agents/claude_code_agent/static_info.py +100 -0
- agentpool/agents/claude_code_agent/usage.py +242 -0
- agentpool/agents/context.py +40 -0
- agentpool/agents/events/__init__.py +24 -0
- agentpool/agents/events/builtin_handlers.py +67 -1
- agentpool/agents/events/event_emitter.py +32 -2
- agentpool/agents/events/events.py +104 -3
- agentpool/agents/events/infer_info.py +145 -0
- agentpool/agents/events/processors.py +254 -0
- agentpool/agents/interactions.py +41 -6
- agentpool/agents/modes.py +67 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_call_accumulator.py +213 -0
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +56 -21
- agentpool/config_resources/__init__.py +38 -1
- agentpool/config_resources/acp_assistant.yml +2 -2
- agentpool/config_resources/agents.yml +3 -0
- agentpool/config_resources/agents_template.yml +1 -0
- agentpool/config_resources/claude_code_agent.yml +10 -6
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +136 -289
- agentpool/delegation/team.py +58 -57
- agentpool/delegation/teamrun.py +51 -55
- agentpool/diagnostics/__init__.py +53 -0
- agentpool/diagnostics/lsp_manager.py +1593 -0
- agentpool/diagnostics/lsp_proxy.py +41 -0
- agentpool/diagnostics/lsp_proxy_script.py +229 -0
- agentpool/diagnostics/models.py +398 -0
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/__init__.py +0 -2
- agentpool/mcp_server/client.py +76 -32
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +34 -54
- agentpool/mcp_server/registries/official_registry_client.py +35 -1
- agentpool/mcp_server/tool_bridge.py +186 -139
- agentpool/messaging/__init__.py +0 -2
- agentpool/messaging/compaction.py +72 -197
- agentpool/messaging/connection_manager.py +11 -10
- agentpool/messaging/event_manager.py +5 -5
- agentpool/messaging/message_container.py +6 -30
- agentpool/messaging/message_history.py +99 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +54 -35
- agentpool/messaging/processing.py +12 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -24
- agentpool/models/acp_agents/mcp_capable.py +126 -157
- agentpool/models/acp_agents/non_mcp.py +129 -95
- agentpool/models/agents.py +98 -76
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +144 -19
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +113 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/repomap.py +1 -1
- agentpool/resource_providers/__init__.py +11 -1
- agentpool/resource_providers/aggregating.py +56 -5
- agentpool/resource_providers/base.py +70 -4
- agentpool/resource_providers/codemode/code_executor.py +72 -5
- agentpool/resource_providers/codemode/helpers.py +2 -2
- agentpool/resource_providers/codemode/provider.py +64 -12
- agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
- agentpool/resource_providers/codemode/remote_provider.py +9 -12
- agentpool/resource_providers/filtering.py +3 -1
- agentpool/resource_providers/mcp_provider.py +89 -12
- agentpool/resource_providers/plan_provider.py +228 -46
- agentpool/resource_providers/pool.py +7 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +4 -2
- agentpool/sessions/__init__.py +4 -1
- agentpool/sessions/manager.py +33 -5
- agentpool/sessions/models.py +59 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/skills/registry.py +13 -8
- agentpool/storage/manager.py +572 -49
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +538 -20
- agentpool/tool_impls/__init__.py +6 -0
- agentpool/tool_impls/agent_cli/__init__.py +42 -0
- agentpool/tool_impls/agent_cli/tool.py +95 -0
- agentpool/tool_impls/bash/__init__.py +64 -0
- agentpool/tool_impls/bash/helpers.py +35 -0
- agentpool/tool_impls/bash/tool.py +171 -0
- agentpool/tool_impls/delete_path/__init__.py +70 -0
- agentpool/tool_impls/delete_path/tool.py +142 -0
- agentpool/tool_impls/download_file/__init__.py +80 -0
- agentpool/tool_impls/download_file/tool.py +183 -0
- agentpool/tool_impls/execute_code/__init__.py +55 -0
- agentpool/tool_impls/execute_code/tool.py +163 -0
- agentpool/tool_impls/grep/__init__.py +80 -0
- agentpool/tool_impls/grep/tool.py +200 -0
- agentpool/tool_impls/list_directory/__init__.py +73 -0
- agentpool/tool_impls/list_directory/tool.py +197 -0
- agentpool/tool_impls/question/__init__.py +42 -0
- agentpool/tool_impls/question/tool.py +127 -0
- agentpool/tool_impls/read/__init__.py +104 -0
- agentpool/tool_impls/read/tool.py +305 -0
- agentpool/tools/__init__.py +2 -1
- agentpool/tools/base.py +114 -34
- agentpool/tools/manager.py +57 -1
- agentpool/ui/base.py +2 -2
- agentpool/ui/mock_provider.py +2 -2
- agentpool/ui/stdlib_provider.py +2 -2
- agentpool/utils/file_watcher.py +269 -0
- agentpool/utils/identifiers.py +121 -0
- agentpool/utils/pydantic_ai_helpers.py +46 -0
- agentpool/utils/streams.py +616 -2
- agentpool/utils/subprocess_utils.py +155 -0
- agentpool/utils/token_breakdown.py +461 -0
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
- agentpool-2.5.0.dist-info/RECORD +579 -0
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +24 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +100 -21
- agentpool_cli/serve_agui.py +87 -0
- agentpool_cli/serve_opencode.py +119 -0
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +42 -5
- agentpool_commands/agents.py +75 -2
- agentpool_commands/history.py +62 -0
- agentpool_commands/mcp.py +176 -0
- agentpool_commands/models.py +56 -3
- agentpool_commands/pool.py +260 -0
- agentpool_commands/session.py +1 -1
- agentpool_commands/text_sharing/__init__.py +119 -0
- agentpool_commands/text_sharing/base.py +123 -0
- agentpool_commands/text_sharing/github_gist.py +80 -0
- agentpool_commands/text_sharing/opencode.py +462 -0
- agentpool_commands/text_sharing/paste_rs.py +59 -0
- agentpool_commands/text_sharing/pastebin.py +116 -0
- agentpool_commands/text_sharing/shittycodingagent.py +112 -0
- agentpool_commands/tools.py +57 -0
- agentpool_commands/utils.py +80 -30
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -0
- agentpool_config/builtin_tools.py +77 -22
- agentpool_config/commands.py +24 -1
- agentpool_config/compaction.py +258 -0
- agentpool_config/converters.py +1 -1
- agentpool_config/event_handlers.py +42 -0
- agentpool_config/events.py +1 -1
- agentpool_config/forward_targets.py +1 -4
- agentpool_config/jinja.py +3 -3
- agentpool_config/mcp_server.py +132 -6
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +82 -38
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -22
- agentpool_config/toolsets.py +109 -233
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +234 -181
- agentpool_server/acp_server/commands/acp_commands.py +151 -156
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +24 -90
- agentpool_server/acp_server/session.py +173 -331
- agentpool_server/acp_server/session_manager.py +8 -34
- agentpool_server/agui_server/server.py +3 -1
- agentpool_server/mcp_server/server.py +5 -2
- agentpool_server/opencode_server/.rules +95 -0
- agentpool_server/opencode_server/ENDPOINTS.md +401 -0
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +19 -0
- agentpool_server/opencode_server/command_validation.py +172 -0
- agentpool_server/opencode_server/converters.py +975 -0
- agentpool_server/opencode_server/dependencies.py +24 -0
- agentpool_server/opencode_server/input_provider.py +421 -0
- agentpool_server/opencode_server/models/__init__.py +250 -0
- agentpool_server/opencode_server/models/agent.py +53 -0
- agentpool_server/opencode_server/models/app.py +72 -0
- agentpool_server/opencode_server/models/base.py +26 -0
- agentpool_server/opencode_server/models/common.py +23 -0
- agentpool_server/opencode_server/models/config.py +37 -0
- agentpool_server/opencode_server/models/events.py +821 -0
- agentpool_server/opencode_server/models/file.py +88 -0
- agentpool_server/opencode_server/models/mcp.py +44 -0
- agentpool_server/opencode_server/models/message.py +179 -0
- agentpool_server/opencode_server/models/parts.py +323 -0
- agentpool_server/opencode_server/models/provider.py +81 -0
- agentpool_server/opencode_server/models/pty.py +43 -0
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +111 -0
- agentpool_server/opencode_server/routes/__init__.py +29 -0
- agentpool_server/opencode_server/routes/agent_routes.py +473 -0
- agentpool_server/opencode_server/routes/app_routes.py +202 -0
- agentpool_server/opencode_server/routes/config_routes.py +302 -0
- agentpool_server/opencode_server/routes/file_routes.py +571 -0
- agentpool_server/opencode_server/routes/global_routes.py +94 -0
- agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
- agentpool_server/opencode_server/routes/message_routes.py +761 -0
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +300 -0
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +1276 -0
- agentpool_server/opencode_server/routes/tui_routes.py +139 -0
- agentpool_server/opencode_server/server.py +475 -0
- agentpool_server/opencode_server/state.py +151 -0
- agentpool_server/opencode_server/time_utils.py +8 -0
- agentpool_storage/__init__.py +12 -0
- agentpool_storage/base.py +184 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/claude_provider/provider.py +1089 -0
- agentpool_storage/file_provider.py +278 -15
- agentpool_storage/memory_provider.py +193 -12
- agentpool_storage/models.py +3 -0
- agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
- agentpool_storage/opencode_provider/__init__.py +16 -0
- agentpool_storage/opencode_provider/helpers.py +414 -0
- agentpool_storage/opencode_provider/provider.py +895 -0
- agentpool_storage/project_store.py +325 -0
- agentpool_storage/session_store.py +26 -6
- agentpool_storage/sql_provider/__init__.py +4 -2
- agentpool_storage/sql_provider/models.py +48 -0
- agentpool_storage/sql_provider/sql_provider.py +269 -3
- agentpool_storage/sql_provider/utils.py +12 -13
- agentpool_storage/zed_provider/__init__.py +16 -0
- agentpool_storage/zed_provider/helpers.py +281 -0
- agentpool_storage/zed_provider/models.py +130 -0
- agentpool_storage/zed_provider/provider.py +442 -0
- agentpool_storage/zed_provider.py +803 -0
- agentpool_toolsets/__init__.py +0 -2
- agentpool_toolsets/builtin/__init__.py +2 -12
- agentpool_toolsets/builtin/code.py +96 -57
- agentpool_toolsets/builtin/debug.py +118 -48
- agentpool_toolsets/builtin/execution_environment.py +115 -230
- agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
- agentpool_toolsets/builtin/skills.py +9 -4
- agentpool_toolsets/builtin/subagent_tools.py +64 -51
- agentpool_toolsets/builtin/workers.py +4 -2
- agentpool_toolsets/composio_toolset.py +2 -2
- agentpool_toolsets/entry_points.py +3 -1
- agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
- agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
- agentpool_toolsets/fsspec_toolset/grep.py +99 -7
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
- agentpool_toolsets/mcp_discovery/__init__.py +5 -0
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +511 -0
- agentpool_toolsets/mcp_run_toolset.py +87 -12
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- agentpool-2.1.9.dist-info/RECORD +0 -474
- agentpool_config/resources.py +0 -33
- agentpool_server/acp_server/acp_tools.py +0 -43
- agentpool_server/acp_server/commands/spawn.py +0 -210
- agentpool_storage/text_log_provider.py +0 -275
- agentpool_toolsets/builtin/agent_management.py +0 -239
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/history.py +0 -36
- agentpool_toolsets/builtin/integration.py +0 -85
- agentpool_toolsets/builtin/tool_management.py +0 -90
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -12,12 +12,14 @@ and communicating with it via JSON-RPC over stdio. This allows:
|
|
|
12
12
|
|
|
13
13
|
Example:
|
|
14
14
|
```python
|
|
15
|
+
from agentpool.models.acp_agents import ACPAgentConfig
|
|
16
|
+
|
|
15
17
|
config = ACPAgentConfig(
|
|
16
18
|
command="claude-code-acp",
|
|
17
19
|
name="claude_coder",
|
|
18
20
|
cwd="/path/to/project",
|
|
19
21
|
)
|
|
20
|
-
async with ACPAgent(config) as agent:
|
|
22
|
+
async with ACPAgent(config=config) as agent:
|
|
21
23
|
result = await agent.run("Write a hello world program")
|
|
22
24
|
print(result.content)
|
|
23
25
|
```
|
|
@@ -26,10 +28,12 @@ Example:
|
|
|
26
28
|
from __future__ import annotations
|
|
27
29
|
|
|
28
30
|
import asyncio
|
|
31
|
+
from dataclasses import replace
|
|
32
|
+
from importlib.metadata import metadata
|
|
29
33
|
import os
|
|
30
34
|
from pathlib import Path
|
|
31
35
|
import subprocess
|
|
32
|
-
from typing import TYPE_CHECKING, Any, Self
|
|
36
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
33
37
|
import uuid
|
|
34
38
|
|
|
35
39
|
import anyio
|
|
@@ -45,17 +49,26 @@ from pydantic_ai import (
|
|
|
45
49
|
UserPromptPart,
|
|
46
50
|
)
|
|
47
51
|
|
|
48
|
-
from agentpool.agents.acp_agent.acp_converters import convert_to_acp_content, mcp_configs_to_acp
|
|
49
|
-
from agentpool.agents.acp_agent.client_handler import ACPClientHandler
|
|
50
52
|
from agentpool.agents.acp_agent.session_state import ACPSessionState
|
|
51
53
|
from agentpool.agents.base_agent import BaseAgent
|
|
52
|
-
from agentpool.agents.events import
|
|
54
|
+
from agentpool.agents.events import (
|
|
55
|
+
RunStartedEvent,
|
|
56
|
+
StreamCompleteEvent,
|
|
57
|
+
ToolCallCompleteEvent,
|
|
58
|
+
ToolCallStartEvent,
|
|
59
|
+
resolve_event_handlers,
|
|
60
|
+
)
|
|
61
|
+
from agentpool.agents.events.processors import FileTracker
|
|
62
|
+
from agentpool.agents.modes import ModeInfo
|
|
63
|
+
from agentpool.common_types import (
|
|
64
|
+
IndividualEventHandler,
|
|
65
|
+
)
|
|
53
66
|
from agentpool.log import get_logger
|
|
54
67
|
from agentpool.messaging import ChatMessage
|
|
55
|
-
from agentpool.messaging.processing import prepare_prompts
|
|
56
68
|
from agentpool.models.acp_agents import ACPAgentConfig, MCPCapableACPAgentConfig
|
|
57
|
-
from agentpool.talk.stats import MessageStats
|
|
58
69
|
from agentpool.utils.streams import merge_queue_into_iterator
|
|
70
|
+
from agentpool.utils.subprocess_utils import SubprocessError, monitor_process
|
|
71
|
+
from agentpool.utils.token_breakdown import calculate_usage_from_parts
|
|
59
72
|
|
|
60
73
|
|
|
61
74
|
if TYPE_CHECKING:
|
|
@@ -63,28 +76,27 @@ if TYPE_CHECKING:
|
|
|
63
76
|
from types import TracebackType
|
|
64
77
|
|
|
65
78
|
from anyio.abc import Process
|
|
66
|
-
from
|
|
79
|
+
from evented_config import EventConfig
|
|
67
80
|
from exxec import ExecutionEnvironment
|
|
68
|
-
from pydantic_ai import
|
|
69
|
-
from
|
|
81
|
+
from pydantic_ai import UserContent
|
|
82
|
+
from slashed import BaseCommand
|
|
83
|
+
from tokonomics.model_discovery.model_info import ModelInfo
|
|
70
84
|
|
|
71
85
|
from acp.agent.protocol import Agent as ACPAgentProtocol
|
|
72
86
|
from acp.client.connection import ClientSideConnection
|
|
73
87
|
from acp.client.protocol import Client
|
|
74
88
|
from acp.schema import (
|
|
75
|
-
|
|
89
|
+
Implementation,
|
|
76
90
|
RequestPermissionRequest,
|
|
77
91
|
RequestPermissionResponse,
|
|
78
|
-
StopReason,
|
|
79
92
|
)
|
|
80
93
|
from acp.schema.mcp import McpServer
|
|
81
94
|
from agentpool.agents import AgentContext
|
|
95
|
+
from agentpool.agents.acp_agent.client_handler import ACPClientHandler
|
|
82
96
|
from agentpool.agents.events import RichAgentStreamEvent
|
|
97
|
+
from agentpool.agents.modes import ModeCategory
|
|
83
98
|
from agentpool.common_types import (
|
|
84
99
|
BuiltinEventHandlerType,
|
|
85
|
-
IndividualEventHandler,
|
|
86
|
-
PromptCompatible,
|
|
87
|
-
SimpleJsonType,
|
|
88
100
|
)
|
|
89
101
|
from agentpool.delegation import AgentPool
|
|
90
102
|
from agentpool.mcp_server.tool_bridge import ToolManagerBridge
|
|
@@ -97,42 +109,6 @@ logger = get_logger(__name__)
|
|
|
97
109
|
|
|
98
110
|
PROTOCOL_VERSION = 1
|
|
99
111
|
|
|
100
|
-
STOP_REASON_MAP: dict[StopReason, FinishReason] = {
|
|
101
|
-
"end_turn": "stop",
|
|
102
|
-
"max_tokens": "length",
|
|
103
|
-
"max_turn_requests": "length",
|
|
104
|
-
"refusal": "content_filter",
|
|
105
|
-
"cancelled": "error",
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def extract_file_path_from_tool_call(tool_name: str, raw_input: dict[str, Any]) -> str | None:
|
|
110
|
-
"""Extract file path from a tool call if it's a file-writing tool.
|
|
111
|
-
|
|
112
|
-
Uses simple heuristics by default:
|
|
113
|
-
- Tool name contains 'write' or 'edit' (case-insensitive)
|
|
114
|
-
- Input contains 'path' or 'file_path' key
|
|
115
|
-
|
|
116
|
-
Override in subclasses for agent-specific tool naming conventions.
|
|
117
|
-
|
|
118
|
-
Args:
|
|
119
|
-
tool_name: Name of the tool being called
|
|
120
|
-
raw_input: Tool call arguments
|
|
121
|
-
|
|
122
|
-
Returns:
|
|
123
|
-
File path if this is a file-writing tool, None otherwise
|
|
124
|
-
"""
|
|
125
|
-
name_lower = tool_name.lower()
|
|
126
|
-
if "write" not in name_lower and "edit" not in name_lower:
|
|
127
|
-
return None
|
|
128
|
-
|
|
129
|
-
# Try common path argument names
|
|
130
|
-
for key in ("file_path", "path", "filepath", "filename", "file"):
|
|
131
|
-
if key in raw_input and isinstance(val := raw_input[key], str):
|
|
132
|
-
return val
|
|
133
|
-
|
|
134
|
-
return None
|
|
135
|
-
|
|
136
112
|
|
|
137
113
|
class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
138
114
|
"""MessageNode that wraps an external ACP agent subprocess.
|
|
@@ -148,57 +124,17 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
148
124
|
|
|
149
125
|
Supports both blocking `run()` and streaming `run_iter()` execution modes.
|
|
150
126
|
|
|
151
|
-
Example
|
|
127
|
+
Example:
|
|
152
128
|
```python
|
|
129
|
+
# From config
|
|
153
130
|
config = ClaudeACPAgentConfig(cwd="/project", model="sonnet")
|
|
154
|
-
agent = ACPAgent(config, agent_pool=pool)
|
|
155
|
-
```
|
|
131
|
+
agent = ACPAgent(config=config, agent_pool=pool)
|
|
156
132
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
agent = ACPAgent(
|
|
160
|
-
command="claude-code-acp",
|
|
161
|
-
cwd="/project",
|
|
162
|
-
providers=["anthropic"],
|
|
163
|
-
)
|
|
133
|
+
# From kwargs
|
|
134
|
+
agent = ACPAgent(command="claude-code-acp", cwd="/project")
|
|
164
135
|
```
|
|
165
136
|
"""
|
|
166
137
|
|
|
167
|
-
@overload
|
|
168
|
-
def __init__(
|
|
169
|
-
self,
|
|
170
|
-
*,
|
|
171
|
-
config: BaseACPAgentConfig,
|
|
172
|
-
input_provider: InputProvider | None = None,
|
|
173
|
-
agent_pool: AgentPool[Any] | None = None,
|
|
174
|
-
enable_logging: bool = True,
|
|
175
|
-
event_configs: Sequence[EventConfig] | None = None,
|
|
176
|
-
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
|
|
177
|
-
) -> None: ...
|
|
178
|
-
|
|
179
|
-
@overload
|
|
180
|
-
def __init__(
|
|
181
|
-
self,
|
|
182
|
-
*,
|
|
183
|
-
command: str,
|
|
184
|
-
name: str | None = None,
|
|
185
|
-
description: str | None = None,
|
|
186
|
-
display_name: str | None = None,
|
|
187
|
-
args: list[str] | None = None,
|
|
188
|
-
cwd: str | None = None,
|
|
189
|
-
env_vars: dict[str, str] | None = None,
|
|
190
|
-
env: ExecutionEnvironment | None = None,
|
|
191
|
-
allow_file_operations: bool = True,
|
|
192
|
-
allow_terminal: bool = True,
|
|
193
|
-
providers: list[ProviderType] | None = None,
|
|
194
|
-
input_provider: InputProvider | None = None,
|
|
195
|
-
agent_pool: AgentPool[Any] | None = None,
|
|
196
|
-
enable_logging: bool = True,
|
|
197
|
-
event_configs: Sequence[EventConfig] | None = None,
|
|
198
|
-
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
|
|
199
|
-
tool_confirmation_mode: ToolConfirmationMode = "always",
|
|
200
|
-
) -> None: ...
|
|
201
|
-
|
|
202
138
|
def __init__(
|
|
203
139
|
self,
|
|
204
140
|
*,
|
|
@@ -210,16 +146,15 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
210
146
|
args: list[str] | None = None,
|
|
211
147
|
cwd: str | None = None,
|
|
212
148
|
env_vars: dict[str, str] | None = None,
|
|
213
|
-
env: ExecutionEnvironment | None = None,
|
|
214
149
|
allow_file_operations: bool = True,
|
|
215
150
|
allow_terminal: bool = True,
|
|
216
|
-
providers: list[ProviderType] | None = None,
|
|
217
151
|
input_provider: InputProvider | None = None,
|
|
218
152
|
agent_pool: AgentPool[Any] | None = None,
|
|
219
153
|
enable_logging: bool = True,
|
|
220
154
|
event_configs: Sequence[EventConfig] | None = None,
|
|
221
155
|
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
|
|
222
156
|
tool_confirmation_mode: ToolConfirmationMode = "always",
|
|
157
|
+
commands: Sequence[BaseCommand] | None = None,
|
|
223
158
|
) -> None:
|
|
224
159
|
# Build config from kwargs if not provided
|
|
225
160
|
if config is None:
|
|
@@ -237,21 +172,21 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
237
172
|
allow_file_operations=allow_file_operations,
|
|
238
173
|
allow_terminal=allow_terminal,
|
|
239
174
|
requires_tool_confirmation=tool_confirmation_mode,
|
|
240
|
-
providers=list(providers) if providers else [],
|
|
241
175
|
)
|
|
242
176
|
|
|
243
177
|
super().__init__(
|
|
244
178
|
name=name or config.name or config.get_command(),
|
|
245
179
|
description=description or config.description,
|
|
246
|
-
display_name=display_name,
|
|
180
|
+
display_name=display_name or config.display_name,
|
|
247
181
|
mcp_servers=config.mcp_servers,
|
|
248
182
|
agent_pool=agent_pool,
|
|
249
183
|
enable_logging=enable_logging,
|
|
250
184
|
event_configs=event_configs or list(config.triggers),
|
|
251
|
-
env=
|
|
185
|
+
env=config.get_execution_environment(),
|
|
252
186
|
input_provider=input_provider,
|
|
253
187
|
tool_confirmation_mode=tool_confirmation_mode,
|
|
254
188
|
event_handlers=event_handlers,
|
|
189
|
+
commands=commands,
|
|
255
190
|
)
|
|
256
191
|
|
|
257
192
|
# ACP-specific state
|
|
@@ -262,7 +197,7 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
262
197
|
self._process: Process | None = None
|
|
263
198
|
self._connection: ClientSideConnection | None = None
|
|
264
199
|
self._client_handler: ACPClientHandler | None = None
|
|
265
|
-
self.
|
|
200
|
+
self._agent_info: Implementation | None = None
|
|
266
201
|
self._session_id: str | None = None
|
|
267
202
|
self._state: ACPSessionState | None = None
|
|
268
203
|
self.deps_type = type(None)
|
|
@@ -274,6 +209,29 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
274
209
|
# Track the prompt task for cancellation
|
|
275
210
|
self._prompt_task: asyncio.Task[Any] | None = None
|
|
276
211
|
|
|
212
|
+
@classmethod
|
|
213
|
+
def from_config(
|
|
214
|
+
cls,
|
|
215
|
+
config: BaseACPAgentConfig,
|
|
216
|
+
*,
|
|
217
|
+
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
|
|
218
|
+
input_provider: InputProvider | None = None,
|
|
219
|
+
agent_pool: AgentPool[Any] | None = None,
|
|
220
|
+
) -> Self:
|
|
221
|
+
"""Create an ACPAgent from a config object."""
|
|
222
|
+
# Merge config-level handlers with provided handlers
|
|
223
|
+
config_handlers = config.get_event_handlers()
|
|
224
|
+
merged_handlers: list[IndividualEventHandler | BuiltinEventHandlerType] = [
|
|
225
|
+
*config_handlers,
|
|
226
|
+
*(event_handlers or []),
|
|
227
|
+
]
|
|
228
|
+
return cls(
|
|
229
|
+
config=config,
|
|
230
|
+
event_handlers=merged_handlers or None,
|
|
231
|
+
input_provider=input_provider,
|
|
232
|
+
agent_pool=agent_pool,
|
|
233
|
+
)
|
|
234
|
+
|
|
277
235
|
@property
|
|
278
236
|
def client_env(self) -> ExecutionEnvironment:
|
|
279
237
|
"""Execution environment for handling subprocess requests.
|
|
@@ -292,30 +250,31 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
292
250
|
|
|
293
251
|
Args:
|
|
294
252
|
data: Optional custom data to attach to the context
|
|
295
|
-
|
|
296
|
-
Returns:
|
|
297
|
-
A new AgentContext instance
|
|
298
253
|
"""
|
|
299
254
|
from agentpool.agents.context import AgentContext
|
|
300
255
|
from agentpool.models.manifest import AgentsManifest
|
|
301
256
|
|
|
302
257
|
defn = self.agent_pool.manifest if self.agent_pool else AgentsManifest()
|
|
303
258
|
return AgentContext(
|
|
304
|
-
node=self,
|
|
259
|
+
node=self,
|
|
260
|
+
pool=self.agent_pool,
|
|
261
|
+
config=self.config,
|
|
262
|
+
definition=defn,
|
|
263
|
+
input_provider=self._input_provider,
|
|
264
|
+
data=data,
|
|
305
265
|
)
|
|
306
266
|
|
|
307
267
|
async def _setup_toolsets(self) -> None:
|
|
308
268
|
"""Initialize toolsets from config and create bridge if needed."""
|
|
309
269
|
from agentpool.mcp_server.tool_bridge import BridgeConfig, ToolManagerBridge
|
|
310
270
|
|
|
311
|
-
if not isinstance(self.config, MCPCapableACPAgentConfig) or not self.config.
|
|
271
|
+
if not isinstance(self.config, MCPCapableACPAgentConfig) or not self.config.tools:
|
|
312
272
|
return
|
|
313
|
-
# Create providers from
|
|
314
|
-
for
|
|
315
|
-
provider = toolset_config.get_provider()
|
|
273
|
+
# Create providers from tool configs and add to tool manager
|
|
274
|
+
for provider in self.config.get_tool_providers():
|
|
316
275
|
self.tools.add_provider(provider)
|
|
317
276
|
# Auto-create bridge to expose tools via MCP
|
|
318
|
-
config = BridgeConfig(
|
|
277
|
+
config = BridgeConfig(server_name=f"agentpool-{self.name}-tools")
|
|
319
278
|
self._tool_bridge = ToolManagerBridge(node=self, config=config)
|
|
320
279
|
await self._tool_bridge.start()
|
|
321
280
|
self._owns_bridge = True
|
|
@@ -327,9 +286,13 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
327
286
|
"""Start subprocess and initialize ACP connection."""
|
|
328
287
|
await super().__aenter__()
|
|
329
288
|
await self._setup_toolsets() # Setup toolsets before session creation
|
|
330
|
-
await self._start_process()
|
|
331
|
-
|
|
332
|
-
|
|
289
|
+
process = await self._start_process()
|
|
290
|
+
try:
|
|
291
|
+
async with monitor_process(process, context="ACP initialization"):
|
|
292
|
+
await self._initialize()
|
|
293
|
+
await self._create_session()
|
|
294
|
+
except SubprocessError as e:
|
|
295
|
+
raise RuntimeError(str(e)) from e
|
|
333
296
|
await anyio.sleep(0.3) # Small delay to let subprocess fully initialize
|
|
334
297
|
return self
|
|
335
298
|
|
|
@@ -343,8 +306,12 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
343
306
|
await self._cleanup()
|
|
344
307
|
await super().__aexit__(exc_type, exc_val, exc_tb)
|
|
345
308
|
|
|
346
|
-
async def _start_process(self) ->
|
|
347
|
-
"""Start the ACP server subprocess.
|
|
309
|
+
async def _start_process(self) -> Process:
|
|
310
|
+
"""Start the ACP server subprocess.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
The started Process instance
|
|
314
|
+
"""
|
|
348
315
|
prompt_manager = self.agent_pool.manifest.prompt_manager if self.agent_pool else None
|
|
349
316
|
args = await self.config.get_args(prompt_manager)
|
|
350
317
|
cmd = [self.config.get_command(), *args]
|
|
@@ -361,11 +328,13 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
361
328
|
if not self._process.stdin or not self._process.stdout:
|
|
362
329
|
msg = "Failed to create subprocess pipes"
|
|
363
330
|
raise RuntimeError(msg)
|
|
331
|
+
return self._process
|
|
364
332
|
|
|
365
333
|
async def _initialize(self) -> None:
|
|
366
334
|
"""Initialize the ACP connection."""
|
|
367
335
|
from acp.client.connection import ClientSideConnection
|
|
368
336
|
from acp.schema import InitializeRequest
|
|
337
|
+
from agentpool.agents.acp_agent.client_handler import ACPClientHandler
|
|
369
338
|
|
|
370
339
|
if not self._process or not self._process.stdin or not self._process.stdout:
|
|
371
340
|
msg = "Process not started"
|
|
@@ -382,21 +351,24 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
382
351
|
input_stream=self._process.stdin,
|
|
383
352
|
output_stream=self._process.stdout,
|
|
384
353
|
)
|
|
354
|
+
pkg_meta = metadata("agentpool")
|
|
385
355
|
init_request = InitializeRequest.create(
|
|
386
|
-
title="
|
|
387
|
-
version="
|
|
356
|
+
title=pkg_meta["Name"],
|
|
357
|
+
version=pkg_meta["Version"],
|
|
388
358
|
name="agentpool",
|
|
389
359
|
protocol_version=PROTOCOL_VERSION,
|
|
390
360
|
terminal=self.config.allow_terminal,
|
|
391
361
|
read_text_file=self.config.allow_file_operations,
|
|
392
362
|
write_text_file=self.config.allow_file_operations,
|
|
393
363
|
)
|
|
394
|
-
|
|
395
|
-
self.
|
|
364
|
+
init_response = await self._connection.initialize(init_request)
|
|
365
|
+
self._agent_info = init_response.agent_info
|
|
366
|
+
self.log.info("ACP connection initialized", agent_info=self._agent_info)
|
|
396
367
|
|
|
397
368
|
async def _create_session(self) -> None:
|
|
398
369
|
"""Create a new ACP session with configured MCP servers."""
|
|
399
370
|
from acp.schema import NewSessionRequest
|
|
371
|
+
from agentpool.agents.acp_agent.acp_converters import mcp_configs_to_acp
|
|
400
372
|
|
|
401
373
|
if not self._connection:
|
|
402
374
|
msg = "Connection not initialized"
|
|
@@ -415,6 +387,10 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
415
387
|
self._session_id = response.session_id
|
|
416
388
|
if self._state:
|
|
417
389
|
self._state.session_id = self._session_id
|
|
390
|
+
# Store config_options if available (newer ACP protocol)
|
|
391
|
+
if response.config_options:
|
|
392
|
+
self._state.config_options = list(response.config_options)
|
|
393
|
+
# Legacy: Store models and modes for backward compatibility
|
|
418
394
|
if response.models: # Store full model info from session response
|
|
419
395
|
self._state.models = response.models
|
|
420
396
|
self._state.current_model_id = response.models.current_model_id
|
|
@@ -422,10 +398,6 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
422
398
|
model = self._state.current_model_id if self._state else None
|
|
423
399
|
self.log.info("ACP session created", session_id=self._session_id, model=model)
|
|
424
400
|
|
|
425
|
-
def add_mcp_server(self, server: McpServer) -> None:
|
|
426
|
-
"""Add an MCP server to be passed to the next session."""
|
|
427
|
-
self._extra_mcp_servers.append(server)
|
|
428
|
-
|
|
429
401
|
async def add_tool_bridge(self, bridge: ToolManagerBridge) -> None:
|
|
430
402
|
"""Add an external tool bridge to expose its tools via MCP.
|
|
431
403
|
|
|
@@ -476,61 +448,30 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
476
448
|
self.log.exception("Error terminating ACP process")
|
|
477
449
|
self._process = None
|
|
478
450
|
|
|
479
|
-
async def
|
|
480
|
-
self,
|
|
481
|
-
*prompts: PromptCompatible,
|
|
482
|
-
message_id: str | None = None,
|
|
483
|
-
input_provider: InputProvider | None = None,
|
|
484
|
-
message_history: MessageHistory | None = None,
|
|
485
|
-
) -> ChatMessage[str]:
|
|
486
|
-
"""Execute prompt against ACP agent.
|
|
487
|
-
|
|
488
|
-
Args:
|
|
489
|
-
prompts: Prompts to send (will be joined with spaces)
|
|
490
|
-
message_id: Optional message id for the returned message
|
|
491
|
-
input_provider: Optional input provider for permission requests
|
|
492
|
-
message_history: Optional MessageHistory to use instead of agent's own
|
|
493
|
-
|
|
494
|
-
Returns:
|
|
495
|
-
ChatMessage containing the agent's aggregated text response
|
|
496
|
-
"""
|
|
497
|
-
# Collect all events through run_stream
|
|
498
|
-
final_message: ChatMessage[str] | None = None
|
|
499
|
-
async for event in self.run_stream(
|
|
500
|
-
*prompts,
|
|
501
|
-
message_id=message_id,
|
|
502
|
-
input_provider=input_provider,
|
|
503
|
-
message_history=message_history,
|
|
504
|
-
):
|
|
505
|
-
if isinstance(event, StreamCompleteEvent):
|
|
506
|
-
final_message = event.message
|
|
507
|
-
|
|
508
|
-
if final_message is None:
|
|
509
|
-
msg = "No final message received from stream"
|
|
510
|
-
raise RuntimeError(msg)
|
|
511
|
-
|
|
512
|
-
return final_message
|
|
513
|
-
|
|
514
|
-
async def run_stream( # noqa: PLR0915
|
|
451
|
+
async def _stream_events( # noqa: PLR0915
|
|
515
452
|
self,
|
|
516
|
-
|
|
453
|
+
prompts: list[UserContent],
|
|
454
|
+
*,
|
|
455
|
+
user_msg: ChatMessage[Any],
|
|
456
|
+
effective_parent_id: str | None,
|
|
517
457
|
message_id: str | None = None,
|
|
458
|
+
conversation_id: str | None = None,
|
|
459
|
+
parent_id: str | None = None,
|
|
518
460
|
input_provider: InputProvider | None = None,
|
|
519
461
|
message_history: MessageHistory | None = None,
|
|
462
|
+
deps: TDeps | None = None,
|
|
463
|
+
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
|
|
464
|
+
wait_for_connections: bool | None = None,
|
|
465
|
+
store_history: bool = True,
|
|
520
466
|
) -> AsyncIterator[RichAgentStreamEvent[str]]:
|
|
521
|
-
|
|
467
|
+
from anyenv import MultiEventHandler
|
|
522
468
|
|
|
523
|
-
|
|
524
|
-
prompts: Prompts to send (will be joined with spaces)
|
|
525
|
-
message_id: Optional message id for the final message
|
|
526
|
-
input_provider: Optional input provider for permission requests
|
|
527
|
-
message_history: Optional MessageHistory to use instead of agent's own
|
|
528
|
-
|
|
529
|
-
Yields:
|
|
530
|
-
RichAgentStreamEvent instances converted from ACP session updates
|
|
531
|
-
"""
|
|
532
|
-
from acp.schema import PromptRequest
|
|
469
|
+
from acp.schema import ForkSessionRequest, PromptRequest
|
|
533
470
|
from acp.utils import to_acp_content_blocks
|
|
471
|
+
from agentpool.agents.acp_agent.acp_converters import (
|
|
472
|
+
convert_to_acp_content,
|
|
473
|
+
to_finish_reason,
|
|
474
|
+
)
|
|
534
475
|
|
|
535
476
|
# Update input provider if provided
|
|
536
477
|
if input_provider is not None:
|
|
@@ -541,14 +482,18 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
541
482
|
msg = "Agent not initialized - use async context manager"
|
|
542
483
|
raise RuntimeError(msg)
|
|
543
484
|
|
|
544
|
-
# Capture state for use in nested function (avoids type narrowing issues)
|
|
545
|
-
state = self._state
|
|
546
|
-
|
|
547
485
|
conversation = message_history if message_history is not None else self.conversation
|
|
548
|
-
#
|
|
549
|
-
|
|
486
|
+
# Use provided event handlers or fall back to agent's handlers
|
|
487
|
+
if event_handlers is not None:
|
|
488
|
+
handlers = resolve_event_handlers(event_handlers)
|
|
489
|
+
handler = MultiEventHandler[IndividualEventHandler](handlers)
|
|
490
|
+
else:
|
|
491
|
+
handler = self.event_handler
|
|
492
|
+
|
|
493
|
+
# Prepare for ACP content block conversion
|
|
494
|
+
processed_prompts = prompts
|
|
550
495
|
run_id = str(uuid.uuid4())
|
|
551
|
-
|
|
496
|
+
self._state.clear() # Reset state
|
|
552
497
|
# Track messages in pydantic-ai format: ModelRequest -> ModelResponse -> ...
|
|
553
498
|
# This mirrors pydantic-ai's new_messages() which includes the initial user request.
|
|
554
499
|
model_messages: list[ModelResponse | ModelRequest] = []
|
|
@@ -557,25 +502,33 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
557
502
|
model_messages.append(initial_request)
|
|
558
503
|
current_response_parts: list[TextPart | ThinkingPart | ToolCallPart] = []
|
|
559
504
|
text_chunks: list[str] = [] # For final content string
|
|
560
|
-
|
|
505
|
+
file_tracker = FileTracker() # Track files modified by tool calls
|
|
506
|
+
assert self.conversation_id is not None # Initialized by BaseAgent.run_stream()
|
|
561
507
|
run_started = RunStartedEvent(
|
|
562
508
|
thread_id=self.conversation_id,
|
|
563
509
|
run_id=run_id,
|
|
564
510
|
agent_name=self.name,
|
|
565
511
|
)
|
|
566
|
-
|
|
567
|
-
await handler(None, run_started)
|
|
512
|
+
await handler(None, run_started)
|
|
568
513
|
yield run_started
|
|
569
514
|
content_blocks = convert_to_acp_content(processed_prompts)
|
|
570
515
|
pending_parts = conversation.get_pending_parts()
|
|
571
516
|
final_blocks = [*to_acp_content_blocks(pending_parts), *content_blocks]
|
|
572
|
-
prompt_request = PromptRequest(session_id=self._session_id, prompt=final_blocks)
|
|
573
|
-
self.log.debug("Starting streaming prompt", num_blocks=len(final_blocks))
|
|
574
|
-
|
|
575
|
-
# Reset cancellation state
|
|
576
|
-
self._cancelled = False
|
|
577
|
-
self._current_stream_task = asyncio.current_task()
|
|
578
517
|
|
|
518
|
+
# Handle ephemeral execution (fork session if store_history=False)
|
|
519
|
+
session_id = self._session_id
|
|
520
|
+
if not store_history and self._session_id:
|
|
521
|
+
# Fork the current session to execute without affecting main history
|
|
522
|
+
|
|
523
|
+
cwd = self.config.cwd or str(Path.cwd())
|
|
524
|
+
fork_request = ForkSessionRequest(session_id=self._session_id, cwd=cwd)
|
|
525
|
+
fork_response = await self._connection.fork_session(fork_request)
|
|
526
|
+
# Use the forked session ID for this prompt
|
|
527
|
+
session_id = fork_response.session_id
|
|
528
|
+
self.log.debug("Forked session", parent=self._session_id, fork=session_id)
|
|
529
|
+
prompt_request = PromptRequest(session_id=session_id, prompt=final_blocks)
|
|
530
|
+
self.log.debug("Starting streaming prompt", num_blocks=len(final_blocks))
|
|
531
|
+
self._cancelled = False # Reset cancellation state
|
|
579
532
|
# Run prompt in background
|
|
580
533
|
prompt_task = asyncio.create_task(self._connection.prompt(prompt_request))
|
|
581
534
|
self._prompt_task = prompt_task
|
|
@@ -584,6 +537,7 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
584
537
|
async def poll_acp_events() -> AsyncIterator[RichAgentStreamEvent[str]]:
|
|
585
538
|
"""Poll events from ACP state until prompt completes."""
|
|
586
539
|
last_idx = 0
|
|
540
|
+
assert self._state
|
|
587
541
|
while not prompt_task.done():
|
|
588
542
|
if self._client_handler:
|
|
589
543
|
try:
|
|
@@ -595,26 +549,59 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
595
549
|
pass
|
|
596
550
|
|
|
597
551
|
# Yield new events from state
|
|
598
|
-
while last_idx < len(
|
|
599
|
-
yield
|
|
552
|
+
while last_idx < len(self._state.events):
|
|
553
|
+
yield self._state.events[last_idx]
|
|
600
554
|
last_idx += 1
|
|
601
555
|
|
|
602
556
|
# Yield remaining events after prompt completes
|
|
603
|
-
while last_idx < len(
|
|
604
|
-
yield
|
|
557
|
+
while last_idx < len(self._state.events):
|
|
558
|
+
yield self._state.events[last_idx]
|
|
605
559
|
last_idx += 1
|
|
606
560
|
|
|
561
|
+
# Set deps on tool bridge for access during tool invocations
|
|
562
|
+
|
|
563
|
+
# (ContextVar doesn't work because MCP server runs in a separate task)
|
|
564
|
+
if self._tool_bridge:
|
|
565
|
+
self._tool_bridge.current_deps = deps
|
|
566
|
+
|
|
567
|
+
# Accumulate metadata events by tool_call_id (workaround for MCP stripping _meta)
|
|
568
|
+
tool_metadata: dict[str, dict[str, Any]] = {}
|
|
569
|
+
|
|
607
570
|
# Merge ACP events with custom events from queue
|
|
608
571
|
try:
|
|
609
572
|
async with merge_queue_into_iterator(
|
|
610
573
|
poll_acp_events(), self._event_queue
|
|
611
574
|
) as merged_events:
|
|
612
|
-
async for event in merged_events:
|
|
575
|
+
async for event in file_tracker(merged_events):
|
|
576
|
+
# Capture metadata events for correlation with tool results
|
|
577
|
+
from agentpool.agents.events import ToolResultMetadataEvent
|
|
578
|
+
|
|
579
|
+
if isinstance(event, ToolResultMetadataEvent):
|
|
580
|
+
tool_metadata[event.tool_call_id] = event.metadata
|
|
581
|
+
# Don't yield metadata events - they're internal correlation only
|
|
582
|
+
continue
|
|
583
|
+
|
|
613
584
|
# Check for cancellation
|
|
614
585
|
if self._cancelled:
|
|
615
586
|
self.log.info("Stream cancelled by user")
|
|
616
587
|
break
|
|
617
588
|
|
|
589
|
+
# Inject metadata into ToolCallCompleteEvent
|
|
590
|
+
# (converted from completed ToolCallProgress)
|
|
591
|
+
if isinstance(event, ToolCallCompleteEvent):
|
|
592
|
+
# Enrich with agent name and metadata from our accumulator
|
|
593
|
+
enriched_event = event
|
|
594
|
+
if not enriched_event.agent_name:
|
|
595
|
+
enriched_event = replace(enriched_event, agent_name=self.name)
|
|
596
|
+
if (
|
|
597
|
+
enriched_event.metadata is None
|
|
598
|
+
and enriched_event.tool_call_id in tool_metadata
|
|
599
|
+
):
|
|
600
|
+
enriched_event = replace(
|
|
601
|
+
enriched_event, metadata=tool_metadata[enriched_event.tool_call_id]
|
|
602
|
+
)
|
|
603
|
+
event = enriched_event # noqa: PLW2901
|
|
604
|
+
|
|
618
605
|
# Extract content from events and build parts in arrival order
|
|
619
606
|
match event:
|
|
620
607
|
case PartDeltaEvent(delta=TextPartDelta(content_delta=delta)):
|
|
@@ -628,134 +615,142 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
628
615
|
current_response_parts.append(
|
|
629
616
|
ToolCallPart(tool_name=tc_name, args=tc_input, tool_call_id=tc_id)
|
|
630
617
|
)
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
tc_name or "", tc_input or {}
|
|
634
|
-
):
|
|
635
|
-
touched_files.add(file_path)
|
|
636
|
-
|
|
637
|
-
# Distribute to handlers
|
|
638
|
-
for handler in self.event_handler._wrapped_handlers:
|
|
639
|
-
await handler(None, event)
|
|
618
|
+
|
|
619
|
+
await handler(None, event)
|
|
640
620
|
yield event
|
|
641
621
|
except asyncio.CancelledError:
|
|
642
622
|
self.log.info("Stream cancelled via task cancellation")
|
|
643
623
|
self._cancelled = True
|
|
624
|
+
finally:
|
|
625
|
+
# Clear deps from tool bridge
|
|
626
|
+
if self._tool_bridge:
|
|
627
|
+
self._tool_bridge.current_deps = None
|
|
644
628
|
|
|
645
629
|
# Handle cancellation - emit partial message
|
|
646
630
|
if self._cancelled:
|
|
647
|
-
text_content = "".join(text_chunks)
|
|
648
|
-
metadata: SimpleJsonType = {}
|
|
649
|
-
if touched_files:
|
|
650
|
-
metadata["touched_files"] = sorted(touched_files)
|
|
651
631
|
message = ChatMessage[str](
|
|
652
|
-
content=
|
|
632
|
+
content="".join(text_chunks),
|
|
653
633
|
role="assistant",
|
|
654
634
|
name=self.name,
|
|
655
635
|
message_id=message_id or str(uuid.uuid4()),
|
|
656
636
|
conversation_id=self.conversation_id,
|
|
637
|
+
parent_id=user_msg.message_id,
|
|
657
638
|
model_name=self.model_name,
|
|
658
639
|
messages=model_messages,
|
|
659
|
-
metadata=
|
|
640
|
+
metadata=file_tracker.get_metadata(),
|
|
660
641
|
finish_reason="stop",
|
|
661
642
|
)
|
|
662
643
|
complete_event = StreamCompleteEvent(message=message)
|
|
663
|
-
|
|
664
|
-
await handler(None, complete_event)
|
|
644
|
+
await handler(None, complete_event)
|
|
665
645
|
yield complete_event
|
|
666
|
-
self._current_stream_task = None
|
|
667
646
|
self._prompt_task = None
|
|
668
647
|
return
|
|
669
648
|
|
|
670
649
|
# Ensure we catch any exceptions from the prompt task
|
|
671
650
|
response = await prompt_task
|
|
672
|
-
finish_reason
|
|
651
|
+
finish_reason = to_finish_reason(response.stop_reason)
|
|
673
652
|
# Flush response parts to model_messages
|
|
674
653
|
if current_response_parts:
|
|
675
|
-
model_messages.append(
|
|
654
|
+
model_messages.append(
|
|
655
|
+
ModelResponse(
|
|
656
|
+
parts=current_response_parts,
|
|
657
|
+
finish_reason=finish_reason,
|
|
658
|
+
model_name=self.model_name,
|
|
659
|
+
provider_name=self.config.type,
|
|
660
|
+
)
|
|
661
|
+
)
|
|
676
662
|
|
|
677
663
|
text_content = "".join(text_chunks)
|
|
678
|
-
#
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
664
|
+
# Calculate approximate token usage from what we can observe
|
|
665
|
+
input_parts = [*processed_prompts, *pending_parts]
|
|
666
|
+
usage, cost_info = await calculate_usage_from_parts(
|
|
667
|
+
input_parts=input_parts,
|
|
668
|
+
response_parts=current_response_parts,
|
|
669
|
+
text_content=text_content,
|
|
670
|
+
model_name=self.model_name,
|
|
671
|
+
provider=self.config.type,
|
|
672
|
+
)
|
|
673
|
+
|
|
682
674
|
message = ChatMessage[str](
|
|
683
675
|
content=text_content,
|
|
684
676
|
role="assistant",
|
|
685
677
|
name=self.name,
|
|
686
678
|
message_id=message_id or str(uuid.uuid4()),
|
|
687
679
|
conversation_id=self.conversation_id,
|
|
680
|
+
parent_id=user_msg.message_id,
|
|
688
681
|
model_name=self.model_name,
|
|
689
682
|
messages=model_messages,
|
|
690
|
-
metadata=
|
|
683
|
+
metadata=file_tracker.get_metadata(),
|
|
691
684
|
finish_reason=finish_reason,
|
|
685
|
+
usage=usage,
|
|
686
|
+
cost_info=cost_info,
|
|
692
687
|
)
|
|
693
688
|
complete_event = StreamCompleteEvent(message=message)
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
yield complete_event # Emit final StreamCompleteEvent with aggregated message
|
|
697
|
-
self.message_sent.emit(message)
|
|
698
|
-
conversation.add_chat_messages([user_msg, message]) # Record to conversation history
|
|
699
|
-
|
|
700
|
-
async def run_iter(
|
|
701
|
-
self,
|
|
702
|
-
*prompt_groups: Sequence[PromptCompatible],
|
|
703
|
-
) -> AsyncIterator[ChatMessage[str]]:
|
|
704
|
-
"""Run agent sequentially on multiple prompt groups.
|
|
705
|
-
|
|
706
|
-
Args:
|
|
707
|
-
prompt_groups: Groups of prompts to process sequentially
|
|
708
|
-
|
|
709
|
-
Yields:
|
|
710
|
-
Response messages in sequence
|
|
711
|
-
"""
|
|
712
|
-
for prompts in prompt_groups:
|
|
713
|
-
response = await self.run(*prompts)
|
|
714
|
-
yield response
|
|
689
|
+
await handler(None, complete_event)
|
|
690
|
+
yield complete_event # Emit final StreamCompleteEvent - post-processing handled by base
|
|
715
691
|
|
|
716
692
|
@property
|
|
717
693
|
def model_name(self) -> str | None:
|
|
718
694
|
"""Get the model name in a consistent format."""
|
|
719
|
-
if self._state and self._state.current_model_id
|
|
720
|
-
return self._state.current_model_id
|
|
721
|
-
if self._init_response and self._init_response.agent_info:
|
|
722
|
-
return self._init_response.agent_info.name
|
|
723
|
-
return None
|
|
695
|
+
return model_id if self._state and (model_id := self._state.current_model_id) else None
|
|
724
696
|
|
|
725
697
|
async def set_model(self, model: str) -> None:
|
|
726
|
-
"""Update the model
|
|
698
|
+
"""Update the model for the current session via ACP protocol.
|
|
699
|
+
|
|
700
|
+
Attempts to use the ACP protocol to change the model:
|
|
701
|
+
1. If config_options exist with a 'model' category, use set_session_config_option
|
|
702
|
+
2. Otherwise, use legacy set_session_model API
|
|
727
703
|
|
|
728
704
|
Args:
|
|
729
|
-
model: New model
|
|
705
|
+
model: New model ID to use
|
|
730
706
|
|
|
731
707
|
Raises:
|
|
732
|
-
|
|
733
|
-
RuntimeError: If agent is currently processing (has active process but no session)
|
|
708
|
+
RuntimeError: If no active session or remote agent doesn't support model changes
|
|
734
709
|
"""
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
# await self._connection.set_session_model(request)
|
|
740
|
-
# if self._state:
|
|
741
|
-
# self._state.current_model_id = model
|
|
742
|
-
# self.log.info("Model changed via ACP protocol", model=model)
|
|
743
|
-
# return
|
|
744
|
-
|
|
745
|
-
if not hasattr(self.config, "model"):
|
|
746
|
-
msg = f"Config type {type(self.config).__name__} doesn't support model changes"
|
|
747
|
-
raise ValueError(msg)
|
|
748
|
-
# Prevent changes during active processing
|
|
749
|
-
if self._process and not self._session_id:
|
|
750
|
-
msg = "Cannot change model while agent is initializing"
|
|
710
|
+
from acp.schema import SetSessionConfigOptionRequest, SetSessionModelRequest
|
|
711
|
+
|
|
712
|
+
if not self._connection or not self._session_id:
|
|
713
|
+
msg = "Cannot set model: no active session"
|
|
751
714
|
raise RuntimeError(msg)
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
715
|
+
|
|
716
|
+
if not self._state:
|
|
717
|
+
msg = "Cannot set model: no session state"
|
|
718
|
+
raise RuntimeError(msg)
|
|
719
|
+
|
|
720
|
+
# Try using the new unified config options API first
|
|
721
|
+
model_cfg = next((i for i in self._state.config_options if i.category == "model"), None)
|
|
722
|
+
if model_cfg:
|
|
723
|
+
# Use new unified API
|
|
724
|
+
request = SetSessionConfigOptionRequest(
|
|
725
|
+
session_id=self._session_id,
|
|
726
|
+
config_id=model_cfg.id,
|
|
727
|
+
value=model,
|
|
728
|
+
)
|
|
729
|
+
response = await self._connection.set_session_config_option(request)
|
|
730
|
+
if response:
|
|
731
|
+
# Update entire config_options state from response
|
|
732
|
+
self._state.config_options = list(response.config_options)
|
|
733
|
+
self.log.info("Model changed via SessionConfigOption", model=model)
|
|
734
|
+
return
|
|
735
|
+
msg = "set_session_config_option returned no response"
|
|
736
|
+
raise RuntimeError(msg)
|
|
737
|
+
|
|
738
|
+
# Fallback to legacy set_session_model API
|
|
739
|
+
request_legacy = SetSessionModelRequest(session_id=self._session_id, model_id=model)
|
|
740
|
+
response_legacy = await self._connection.set_session_model(request_legacy)
|
|
741
|
+
if response_legacy:
|
|
742
|
+
# Update legacy state
|
|
743
|
+
self._state.current_model_id = model
|
|
744
|
+
self.log.info("Model changed via legacy set_session_model", model=model)
|
|
745
|
+
return
|
|
746
|
+
|
|
747
|
+
# If we get here, the remote agent doesn't support model changes
|
|
748
|
+
msg = (
|
|
749
|
+
"Remote ACP agent does not support model changes. "
|
|
750
|
+
"No config_options with category='model' found and set_session_model "
|
|
751
|
+
"returned no response."
|
|
752
|
+
)
|
|
753
|
+
raise RuntimeError(msg)
|
|
759
754
|
|
|
760
755
|
async def set_tool_confirmation_mode(self, mode: ToolConfirmationMode) -> None:
|
|
761
756
|
"""Set the tool confirmation mode for this agent.
|
|
@@ -790,10 +785,6 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
790
785
|
else:
|
|
791
786
|
self.log.info("Tool confirmation mode changed (local only)", mode=mode)
|
|
792
787
|
|
|
793
|
-
async def get_stats(self) -> MessageStats:
|
|
794
|
-
"""Get message statistics."""
|
|
795
|
-
return MessageStats(messages=list(self.conversation.chat_messages))
|
|
796
|
-
|
|
797
788
|
async def interrupt(self) -> None:
|
|
798
789
|
"""Interrupt the currently running stream.
|
|
799
790
|
|
|
@@ -822,14 +813,153 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
822
813
|
if self._current_stream_task and not self._current_stream_task.done():
|
|
823
814
|
self._current_stream_task.cancel()
|
|
824
815
|
|
|
816
|
+
async def get_available_models(self) -> list[ModelInfo] | None:
|
|
817
|
+
"""Get available models from the ACP session state.
|
|
818
|
+
|
|
819
|
+
Converts ACP ModelInfo to tokonomics ModelInfo format.
|
|
820
|
+
|
|
821
|
+
Returns:
|
|
822
|
+
List of tokonomics ModelInfo, or None if not available
|
|
823
|
+
"""
|
|
824
|
+
from tokonomics.model_discovery.model_info import ModelInfo
|
|
825
|
+
|
|
826
|
+
if not self._state or not self._state.models:
|
|
827
|
+
return None
|
|
828
|
+
|
|
829
|
+
# Convert ACP ModelInfo to tokonomics ModelInfo
|
|
830
|
+
result: list[ModelInfo] = []
|
|
831
|
+
for acp_model in self._state.models.available_models:
|
|
832
|
+
toko_model = ModelInfo(
|
|
833
|
+
id=acp_model.model_id,
|
|
834
|
+
name=acp_model.name,
|
|
835
|
+
description=acp_model.description,
|
|
836
|
+
)
|
|
837
|
+
result.append(toko_model)
|
|
838
|
+
return result
|
|
839
|
+
|
|
840
|
+
async def get_modes(self) -> list[ModeCategory]:
|
|
841
|
+
"""Get available modes from the ACP session state.
|
|
842
|
+
|
|
843
|
+
Passthrough from remote ACP server's mode and model state.
|
|
844
|
+
Prefers new config_options format, falls back to legacy modes/models.
|
|
845
|
+
|
|
846
|
+
Returns:
|
|
847
|
+
List of ModeCategory from remote server, empty if not available
|
|
848
|
+
"""
|
|
849
|
+
from agentpool.agents.acp_agent.acp_converters import get_modes
|
|
850
|
+
|
|
851
|
+
if not self._state:
|
|
852
|
+
return []
|
|
853
|
+
|
|
854
|
+
# Prefer new SessionConfigOption format if available
|
|
855
|
+
return get_modes(
|
|
856
|
+
self._state.config_options,
|
|
857
|
+
available_modes=self._state.modes,
|
|
858
|
+
available_models=self._state.models,
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
async def set_mode(self, mode: ModeInfo | str, category_id: str | None = None) -> None:
|
|
862
|
+
"""Set a mode on the remote ACP server.
|
|
863
|
+
|
|
864
|
+
For ACPAgent, this forwards the mode/model change to the remote ACP server.
|
|
865
|
+
Prefers new set_session_config_option if config_options are available,
|
|
866
|
+
falls back to legacy set_session_mode/set_session_model.
|
|
867
|
+
|
|
868
|
+
Args:
|
|
869
|
+
mode: The mode to set - ModeInfo object or mode ID string
|
|
870
|
+
category_id: Category ID (config option ID)
|
|
871
|
+
|
|
872
|
+
Raises:
|
|
873
|
+
RuntimeError: If not connected to ACP server
|
|
874
|
+
ValueError: If mode is not available
|
|
875
|
+
"""
|
|
876
|
+
from acp.schema import (
|
|
877
|
+
SetSessionConfigOptionRequest,
|
|
878
|
+
SetSessionModelRequest,
|
|
879
|
+
SetSessionModeRequest,
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
# Extract mode_id and category from ModeInfo if provided
|
|
883
|
+
if isinstance(mode, ModeInfo):
|
|
884
|
+
mode_id = mode.id
|
|
885
|
+
category_id = category_id or mode.category_id
|
|
886
|
+
else:
|
|
887
|
+
mode_id = mode
|
|
888
|
+
|
|
889
|
+
if not self._connection or not self._session_id or not self._state:
|
|
890
|
+
msg = "Not connected to ACP server"
|
|
891
|
+
raise RuntimeError(msg)
|
|
892
|
+
|
|
893
|
+
# Validate mode is available
|
|
894
|
+
available_modes = await self.get_modes()
|
|
895
|
+
matching_category = (
|
|
896
|
+
next((c for c in available_modes if c.id == category_id), None) if category_id else None
|
|
897
|
+
)
|
|
898
|
+
|
|
899
|
+
if matching_category:
|
|
900
|
+
valid_ids = {m.id for m in matching_category.available_modes}
|
|
901
|
+
if mode_id not in valid_ids:
|
|
902
|
+
msg = f"Unknown {category_id}: {mode_id}. Available: {valid_ids}"
|
|
903
|
+
raise ValueError(msg)
|
|
904
|
+
elif category_id:
|
|
905
|
+
# Category specified but not found
|
|
906
|
+
available_cats = {c.id for c in available_modes}
|
|
907
|
+
msg = f"Unknown category: {category_id}. Available: {available_cats}"
|
|
908
|
+
raise ValueError(msg)
|
|
909
|
+
else:
|
|
910
|
+
# No category specified and no match found
|
|
911
|
+
msg = "category_id is required when mode is a string"
|
|
912
|
+
raise ValueError(msg)
|
|
913
|
+
|
|
914
|
+
# Prefer new config_options API if available
|
|
915
|
+
if self._state.config_options:
|
|
916
|
+
assert category_id
|
|
917
|
+
config_request = SetSessionConfigOptionRequest(
|
|
918
|
+
session_id=self._session_id,
|
|
919
|
+
config_id=category_id,
|
|
920
|
+
value=mode_id,
|
|
921
|
+
)
|
|
922
|
+
response = await self._connection.set_session_config_option(config_request)
|
|
923
|
+
# Update local state from response
|
|
924
|
+
if response.config_options:
|
|
925
|
+
self._state.config_options = list(response.config_options)
|
|
926
|
+
|
|
927
|
+
self.log.info("ACP server Config option changed", config_id=category_id, value=mode_id)
|
|
928
|
+
return
|
|
929
|
+
|
|
930
|
+
# Legacy: Use old set_session_mode/set_session_model APIs
|
|
931
|
+
if category_id == "permissions":
|
|
932
|
+
mode_request = SetSessionModeRequest(session_id=self._session_id, mode_id=mode_id)
|
|
933
|
+
await self._connection.set_session_mode(mode_request)
|
|
934
|
+
|
|
935
|
+
# Update local state
|
|
936
|
+
if self._state.modes:
|
|
937
|
+
self._state.modes.current_mode_id = mode_id
|
|
938
|
+
|
|
939
|
+
self.log.info("Mode changed on remote ACP server (legacy)", mode_id=mode_id)
|
|
940
|
+
|
|
941
|
+
elif category_id == "model":
|
|
942
|
+
model_request = SetSessionModelRequest(session_id=self._session_id, model_id=mode_id)
|
|
943
|
+
await self._connection.set_session_model(model_request)
|
|
944
|
+
|
|
945
|
+
# Update local state
|
|
946
|
+
if self._state.models:
|
|
947
|
+
self._state.models.current_model_id = mode_id
|
|
948
|
+
|
|
949
|
+
self.log.info("Model changed on remote ACP server (legacy)", model_id=mode_id)
|
|
950
|
+
|
|
951
|
+
else:
|
|
952
|
+
msg = f"Unknown category: {category_id}. Available: permissions, model"
|
|
953
|
+
raise ValueError(msg)
|
|
954
|
+
|
|
825
955
|
|
|
826
956
|
if __name__ == "__main__":
|
|
957
|
+
from agentpool.models.acp_agents import ACPAgentConfig
|
|
827
958
|
|
|
828
959
|
async def main() -> None:
|
|
829
960
|
"""Demo: Basic call to an ACP agent."""
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
async with ACPAgent(command="uv", args=args, cwd=cwd, event_handlers=["detailed"]) as agent:
|
|
961
|
+
config = ACPAgentConfig(command="uv", args=["run", "agentpool", "serve-acp"])
|
|
962
|
+
async with ACPAgent(config=config, event_handlers=["detailed"]) as agent:
|
|
833
963
|
print("Response (streaming): ", end="", flush=True)
|
|
834
964
|
async for chunk in agent.run_stream("Say hello briefly."):
|
|
835
965
|
print(chunk, end="", flush=True)
|