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
|
@@ -8,12 +8,15 @@ from agentpool.agents.events import (
|
|
|
8
8
|
CustomEvent,
|
|
9
9
|
LocationContentItem,
|
|
10
10
|
PlanUpdateEvent,
|
|
11
|
+
TextContentItem,
|
|
11
12
|
ToolCallProgressEvent,
|
|
12
13
|
ToolCallStartEvent,
|
|
13
14
|
)
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Sequence
|
|
19
|
+
|
|
17
20
|
from agentpool.agents.context import AgentContext
|
|
18
21
|
from agentpool.agents.events import RichAgentStreamEvent, ToolCallContentItem
|
|
19
22
|
from agentpool.resource_providers.plan_provider import PlanEntry
|
|
@@ -78,7 +81,7 @@ class StreamEventEmitter:
|
|
|
78
81
|
title: str,
|
|
79
82
|
*,
|
|
80
83
|
status: Literal["pending", "in_progress", "completed", "failed"] = "in_progress",
|
|
81
|
-
items:
|
|
84
|
+
items: Sequence[ToolCallContentItem | str] | None = None,
|
|
82
85
|
replace_content: bool = False,
|
|
83
86
|
) -> None:
|
|
84
87
|
"""Emit a progress event.
|
|
@@ -89,12 +92,26 @@ class StreamEventEmitter:
|
|
|
89
92
|
items: Rich content items (terminals, diffs, locations, text)
|
|
90
93
|
replace_content: If True, items replace existing content instead of appending
|
|
91
94
|
"""
|
|
95
|
+
# Record file changes from DiffContentItem for diff/revert support
|
|
96
|
+
if self._context.pool and self._context.pool.file_ops and items:
|
|
97
|
+
from agentpool.agents.events import DiffContentItem
|
|
98
|
+
|
|
99
|
+
for item in items:
|
|
100
|
+
if isinstance(item, DiffContentItem):
|
|
101
|
+
self._context.pool.file_ops.record_change(
|
|
102
|
+
path=item.path,
|
|
103
|
+
old_content=item.old_text,
|
|
104
|
+
new_content=item.new_text,
|
|
105
|
+
operation="create" if item.old_text is None else "write",
|
|
106
|
+
agent_name=self._context.node_name,
|
|
107
|
+
)
|
|
108
|
+
|
|
92
109
|
event = ToolCallProgressEvent(
|
|
93
110
|
tool_call_id=self._context.tool_call_id or "",
|
|
94
111
|
tool_name=self._context.tool_name,
|
|
95
112
|
status=status,
|
|
96
113
|
title=title,
|
|
97
|
-
items=items or [],
|
|
114
|
+
items=[TextContentItem(text=i) if isinstance(i, str) else i for i in items or []],
|
|
98
115
|
replace_content=replace_content,
|
|
99
116
|
)
|
|
100
117
|
await self._emit(event)
|
|
@@ -163,6 +180,16 @@ class StreamEventEmitter:
|
|
|
163
180
|
new_text: New file content
|
|
164
181
|
status: Current status of the edit operation
|
|
165
182
|
"""
|
|
183
|
+
# Record file change for diff/revert support
|
|
184
|
+
if self._context.pool and self._context.pool.file_ops:
|
|
185
|
+
self._context.pool.file_ops.record_change(
|
|
186
|
+
path=path,
|
|
187
|
+
old_content=old_text,
|
|
188
|
+
new_content=new_text,
|
|
189
|
+
operation="edit",
|
|
190
|
+
agent_name=self._context.node_name,
|
|
191
|
+
)
|
|
192
|
+
|
|
166
193
|
event = ToolCallProgressEvent.file_edit(
|
|
167
194
|
tool_call_id=self._context.tool_call_id or "",
|
|
168
195
|
tool_name=self._context.tool_name,
|
|
@@ -543,6 +543,25 @@ class PlanUpdateEvent:
|
|
|
543
543
|
"""Event type identifier."""
|
|
544
544
|
|
|
545
545
|
|
|
546
|
+
@dataclass(kw_only=True)
|
|
547
|
+
class CompactionEvent:
|
|
548
|
+
"""Event indicating context compaction is starting or completed.
|
|
549
|
+
|
|
550
|
+
This is a semantic event that consumers (ACP, OpenCode) handle differently:
|
|
551
|
+
- ACP: Converts to a text message for display
|
|
552
|
+
- OpenCode: Emits session.compacted SSE event
|
|
553
|
+
"""
|
|
554
|
+
|
|
555
|
+
session_id: str
|
|
556
|
+
"""The session ID being compacted."""
|
|
557
|
+
trigger: Literal["auto", "manual"] = "auto"
|
|
558
|
+
"""What triggered the compaction (auto = context overflow, manual = slash command)."""
|
|
559
|
+
phase: Literal["starting", "completed"] = "starting"
|
|
560
|
+
"""Current phase of compaction."""
|
|
561
|
+
event_kind: Literal["compaction"] = "compaction"
|
|
562
|
+
"""Event type identifier."""
|
|
563
|
+
|
|
564
|
+
|
|
546
565
|
type RichAgentStreamEvent[OutputDataT] = (
|
|
547
566
|
AgentStreamEvent
|
|
548
567
|
| StreamCompleteEvent[OutputDataT]
|
|
@@ -552,6 +571,7 @@ type RichAgentStreamEvent[OutputDataT] = (
|
|
|
552
571
|
| ToolCallProgressEvent
|
|
553
572
|
| ToolCallCompleteEvent
|
|
554
573
|
| PlanUpdateEvent
|
|
574
|
+
| CompactionEvent
|
|
555
575
|
| CustomEvent[Any]
|
|
556
576
|
)
|
|
557
577
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Mode definitions for agent behavior configuration.
|
|
2
|
+
|
|
3
|
+
Modes represent switchable behaviors/configurations that agents can expose
|
|
4
|
+
to clients. Each agent type can define its own mode categories.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ModeInfo:
|
|
14
|
+
"""Information about a single mode option.
|
|
15
|
+
|
|
16
|
+
Represents one selectable option within a mode category.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
id: str
|
|
20
|
+
"""Unique identifier for this mode."""
|
|
21
|
+
|
|
22
|
+
name: str
|
|
23
|
+
"""Human-readable display name."""
|
|
24
|
+
|
|
25
|
+
description: str = ""
|
|
26
|
+
"""Optional description of what this mode does."""
|
|
27
|
+
|
|
28
|
+
category_id: str = ""
|
|
29
|
+
"""ID of the category this mode belongs to."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class ModeCategory:
|
|
34
|
+
"""A category of modes that can be switched.
|
|
35
|
+
|
|
36
|
+
Represents a group of mutually exclusive modes. In the future,
|
|
37
|
+
ACP may support multiple categories rendered as separate dropdowns.
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
- Permissions: default, acceptEdits
|
|
41
|
+
- Behavior: plan, code, architect
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
id: str
|
|
45
|
+
"""Unique identifier for this category."""
|
|
46
|
+
|
|
47
|
+
name: str
|
|
48
|
+
"""Human-readable display name for the category."""
|
|
49
|
+
|
|
50
|
+
available_modes: list[ModeInfo] = field(default_factory=list)
|
|
51
|
+
"""List of available modes in this category."""
|
|
52
|
+
|
|
53
|
+
current_mode_id: str = ""
|
|
54
|
+
"""ID of the currently active mode."""
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Tool call accumulator for streaming tool arguments.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for accumulating streamed tool call arguments
|
|
4
|
+
from LLM APIs that stream JSON arguments incrementally (like Anthropic's
|
|
5
|
+
input_json_delta or OpenAI's function call streaming).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import anyenv
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def repair_partial_json(buffer: str) -> str:
|
|
16
|
+
"""Attempt to repair truncated JSON for preview purposes.
|
|
17
|
+
|
|
18
|
+
Handles common truncation cases:
|
|
19
|
+
- Unclosed strings
|
|
20
|
+
- Missing closing braces/brackets
|
|
21
|
+
- Trailing commas
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
buffer: Potentially incomplete JSON string
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Repaired JSON string (may still be invalid in edge cases)
|
|
28
|
+
"""
|
|
29
|
+
if not buffer:
|
|
30
|
+
return "{}"
|
|
31
|
+
|
|
32
|
+
result = buffer.rstrip()
|
|
33
|
+
|
|
34
|
+
# Check if we're in the middle of a string by counting unescaped quotes
|
|
35
|
+
in_string = False
|
|
36
|
+
i = 0
|
|
37
|
+
while i < len(result):
|
|
38
|
+
char = result[i]
|
|
39
|
+
if char == "\\" and i + 1 < len(result):
|
|
40
|
+
i += 2 # Skip escaped character
|
|
41
|
+
continue
|
|
42
|
+
if char == '"':
|
|
43
|
+
in_string = not in_string
|
|
44
|
+
i += 1
|
|
45
|
+
|
|
46
|
+
# Close unclosed string
|
|
47
|
+
if in_string:
|
|
48
|
+
result += '"'
|
|
49
|
+
|
|
50
|
+
# Remove trailing comma (invalid JSON)
|
|
51
|
+
result = result.rstrip()
|
|
52
|
+
if result.endswith(","):
|
|
53
|
+
result = result[:-1]
|
|
54
|
+
|
|
55
|
+
# Balance braces and brackets
|
|
56
|
+
open_braces = result.count("{") - result.count("}")
|
|
57
|
+
open_brackets = result.count("[") - result.count("]")
|
|
58
|
+
|
|
59
|
+
result += "]" * max(0, open_brackets)
|
|
60
|
+
result += "}" * max(0, open_braces)
|
|
61
|
+
|
|
62
|
+
return result
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ToolCallAccumulator:
|
|
66
|
+
"""Accumulates streamed tool call arguments.
|
|
67
|
+
|
|
68
|
+
LLM APIs stream tool call arguments as deltas. This class accumulates them
|
|
69
|
+
and provides the complete arguments when the tool call ends, as well as
|
|
70
|
+
best-effort partial argument parsing during streaming.
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
```python
|
|
74
|
+
accumulator = ToolCallAccumulator()
|
|
75
|
+
|
|
76
|
+
# On content_block_start with tool_use
|
|
77
|
+
accumulator.start("toolu_123", "write_file")
|
|
78
|
+
|
|
79
|
+
# On input_json_delta events
|
|
80
|
+
accumulator.add_args("toolu_123", '{"path": "/tmp/')
|
|
81
|
+
accumulator.add_args("toolu_123", 'test.txt", "content"')
|
|
82
|
+
accumulator.add_args("toolu_123", ': "hello"}')
|
|
83
|
+
|
|
84
|
+
# Get partial args for UI preview (repairs incomplete JSON)
|
|
85
|
+
partial = accumulator.get_partial_args("toolu_123")
|
|
86
|
+
|
|
87
|
+
# On content_block_stop, get final parsed args
|
|
88
|
+
name, args = accumulator.complete("toolu_123")
|
|
89
|
+
```
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(self) -> None:
|
|
93
|
+
self._calls: dict[str, dict[str, Any]] = {}
|
|
94
|
+
|
|
95
|
+
def start(self, tool_call_id: str, tool_name: str) -> None:
|
|
96
|
+
"""Start tracking a new tool call.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
tool_call_id: Unique identifier for the tool call
|
|
100
|
+
tool_name: Name of the tool being called
|
|
101
|
+
"""
|
|
102
|
+
self._calls[tool_call_id] = {"name": tool_name, "args_buffer": ""}
|
|
103
|
+
|
|
104
|
+
def add_args(self, tool_call_id: str, delta: str) -> None:
|
|
105
|
+
"""Add argument delta to a tool call.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
tool_call_id: Tool call identifier
|
|
109
|
+
delta: JSON string fragment to append
|
|
110
|
+
"""
|
|
111
|
+
if tool_call_id in self._calls:
|
|
112
|
+
self._calls[tool_call_id]["args_buffer"] += delta
|
|
113
|
+
|
|
114
|
+
def complete(self, tool_call_id: str) -> tuple[str, dict[str, Any]] | None:
|
|
115
|
+
"""Complete a tool call and return (tool_name, parsed_args).
|
|
116
|
+
|
|
117
|
+
Removes the tool call from tracking and returns the final parsed arguments.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
tool_call_id: Tool call identifier
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Tuple of (tool_name, args_dict) or None if call not found
|
|
124
|
+
"""
|
|
125
|
+
if tool_call_id not in self._calls:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
call_data = self._calls.pop(tool_call_id)
|
|
129
|
+
args_str = call_data["args_buffer"]
|
|
130
|
+
try:
|
|
131
|
+
args = anyenv.load_json(args_str) if args_str else {}
|
|
132
|
+
except anyenv.JsonLoadError:
|
|
133
|
+
args = {"_raw": args_str}
|
|
134
|
+
return call_data["name"], args
|
|
135
|
+
|
|
136
|
+
def get_pending(self, tool_call_id: str) -> tuple[str, str] | None:
|
|
137
|
+
"""Get pending call data (tool_name, args_buffer) without completing.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
tool_call_id: Tool call identifier
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Tuple of (tool_name, args_buffer) or None if not found
|
|
144
|
+
"""
|
|
145
|
+
if tool_call_id not in self._calls:
|
|
146
|
+
return None
|
|
147
|
+
data = self._calls[tool_call_id]
|
|
148
|
+
return data["name"], data["args_buffer"]
|
|
149
|
+
|
|
150
|
+
def get_partial_args(self, tool_call_id: str) -> dict[str, Any]:
|
|
151
|
+
"""Get best-effort parsed args from incomplete JSON stream.
|
|
152
|
+
|
|
153
|
+
Uses heuristics to complete truncated JSON for preview purposes.
|
|
154
|
+
Handles unclosed strings, missing braces/brackets, and trailing commas.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
tool_call_id: Tool call ID
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Partially parsed arguments or empty dict
|
|
161
|
+
"""
|
|
162
|
+
if tool_call_id not in self._calls:
|
|
163
|
+
return {}
|
|
164
|
+
|
|
165
|
+
buffer = self._calls[tool_call_id]["args_buffer"]
|
|
166
|
+
if not buffer:
|
|
167
|
+
return {}
|
|
168
|
+
|
|
169
|
+
# Try direct parse first
|
|
170
|
+
try:
|
|
171
|
+
result: dict[str, Any] = anyenv.load_json(buffer)
|
|
172
|
+
except anyenv.JsonLoadError:
|
|
173
|
+
pass
|
|
174
|
+
else:
|
|
175
|
+
return result
|
|
176
|
+
|
|
177
|
+
# Try to repair truncated JSON
|
|
178
|
+
try:
|
|
179
|
+
repaired = repair_partial_json(buffer)
|
|
180
|
+
result = anyenv.load_json(repaired)
|
|
181
|
+
except anyenv.JsonLoadError:
|
|
182
|
+
return {}
|
|
183
|
+
else:
|
|
184
|
+
return result
|
|
185
|
+
|
|
186
|
+
def is_pending(self, tool_call_id: str) -> bool:
|
|
187
|
+
"""Check if a tool call is being tracked.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
tool_call_id: Tool call identifier
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
True if the tool call is being accumulated
|
|
194
|
+
"""
|
|
195
|
+
return tool_call_id in self._calls
|
|
196
|
+
|
|
197
|
+
def get_tool_name(self, tool_call_id: str) -> str | None:
|
|
198
|
+
"""Get the tool name for a pending call.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
tool_call_id: Tool call identifier
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Tool name or None if not found
|
|
205
|
+
"""
|
|
206
|
+
if tool_call_id not in self._calls:
|
|
207
|
+
return None
|
|
208
|
+
name: str = self._calls[tool_call_id]["name"]
|
|
209
|
+
return name
|
|
210
|
+
|
|
211
|
+
def clear(self) -> None:
|
|
212
|
+
"""Clear all pending tool calls."""
|
|
213
|
+
self._calls.clear()
|
agentpool/common_types.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from collections.abc import Awaitable, Callable
|
|
6
|
+
from dataclasses import dataclass
|
|
6
7
|
from typing import (
|
|
7
8
|
TYPE_CHECKING,
|
|
8
9
|
Any,
|
|
@@ -50,6 +51,26 @@ type JsonObject = dict[str, JsonValue]
|
|
|
50
51
|
type JsonArray = list[JsonValue]
|
|
51
52
|
|
|
52
53
|
|
|
54
|
+
MCPConnectionStatus = Literal["connected", "disconnected", "error"]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True, slots=True)
|
|
58
|
+
class MCPServerStatus:
|
|
59
|
+
"""Status information for an MCP server.
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
name: Server name/identifier
|
|
63
|
+
status: Connection status
|
|
64
|
+
server_type: Transport type (stdio, sse, http)
|
|
65
|
+
error: Error message if status is "error"
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
name: str
|
|
69
|
+
status: MCPConnectionStatus
|
|
70
|
+
server_type: str = "unknown"
|
|
71
|
+
error: str | None = None
|
|
72
|
+
|
|
73
|
+
|
|
53
74
|
NodeName = str
|
|
54
75
|
TeamName = str
|
|
55
76
|
AgentName = str
|
|
@@ -7,10 +7,47 @@ from typing import Final
|
|
|
7
7
|
|
|
8
8
|
_RESOURCES = importlib.resources.files("agentpool.config_resources")
|
|
9
9
|
|
|
10
|
+
# Template configuration
|
|
10
11
|
AGENTS_TEMPLATE: Final[str] = str(_RESOURCES / "agents_template.yml")
|
|
11
12
|
"""Path to the agents template configuration."""
|
|
12
13
|
|
|
14
|
+
# Pool configurations
|
|
13
15
|
ACP_ASSISTANT: Final[str] = str(_RESOURCES / "acp_assistant.yml")
|
|
14
16
|
"""Path to default ACP assistant configuration."""
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
AGENTS: Final[str] = str(_RESOURCES / "agents.yml")
|
|
19
|
+
"""Path to the main agents configuration."""
|
|
20
|
+
|
|
21
|
+
AGUI_TEST: Final[str] = str(_RESOURCES / "agui_test.yml")
|
|
22
|
+
"""Path to AGUI test configuration."""
|
|
23
|
+
|
|
24
|
+
CLAUDE_CODE_ASSISTANT: Final[str] = str(_RESOURCES / "claude_code_agent.yml")
|
|
25
|
+
"""Path to default Claude code assistant configuration."""
|
|
26
|
+
|
|
27
|
+
EXTERNAL_ACP_AGENTS: Final[str] = str(_RESOURCES / "external_acp_agents.yml")
|
|
28
|
+
"""Path to external ACP agents configuration."""
|
|
29
|
+
|
|
30
|
+
TTS_TEST_AGENTS: Final[str] = str(_RESOURCES / "tts_test_agents.yml")
|
|
31
|
+
"""Path to TTS test agents configuration."""
|
|
32
|
+
|
|
33
|
+
# All pool configuration paths for validation
|
|
34
|
+
ALL_POOL_CONFIGS: Final[tuple[str, ...]] = (
|
|
35
|
+
ACP_ASSISTANT,
|
|
36
|
+
AGENTS,
|
|
37
|
+
AGUI_TEST,
|
|
38
|
+
CLAUDE_CODE_ASSISTANT,
|
|
39
|
+
EXTERNAL_ACP_AGENTS,
|
|
40
|
+
TTS_TEST_AGENTS,
|
|
41
|
+
)
|
|
42
|
+
"""All pool configuration file paths."""
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"ACP_ASSISTANT",
|
|
46
|
+
"AGENTS",
|
|
47
|
+
"AGENTS_TEMPLATE",
|
|
48
|
+
"AGUI_TEST",
|
|
49
|
+
"ALL_POOL_CONFIGS",
|
|
50
|
+
"CLAUDE_CODE_ASSISTANT",
|
|
51
|
+
"EXTERNAL_ACP_AGENTS",
|
|
52
|
+
"TTS_TEST_AGENTS",
|
|
53
|
+
]
|
|
@@ -7,10 +7,13 @@ agents:
|
|
|
7
7
|
type: claude_code
|
|
8
8
|
display_name: "AI Assistant"
|
|
9
9
|
builtin_tools: []
|
|
10
|
+
env:
|
|
11
|
+
ANTHROPIC_API_KEY: ""
|
|
10
12
|
description: "Claude code agent with enhanced tools"
|
|
11
13
|
toolsets:
|
|
12
14
|
- type: execution
|
|
13
15
|
- type: file_access
|
|
16
|
+
edit_tool: batch
|
|
14
17
|
- type: code
|
|
15
18
|
- type: search
|
|
16
19
|
- type: plan
|
agentpool/delegation/pool.py
CHANGED
|
@@ -40,7 +40,6 @@ if TYPE_CHECKING:
|
|
|
40
40
|
from pydantic_ai import ModelSettings
|
|
41
41
|
from pydantic_ai.models import Model
|
|
42
42
|
from pydantic_ai.output import OutputSpec
|
|
43
|
-
from tokonomics.model_names import ModelId
|
|
44
43
|
from upathtools import JoinablePathLike
|
|
45
44
|
|
|
46
45
|
from agentpool.agents.acp_agent import ACPAgent
|
|
@@ -57,6 +56,7 @@ if TYPE_CHECKING:
|
|
|
57
56
|
)
|
|
58
57
|
from agentpool.delegation.base_team import BaseTeam
|
|
59
58
|
from agentpool.mcp_server.tool_bridge import ToolManagerBridge
|
|
59
|
+
from agentpool.messaging.compaction import CompactionPipeline
|
|
60
60
|
from agentpool.models import (
|
|
61
61
|
AGUIAgentConfig,
|
|
62
62
|
AnyAgentConfig,
|
|
@@ -118,6 +118,7 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
|
|
|
118
118
|
from agentpool.sessions import SessionManager
|
|
119
119
|
from agentpool.skills.manager import SkillsManager
|
|
120
120
|
from agentpool.storage import StorageManager
|
|
121
|
+
from agentpool.utils.streams import FileOpsTracker, TodoTracker
|
|
121
122
|
|
|
122
123
|
match manifest:
|
|
123
124
|
case None:
|
|
@@ -131,6 +132,12 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
|
|
|
131
132
|
raise ValueError(msg)
|
|
132
133
|
|
|
133
134
|
registry.configure_observability(self.manifest.observability)
|
|
135
|
+
|
|
136
|
+
# Install memory log handler early so all logs are captured
|
|
137
|
+
from agentpool_toolsets.builtin.debug import install_memory_handler
|
|
138
|
+
|
|
139
|
+
self._memory_log_handler = install_memory_handler()
|
|
140
|
+
|
|
134
141
|
self.shared_deps_type = shared_deps_type
|
|
135
142
|
self._input_provider = input_provider
|
|
136
143
|
self.exit_stack = AsyncExitStack()
|
|
@@ -150,14 +157,12 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
|
|
|
150
157
|
self._tasks.register(name, task)
|
|
151
158
|
self.process_manager = ProcessManager()
|
|
152
159
|
self.pool_talk = TeamTalk[Any].from_nodes(list(self.nodes.values()))
|
|
153
|
-
|
|
160
|
+
self.file_ops = FileOpsTracker()
|
|
161
|
+
# Todo/plan tracker for task management
|
|
162
|
+
self.todos = TodoTracker()
|
|
154
163
|
# Create all agents from unified manifest.agents dict
|
|
155
164
|
for name, config in self.manifest.agents.items():
|
|
156
|
-
agent = self._create_agent_from_config(
|
|
157
|
-
name,
|
|
158
|
-
config,
|
|
159
|
-
deps_type=shared_deps_type,
|
|
160
|
-
)
|
|
165
|
+
agent = self._create_agent_from_config(name, config, deps_type=shared_deps_type)
|
|
161
166
|
agent.agent_pool = self
|
|
162
167
|
self.register(name, agent)
|
|
163
168
|
|
|
@@ -179,32 +184,23 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
|
|
|
179
184
|
await self.exit_stack.enter_async_context(self.mcp)
|
|
180
185
|
await self.exit_stack.enter_async_context(self.skills)
|
|
181
186
|
aggregating_provider = self.mcp.get_aggregating_provider()
|
|
182
|
-
agents = list(self.
|
|
183
|
-
acp_agents = list(self.acp_agents.values())
|
|
184
|
-
agui_agents = list(self.agui_agents.values())
|
|
185
|
-
claude_code_agents = list(self.claude_code_agents.values())
|
|
187
|
+
agents = list(self.all_agents.values())
|
|
186
188
|
teams = list(self.teams.values())
|
|
187
189
|
for agent in agents:
|
|
188
190
|
agent.tools.add_provider(aggregating_provider)
|
|
189
191
|
# Collect remaining components to initialize (MCP already initialized)
|
|
190
|
-
|
|
192
|
+
comps: list[AbstractAsyncContextManager[Any]] = [
|
|
191
193
|
self.storage,
|
|
192
194
|
self.sessions,
|
|
193
195
|
*agents,
|
|
194
|
-
*acp_agents,
|
|
195
|
-
*agui_agents,
|
|
196
|
-
*claude_code_agents,
|
|
197
196
|
*teams,
|
|
198
197
|
]
|
|
199
|
-
|
|
200
|
-
# Initialize all components
|
|
198
|
+
node_inits = [self.exit_stack.enter_async_context(c) for c in comps]
|
|
201
199
|
if self.parallel_load:
|
|
202
|
-
await asyncio.gather(
|
|
203
|
-
*(self.exit_stack.enter_async_context(c) for c in components)
|
|
204
|
-
)
|
|
200
|
+
await asyncio.gather(*node_inits)
|
|
205
201
|
else:
|
|
206
|
-
for
|
|
207
|
-
await
|
|
202
|
+
for init in node_inits:
|
|
203
|
+
await init
|
|
208
204
|
|
|
209
205
|
except Exception as e:
|
|
210
206
|
await self.cleanup()
|
|
@@ -538,6 +534,15 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
|
|
|
538
534
|
|
|
539
535
|
return {i.name: i for i in self._items.values() if isinstance(i, MessageNode)}
|
|
540
536
|
|
|
537
|
+
@property
|
|
538
|
+
def compaction_pipeline(self) -> CompactionPipeline | None:
|
|
539
|
+
"""Get the configured compaction pipeline for message history management.
|
|
540
|
+
|
|
541
|
+
Returns:
|
|
542
|
+
CompactionPipeline instance or None if not configured
|
|
543
|
+
"""
|
|
544
|
+
return self.manifest.get_compaction_pipeline()
|
|
545
|
+
|
|
541
546
|
def _validate_item(self, item: MessageNode[Any, Any] | Any) -> MessageNode[Any, Any]:
|
|
542
547
|
"""Validate and convert items before registration.
|
|
543
548
|
|
|
@@ -639,7 +644,6 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
|
|
|
639
644
|
agent: AgentName | Agent[Any, str],
|
|
640
645
|
*,
|
|
641
646
|
return_type: type[TResult] = str, # type: ignore
|
|
642
|
-
model_override: ModelId | str | None = None,
|
|
643
647
|
session: SessionIdType | SessionQuery = None,
|
|
644
648
|
) -> Agent[TPoolDeps, TResult]: ...
|
|
645
649
|
|
|
@@ -650,7 +654,6 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
|
|
|
650
654
|
*,
|
|
651
655
|
deps_type: type[TCustomDeps],
|
|
652
656
|
return_type: type[TResult] = str, # type: ignore
|
|
653
|
-
model_override: ModelId | str | None = None,
|
|
654
657
|
session: SessionIdType | SessionQuery = None,
|
|
655
658
|
) -> Agent[TCustomDeps, TResult]: ...
|
|
656
659
|
|
|
@@ -660,7 +663,6 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
|
|
|
660
663
|
*,
|
|
661
664
|
deps_type: Any | None = None,
|
|
662
665
|
return_type: Any = str,
|
|
663
|
-
model_override: ModelId | str | None = None,
|
|
664
666
|
session: SessionIdType | SessionQuery = None,
|
|
665
667
|
) -> Agent[Any, Any]:
|
|
666
668
|
"""Get or configure an agent from the pool.
|
|
@@ -673,7 +675,6 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
|
|
|
673
675
|
agent: Either agent name or instance
|
|
674
676
|
deps_type: Optional custom dependencies type (overrides shared deps)
|
|
675
677
|
return_type: Optional type for structured responses
|
|
676
|
-
model_override: Optional model override
|
|
677
678
|
session: Optional session ID or query to recover conversation
|
|
678
679
|
|
|
679
680
|
Returns:
|
|
@@ -684,6 +685,10 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
|
|
|
684
685
|
Raises:
|
|
685
686
|
KeyError: If agent name not found
|
|
686
687
|
ValueError: If configuration is invalid
|
|
688
|
+
|
|
689
|
+
Note:
|
|
690
|
+
To change the model, call `await agent.set_model(model_id)` after
|
|
691
|
+
getting the agent.
|
|
687
692
|
"""
|
|
688
693
|
from agentpool.agents import Agent
|
|
689
694
|
|
|
@@ -692,8 +697,6 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
|
|
|
692
697
|
# base.context.data = deps if deps is not None else self.shared_deps
|
|
693
698
|
base.deps_type = deps_type
|
|
694
699
|
base.agent_pool = self
|
|
695
|
-
if model_override:
|
|
696
|
-
base.set_model(model_override)
|
|
697
700
|
if session:
|
|
698
701
|
base.conversation.load_history_from_database(session=session)
|
|
699
702
|
if return_type not in {str, None}:
|
|
@@ -987,6 +990,9 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
|
|
|
987
990
|
case _:
|
|
988
991
|
model = None
|
|
989
992
|
model_settings = None
|
|
993
|
+
# Extract pydantic-ai builtin tools from config
|
|
994
|
+
builtin_tools = config.get_builtin_tools()
|
|
995
|
+
|
|
990
996
|
return Agent(
|
|
991
997
|
# context=context,
|
|
992
998
|
model=model,
|
|
@@ -1009,9 +1015,11 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
|
|
|
1009
1015
|
tool_mode=config.tool_mode,
|
|
1010
1016
|
knowledge=config.knowledge,
|
|
1011
1017
|
toolsets=toolsets_list,
|
|
1012
|
-
auto_cache=config.auto_cache,
|
|
1013
1018
|
hooks=config.hooks.get_agent_hooks() if config.hooks else None,
|
|
1014
1019
|
tool_confirmation_mode=config.requires_tool_confirmation,
|
|
1020
|
+
builtin_tools=builtin_tools or None,
|
|
1021
|
+
usage_limits=config.usage_limits,
|
|
1022
|
+
providers=config.model_providers,
|
|
1015
1023
|
)
|
|
1016
1024
|
|
|
1017
1025
|
def create_acp_agent[TDeps](
|
agentpool/delegation/team.py
CHANGED
|
@@ -176,6 +176,7 @@ class Team[TDeps = None](BaseTeam[TDeps, Any]):
|
|
|
176
176
|
name=self.name,
|
|
177
177
|
message_id=message_id,
|
|
178
178
|
conversation_id=user_msg.conversation_id,
|
|
179
|
+
parent_id=user_msg.message_id,
|
|
179
180
|
metadata={
|
|
180
181
|
"agent_names": [r.agent_name for r in result],
|
|
181
182
|
"errors": {name: str(error) for name, error in result.errors.items()},
|
agentpool/delegation/teamrun.py
CHANGED
|
@@ -172,6 +172,7 @@ class TeamRun[TDeps, TResult](BaseTeam[TDeps, TResult]):
|
|
|
172
172
|
associated_messages=all_messages,
|
|
173
173
|
message_id=message_id,
|
|
174
174
|
conversation_id=user_msg.conversation_id,
|
|
175
|
+
parent_id=user_msg.message_id,
|
|
175
176
|
metadata={
|
|
176
177
|
"execution_order": [r.agent_name for r in result],
|
|
177
178
|
"start_time": result.start_time.isoformat(),
|