agentpool 2.2.3__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 +0 -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/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 +18 -49
- 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/task/supervisor.py +2 -2
- acp/utils.py +2 -2
- agentpool/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +278 -263
- agentpool/agents/acp_agent/acp_converters.py +150 -17
- agentpool/agents/acp_agent/client_handler.py +35 -24
- agentpool/agents/acp_agent/session_state.py +14 -6
- agentpool/agents/agent.py +471 -643
- agentpool/agents/agui_agent/agui_agent.py +104 -107
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +485 -32
- 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 +654 -334
- agentpool/agents/claude_code_agent/converters.py +4 -141
- 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/events/__init__.py +22 -0
- agentpool/agents/events/builtin_handlers.py +65 -0
- agentpool/agents/events/event_emitter.py +3 -0
- agentpool/agents/events/events.py +84 -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 +13 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +35 -21
- 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 +9 -8
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +104 -265
- agentpool/delegation/team.py +57 -57
- agentpool/delegation/teamrun.py +50 -55
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/client.py +73 -38
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +9 -23
- agentpool/mcp_server/registries/official_registry_client.py +10 -1
- agentpool/mcp_server/tool_bridge.py +114 -79
- 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 +87 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +2 -26
- agentpool/messaging/processing.py +10 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -2
- agentpool/models/acp_agents/mcp_capable.py +124 -15
- agentpool/models/acp_agents/non_mcp.py +0 -23
- agentpool/models/agents.py +66 -66
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +111 -17
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +70 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/resource_providers/__init__.py +2 -0
- agentpool/resource_providers/aggregating.py +4 -2
- agentpool/resource_providers/base.py +13 -3
- 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 +66 -12
- agentpool/resource_providers/plan_provider.py +111 -18
- agentpool/resource_providers/pool.py +5 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +2 -2
- agentpool/sessions/__init__.py +2 -0
- agentpool/sessions/manager.py +2 -3
- agentpool/sessions/models.py +9 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/storage/manager.py +361 -54
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +1 -1
- 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/streams.py +21 -96
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/METADATA +16 -22
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/RECORD +242 -195
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +20 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +59 -1
- agentpool_cli/serve_opencode.py +1 -1
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +12 -5
- agentpool_commands/agents.py +1 -1
- 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/utils.py +31 -32
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -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 +1 -5
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +38 -39
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -28
- agentpool_config/toolsets.py +22 -90
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +125 -56
- agentpool_server/acp_server/commands/acp_commands.py +46 -216
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +8 -7
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +1 -11
- agentpool_server/acp_server/session.py +90 -410
- 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/ENDPOINTS.md +53 -14
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +0 -8
- agentpool_server/opencode_server/converters.py +132 -26
- agentpool_server/opencode_server/input_provider.py +160 -8
- agentpool_server/opencode_server/models/__init__.py +42 -20
- agentpool_server/opencode_server/models/app.py +12 -0
- agentpool_server/opencode_server/models/events.py +203 -29
- agentpool_server/opencode_server/models/mcp.py +19 -0
- agentpool_server/opencode_server/models/message.py +18 -1
- agentpool_server/opencode_server/models/parts.py +134 -1
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +13 -1
- agentpool_server/opencode_server/routes/__init__.py +4 -0
- agentpool_server/opencode_server/routes/agent_routes.py +33 -2
- agentpool_server/opencode_server/routes/app_routes.py +66 -3
- agentpool_server/opencode_server/routes/config_routes.py +66 -5
- agentpool_server/opencode_server/routes/file_routes.py +184 -5
- agentpool_server/opencode_server/routes/global_routes.py +1 -1
- agentpool_server/opencode_server/routes/lsp_routes.py +1 -1
- agentpool_server/opencode_server/routes/message_routes.py +122 -66
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +23 -22
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +139 -68
- agentpool_server/opencode_server/routes/tui_routes.py +1 -1
- agentpool_server/opencode_server/server.py +47 -2
- agentpool_server/opencode_server/state.py +30 -0
- agentpool_storage/__init__.py +0 -4
- agentpool_storage/base.py +81 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/{claude_provider.py → claude_provider/provider.py} +190 -8
- agentpool_storage/file_provider.py +149 -15
- agentpool_storage/memory_provider.py +132 -12
- 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/session_store.py +20 -6
- agentpool_storage/sql_provider/sql_provider.py +135 -2
- agentpool_storage/sql_provider/utils.py +2 -12
- 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 -4
- agentpool_toolsets/builtin/code.py +4 -4
- agentpool_toolsets/builtin/debug.py +115 -40
- agentpool_toolsets/builtin/execution_environment.py +54 -165
- agentpool_toolsets/builtin/skills.py +0 -77
- 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/grep.py +25 -5
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/toolset.py +350 -66
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +74 -17
- agentpool_toolsets/mcp_run_toolset.py +8 -11
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- 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/opencode_provider.py +0 -730
- agentpool_storage/text_log_provider.py +0 -276
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {agentpool-2.2.3.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,20 +49,24 @@ 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
|
|
53
62
|
from agentpool.agents.modes import ModeInfo
|
|
63
|
+
from agentpool.common_types import (
|
|
64
|
+
IndividualEventHandler,
|
|
65
|
+
)
|
|
54
66
|
from agentpool.log import get_logger
|
|
55
67
|
from agentpool.messaging import ChatMessage
|
|
56
|
-
from agentpool.messaging.processing import prepare_prompts
|
|
57
68
|
from agentpool.models.acp_agents import ACPAgentConfig, MCPCapableACPAgentConfig
|
|
58
|
-
from agentpool.utils.streams import
|
|
59
|
-
FileTracker,
|
|
60
|
-
merge_queue_into_iterator,
|
|
61
|
-
)
|
|
69
|
+
from agentpool.utils.streams import merge_queue_into_iterator
|
|
62
70
|
from agentpool.utils.subprocess_utils import SubprocessError, monitor_process
|
|
63
71
|
from agentpool.utils.token_breakdown import calculate_usage_from_parts
|
|
64
72
|
|
|
@@ -68,9 +76,9 @@ if TYPE_CHECKING:
|
|
|
68
76
|
from types import TracebackType
|
|
69
77
|
|
|
70
78
|
from anyio.abc import Process
|
|
71
|
-
from
|
|
79
|
+
from evented_config import EventConfig
|
|
72
80
|
from exxec import ExecutionEnvironment
|
|
73
|
-
from pydantic_ai import
|
|
81
|
+
from pydantic_ai import UserContent
|
|
74
82
|
from slashed import BaseCommand
|
|
75
83
|
from tokonomics.model_discovery.model_info import ModelInfo
|
|
76
84
|
|
|
@@ -78,20 +86,17 @@ if TYPE_CHECKING:
|
|
|
78
86
|
from acp.client.connection import ClientSideConnection
|
|
79
87
|
from acp.client.protocol import Client
|
|
80
88
|
from acp.schema import (
|
|
81
|
-
|
|
89
|
+
Implementation,
|
|
82
90
|
RequestPermissionRequest,
|
|
83
91
|
RequestPermissionResponse,
|
|
84
|
-
StopReason,
|
|
85
92
|
)
|
|
86
93
|
from acp.schema.mcp import McpServer
|
|
87
94
|
from agentpool.agents import AgentContext
|
|
95
|
+
from agentpool.agents.acp_agent.client_handler import ACPClientHandler
|
|
88
96
|
from agentpool.agents.events import RichAgentStreamEvent
|
|
89
97
|
from agentpool.agents.modes import ModeCategory
|
|
90
98
|
from agentpool.common_types import (
|
|
91
99
|
BuiltinEventHandlerType,
|
|
92
|
-
IndividualEventHandler,
|
|
93
|
-
PromptCompatible,
|
|
94
|
-
SimpleJsonType,
|
|
95
100
|
)
|
|
96
101
|
from agentpool.delegation import AgentPool
|
|
97
102
|
from agentpool.mcp_server.tool_bridge import ToolManagerBridge
|
|
@@ -104,14 +109,6 @@ logger = get_logger(__name__)
|
|
|
104
109
|
|
|
105
110
|
PROTOCOL_VERSION = 1
|
|
106
111
|
|
|
107
|
-
STOP_REASON_MAP: dict[StopReason, FinishReason] = {
|
|
108
|
-
"end_turn": "stop",
|
|
109
|
-
"max_tokens": "length",
|
|
110
|
-
"max_turn_requests": "length",
|
|
111
|
-
"refusal": "content_filter",
|
|
112
|
-
"cancelled": "error",
|
|
113
|
-
}
|
|
114
|
-
|
|
115
112
|
|
|
116
113
|
class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
117
114
|
"""MessageNode that wraps an external ACP agent subprocess.
|
|
@@ -127,58 +124,17 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
127
124
|
|
|
128
125
|
Supports both blocking `run()` and streaming `run_iter()` execution modes.
|
|
129
126
|
|
|
130
|
-
Example
|
|
127
|
+
Example:
|
|
131
128
|
```python
|
|
129
|
+
# From config
|
|
132
130
|
config = ClaudeACPAgentConfig(cwd="/project", model="sonnet")
|
|
133
|
-
agent = ACPAgent(config, agent_pool=pool)
|
|
134
|
-
```
|
|
131
|
+
agent = ACPAgent(config=config, agent_pool=pool)
|
|
135
132
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
agent = ACPAgent(
|
|
139
|
-
command="claude-code-acp",
|
|
140
|
-
cwd="/project",
|
|
141
|
-
providers=["anthropic"],
|
|
142
|
-
)
|
|
133
|
+
# From kwargs
|
|
134
|
+
agent = ACPAgent(command="claude-code-acp", cwd="/project")
|
|
143
135
|
```
|
|
144
136
|
"""
|
|
145
137
|
|
|
146
|
-
@overload
|
|
147
|
-
def __init__(
|
|
148
|
-
self,
|
|
149
|
-
*,
|
|
150
|
-
config: BaseACPAgentConfig,
|
|
151
|
-
input_provider: InputProvider | None = None,
|
|
152
|
-
agent_pool: AgentPool[Any] | None = None,
|
|
153
|
-
enable_logging: bool = True,
|
|
154
|
-
event_configs: Sequence[EventConfig] | None = None,
|
|
155
|
-
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
|
|
156
|
-
commands: Sequence[BaseCommand] | None = None,
|
|
157
|
-
) -> None: ...
|
|
158
|
-
|
|
159
|
-
@overload
|
|
160
|
-
def __init__(
|
|
161
|
-
self,
|
|
162
|
-
*,
|
|
163
|
-
command: str,
|
|
164
|
-
name: str | None = None,
|
|
165
|
-
description: str | None = None,
|
|
166
|
-
display_name: str | None = None,
|
|
167
|
-
args: list[str] | None = None,
|
|
168
|
-
cwd: str | None = None,
|
|
169
|
-
env_vars: dict[str, str] | None = None,
|
|
170
|
-
env: ExecutionEnvironment | None = None,
|
|
171
|
-
allow_file_operations: bool = True,
|
|
172
|
-
allow_terminal: bool = True,
|
|
173
|
-
input_provider: InputProvider | None = None,
|
|
174
|
-
agent_pool: AgentPool[Any] | None = None,
|
|
175
|
-
enable_logging: bool = True,
|
|
176
|
-
event_configs: Sequence[EventConfig] | None = None,
|
|
177
|
-
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
|
|
178
|
-
tool_confirmation_mode: ToolConfirmationMode = "always",
|
|
179
|
-
commands: Sequence[BaseCommand] | None = None,
|
|
180
|
-
) -> None: ...
|
|
181
|
-
|
|
182
138
|
def __init__(
|
|
183
139
|
self,
|
|
184
140
|
*,
|
|
@@ -190,7 +146,6 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
190
146
|
args: list[str] | None = None,
|
|
191
147
|
cwd: str | None = None,
|
|
192
148
|
env_vars: dict[str, str] | None = None,
|
|
193
|
-
env: ExecutionEnvironment | None = None,
|
|
194
149
|
allow_file_operations: bool = True,
|
|
195
150
|
allow_terminal: bool = True,
|
|
196
151
|
input_provider: InputProvider | None = None,
|
|
@@ -222,12 +177,12 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
222
177
|
super().__init__(
|
|
223
178
|
name=name or config.name or config.get_command(),
|
|
224
179
|
description=description or config.description,
|
|
225
|
-
display_name=display_name,
|
|
180
|
+
display_name=display_name or config.display_name,
|
|
226
181
|
mcp_servers=config.mcp_servers,
|
|
227
182
|
agent_pool=agent_pool,
|
|
228
183
|
enable_logging=enable_logging,
|
|
229
184
|
event_configs=event_configs or list(config.triggers),
|
|
230
|
-
env=
|
|
185
|
+
env=config.get_execution_environment(),
|
|
231
186
|
input_provider=input_provider,
|
|
232
187
|
tool_confirmation_mode=tool_confirmation_mode,
|
|
233
188
|
event_handlers=event_handlers,
|
|
@@ -242,7 +197,7 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
242
197
|
self._process: Process | None = None
|
|
243
198
|
self._connection: ClientSideConnection | None = None
|
|
244
199
|
self._client_handler: ACPClientHandler | None = None
|
|
245
|
-
self.
|
|
200
|
+
self._agent_info: Implementation | None = None
|
|
246
201
|
self._session_id: str | None = None
|
|
247
202
|
self._state: ACPSessionState | None = None
|
|
248
203
|
self.deps_type = type(None)
|
|
@@ -254,6 +209,29 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
254
209
|
# Track the prompt task for cancellation
|
|
255
210
|
self._prompt_task: asyncio.Task[Any] | None = None
|
|
256
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
|
+
|
|
257
235
|
@property
|
|
258
236
|
def client_env(self) -> ExecutionEnvironment:
|
|
259
237
|
"""Execution environment for handling subprocess requests.
|
|
@@ -272,30 +250,31 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
272
250
|
|
|
273
251
|
Args:
|
|
274
252
|
data: Optional custom data to attach to the context
|
|
275
|
-
|
|
276
|
-
Returns:
|
|
277
|
-
A new AgentContext instance
|
|
278
253
|
"""
|
|
279
254
|
from agentpool.agents.context import AgentContext
|
|
280
255
|
from agentpool.models.manifest import AgentsManifest
|
|
281
256
|
|
|
282
257
|
defn = self.agent_pool.manifest if self.agent_pool else AgentsManifest()
|
|
283
258
|
return AgentContext(
|
|
284
|
-
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,
|
|
285
265
|
)
|
|
286
266
|
|
|
287
267
|
async def _setup_toolsets(self) -> None:
|
|
288
268
|
"""Initialize toolsets from config and create bridge if needed."""
|
|
289
269
|
from agentpool.mcp_server.tool_bridge import BridgeConfig, ToolManagerBridge
|
|
290
270
|
|
|
291
|
-
if not isinstance(self.config, MCPCapableACPAgentConfig) or not self.config.
|
|
271
|
+
if not isinstance(self.config, MCPCapableACPAgentConfig) or not self.config.tools:
|
|
292
272
|
return
|
|
293
|
-
# Create providers from
|
|
294
|
-
for
|
|
295
|
-
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():
|
|
296
275
|
self.tools.add_provider(provider)
|
|
297
276
|
# Auto-create bridge to expose tools via MCP
|
|
298
|
-
config = BridgeConfig(
|
|
277
|
+
config = BridgeConfig(server_name=f"agentpool-{self.name}-tools")
|
|
299
278
|
self._tool_bridge = ToolManagerBridge(node=self, config=config)
|
|
300
279
|
await self._tool_bridge.start()
|
|
301
280
|
self._owns_bridge = True
|
|
@@ -353,10 +332,9 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
353
332
|
|
|
354
333
|
async def _initialize(self) -> None:
|
|
355
334
|
"""Initialize the ACP connection."""
|
|
356
|
-
from importlib.metadata import metadata
|
|
357
|
-
|
|
358
335
|
from acp.client.connection import ClientSideConnection
|
|
359
336
|
from acp.schema import InitializeRequest
|
|
337
|
+
from agentpool.agents.acp_agent.client_handler import ACPClientHandler
|
|
360
338
|
|
|
361
339
|
if not self._process or not self._process.stdin or not self._process.stdout:
|
|
362
340
|
msg = "Process not started"
|
|
@@ -383,12 +361,14 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
383
361
|
read_text_file=self.config.allow_file_operations,
|
|
384
362
|
write_text_file=self.config.allow_file_operations,
|
|
385
363
|
)
|
|
386
|
-
|
|
387
|
-
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)
|
|
388
367
|
|
|
389
368
|
async def _create_session(self) -> None:
|
|
390
369
|
"""Create a new ACP session with configured MCP servers."""
|
|
391
370
|
from acp.schema import NewSessionRequest
|
|
371
|
+
from agentpool.agents.acp_agent.acp_converters import mcp_configs_to_acp
|
|
392
372
|
|
|
393
373
|
if not self._connection:
|
|
394
374
|
msg = "Connection not initialized"
|
|
@@ -407,6 +387,10 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
407
387
|
self._session_id = response.session_id
|
|
408
388
|
if self._state:
|
|
409
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
|
|
410
394
|
if response.models: # Store full model info from session response
|
|
411
395
|
self._state.models = response.models
|
|
412
396
|
self._state.current_model_id = response.models.current_model_id
|
|
@@ -464,65 +448,30 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
464
448
|
self.log.exception("Error terminating ACP process")
|
|
465
449
|
self._process = None
|
|
466
450
|
|
|
467
|
-
async def
|
|
468
|
-
self,
|
|
469
|
-
*prompts: PromptCompatible,
|
|
470
|
-
message_id: str | None = None,
|
|
471
|
-
input_provider: InputProvider | None = None,
|
|
472
|
-
message_history: MessageHistory | None = None,
|
|
473
|
-
) -> ChatMessage[str]:
|
|
474
|
-
"""Execute prompt against ACP agent.
|
|
475
|
-
|
|
476
|
-
Args:
|
|
477
|
-
prompts: Prompts to send (will be joined with spaces)
|
|
478
|
-
message_id: Optional message id for the returned message
|
|
479
|
-
input_provider: Optional input provider for permission requests
|
|
480
|
-
message_history: Optional MessageHistory to use instead of agent's own
|
|
481
|
-
|
|
482
|
-
Returns:
|
|
483
|
-
ChatMessage containing the agent's aggregated text response
|
|
484
|
-
"""
|
|
485
|
-
# Collect all events through run_stream
|
|
486
|
-
final_message: ChatMessage[str] | None = None
|
|
487
|
-
async for event in self.run_stream(
|
|
488
|
-
*prompts,
|
|
489
|
-
message_id=message_id,
|
|
490
|
-
input_provider=input_provider,
|
|
491
|
-
message_history=message_history,
|
|
492
|
-
):
|
|
493
|
-
if isinstance(event, StreamCompleteEvent):
|
|
494
|
-
final_message = event.message
|
|
495
|
-
|
|
496
|
-
if final_message is None:
|
|
497
|
-
msg = "No final message received from stream"
|
|
498
|
-
raise RuntimeError(msg)
|
|
499
|
-
|
|
500
|
-
return final_message
|
|
501
|
-
|
|
502
|
-
async def run_stream( # noqa: PLR0915
|
|
451
|
+
async def _stream_events( # noqa: PLR0915
|
|
503
452
|
self,
|
|
504
|
-
|
|
453
|
+
prompts: list[UserContent],
|
|
454
|
+
*,
|
|
455
|
+
user_msg: ChatMessage[Any],
|
|
456
|
+
effective_parent_id: str | None,
|
|
505
457
|
message_id: str | None = None,
|
|
458
|
+
conversation_id: str | None = None,
|
|
459
|
+
parent_id: str | None = None,
|
|
506
460
|
input_provider: InputProvider | None = None,
|
|
507
461
|
message_history: MessageHistory | None = None,
|
|
508
462
|
deps: TDeps | None = None,
|
|
509
463
|
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
|
|
464
|
+
wait_for_connections: bool | None = None,
|
|
465
|
+
store_history: bool = True,
|
|
510
466
|
) -> AsyncIterator[RichAgentStreamEvent[str]]:
|
|
511
|
-
|
|
467
|
+
from anyenv import MultiEventHandler
|
|
512
468
|
|
|
513
|
-
|
|
514
|
-
prompts: Prompts to send (will be joined with spaces)
|
|
515
|
-
message_id: Optional message id for the final message
|
|
516
|
-
input_provider: Optional input provider for permission requests
|
|
517
|
-
message_history: Optional MessageHistory to use instead of agent's own
|
|
518
|
-
deps: Optional dependencies accessible via ctx.data in tools
|
|
519
|
-
event_handlers: Optional event handlers for this run (overrides agent's handlers)
|
|
520
|
-
|
|
521
|
-
Yields:
|
|
522
|
-
RichAgentStreamEvent instances converted from ACP session updates
|
|
523
|
-
"""
|
|
524
|
-
from acp.schema import PromptRequest
|
|
469
|
+
from acp.schema import ForkSessionRequest, PromptRequest
|
|
525
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
|
+
)
|
|
526
475
|
|
|
527
476
|
# Update input provider if provided
|
|
528
477
|
if input_provider is not None:
|
|
@@ -536,21 +485,13 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
536
485
|
conversation = message_history if message_history is not None else self.conversation
|
|
537
486
|
# Use provided event handlers or fall back to agent's handlers
|
|
538
487
|
if event_handlers is not None:
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
from agentpool.agents.events import resolve_event_handlers
|
|
542
|
-
|
|
543
|
-
handler: MultiEventHandler[IndividualEventHandler] = MultiEventHandler(
|
|
544
|
-
resolve_event_handlers(event_handlers)
|
|
545
|
-
)
|
|
488
|
+
handlers = resolve_event_handlers(event_handlers)
|
|
489
|
+
handler = MultiEventHandler[IndividualEventHandler](handlers)
|
|
546
490
|
else:
|
|
547
491
|
handler = self.event_handler
|
|
548
|
-
|
|
549
|
-
#
|
|
550
|
-
|
|
551
|
-
user_msg, processed_prompts, _original_message = await prepare_prompts(
|
|
552
|
-
*prompts, parent_id=last_msg_id
|
|
553
|
-
)
|
|
492
|
+
|
|
493
|
+
# Prepare for ACP content block conversion
|
|
494
|
+
processed_prompts = prompts
|
|
554
495
|
run_id = str(uuid.uuid4())
|
|
555
496
|
self._state.clear() # Reset state
|
|
556
497
|
# Track messages in pydantic-ai format: ModelRequest -> ModelResponse -> ...
|
|
@@ -562,6 +503,7 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
562
503
|
current_response_parts: list[TextPart | ThinkingPart | ToolCallPart] = []
|
|
563
504
|
text_chunks: list[str] = [] # For final content string
|
|
564
505
|
file_tracker = FileTracker() # Track files modified by tool calls
|
|
506
|
+
assert self.conversation_id is not None # Initialized by BaseAgent.run_stream()
|
|
565
507
|
run_started = RunStartedEvent(
|
|
566
508
|
thread_id=self.conversation_id,
|
|
567
509
|
run_id=run_id,
|
|
@@ -572,11 +514,21 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
572
514
|
content_blocks = convert_to_acp_content(processed_prompts)
|
|
573
515
|
pending_parts = conversation.get_pending_parts()
|
|
574
516
|
final_blocks = [*to_acp_content_blocks(pending_parts), *content_blocks]
|
|
575
|
-
|
|
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)
|
|
576
530
|
self.log.debug("Starting streaming prompt", num_blocks=len(final_blocks))
|
|
577
|
-
# Reset cancellation state
|
|
578
|
-
self._cancelled = False
|
|
579
|
-
self._current_stream_task = asyncio.current_task()
|
|
531
|
+
self._cancelled = False # Reset cancellation state
|
|
580
532
|
# Run prompt in background
|
|
581
533
|
prompt_task = asyncio.create_task(self._connection.prompt(prompt_request))
|
|
582
534
|
self._prompt_task = prompt_task
|
|
@@ -606,22 +558,50 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
606
558
|
yield self._state.events[last_idx]
|
|
607
559
|
last_idx += 1
|
|
608
560
|
|
|
609
|
-
|
|
561
|
+
# Set deps on tool bridge for access during tool invocations
|
|
562
|
+
|
|
610
563
|
# (ContextVar doesn't work because MCP server runs in a separate task)
|
|
611
564
|
if self._tool_bridge:
|
|
612
565
|
self._tool_bridge.current_deps = deps
|
|
613
566
|
|
|
567
|
+
# Accumulate metadata events by tool_call_id (workaround for MCP stripping _meta)
|
|
568
|
+
tool_metadata: dict[str, dict[str, Any]] = {}
|
|
569
|
+
|
|
614
570
|
# Merge ACP events with custom events from queue
|
|
615
571
|
try:
|
|
616
572
|
async with merge_queue_into_iterator(
|
|
617
573
|
poll_acp_events(), self._event_queue
|
|
618
574
|
) as merged_events:
|
|
619
|
-
async for event in file_tracker
|
|
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
|
+
|
|
620
584
|
# Check for cancellation
|
|
621
585
|
if self._cancelled:
|
|
622
586
|
self.log.info("Stream cancelled by user")
|
|
623
587
|
break
|
|
624
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
|
+
|
|
625
605
|
# Extract content from events and build parts in arrival order
|
|
626
606
|
match event:
|
|
627
607
|
case PartDeltaEvent(delta=TextPartDelta(content_delta=delta)):
|
|
@@ -648,10 +628,8 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
648
628
|
|
|
649
629
|
# Handle cancellation - emit partial message
|
|
650
630
|
if self._cancelled:
|
|
651
|
-
text_content = "".join(text_chunks)
|
|
652
|
-
metadata: SimpleJsonType = file_tracker.get_metadata()
|
|
653
631
|
message = ChatMessage[str](
|
|
654
|
-
content=
|
|
632
|
+
content="".join(text_chunks),
|
|
655
633
|
role="assistant",
|
|
656
634
|
name=self.name,
|
|
657
635
|
message_id=message_id or str(uuid.uuid4()),
|
|
@@ -659,19 +637,18 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
659
637
|
parent_id=user_msg.message_id,
|
|
660
638
|
model_name=self.model_name,
|
|
661
639
|
messages=model_messages,
|
|
662
|
-
metadata=
|
|
640
|
+
metadata=file_tracker.get_metadata(),
|
|
663
641
|
finish_reason="stop",
|
|
664
642
|
)
|
|
665
643
|
complete_event = StreamCompleteEvent(message=message)
|
|
666
644
|
await handler(None, complete_event)
|
|
667
645
|
yield complete_event
|
|
668
|
-
self._current_stream_task = None
|
|
669
646
|
self._prompt_task = None
|
|
670
647
|
return
|
|
671
648
|
|
|
672
649
|
# Ensure we catch any exceptions from the prompt task
|
|
673
650
|
response = await prompt_task
|
|
674
|
-
finish_reason
|
|
651
|
+
finish_reason = to_finish_reason(response.stop_reason)
|
|
675
652
|
# Flush response parts to model_messages
|
|
676
653
|
if current_response_parts:
|
|
677
654
|
model_messages.append(
|
|
@@ -684,8 +661,6 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
684
661
|
)
|
|
685
662
|
|
|
686
663
|
text_content = "".join(text_chunks)
|
|
687
|
-
metadata = file_tracker.get_metadata()
|
|
688
|
-
|
|
689
664
|
# Calculate approximate token usage from what we can observe
|
|
690
665
|
input_parts = [*processed_prompts, *pending_parts]
|
|
691
666
|
usage, cost_info = await calculate_usage_from_parts(
|
|
@@ -705,77 +680,77 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
705
680
|
parent_id=user_msg.message_id,
|
|
706
681
|
model_name=self.model_name,
|
|
707
682
|
messages=model_messages,
|
|
708
|
-
metadata=
|
|
683
|
+
metadata=file_tracker.get_metadata(),
|
|
709
684
|
finish_reason=finish_reason,
|
|
710
685
|
usage=usage,
|
|
711
686
|
cost_info=cost_info,
|
|
712
687
|
)
|
|
713
688
|
complete_event = StreamCompleteEvent(message=message)
|
|
714
689
|
await handler(None, complete_event)
|
|
715
|
-
yield complete_event # Emit final StreamCompleteEvent
|
|
716
|
-
self.message_sent.emit(message)
|
|
717
|
-
conversation.add_chat_messages([user_msg, message]) # Record to conversation history
|
|
718
|
-
|
|
719
|
-
async def run_iter(
|
|
720
|
-
self,
|
|
721
|
-
*prompt_groups: Sequence[PromptCompatible],
|
|
722
|
-
) -> AsyncIterator[ChatMessage[str]]:
|
|
723
|
-
"""Run agent sequentially on multiple prompt groups.
|
|
724
|
-
|
|
725
|
-
Args:
|
|
726
|
-
prompt_groups: Groups of prompts to process sequentially
|
|
727
|
-
|
|
728
|
-
Yields:
|
|
729
|
-
Response messages in sequence
|
|
730
|
-
"""
|
|
731
|
-
for prompts in prompt_groups:
|
|
732
|
-
response = await self.run(*prompts)
|
|
733
|
-
yield response
|
|
690
|
+
yield complete_event # Emit final StreamCompleteEvent - post-processing handled by base
|
|
734
691
|
|
|
735
692
|
@property
|
|
736
693
|
def model_name(self) -> str | None:
|
|
737
694
|
"""Get the model name in a consistent format."""
|
|
738
|
-
if self._state and self._state.current_model_id
|
|
739
|
-
return self._state.current_model_id
|
|
740
|
-
if self._init_response and self._init_response.agent_info:
|
|
741
|
-
return self._init_response.agent_info.name
|
|
742
|
-
return None
|
|
695
|
+
return model_id if self._state and (model_id := self._state.current_model_id) else None
|
|
743
696
|
|
|
744
697
|
async def set_model(self, model: str) -> None:
|
|
745
|
-
"""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
|
|
746
703
|
|
|
747
704
|
Args:
|
|
748
|
-
model: New model
|
|
705
|
+
model: New model ID to use
|
|
749
706
|
|
|
750
707
|
Raises:
|
|
751
|
-
|
|
752
|
-
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
|
|
753
709
|
"""
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
# await self._connection.set_session_model(request)
|
|
759
|
-
# if self._state:
|
|
760
|
-
# self._state.current_model_id = model
|
|
761
|
-
# self.log.info("Model changed via ACP protocol", model=model)
|
|
762
|
-
# return
|
|
763
|
-
|
|
764
|
-
if not hasattr(self.config, "model"):
|
|
765
|
-
msg = f"Config type {type(self.config).__name__} doesn't support model changes"
|
|
766
|
-
raise ValueError(msg)
|
|
767
|
-
# Prevent changes during active processing
|
|
768
|
-
if self._process and not self._session_id:
|
|
769
|
-
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"
|
|
770
714
|
raise RuntimeError(msg)
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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)
|
|
779
754
|
|
|
780
755
|
async def set_tool_confirmation_mode(self, mode: ToolConfirmationMode) -> None:
|
|
781
756
|
"""Set the tool confirmation mode for this agent.
|
|
@@ -862,89 +837,129 @@ class ACPAgent[TDeps = None](BaseAgent[TDeps, str]):
|
|
|
862
837
|
result.append(toko_model)
|
|
863
838
|
return result
|
|
864
839
|
|
|
865
|
-
def get_modes(self) -> list[ModeCategory]:
|
|
840
|
+
async def get_modes(self) -> list[ModeCategory]:
|
|
866
841
|
"""Get available modes from the ACP session state.
|
|
867
842
|
|
|
868
|
-
Passthrough from remote ACP server's mode state.
|
|
843
|
+
Passthrough from remote ACP server's mode and model state.
|
|
844
|
+
Prefers new config_options format, falls back to legacy modes/models.
|
|
869
845
|
|
|
870
846
|
Returns:
|
|
871
847
|
List of ModeCategory from remote server, empty if not available
|
|
872
848
|
"""
|
|
873
|
-
from agentpool.agents.
|
|
849
|
+
from agentpool.agents.acp_agent.acp_converters import get_modes
|
|
874
850
|
|
|
875
|
-
if not self._state
|
|
851
|
+
if not self._state:
|
|
876
852
|
return []
|
|
877
853
|
|
|
878
|
-
#
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
name=m.name,
|
|
885
|
-
description=m.description or "",
|
|
886
|
-
category_id=category_id,
|
|
887
|
-
)
|
|
888
|
-
for m in acp_modes.available_modes
|
|
889
|
-
]
|
|
890
|
-
|
|
891
|
-
return [
|
|
892
|
-
ModeCategory(
|
|
893
|
-
id=category_id,
|
|
894
|
-
name="Mode",
|
|
895
|
-
available_modes=modes,
|
|
896
|
-
current_mode_id=acp_modes.current_mode_id,
|
|
897
|
-
)
|
|
898
|
-
]
|
|
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
|
+
)
|
|
899
860
|
|
|
900
861
|
async def set_mode(self, mode: ModeInfo | str, category_id: str | None = None) -> None:
|
|
901
862
|
"""Set a mode on the remote ACP server.
|
|
902
863
|
|
|
903
|
-
For ACPAgent, this forwards the mode change to the remote ACP server.
|
|
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.
|
|
904
867
|
|
|
905
868
|
Args:
|
|
906
869
|
mode: The mode to set - ModeInfo object or mode ID string
|
|
907
|
-
category_id:
|
|
870
|
+
category_id: Category ID (config option ID)
|
|
908
871
|
|
|
909
872
|
Raises:
|
|
910
873
|
RuntimeError: If not connected to ACP server
|
|
911
874
|
ValueError: If mode is not available
|
|
912
875
|
"""
|
|
913
|
-
from acp.schema import
|
|
876
|
+
from acp.schema import (
|
|
877
|
+
SetSessionConfigOptionRequest,
|
|
878
|
+
SetSessionModelRequest,
|
|
879
|
+
SetSessionModeRequest,
|
|
880
|
+
)
|
|
914
881
|
|
|
915
|
-
# Extract mode_id from ModeInfo if provided
|
|
916
|
-
|
|
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
|
|
917
888
|
|
|
918
|
-
if not self._connection or not self._session_id:
|
|
889
|
+
if not self._connection or not self._session_id or not self._state:
|
|
919
890
|
msg = "Not connected to ACP server"
|
|
920
891
|
raise RuntimeError(msg)
|
|
921
892
|
|
|
922
893
|
# Validate mode is available
|
|
923
|
-
available_modes = self.get_modes()
|
|
924
|
-
|
|
925
|
-
|
|
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}
|
|
926
901
|
if mode_id not in valid_ids:
|
|
927
|
-
msg = f"Unknown
|
|
902
|
+
msg = f"Unknown {category_id}: {mode_id}. Available: {valid_ids}"
|
|
928
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)
|
|
929
926
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
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)
|
|
933
940
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
self.
|
|
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)
|
|
937
944
|
|
|
938
|
-
|
|
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)
|
|
939
954
|
|
|
940
955
|
|
|
941
956
|
if __name__ == "__main__":
|
|
957
|
+
from agentpool.models.acp_agents import ACPAgentConfig
|
|
942
958
|
|
|
943
959
|
async def main() -> None:
|
|
944
960
|
"""Demo: Basic call to an ACP agent."""
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
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:
|
|
948
963
|
print("Response (streaming): ", end="", flush=True)
|
|
949
964
|
async for chunk in agent.run_stream("Say hello briefly."):
|
|
950
965
|
print(chunk, end="", flush=True)
|