agentpool 2.1.9__py3-none-any.whl → 2.2.3__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 -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/notifications.py +2 -1
- acp/stdio.py +39 -9
- acp/transports.py +362 -2
- acp/utils.py +15 -2
- agentpool/__init__.py +4 -1
- agentpool/agents/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +203 -88
- agentpool/agents/acp_agent/acp_converters.py +46 -21
- agentpool/agents/acp_agent/client_handler.py +157 -3
- agentpool/agents/acp_agent/session_state.py +4 -1
- agentpool/agents/agent.py +314 -107
- agentpool/agents/agui_agent/__init__.py +0 -2
- agentpool/agents/agui_agent/agui_agent.py +90 -21
- agentpool/agents/agui_agent/agui_converters.py +0 -131
- agentpool/agents/base_agent.py +163 -1
- agentpool/agents/claude_code_agent/claude_code_agent.py +626 -179
- agentpool/agents/claude_code_agent/converters.py +71 -3
- agentpool/agents/claude_code_agent/history.py +474 -0
- agentpool/agents/context.py +40 -0
- agentpool/agents/events/__init__.py +2 -0
- agentpool/agents/events/builtin_handlers.py +2 -1
- agentpool/agents/events/event_emitter.py +29 -2
- agentpool/agents/events/events.py +20 -0
- agentpool/agents/modes.py +54 -0
- agentpool/agents/tool_call_accumulator.py +213 -0
- agentpool/common_types.py +21 -0
- agentpool/config_resources/__init__.py +38 -1
- agentpool/config_resources/claude_code_agent.yml +3 -0
- agentpool/delegation/pool.py +37 -29
- agentpool/delegation/team.py +1 -0
- agentpool/delegation/teamrun.py +1 -0
- 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/mcp_server/__init__.py +0 -2
- agentpool/mcp_server/client.py +12 -3
- agentpool/mcp_server/manager.py +25 -31
- agentpool/mcp_server/registries/official_registry_client.py +25 -0
- agentpool/mcp_server/tool_bridge.py +78 -66
- agentpool/messaging/__init__.py +0 -2
- agentpool/messaging/compaction.py +72 -197
- agentpool/messaging/message_history.py +12 -0
- agentpool/messaging/messages.py +52 -9
- agentpool/messaging/processing.py +3 -1
- agentpool/models/acp_agents/base.py +0 -22
- agentpool/models/acp_agents/mcp_capable.py +8 -148
- agentpool/models/acp_agents/non_mcp.py +129 -72
- agentpool/models/agents.py +35 -13
- agentpool/models/claude_code_agents.py +33 -2
- agentpool/models/manifest.py +43 -0
- agentpool/repomap.py +1 -1
- agentpool/resource_providers/__init__.py +9 -1
- agentpool/resource_providers/aggregating.py +52 -3
- agentpool/resource_providers/base.py +57 -1
- agentpool/resource_providers/mcp_provider.py +23 -0
- agentpool/resource_providers/plan_provider.py +130 -41
- agentpool/resource_providers/pool.py +2 -0
- agentpool/resource_providers/static.py +2 -0
- agentpool/sessions/__init__.py +2 -1
- agentpool/sessions/manager.py +31 -2
- agentpool/sessions/models.py +50 -0
- agentpool/skills/registry.py +13 -8
- agentpool/storage/manager.py +217 -1
- agentpool/testing.py +537 -19
- 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 +690 -1
- agentpool/utils/subprocess_utils.py +155 -0
- agentpool/utils/token_breakdown.py +461 -0
- {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/METADATA +27 -7
- {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/RECORD +170 -112
- {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +4 -0
- agentpool_cli/serve_acp.py +41 -20
- agentpool_cli/serve_agui.py +87 -0
- agentpool_cli/serve_opencode.py +119 -0
- agentpool_commands/__init__.py +30 -0
- agentpool_commands/agents.py +74 -1
- agentpool_commands/history.py +62 -0
- agentpool_commands/mcp.py +176 -0
- agentpool_commands/models.py +56 -3
- agentpool_commands/tools.py +57 -0
- agentpool_commands/utils.py +51 -0
- agentpool_config/builtin_tools.py +77 -22
- agentpool_config/commands.py +24 -1
- agentpool_config/compaction.py +258 -0
- agentpool_config/mcp_server.py +131 -1
- agentpool_config/storage.py +46 -1
- agentpool_config/tools.py +7 -1
- agentpool_config/toolsets.py +92 -148
- agentpool_server/acp_server/acp_agent.py +134 -150
- agentpool_server/acp_server/commands/acp_commands.py +216 -51
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +10 -10
- agentpool_server/acp_server/server.py +23 -79
- agentpool_server/acp_server/session.py +181 -19
- agentpool_server/opencode_server/.rules +95 -0
- agentpool_server/opencode_server/ENDPOINTS.md +362 -0
- agentpool_server/opencode_server/__init__.py +27 -0
- agentpool_server/opencode_server/command_validation.py +172 -0
- agentpool_server/opencode_server/converters.py +869 -0
- agentpool_server/opencode_server/dependencies.py +24 -0
- agentpool_server/opencode_server/input_provider.py +269 -0
- agentpool_server/opencode_server/models/__init__.py +228 -0
- agentpool_server/opencode_server/models/agent.py +53 -0
- agentpool_server/opencode_server/models/app.py +60 -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 +647 -0
- agentpool_server/opencode_server/models/file.py +88 -0
- agentpool_server/opencode_server/models/mcp.py +25 -0
- agentpool_server/opencode_server/models/message.py +162 -0
- agentpool_server/opencode_server/models/parts.py +190 -0
- agentpool_server/opencode_server/models/provider.py +81 -0
- agentpool_server/opencode_server/models/pty.py +43 -0
- agentpool_server/opencode_server/models/session.py +99 -0
- agentpool_server/opencode_server/routes/__init__.py +25 -0
- agentpool_server/opencode_server/routes/agent_routes.py +442 -0
- agentpool_server/opencode_server/routes/app_routes.py +139 -0
- agentpool_server/opencode_server/routes/config_routes.py +241 -0
- agentpool_server/opencode_server/routes/file_routes.py +392 -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 +705 -0
- agentpool_server/opencode_server/routes/pty_routes.py +299 -0
- agentpool_server/opencode_server/routes/session_routes.py +1205 -0
- agentpool_server/opencode_server/routes/tui_routes.py +139 -0
- agentpool_server/opencode_server/server.py +430 -0
- agentpool_server/opencode_server/state.py +121 -0
- agentpool_server/opencode_server/time_utils.py +8 -0
- agentpool_storage/__init__.py +16 -0
- agentpool_storage/base.py +103 -0
- agentpool_storage/claude_provider.py +907 -0
- agentpool_storage/file_provider.py +129 -0
- agentpool_storage/memory_provider.py +61 -0
- agentpool_storage/models.py +3 -0
- agentpool_storage/opencode_provider.py +730 -0
- agentpool_storage/project_store.py +325 -0
- agentpool_storage/session_store.py +6 -0
- agentpool_storage/sql_provider/__init__.py +4 -2
- agentpool_storage/sql_provider/models.py +48 -0
- agentpool_storage/sql_provider/sql_provider.py +134 -1
- agentpool_storage/sql_provider/utils.py +10 -1
- agentpool_storage/text_log_provider.py +1 -0
- agentpool_toolsets/builtin/__init__.py +0 -8
- agentpool_toolsets/builtin/code.py +95 -56
- agentpool_toolsets/builtin/debug.py +16 -21
- agentpool_toolsets/builtin/execution_environment.py +99 -103
- agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
- agentpool_toolsets/builtin/skills.py +86 -4
- agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
- agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
- agentpool_toolsets/fsspec_toolset/grep.py +74 -2
- agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +159 -38
- agentpool_toolsets/mcp_discovery/__init__.py +5 -0
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +454 -0
- agentpool_toolsets/mcp_run_toolset.py +84 -6
- agentpool_toolsets/builtin/agent_management.py +0 -239
- agentpool_toolsets/builtin/history.py +0 -36
- agentpool_toolsets/builtin/integration.py +0 -85
- agentpool_toolsets/builtin/tool_management.py +0 -90
- {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/entry_points.txt +0 -0
- {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/licenses/LICENSE +0 -0
agentpool/agents/agent.py
CHANGED
|
@@ -19,37 +19,68 @@ from pydantic import ValidationError
|
|
|
19
19
|
from pydantic._internal import _typing_extra
|
|
20
20
|
from pydantic_ai import (
|
|
21
21
|
Agent as PydanticAgent,
|
|
22
|
-
AgentRunResultEvent,
|
|
23
22
|
BaseToolCallPart,
|
|
23
|
+
CallToolsNode,
|
|
24
24
|
FunctionToolCallEvent,
|
|
25
25
|
FunctionToolResultEvent,
|
|
26
|
-
|
|
26
|
+
ModelRequestNode,
|
|
27
27
|
PartStartEvent,
|
|
28
28
|
RunContext,
|
|
29
|
-
TextPart,
|
|
30
|
-
TextPartDelta,
|
|
31
29
|
ToolReturnPart,
|
|
32
30
|
)
|
|
33
31
|
|
|
34
32
|
from agentpool.agents.base_agent import BaseAgent
|
|
35
|
-
from agentpool.agents.events import
|
|
33
|
+
from agentpool.agents.events import (
|
|
34
|
+
RunStartedEvent,
|
|
35
|
+
StreamCompleteEvent,
|
|
36
|
+
ToolCallCompleteEvent,
|
|
37
|
+
)
|
|
38
|
+
from agentpool.agents.modes import ModeInfo
|
|
36
39
|
from agentpool.log import get_logger
|
|
37
40
|
from agentpool.messaging import ChatMessage, MessageHistory, MessageNode
|
|
38
41
|
from agentpool.messaging.processing import prepare_prompts
|
|
39
42
|
from agentpool.prompts.convert import convert_prompts
|
|
40
43
|
from agentpool.storage import StorageManager
|
|
41
|
-
from agentpool.talk.stats import MessageStats
|
|
42
44
|
from agentpool.tools import Tool, ToolManager
|
|
43
45
|
from agentpool.tools.exceptions import ToolError
|
|
44
46
|
from agentpool.utils.inspection import call_with_context, get_argument_key
|
|
45
47
|
from agentpool.utils.now import get_now
|
|
48
|
+
from agentpool.utils.pydantic_ai_helpers import safe_args_as_dict
|
|
46
49
|
from agentpool.utils.result_utils import to_type
|
|
47
|
-
from agentpool.utils.streams import merge_queue_into_iterator
|
|
50
|
+
from agentpool.utils.streams import FileTracker, merge_queue_into_iterator
|
|
48
51
|
|
|
49
52
|
|
|
50
53
|
TResult = TypeVar("TResult")
|
|
51
54
|
|
|
52
55
|
|
|
56
|
+
def _extract_text_from_messages(
|
|
57
|
+
messages: list[Any], include_interruption_note: bool = False
|
|
58
|
+
) -> str:
|
|
59
|
+
"""Extract text content from pydantic-ai messages.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
messages: List of ModelRequest/ModelResponse messages
|
|
63
|
+
include_interruption_note: Whether to append interruption notice
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Concatenated text content from all ModelResponse TextParts
|
|
67
|
+
"""
|
|
68
|
+
from pydantic_ai.messages import ModelResponse, TextPart as PydanticTextPart
|
|
69
|
+
|
|
70
|
+
content = "".join(
|
|
71
|
+
part.content
|
|
72
|
+
for msg in messages
|
|
73
|
+
if isinstance(msg, ModelResponse)
|
|
74
|
+
for part in msg.parts
|
|
75
|
+
if isinstance(part, PydanticTextPart)
|
|
76
|
+
)
|
|
77
|
+
if include_interruption_note:
|
|
78
|
+
if content:
|
|
79
|
+
content += "\n\n"
|
|
80
|
+
content += "[Request interrupted by user]"
|
|
81
|
+
return content
|
|
82
|
+
|
|
83
|
+
|
|
53
84
|
if TYPE_CHECKING:
|
|
54
85
|
from collections.abc import AsyncIterator, Coroutine, Sequence
|
|
55
86
|
from datetime import datetime
|
|
@@ -57,13 +88,18 @@ if TYPE_CHECKING:
|
|
|
57
88
|
|
|
58
89
|
from exxec import ExecutionEnvironment
|
|
59
90
|
from pydantic_ai import UsageLimits
|
|
91
|
+
from pydantic_ai.builtin_tools import AbstractBuiltinTool
|
|
60
92
|
from pydantic_ai.output import OutputSpec
|
|
61
93
|
from pydantic_ai.settings import ModelSettings
|
|
94
|
+
from slashed import BaseCommand
|
|
95
|
+
from tokonomics.model_discovery import ProviderType
|
|
96
|
+
from tokonomics.model_discovery.model_info import ModelInfo
|
|
62
97
|
from toprompt import AnyPromptType
|
|
63
98
|
from upathtools import JoinablePathLike
|
|
64
99
|
|
|
65
100
|
from agentpool.agents import AgentContext
|
|
66
101
|
from agentpool.agents.events import RichAgentStreamEvent
|
|
102
|
+
from agentpool.agents.modes import ModeCategory
|
|
67
103
|
from agentpool.common_types import (
|
|
68
104
|
AgentName,
|
|
69
105
|
BuiltinEventHandlerType,
|
|
@@ -77,7 +113,7 @@ if TYPE_CHECKING:
|
|
|
77
113
|
)
|
|
78
114
|
from agentpool.delegation import AgentPool, Team, TeamRun
|
|
79
115
|
from agentpool.hooks import AgentHooks
|
|
80
|
-
from agentpool.models.agents import
|
|
116
|
+
from agentpool.models.agents import NativeAgentConfig, ToolMode
|
|
81
117
|
from agentpool.prompts.prompts import PromptType
|
|
82
118
|
from agentpool.resource_providers import ResourceProvider
|
|
83
119
|
from agentpool.ui.base import InputProvider
|
|
@@ -111,9 +147,11 @@ class AgentKwargs(TypedDict, total=False):
|
|
|
111
147
|
input_provider: InputProvider | None
|
|
112
148
|
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None
|
|
113
149
|
env: ExecutionEnvironment | None
|
|
114
|
-
|
|
150
|
+
|
|
115
151
|
hooks: AgentHooks | None
|
|
116
152
|
model_settings: ModelSettings | None
|
|
153
|
+
usage_limits: UsageLimits | None
|
|
154
|
+
providers: Sequence[ProviderType] | None
|
|
117
155
|
|
|
118
156
|
|
|
119
157
|
class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
@@ -164,9 +202,12 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
164
202
|
knowledge: Knowledge | None = None,
|
|
165
203
|
agent_config: NativeAgentConfig | None = None,
|
|
166
204
|
env: ExecutionEnvironment | None = None,
|
|
167
|
-
auto_cache: AutoCache = "off",
|
|
168
205
|
hooks: AgentHooks | None = None,
|
|
169
206
|
tool_confirmation_mode: ToolConfirmationMode = "per_tool",
|
|
207
|
+
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
|
|
208
|
+
usage_limits: UsageLimits | None = None,
|
|
209
|
+
providers: Sequence[ProviderType] | None = None,
|
|
210
|
+
commands: Sequence[BaseCommand] | None = None,
|
|
170
211
|
) -> None:
|
|
171
212
|
"""Initialize agent.
|
|
172
213
|
|
|
@@ -206,9 +247,13 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
206
247
|
knowledge: Knowledge sources for this agent
|
|
207
248
|
agent_config: Agent configuration
|
|
208
249
|
env: Execution environment for code/command execution and filesystem access
|
|
209
|
-
auto_cache: Automatic caching configuration ("off", "5m", or "1h")
|
|
210
250
|
hooks: AgentHooks instance for intercepting agent behavior at run and tool events
|
|
211
251
|
tool_confirmation_mode: Tool confirmation mode
|
|
252
|
+
builtin_tools: PydanticAI builtin tools (WebSearchTool, CodeExecutionTool, etc.)
|
|
253
|
+
usage_limits: Usage limits for the agent
|
|
254
|
+
providers: Model providers for model discovery (e.g., ["openai", "anthropic"]).
|
|
255
|
+
Defaults to ["models.dev"] if not specified.
|
|
256
|
+
commands: Slash commands
|
|
212
257
|
"""
|
|
213
258
|
from agentpool.agents.interactions import Interactions
|
|
214
259
|
from agentpool.agents.sys_prompts import SystemPrompts
|
|
@@ -241,11 +286,15 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
241
286
|
output_type=to_type(output_type), # type: ignore[arg-type]
|
|
242
287
|
tool_confirmation_mode=tool_confirmation_mode,
|
|
243
288
|
event_handlers=event_handlers,
|
|
289
|
+
commands=commands,
|
|
244
290
|
)
|
|
245
291
|
|
|
246
292
|
# Store config for context creation
|
|
247
293
|
self._agent_config = agent_config or NativeAgentConfig(name=name)
|
|
248
294
|
|
|
295
|
+
# Store builtin tools for pydantic-ai
|
|
296
|
+
self._builtin_tools = list(builtin_tools) if builtin_tools else []
|
|
297
|
+
|
|
249
298
|
# Override tools with Agent-specific ToolManager (with tools and tool_mode)
|
|
250
299
|
all_tools = list(tools or [])
|
|
251
300
|
self.tools = ToolManager(all_tools, tool_mode=tool_mode)
|
|
@@ -284,8 +333,11 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
284
333
|
# Store hooks
|
|
285
334
|
self.hooks = hooks
|
|
286
335
|
|
|
287
|
-
# Store
|
|
288
|
-
self.
|
|
336
|
+
# Store default usage limits
|
|
337
|
+
self._default_usage_limits = usage_limits
|
|
338
|
+
|
|
339
|
+
# Store providers for model discovery
|
|
340
|
+
self._providers = list(providers) if providers else None
|
|
289
341
|
|
|
290
342
|
def __repr__(self) -> str:
|
|
291
343
|
desc = f", {self.description!r}" if self.description else ""
|
|
@@ -574,6 +626,7 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
574
626
|
output_retries=self._output_retries,
|
|
575
627
|
deps_type=self.deps_type or NoneType,
|
|
576
628
|
output_type=final_type,
|
|
629
|
+
builtin_tools=self._builtin_tools,
|
|
577
630
|
)
|
|
578
631
|
|
|
579
632
|
base_context = self.get_context()
|
|
@@ -715,6 +768,7 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
715
768
|
wait_for_connections: bool | None = None,
|
|
716
769
|
deps: TDeps | None = None,
|
|
717
770
|
instructions: str | None = None,
|
|
771
|
+
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None = None,
|
|
718
772
|
) -> AsyncIterator[RichAgentStreamEvent[OutputDataT]]:
|
|
719
773
|
"""Run agent with prompt and get a streaming response.
|
|
720
774
|
|
|
@@ -734,6 +788,8 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
734
788
|
wait_for_connections: Whether to wait for connected agents to complete
|
|
735
789
|
deps: Optional dependencies for the agent
|
|
736
790
|
instructions: Optional instructions to override the agent's system prompt
|
|
791
|
+
event_handlers: Optional event handlers for this run (overrides agent's handlers)
|
|
792
|
+
|
|
737
793
|
Returns:
|
|
738
794
|
An async iterator yielding streaming events with final message embedded.
|
|
739
795
|
|
|
@@ -741,9 +797,22 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
741
797
|
UnexpectedModelBehavior: If the model fails or behaves unexpectedly
|
|
742
798
|
"""
|
|
743
799
|
conversation = message_history if message_history is not None else self.conversation
|
|
800
|
+
# Use provided event handlers or fall back to agent's handlers
|
|
801
|
+
if event_handlers is not None:
|
|
802
|
+
from anyenv import MultiEventHandler
|
|
803
|
+
|
|
804
|
+
from agentpool.agents.events import resolve_event_handlers
|
|
805
|
+
|
|
806
|
+
handler: MultiEventHandler[IndividualEventHandler] = MultiEventHandler(
|
|
807
|
+
resolve_event_handlers(event_handlers)
|
|
808
|
+
)
|
|
809
|
+
else:
|
|
810
|
+
handler = self.event_handler
|
|
744
811
|
message_id = message_id or str(uuid4())
|
|
745
812
|
run_id = str(uuid4())
|
|
746
|
-
|
|
813
|
+
# Get parent_id from last message in history for tree structure
|
|
814
|
+
last_msg_id = conversation.get_last_message_id()
|
|
815
|
+
user_msg, prompts, original_message = await prepare_prompts(*prompt, parent_id=last_msg_id)
|
|
747
816
|
self.message_received.emit(user_msg)
|
|
748
817
|
start_time = time.perf_counter()
|
|
749
818
|
history_list = conversation.get_history()
|
|
@@ -753,9 +822,6 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
753
822
|
self._cancelled = False
|
|
754
823
|
self._current_stream_task = asyncio.current_task()
|
|
755
824
|
|
|
756
|
-
# Track accumulated content for partial message on cancellation
|
|
757
|
-
accumulated_text: list[str] = []
|
|
758
|
-
|
|
759
825
|
# Execute pre-run hooks
|
|
760
826
|
if self.hooks:
|
|
761
827
|
pre_run_result = await self.hooks.run_pre_run_hooks(
|
|
@@ -770,112 +836,120 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
770
836
|
msg = f"Run blocked: {reason}"
|
|
771
837
|
raise RuntimeError(msg)
|
|
772
838
|
|
|
773
|
-
|
|
839
|
+
run_started = RunStartedEvent(
|
|
840
|
+
thread_id=self.conversation_id, run_id=run_id, agent_name=self.name
|
|
841
|
+
)
|
|
842
|
+
await handler(None, run_started)
|
|
843
|
+
yield run_started
|
|
774
844
|
try:
|
|
845
|
+
from pydantic_graph import End
|
|
846
|
+
|
|
775
847
|
agentlet = await self.get_agentlet(tool_choice, model, output_type, input_provider)
|
|
776
848
|
content = await convert_prompts(prompts)
|
|
777
849
|
response_msg: ChatMessage[Any] | None = None
|
|
778
850
|
# Prepend pending context parts (content is already pydantic-ai format)
|
|
779
851
|
converted = [*pending_parts, *content]
|
|
780
852
|
|
|
781
|
-
#
|
|
782
|
-
|
|
783
|
-
|
|
853
|
+
# Use provided usage_limits or fall back to default
|
|
854
|
+
effective_limits = usage_limits or self._default_usage_limits
|
|
855
|
+
history = [m for run in history_list for m in run.to_pydantic_ai()]
|
|
784
856
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
857
|
+
# Track tool call starts to combine with results later
|
|
858
|
+
pending_tcs: dict[str, BaseToolCallPart] = {}
|
|
859
|
+
file_tracker = FileTracker()
|
|
860
|
+
|
|
861
|
+
async with agentlet.iter(
|
|
788
862
|
converted,
|
|
789
863
|
deps=deps, # type: ignore[arg-type]
|
|
790
|
-
message_history=
|
|
791
|
-
usage_limits=
|
|
864
|
+
message_history=history,
|
|
865
|
+
usage_limits=effective_limits,
|
|
792
866
|
instructions=instructions,
|
|
793
|
-
)
|
|
794
|
-
|
|
795
|
-
# Stream events through merge_queue for progress events
|
|
796
|
-
async with merge_queue_into_iterator(stream_events, self._event_queue) as events:
|
|
797
|
-
# Track tool call starts to combine with results later
|
|
798
|
-
pending_tcs: dict[str, BaseToolCallPart] = {}
|
|
867
|
+
) as agent_run:
|
|
799
868
|
try:
|
|
800
|
-
async for
|
|
801
|
-
# Check for cancellation
|
|
869
|
+
async for node in agent_run:
|
|
802
870
|
if self._cancelled:
|
|
803
871
|
self.log.info("Stream cancelled by user")
|
|
804
872
|
break
|
|
873
|
+
if isinstance(node, End):
|
|
874
|
+
break
|
|
805
875
|
|
|
806
|
-
#
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
876
|
+
# Stream events from model request node
|
|
877
|
+
if isinstance(node, ModelRequestNode):
|
|
878
|
+
async with (
|
|
879
|
+
node.stream(agent_run.ctx) as agent_stream,
|
|
880
|
+
merge_queue_into_iterator(
|
|
881
|
+
agent_stream, # type: ignore[arg-type]
|
|
882
|
+
self._event_queue,
|
|
883
|
+
) as merged,
|
|
884
|
+
):
|
|
885
|
+
async for event in file_tracker.track(merged):
|
|
886
|
+
if self._cancelled:
|
|
887
|
+
break
|
|
888
|
+
await handler(None, event)
|
|
889
|
+
yield event
|
|
890
|
+
combined = self._process_tool_event(
|
|
891
|
+
event, pending_tcs, message_id
|
|
892
|
+
)
|
|
893
|
+
if combined:
|
|
894
|
+
await handler(None, combined)
|
|
895
|
+
yield combined
|
|
896
|
+
|
|
897
|
+
# Stream events from tool call node
|
|
898
|
+
elif isinstance(node, CallToolsNode):
|
|
899
|
+
async with (
|
|
900
|
+
node.stream(agent_run.ctx) as tool_stream,
|
|
901
|
+
merge_queue_into_iterator(tool_stream, self._event_queue) as merged,
|
|
823
902
|
):
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
tool_name=call_info.tool_name,
|
|
832
|
-
tool_call_id=call_id,
|
|
833
|
-
tool_input=call_info.args_as_dict(),
|
|
834
|
-
tool_result=result_event.result.content
|
|
835
|
-
if isinstance(result_event.result, ToolReturnPart)
|
|
836
|
-
else result_event.result,
|
|
837
|
-
agent_name=self.name,
|
|
838
|
-
message_id=message_id,
|
|
903
|
+
async for event in file_tracker.track(merged):
|
|
904
|
+
if self._cancelled:
|
|
905
|
+
break
|
|
906
|
+
await handler(None, event)
|
|
907
|
+
yield event
|
|
908
|
+
combined = self._process_tool_event(
|
|
909
|
+
event, pending_tcs, message_id
|
|
839
910
|
)
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
response_time = time.perf_counter() - start_time
|
|
844
|
-
response_msg = await ChatMessage.from_run_result(
|
|
845
|
-
event.result,
|
|
846
|
-
agent_name=self.name,
|
|
847
|
-
message_id=message_id,
|
|
848
|
-
conversation_id=conversation_id or user_msg.conversation_id,
|
|
849
|
-
response_time=response_time,
|
|
850
|
-
)
|
|
911
|
+
if combined:
|
|
912
|
+
await handler(None, combined)
|
|
913
|
+
yield combined
|
|
851
914
|
except asyncio.CancelledError:
|
|
852
915
|
self.log.info("Stream cancelled via task cancellation")
|
|
853
916
|
self._cancelled = True
|
|
854
917
|
|
|
855
|
-
|
|
856
|
-
if self._cancelled:
|
|
918
|
+
# Build response message
|
|
857
919
|
response_time = time.perf_counter() - start_time
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
920
|
+
if self._cancelled:
|
|
921
|
+
partial_content = _extract_text_from_messages(
|
|
922
|
+
agent_run.all_messages(), include_interruption_note=True
|
|
923
|
+
)
|
|
924
|
+
response_msg = ChatMessage(
|
|
925
|
+
content=partial_content,
|
|
926
|
+
role="assistant",
|
|
927
|
+
name=self.name,
|
|
928
|
+
message_id=message_id,
|
|
929
|
+
conversation_id=conversation_id or user_msg.conversation_id,
|
|
930
|
+
parent_id=user_msg.message_id,
|
|
931
|
+
response_time=response_time,
|
|
932
|
+
finish_reason="stop",
|
|
933
|
+
)
|
|
934
|
+
complete_event = StreamCompleteEvent(message=response_msg)
|
|
935
|
+
await handler(None, complete_event)
|
|
936
|
+
yield complete_event
|
|
937
|
+
self._current_stream_task = None
|
|
938
|
+
return
|
|
939
|
+
|
|
940
|
+
if agent_run.result:
|
|
941
|
+
response_msg = await ChatMessage.from_run_result(
|
|
942
|
+
agent_run.result,
|
|
943
|
+
agent_name=self.name,
|
|
944
|
+
message_id=message_id,
|
|
945
|
+
conversation_id=conversation_id or user_msg.conversation_id,
|
|
946
|
+
parent_id=user_msg.message_id,
|
|
947
|
+
response_time=response_time,
|
|
948
|
+
metadata=file_tracker.get_metadata(),
|
|
949
|
+
)
|
|
950
|
+
else:
|
|
951
|
+
msg = "Stream completed without producing a result"
|
|
952
|
+
raise RuntimeError(msg) # noqa: TRY301
|
|
879
953
|
|
|
880
954
|
# Execute post-run hooks
|
|
881
955
|
if self.hooks:
|
|
@@ -893,7 +967,9 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
893
967
|
if original_message:
|
|
894
968
|
response_msg = response_msg.forwarded(original_message)
|
|
895
969
|
# Send additional enriched completion event
|
|
896
|
-
|
|
970
|
+
complete_event = StreamCompleteEvent(message=response_msg)
|
|
971
|
+
await handler(None, complete_event)
|
|
972
|
+
yield complete_event
|
|
897
973
|
self.message_sent.emit(response_msg)
|
|
898
974
|
await self.log_message(response_msg)
|
|
899
975
|
if store_history:
|
|
@@ -907,6 +983,41 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
907
983
|
finally:
|
|
908
984
|
self._current_stream_task = None
|
|
909
985
|
|
|
986
|
+
def _process_tool_event(
|
|
987
|
+
self,
|
|
988
|
+
event: RichAgentStreamEvent[Any],
|
|
989
|
+
pending_tool_calls: dict[str, BaseToolCallPart],
|
|
990
|
+
message_id: str,
|
|
991
|
+
) -> ToolCallCompleteEvent | None:
|
|
992
|
+
"""Process tool-related events and return combined event when complete.
|
|
993
|
+
|
|
994
|
+
Args:
|
|
995
|
+
event: The streaming event to process
|
|
996
|
+
pending_tool_calls: Dict tracking in-progress tool calls by ID
|
|
997
|
+
message_id: Message ID for the combined event
|
|
998
|
+
|
|
999
|
+
Returns:
|
|
1000
|
+
ToolCallCompleteEvent if a tool call completed, None otherwise
|
|
1001
|
+
"""
|
|
1002
|
+
match event:
|
|
1003
|
+
case PartStartEvent(part=BaseToolCallPart() as tool_part):
|
|
1004
|
+
pending_tool_calls[tool_part.tool_call_id] = tool_part
|
|
1005
|
+
case FunctionToolCallEvent(part=tool_part):
|
|
1006
|
+
pending_tool_calls[tool_part.tool_call_id] = tool_part
|
|
1007
|
+
case FunctionToolResultEvent(tool_call_id=call_id) as result_event:
|
|
1008
|
+
if call_info := pending_tool_calls.pop(call_id, None):
|
|
1009
|
+
return ToolCallCompleteEvent(
|
|
1010
|
+
tool_name=call_info.tool_name,
|
|
1011
|
+
tool_call_id=call_id,
|
|
1012
|
+
tool_input=safe_args_as_dict(call_info),
|
|
1013
|
+
tool_result=result_event.result.content
|
|
1014
|
+
if isinstance(result_event.result, ToolReturnPart)
|
|
1015
|
+
else result_event.result,
|
|
1016
|
+
agent_name=self.name,
|
|
1017
|
+
message_id=message_id,
|
|
1018
|
+
)
|
|
1019
|
+
return None
|
|
1020
|
+
|
|
910
1021
|
async def run_iter(
|
|
911
1022
|
self,
|
|
912
1023
|
*prompt_groups: Sequence[PromptCompatible],
|
|
@@ -1131,7 +1242,7 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
1131
1242
|
parent=self if pass_message_history else None,
|
|
1132
1243
|
)
|
|
1133
1244
|
|
|
1134
|
-
def set_model(self, model: ModelType) -> None:
|
|
1245
|
+
async def set_model(self, model: ModelType) -> None:
|
|
1135
1246
|
"""Set the model for this agent.
|
|
1136
1247
|
|
|
1137
1248
|
Args:
|
|
@@ -1166,11 +1277,6 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
1166
1277
|
)
|
|
1167
1278
|
self.agent_reset.emit(event)
|
|
1168
1279
|
|
|
1169
|
-
async def get_stats(self) -> MessageStats:
|
|
1170
|
-
"""Get message statistics (async version)."""
|
|
1171
|
-
messages = await self.get_message_history()
|
|
1172
|
-
return MessageStats(messages=messages)
|
|
1173
|
-
|
|
1174
1280
|
@asynccontextmanager
|
|
1175
1281
|
async def temporary_state[T](
|
|
1176
1282
|
self,
|
|
@@ -1247,6 +1353,107 @@ class Agent[TDeps = None, OutputDataT = str](BaseAgent[TDeps, OutputDataT]):
|
|
|
1247
1353
|
else:
|
|
1248
1354
|
return True
|
|
1249
1355
|
|
|
1356
|
+
async def get_available_models(self) -> list[ModelInfo] | None:
|
|
1357
|
+
"""Get available models for this agent.
|
|
1358
|
+
|
|
1359
|
+
Uses tokonomics model discovery to fetch models from configured providers.
|
|
1360
|
+
Defaults to openai, anthropic, and gemini if no providers specified.
|
|
1361
|
+
|
|
1362
|
+
Returns:
|
|
1363
|
+
List of tokonomics ModelInfo, or None if discovery fails
|
|
1364
|
+
"""
|
|
1365
|
+
from datetime import timedelta
|
|
1366
|
+
|
|
1367
|
+
from tokonomics.model_discovery import get_all_models
|
|
1368
|
+
|
|
1369
|
+
try:
|
|
1370
|
+
max_age = timedelta(days=200)
|
|
1371
|
+
return await get_all_models(
|
|
1372
|
+
providers=self._providers or ["models.dev"], max_age=max_age
|
|
1373
|
+
)
|
|
1374
|
+
except Exception:
|
|
1375
|
+
self.log.exception("Failed to discover models")
|
|
1376
|
+
return None
|
|
1377
|
+
|
|
1378
|
+
def get_modes(self) -> list[ModeCategory]:
|
|
1379
|
+
"""Get available mode categories for this agent.
|
|
1380
|
+
|
|
1381
|
+
Native agents expose tool confirmation modes.
|
|
1382
|
+
|
|
1383
|
+
Returns:
|
|
1384
|
+
List with single ModeCategory for tool confirmation
|
|
1385
|
+
"""
|
|
1386
|
+
from agentpool.agents.modes import ModeCategory, ModeInfo
|
|
1387
|
+
|
|
1388
|
+
# Map current confirmation mode to mode ID
|
|
1389
|
+
mode_id_map = {
|
|
1390
|
+
"per_tool": "default",
|
|
1391
|
+
"always": "default",
|
|
1392
|
+
"never": "acceptEdits",
|
|
1393
|
+
}
|
|
1394
|
+
current_id = mode_id_map.get(self.tool_confirmation_mode, "default")
|
|
1395
|
+
|
|
1396
|
+
category_id = "permissions"
|
|
1397
|
+
return [
|
|
1398
|
+
ModeCategory(
|
|
1399
|
+
id=category_id,
|
|
1400
|
+
name="Permissions",
|
|
1401
|
+
available_modes=[
|
|
1402
|
+
ModeInfo(
|
|
1403
|
+
id="default",
|
|
1404
|
+
name="Default",
|
|
1405
|
+
description="Require confirmation for tools marked as needing it",
|
|
1406
|
+
category_id=category_id,
|
|
1407
|
+
),
|
|
1408
|
+
ModeInfo(
|
|
1409
|
+
id="acceptEdits",
|
|
1410
|
+
name="Accept Edits",
|
|
1411
|
+
description="Auto-approve all tool calls without confirmation",
|
|
1412
|
+
category_id=category_id,
|
|
1413
|
+
),
|
|
1414
|
+
],
|
|
1415
|
+
current_mode_id=current_id,
|
|
1416
|
+
)
|
|
1417
|
+
]
|
|
1418
|
+
|
|
1419
|
+
async def set_mode(self, mode: ModeInfo | str, category_id: str | None = None) -> None:
|
|
1420
|
+
"""Set a mode for this agent.
|
|
1421
|
+
|
|
1422
|
+
Native agents support the "permissions" category with modes:
|
|
1423
|
+
- "default": per_tool confirmation
|
|
1424
|
+
- "acceptEdits": never confirm (auto-approve)
|
|
1425
|
+
|
|
1426
|
+
Args:
|
|
1427
|
+
mode: Mode to activate - ModeInfo object or mode ID string
|
|
1428
|
+
category_id: Optional category (only "permissions" supported)
|
|
1429
|
+
|
|
1430
|
+
Raises:
|
|
1431
|
+
ValueError: If mode_id is invalid
|
|
1432
|
+
"""
|
|
1433
|
+
# Extract mode_id and category from ModeInfo if provided
|
|
1434
|
+
if isinstance(mode, ModeInfo):
|
|
1435
|
+
mode_id = mode.id
|
|
1436
|
+
category_id = category_id or mode.category_id or None
|
|
1437
|
+
else:
|
|
1438
|
+
mode_id = mode
|
|
1439
|
+
|
|
1440
|
+
# Validate category if provided
|
|
1441
|
+
if category_id is not None and category_id != "permissions":
|
|
1442
|
+
msg = f"Unknown category: {category_id}. Only 'permissions' is supported."
|
|
1443
|
+
raise ValueError(msg)
|
|
1444
|
+
|
|
1445
|
+
# Map mode_id to confirmation mode
|
|
1446
|
+
mode_map: dict[str, ToolConfirmationMode] = {
|
|
1447
|
+
"default": "per_tool",
|
|
1448
|
+
"acceptEdits": "never",
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
if mode_id not in mode_map:
|
|
1452
|
+
msg = f"Unknown mode: {mode_id}. Available: {list(mode_map.keys())}"
|
|
1453
|
+
raise ValueError(msg)
|
|
1454
|
+
|
|
1455
|
+
await self.set_tool_confirmation_mode(mode_map[mode_id])
|
|
1456
|
+
|
|
1250
1457
|
|
|
1251
1458
|
if __name__ == "__main__":
|
|
1252
1459
|
import logging
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from agentpool.agents.agui_agent.agui_agent import AGUIAgent
|
|
4
4
|
from agentpool.agents.agui_agent.agui_converters import (
|
|
5
|
-
ToolCallAccumulator,
|
|
6
5
|
agui_to_native_event,
|
|
7
6
|
to_agui_input_content,
|
|
8
7
|
to_agui_tool,
|
|
@@ -12,7 +11,6 @@ from agentpool.agents.agui_agent.chunk_transformer import ChunkTransformer
|
|
|
12
11
|
__all__ = [
|
|
13
12
|
"AGUIAgent",
|
|
14
13
|
"ChunkTransformer",
|
|
15
|
-
"ToolCallAccumulator",
|
|
16
14
|
"agui_to_native_event",
|
|
17
15
|
"to_agui_input_content",
|
|
18
16
|
"to_agui_tool",
|