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
|
@@ -5,16 +5,6 @@ from __future__ import annotations
|
|
|
5
5
|
import base64
|
|
6
6
|
from typing import TYPE_CHECKING, Any, assert_never
|
|
7
7
|
|
|
8
|
-
from mcp.types import (
|
|
9
|
-
AudioContent,
|
|
10
|
-
BlobResourceContents,
|
|
11
|
-
EmbeddedResource,
|
|
12
|
-
ImageContent,
|
|
13
|
-
PromptMessage,
|
|
14
|
-
ResourceLink,
|
|
15
|
-
TextContent,
|
|
16
|
-
TextResourceContents,
|
|
17
|
-
)
|
|
18
8
|
from pydantic_ai import (
|
|
19
9
|
AudioUrl,
|
|
20
10
|
BinaryContent,
|
|
@@ -35,9 +25,14 @@ if TYPE_CHECKING:
|
|
|
35
25
|
from collections.abc import Sequence
|
|
36
26
|
|
|
37
27
|
from fastmcp import Client
|
|
38
|
-
from mcp.types import
|
|
39
|
-
|
|
40
|
-
|
|
28
|
+
from mcp.types import (
|
|
29
|
+
BlobResourceContents,
|
|
30
|
+
ContentBlock,
|
|
31
|
+
PromptMessage,
|
|
32
|
+
SamplingMessage,
|
|
33
|
+
TextResourceContents,
|
|
34
|
+
)
|
|
35
|
+
from pydantic_ai import ModelRequestPart, ModelResponsePart, UserContent
|
|
41
36
|
|
|
42
37
|
logger = get_logger(__name__)
|
|
43
38
|
|
|
@@ -46,6 +41,13 @@ def to_mcp_messages(
|
|
|
46
41
|
part: ModelRequestPart | ModelResponsePart,
|
|
47
42
|
) -> list[PromptMessage]:
|
|
48
43
|
"""Convert internal PromptMessage to MCP PromptMessage."""
|
|
44
|
+
from mcp.types import (
|
|
45
|
+
AudioContent,
|
|
46
|
+
ImageContent,
|
|
47
|
+
PromptMessage,
|
|
48
|
+
TextContent,
|
|
49
|
+
)
|
|
50
|
+
|
|
49
51
|
messages = []
|
|
50
52
|
match part:
|
|
51
53
|
case UserPromptPart(content=str() as c):
|
|
@@ -94,6 +96,24 @@ def _url_from_mime_type(uri: str, mime_type: str | None) -> FileUrl:
|
|
|
94
96
|
return DocumentUrl(url=uri)
|
|
95
97
|
|
|
96
98
|
|
|
99
|
+
def sampling_messages_to_user_content(msgs: list[SamplingMessage]) -> list[UserContent]:
|
|
100
|
+
from mcp import types
|
|
101
|
+
|
|
102
|
+
# Convert messages to prompts for the agent
|
|
103
|
+
prompts: list[UserContent] = []
|
|
104
|
+
for mcp_msg in msgs:
|
|
105
|
+
match mcp_msg.content:
|
|
106
|
+
case types.TextContent(text=text):
|
|
107
|
+
prompts.append(text)
|
|
108
|
+
case types.ImageContent(data=data, mimeType=mime_type):
|
|
109
|
+
binary_data = base64.b64decode(data)
|
|
110
|
+
prompts.append(BinaryImage(data=binary_data, media_type=mime_type))
|
|
111
|
+
case types.AudioContent(data=data, mimeType=mime_type):
|
|
112
|
+
binary_data = base64.b64decode(data)
|
|
113
|
+
prompts.append(BinaryContent(data=binary_data, media_type=mime_type))
|
|
114
|
+
return prompts
|
|
115
|
+
|
|
116
|
+
|
|
97
117
|
async def from_mcp_content(
|
|
98
118
|
mcp_content: Sequence[ContentBlock | TextResourceContents | BlobResourceContents],
|
|
99
119
|
client: Client[Any] | None = None,
|
|
@@ -103,6 +123,16 @@ async def from_mcp_content(
|
|
|
103
123
|
If a FastMCP client is given, this function will try to resolve the ResourceLinks.
|
|
104
124
|
|
|
105
125
|
"""
|
|
126
|
+
from mcp.types import (
|
|
127
|
+
AudioContent,
|
|
128
|
+
BlobResourceContents,
|
|
129
|
+
EmbeddedResource,
|
|
130
|
+
ImageContent,
|
|
131
|
+
ResourceLink,
|
|
132
|
+
TextContent,
|
|
133
|
+
TextResourceContents,
|
|
134
|
+
)
|
|
135
|
+
|
|
106
136
|
contents: list[Any] = []
|
|
107
137
|
|
|
108
138
|
for block in mcp_content:
|
|
@@ -151,6 +181,17 @@ async def from_mcp_content(
|
|
|
151
181
|
|
|
152
182
|
|
|
153
183
|
def content_block_as_text(content: ContentBlock) -> str:
|
|
184
|
+
|
|
185
|
+
from mcp.types import (
|
|
186
|
+
AudioContent,
|
|
187
|
+
BlobResourceContents,
|
|
188
|
+
EmbeddedResource,
|
|
189
|
+
ImageContent,
|
|
190
|
+
ResourceLink,
|
|
191
|
+
TextContent,
|
|
192
|
+
TextResourceContents,
|
|
193
|
+
)
|
|
194
|
+
|
|
154
195
|
match content:
|
|
155
196
|
case TextContent(text=text):
|
|
156
197
|
return text
|
agentpool/mcp_server/manager.py
CHANGED
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
-
import base64
|
|
7
6
|
from contextlib import AsyncExitStack
|
|
8
7
|
from typing import TYPE_CHECKING, Any, Self, cast
|
|
9
8
|
|
|
10
9
|
import anyio
|
|
11
|
-
from pydantic_ai import BinaryContent, BinaryImage, UsageLimits
|
|
12
10
|
|
|
13
11
|
from agentpool.log import get_logger
|
|
14
12
|
from agentpool.resource_providers import AggregatingResourceProvider, ResourceProvider
|
|
@@ -23,7 +21,6 @@ if TYPE_CHECKING:
|
|
|
23
21
|
from mcp import types
|
|
24
22
|
from mcp.shared.context import RequestContext
|
|
25
23
|
from mcp.types import SamplingMessage
|
|
26
|
-
from pydantic_ai import UserContent
|
|
27
24
|
|
|
28
25
|
from agentpool_config.mcp_server import MCPServerConfig
|
|
29
26
|
|
|
@@ -38,6 +35,7 @@ class MCPManager:
|
|
|
38
35
|
self,
|
|
39
36
|
name: str = "mcp",
|
|
40
37
|
owner: str | None = None,
|
|
38
|
+
sampling_model: str = "openai:gpt-5-nano",
|
|
41
39
|
servers: Sequence[MCPServerConfig | str] | None = None,
|
|
42
40
|
accessible_roots: list[str] | None = None,
|
|
43
41
|
) -> None:
|
|
@@ -47,6 +45,7 @@ class MCPManager:
|
|
|
47
45
|
for server in servers or []:
|
|
48
46
|
self.add_server_config(server)
|
|
49
47
|
self.providers: list[MCPResourceProvider] = []
|
|
48
|
+
self.sampling_model = sampling_model
|
|
50
49
|
self.aggregating_provider = AggregatingResourceProvider(
|
|
51
50
|
providers=cast(list[ResourceProvider], self.providers),
|
|
52
51
|
name=f"{name}_aggregated",
|
|
@@ -88,35 +87,22 @@ class MCPManager:
|
|
|
88
87
|
context: RequestContext[Any, Any, Any],
|
|
89
88
|
) -> str:
|
|
90
89
|
"""Handle MCP sampling by creating a new agent with specified preferences."""
|
|
91
|
-
from mcp import types
|
|
92
|
-
|
|
93
90
|
from agentpool.agents import Agent
|
|
91
|
+
from agentpool.mcp_server.conversions import sampling_messages_to_user_content
|
|
94
92
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
for mcp_msg in messages:
|
|
98
|
-
match mcp_msg.content:
|
|
99
|
-
case types.TextContent(text=text):
|
|
100
|
-
prompts.append(text)
|
|
101
|
-
case types.ImageContent(data=data, mimeType=mime_type):
|
|
102
|
-
binary_data = base64.b64decode(data)
|
|
103
|
-
prompts.append(BinaryImage(data=binary_data, media_type=mime_type))
|
|
104
|
-
case types.AudioContent(data=data, mimeType=mime_type):
|
|
105
|
-
binary_data = base64.b64decode(data)
|
|
106
|
-
prompts.append(BinaryContent(data=binary_data, media_type=mime_type))
|
|
107
|
-
|
|
108
|
-
# Extract model from preferences
|
|
109
|
-
model = None
|
|
93
|
+
prompts = sampling_messages_to_user_content(messages)
|
|
94
|
+
model = self.sampling_model
|
|
110
95
|
if (prefs := params.modelPreferences) and prefs.hints and prefs.hints[0].name:
|
|
111
|
-
model = prefs.hints[0].name
|
|
96
|
+
model = prefs.hints[0].name # Extract model from preferences
|
|
112
97
|
# Create usage limits from sampling parameters
|
|
113
|
-
limits = UsageLimits(output_tokens_limit=params.maxTokens, request_limit=1)
|
|
98
|
+
# limits = UsageLimits(output_tokens_limit=params.maxTokens, request_limit=1)
|
|
99
|
+
# TODO: Re-add per-turn usage_limits once implemented for all agents
|
|
114
100
|
# TODO: Apply temperature from params.temperature
|
|
115
101
|
sys_prompt = params.systemPrompt or ""
|
|
116
102
|
agent = Agent(name="sampling-agent", model=model, system_prompt=sys_prompt, session=False)
|
|
117
103
|
try:
|
|
118
104
|
async with agent:
|
|
119
|
-
result = await agent.run(*prompts, store_history=False
|
|
105
|
+
result = await agent.run(*prompts, store_history=False)
|
|
120
106
|
return result.content
|
|
121
107
|
|
|
122
108
|
except Exception as e:
|
|
@@ -51,6 +51,9 @@ class RegistryTransport(Schema):
|
|
|
51
51
|
url: str | None = None
|
|
52
52
|
"""URL for HTTP transports."""
|
|
53
53
|
|
|
54
|
+
headers: list[dict[str, Any]] = Field(default_factory=list)
|
|
55
|
+
"""Request headers."""
|
|
56
|
+
|
|
54
57
|
|
|
55
58
|
class RegistryPackage(Schema):
|
|
56
59
|
"""Package information for installing an MCP server."""
|
|
@@ -75,6 +78,9 @@ class RegistryPackage(Schema):
|
|
|
75
78
|
package_arguments: list[dict[str, Any]] = Field(default_factory=list, alias="packageArguments")
|
|
76
79
|
"""Package arguments."""
|
|
77
80
|
|
|
81
|
+
runtime_arguments: list[dict[str, Any]] = Field(default_factory=list, alias="runtimeArguments")
|
|
82
|
+
"""Runtime arguments."""
|
|
83
|
+
|
|
78
84
|
runtime_hint: str | None = Field(None, alias="runtimeHint")
|
|
79
85
|
"""Runtime hint."""
|
|
80
86
|
|
|
@@ -97,6 +103,9 @@ class RegistryRemote(Schema):
|
|
|
97
103
|
headers: list[dict[str, Any]] = Field(default_factory=list)
|
|
98
104
|
"""Request headers."""
|
|
99
105
|
|
|
106
|
+
variables: dict[str, Any] = Field(default_factory=dict)
|
|
107
|
+
"""URL template variables."""
|
|
108
|
+
|
|
100
109
|
|
|
101
110
|
class RegistryIcon(Schema):
|
|
102
111
|
"""Icon configuration for a server."""
|
|
@@ -123,7 +132,7 @@ class RegistryServer(Schema):
|
|
|
123
132
|
version: str
|
|
124
133
|
"""Server version."""
|
|
125
134
|
|
|
126
|
-
repository: RegistryRepository
|
|
135
|
+
repository: RegistryRepository | None = None
|
|
127
136
|
"""Repository information."""
|
|
128
137
|
|
|
129
138
|
packages: list[RegistryPackage] = Field(default_factory=list)
|
|
@@ -15,12 +15,10 @@ import asyncio
|
|
|
15
15
|
from contextlib import asynccontextmanager, suppress
|
|
16
16
|
from dataclasses import dataclass, field, replace
|
|
17
17
|
import inspect
|
|
18
|
-
from typing import TYPE_CHECKING, Any,
|
|
18
|
+
from typing import TYPE_CHECKING, Any, Self, get_args, get_origin
|
|
19
19
|
from uuid import uuid4
|
|
20
20
|
|
|
21
21
|
import anyio
|
|
22
|
-
from fastmcp import FastMCP
|
|
23
|
-
from fastmcp.tools import Tool as FastMCPTool
|
|
24
22
|
from pydantic import BaseModel, HttpUrl
|
|
25
23
|
|
|
26
24
|
from agentpool.agents import Agent
|
|
@@ -33,12 +31,12 @@ if TYPE_CHECKING:
|
|
|
33
31
|
from collections.abc import AsyncIterator, Callable
|
|
34
32
|
|
|
35
33
|
from claude_agent_sdk.types import McpServerConfig
|
|
36
|
-
from fastmcp import Context
|
|
34
|
+
from fastmcp import Context, FastMCP
|
|
37
35
|
from fastmcp.tools.tool import ToolResult
|
|
38
36
|
from pydantic_ai import RunContext
|
|
39
37
|
from uvicorn import Server
|
|
40
38
|
|
|
41
|
-
from acp.schema.mcp import HttpMcpServer
|
|
39
|
+
from acp.schema.mcp import HttpMcpServer
|
|
42
40
|
from agentpool.agents import AgentContext
|
|
43
41
|
from agentpool.agents.base_agent import BaseAgent
|
|
44
42
|
from agentpool.tools.base import Tool
|
|
@@ -154,25 +152,35 @@ def _convert_to_tool_result(result: Any) -> ToolResult:
|
|
|
154
152
|
"""Convert a tool's return value to a FastMCP ToolResult.
|
|
155
153
|
|
|
156
154
|
Handles different result types appropriately:
|
|
157
|
-
- ToolResult: Pass through unchanged
|
|
155
|
+
- FastMCP ToolResult: Pass through unchanged
|
|
156
|
+
- AgentPool ToolResult: Convert to FastMCP format
|
|
158
157
|
- dict: Use as structured_content (enables programmatic access by clients)
|
|
159
158
|
- Pydantic models: Serialize to dict for structured_content
|
|
160
159
|
- Other types: Pass to ToolResult(content=...) which handles conversion internally
|
|
161
160
|
"""
|
|
162
|
-
from fastmcp.tools.tool import ToolResult
|
|
161
|
+
from fastmcp.tools.tool import ToolResult as FastMCPToolResult
|
|
162
|
+
|
|
163
|
+
from agentpool.tools.base import ToolResult as AgentPoolToolResult
|
|
163
164
|
|
|
164
|
-
# Already a ToolResult - pass through
|
|
165
|
-
if isinstance(result,
|
|
165
|
+
# Already a FastMCP ToolResult - pass through
|
|
166
|
+
if isinstance(result, FastMCPToolResult):
|
|
166
167
|
return result
|
|
168
|
+
# AgentPool ToolResult - convert to FastMCP format
|
|
169
|
+
if isinstance(result, AgentPoolToolResult):
|
|
170
|
+
return FastMCPToolResult(
|
|
171
|
+
content=result.content,
|
|
172
|
+
structured_content=result.structured_content,
|
|
173
|
+
meta=result.metadata,
|
|
174
|
+
)
|
|
167
175
|
# Dict - use as structured_content (FastMCP auto-populates content as JSON)
|
|
168
176
|
if isinstance(result, dict):
|
|
169
|
-
return
|
|
177
|
+
return FastMCPToolResult(structured_content=result)
|
|
170
178
|
# Pydantic model - serialize to dict for structured_content
|
|
171
179
|
if isinstance(result, BaseModel):
|
|
172
|
-
return
|
|
180
|
+
return FastMCPToolResult(structured_content=result.model_dump(mode="json"))
|
|
173
181
|
# All other types (str, list, ContentBlock, Image, None, primitives, etc.)
|
|
174
182
|
# ToolResult's internal _convert_to_content handles these correctly
|
|
175
|
-
return
|
|
183
|
+
return FastMCPToolResult(content=result if result is not None else "")
|
|
176
184
|
|
|
177
185
|
|
|
178
186
|
def _extract_tool_call_id(context: Context | None) -> str:
|
|
@@ -210,9 +218,6 @@ class BridgeConfig:
|
|
|
210
218
|
port: int = 0
|
|
211
219
|
"""Port to bind to (0 = auto-select available port)."""
|
|
212
220
|
|
|
213
|
-
transport: Literal["sse", "streamable-http"] = "sse"
|
|
214
|
-
"""Transport protocol: 'sse' or 'streamable-http'."""
|
|
215
|
-
|
|
216
221
|
server_name: str = "agentpool-toolmanager"
|
|
217
222
|
"""Name for the MCP server."""
|
|
218
223
|
|
|
@@ -271,6 +276,8 @@ class ToolManagerBridge:
|
|
|
271
276
|
|
|
272
277
|
async def start(self) -> None:
|
|
273
278
|
"""Start the HTTP MCP server in the background."""
|
|
279
|
+
from fastmcp import FastMCP
|
|
280
|
+
|
|
274
281
|
self._mcp = FastMCP(name=self.config.server_name)
|
|
275
282
|
await self._register_tools()
|
|
276
283
|
self._subscribe_to_tool_changes()
|
|
@@ -333,7 +340,22 @@ class ToolManagerBridge:
|
|
|
333
340
|
return
|
|
334
341
|
|
|
335
342
|
# Get current and new tool sets
|
|
336
|
-
|
|
343
|
+
# Support both old and new FastMCP API
|
|
344
|
+
if hasattr(self._mcp, "_tool_manager"):
|
|
345
|
+
# Old API (<=2.12.4): direct access to _tool_manager
|
|
346
|
+
current_names = set(self._mcp._tool_manager._tools.keys())
|
|
347
|
+
elif hasattr(self._mcp, "_local_provider"):
|
|
348
|
+
# New API (git): tools stored in _local_provider._components
|
|
349
|
+
# Keys are prefixed with 'tool:', e.g., 'tool:bash'
|
|
350
|
+
current_names = {
|
|
351
|
+
key.removeprefix("tool:")
|
|
352
|
+
for key in self._mcp._local_provider._components
|
|
353
|
+
if key.startswith("tool:")
|
|
354
|
+
}
|
|
355
|
+
else:
|
|
356
|
+
# Fallback: use async get_tools() method
|
|
357
|
+
current_tools = await self._mcp.get_tools()
|
|
358
|
+
current_names = {t.name for t in current_tools} # type: ignore[attr-defined]
|
|
337
359
|
new_tools = await self.node.tools.get_tools(state="enabled")
|
|
338
360
|
new_names = {t.name for t in new_tools}
|
|
339
361
|
|
|
@@ -361,19 +383,16 @@ class ToolManagerBridge:
|
|
|
361
383
|
@property
|
|
362
384
|
def url(self) -> str:
|
|
363
385
|
"""Get the server URL."""
|
|
364
|
-
|
|
365
|
-
return f"http://{self.config.host}:{self.port}{path}"
|
|
386
|
+
return f"http://{self.config.host}:{self.port}/mcp"
|
|
366
387
|
|
|
367
|
-
def get_mcp_server_config(self) -> HttpMcpServer
|
|
388
|
+
def get_mcp_server_config(self) -> HttpMcpServer:
|
|
368
389
|
"""Get ACP-compatible MCP server configuration.
|
|
369
390
|
|
|
370
391
|
Returns config suitable for passing to ACP agent's NewSessionRequest.
|
|
371
392
|
"""
|
|
372
|
-
from acp.schema import HttpMcpServer
|
|
393
|
+
from acp.schema import HttpMcpServer
|
|
373
394
|
|
|
374
395
|
url = HttpUrl(self.url)
|
|
375
|
-
if self.config.transport == "sse":
|
|
376
|
-
return SseMcpServer(name=self.config.server_name, url=url, headers=[])
|
|
377
396
|
return HttpMcpServer(name=self.config.server_name, url=url, headers=[])
|
|
378
397
|
|
|
379
398
|
def get_claude_mcp_server_config(self) -> dict[str, McpServerConfig]:
|
|
@@ -416,6 +435,75 @@ class ToolManagerBridge:
|
|
|
416
435
|
"""Register a single tool with the FastMCP server."""
|
|
417
436
|
if not self._mcp:
|
|
418
437
|
return
|
|
438
|
+
from fastmcp.tools import Tool as FastMCPTool
|
|
439
|
+
|
|
440
|
+
class _BridgeTool(FastMCPTool):
|
|
441
|
+
"""Custom FastMCP Tool that wraps a agentpool Tool.
|
|
442
|
+
|
|
443
|
+
This allows us to use our own schema and invoke tools with AgentContext.
|
|
444
|
+
"""
|
|
445
|
+
|
|
446
|
+
def __init__(self, tool: Tool, bridge: ToolManagerBridge) -> None:
|
|
447
|
+
# Get input schema from our tool
|
|
448
|
+
schema = tool.schema["function"]
|
|
449
|
+
input_schema = schema.get("parameters", {"type": "object", "properties": {}})
|
|
450
|
+
# Filter out context parameters - they're auto-injected by the bridge
|
|
451
|
+
context_params = _get_context_param_names(tool.get_callable())
|
|
452
|
+
run_context_params = _get_run_context_param_names(tool.get_callable())
|
|
453
|
+
all_context_params = context_params | run_context_params
|
|
454
|
+
filtered_schema = filter_schema_params(input_schema, all_context_params)
|
|
455
|
+
desc = tool.description or "No description"
|
|
456
|
+
super().__init__(name=tool.name, description=desc, parameters=filtered_schema)
|
|
457
|
+
# Set these AFTER super().__init__() to avoid being overwritten
|
|
458
|
+
self._tool = tool
|
|
459
|
+
self._bridge = bridge
|
|
460
|
+
|
|
461
|
+
async def run(self, arguments: dict[str, Any]) -> ToolResult:
|
|
462
|
+
"""Execute the wrapped tool with context bridging."""
|
|
463
|
+
from fastmcp.server.dependencies import get_context
|
|
464
|
+
|
|
465
|
+
# Get FastMCP context from context variable (not passed as parameter)
|
|
466
|
+
try:
|
|
467
|
+
mcp_context: Context | None = get_context()
|
|
468
|
+
except LookupError:
|
|
469
|
+
mcp_context = None
|
|
470
|
+
|
|
471
|
+
# Try to get Claude's original tool_call_id from request metadata
|
|
472
|
+
tool_call_id = _extract_tool_call_id(mcp_context)
|
|
473
|
+
# Get deps from bridge (set by run_stream on the agent)
|
|
474
|
+
current_deps = self._bridge.current_deps
|
|
475
|
+
# Create context with tool-specific metadata from node's context.
|
|
476
|
+
ctx = replace(
|
|
477
|
+
self._bridge.node.get_context(data=current_deps),
|
|
478
|
+
tool_name=self._tool.name,
|
|
479
|
+
tool_call_id=tool_call_id,
|
|
480
|
+
tool_input=arguments,
|
|
481
|
+
)
|
|
482
|
+
# Invoke with context - copy arguments since invoke_tool_with_context
|
|
483
|
+
# modifies kwargs in-place to inject context parameters
|
|
484
|
+
result = await self._bridge.invoke_tool_with_context(
|
|
485
|
+
self._tool, ctx, arguments.copy()
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
# Emit metadata event for ClaudeCodeAgent to correlate
|
|
489
|
+
# (works around Claude SDK stripping MCP _meta field)
|
|
490
|
+
from agentpool.agents.events import ToolResultMetadataEvent
|
|
491
|
+
from agentpool.tools.base import ToolResult as AgentPoolToolResult
|
|
492
|
+
|
|
493
|
+
if isinstance(result, AgentPoolToolResult) and result.metadata:
|
|
494
|
+
logger.info(
|
|
495
|
+
"Emitting ToolResultMetadataEvent",
|
|
496
|
+
tool_call_id=tool_call_id,
|
|
497
|
+
metadata_keys=list(result.metadata.keys()),
|
|
498
|
+
)
|
|
499
|
+
event = ToolResultMetadataEvent(
|
|
500
|
+
tool_call_id=tool_call_id,
|
|
501
|
+
metadata=result.metadata,
|
|
502
|
+
)
|
|
503
|
+
await ctx.events.emit_event(event)
|
|
504
|
+
|
|
505
|
+
return _convert_to_tool_result(result)
|
|
506
|
+
|
|
419
507
|
# Create a custom FastMCP Tool that wraps our tool
|
|
420
508
|
bridge_tool = _BridgeTool(tool=tool, bridge=self)
|
|
421
509
|
self._mcp.add_tool(bridge_tool)
|
|
@@ -430,7 +518,7 @@ class ToolManagerBridge:
|
|
|
430
518
|
|
|
431
519
|
Handles tools that expect AgentContext, RunContext, or neither.
|
|
432
520
|
"""
|
|
433
|
-
fn = tool.
|
|
521
|
+
fn = tool.get_callable()
|
|
434
522
|
|
|
435
523
|
# Inject AgentContext parameters
|
|
436
524
|
context_param_names = _get_context_param_names(fn)
|
|
@@ -470,7 +558,7 @@ class ToolManagerBridge:
|
|
|
470
558
|
port = s.getsockname()[1]
|
|
471
559
|
self._actual_port = port
|
|
472
560
|
# Create the ASGI app
|
|
473
|
-
app = self._mcp.http_app(transport=
|
|
561
|
+
app = self._mcp.http_app(transport="http")
|
|
474
562
|
# Configure uvicorn
|
|
475
563
|
cfg = uvicorn.Config(
|
|
476
564
|
app=app, host=self.config.host, port=port, log_level="warning", ws="websockets-sansio"
|
|
@@ -481,58 +569,7 @@ class ToolManagerBridge:
|
|
|
481
569
|
self._server_task = asyncio.create_task(self._server.serve(), name=name)
|
|
482
570
|
await anyio.sleep(0.1) # Wait briefly for server to start
|
|
483
571
|
msg = "ToolManagerBridge started"
|
|
484
|
-
logger.info(msg, url=self.url
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
class _BridgeTool(FastMCPTool):
|
|
488
|
-
"""Custom FastMCP Tool that wraps a agentpool Tool.
|
|
489
|
-
|
|
490
|
-
This allows us to use our own schema and invoke tools with AgentContext.
|
|
491
|
-
"""
|
|
492
|
-
|
|
493
|
-
def __init__(self, tool: Tool, bridge: ToolManagerBridge) -> None:
|
|
494
|
-
# Get input schema from our tool
|
|
495
|
-
schema = tool.schema["function"]
|
|
496
|
-
input_schema = schema.get("parameters", {"type": "object", "properties": {}})
|
|
497
|
-
# Filter out context parameters - they're auto-injected by the bridge
|
|
498
|
-
context_params = _get_context_param_names(tool.callable)
|
|
499
|
-
run_context_params = _get_run_context_param_names(tool.callable)
|
|
500
|
-
all_context_params = context_params | run_context_params
|
|
501
|
-
filtered_schema = filter_schema_params(input_schema, all_context_params)
|
|
502
|
-
desc = tool.description or "No description"
|
|
503
|
-
super().__init__(name=tool.name, description=desc, parameters=filtered_schema)
|
|
504
|
-
# Set these AFTER super().__init__() to avoid being overwritten
|
|
505
|
-
self._tool = tool
|
|
506
|
-
self._bridge = bridge
|
|
507
|
-
|
|
508
|
-
async def run(self, arguments: dict[str, Any]) -> ToolResult:
|
|
509
|
-
"""Execute the wrapped tool with context bridging."""
|
|
510
|
-
from fastmcp.server.dependencies import get_context
|
|
511
|
-
|
|
512
|
-
# Get FastMCP context from context variable (not passed as parameter)
|
|
513
|
-
try:
|
|
514
|
-
mcp_context: Context | None = get_context()
|
|
515
|
-
except LookupError:
|
|
516
|
-
mcp_context = None
|
|
517
|
-
|
|
518
|
-
# Try to get Claude's original tool_call_id from request metadata
|
|
519
|
-
tool_call_id = _extract_tool_call_id(mcp_context)
|
|
520
|
-
|
|
521
|
-
# Get deps from bridge (set by run_stream on the agent)
|
|
522
|
-
current_deps = self._bridge.current_deps
|
|
523
|
-
|
|
524
|
-
# Create context with tool-specific metadata from node's context.
|
|
525
|
-
ctx = replace(
|
|
526
|
-
self._bridge.node.get_context(data=current_deps),
|
|
527
|
-
tool_name=self._tool.name,
|
|
528
|
-
tool_call_id=tool_call_id,
|
|
529
|
-
tool_input=arguments,
|
|
530
|
-
)
|
|
531
|
-
|
|
532
|
-
# Invoke with context - copy arguments since invoke_tool_with_context
|
|
533
|
-
# modifies kwargs in-place to inject context parameters
|
|
534
|
-
result = await self._bridge.invoke_tool_with_context(self._tool, ctx, arguments.copy())
|
|
535
|
-
return _convert_to_tool_result(result)
|
|
572
|
+
logger.info(msg, url=self.url)
|
|
536
573
|
|
|
537
574
|
|
|
538
575
|
@asynccontextmanager
|
|
@@ -541,7 +578,6 @@ async def create_tool_bridge(
|
|
|
541
578
|
*,
|
|
542
579
|
host: str = "127.0.0.1",
|
|
543
580
|
port: int = 0,
|
|
544
|
-
transport: Literal["sse", "streamable-http"] = "sse",
|
|
545
581
|
) -> AsyncIterator[ToolManagerBridge]:
|
|
546
582
|
"""Create and start a ToolManagerBridge as a context manager.
|
|
547
583
|
|
|
@@ -549,12 +585,11 @@ async def create_tool_bridge(
|
|
|
549
585
|
node: The node whose tools to expose
|
|
550
586
|
host: Host to bind to
|
|
551
587
|
port: Port to bind to (0 = auto-select)
|
|
552
|
-
transport: Transport protocol ('sse' or 'streamable-http')
|
|
553
588
|
|
|
554
589
|
Yields:
|
|
555
590
|
Running ToolManagerBridge instance
|
|
556
591
|
"""
|
|
557
|
-
config = BridgeConfig(host=host, port=port
|
|
592
|
+
config = BridgeConfig(host=host, port=port)
|
|
558
593
|
bridge = ToolManagerBridge(node=node, config=config)
|
|
559
594
|
async with bridge:
|
|
560
595
|
yield bridge
|
|
@@ -6,7 +6,8 @@ from collections.abc import Sequence
|
|
|
6
6
|
from contextlib import asynccontextmanager
|
|
7
7
|
from typing import TYPE_CHECKING, Any, Self
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from anyenv.signals import Signal
|
|
10
|
+
from psygnal import Signal as Psygnal
|
|
10
11
|
from psygnal.containers import EventedList
|
|
11
12
|
|
|
12
13
|
from agentpool.log import get_logger
|
|
@@ -27,10 +28,10 @@ logger = get_logger(__name__)
|
|
|
27
28
|
class ConnectionManager:
|
|
28
29
|
"""Manages connections for both Agents and Teams."""
|
|
29
30
|
|
|
30
|
-
connection_processed = Signal
|
|
31
|
+
connection_processed = Signal[Talk.ConnectionProcessed]()
|
|
31
32
|
|
|
32
|
-
node_connected =
|
|
33
|
-
connection_added =
|
|
33
|
+
node_connected = Psygnal(object) # Node
|
|
34
|
+
connection_added = Psygnal(Talk) # Agent
|
|
34
35
|
|
|
35
36
|
def __init__(self, owner: MessageNode[Any, Any]) -> None:
|
|
36
37
|
self.owner = owner
|
|
@@ -54,9 +55,9 @@ class ConnectionManager:
|
|
|
54
55
|
old.connection_processed.disconnect(self._handle_message_flow)
|
|
55
56
|
new.connection_processed.connect(self._handle_message_flow)
|
|
56
57
|
|
|
57
|
-
def _handle_message_flow(self, event: Talk.ConnectionProcessed) -> None:
|
|
58
|
+
async def _handle_message_flow(self, event: Talk.ConnectionProcessed) -> None:
|
|
58
59
|
"""Forward message flow to our aggregated signal."""
|
|
59
|
-
self.connection_processed.emit(event)
|
|
60
|
+
await self.connection_processed.emit(event)
|
|
60
61
|
|
|
61
62
|
def set_wait_state(self, target: MessageNode[Any, Any] | AgentName, wait: bool = True) -> None:
|
|
62
63
|
"""Set waiting behavior for target."""
|
|
@@ -308,10 +309,10 @@ class ConnectionManager:
|
|
|
308
309
|
if __name__ == "__main__":
|
|
309
310
|
from agentpool.agents import Agent
|
|
310
311
|
|
|
311
|
-
agent = Agent("test_agent")
|
|
312
|
-
agent_2 = Agent("test_agent_2")
|
|
313
|
-
agent_3 = Agent("test_agent_3")
|
|
314
|
-
agent_4 = Agent("test_agent_4")
|
|
312
|
+
agent = Agent("test_agent", model="openai:gpt-5-nano")
|
|
313
|
+
agent_2 = Agent("test_agent_2", model="openai:gpt-5-nano")
|
|
314
|
+
agent_3 = Agent("test_agent_3", model="openai:gpt-5-nano")
|
|
315
|
+
agent_4 = Agent("test_agent_4", model="openai:gpt-5-nano")
|
|
315
316
|
_conn_1 = agent >> agent_2
|
|
316
317
|
_conn_2 = agent >> agent_3
|
|
317
318
|
_conn_3 = agent_2 >> agent_4
|
|
@@ -10,9 +10,9 @@ from functools import wraps
|
|
|
10
10
|
import inspect
|
|
11
11
|
from typing import TYPE_CHECKING, Any, Self
|
|
12
12
|
|
|
13
|
-
from
|
|
13
|
+
from anyenv.signals import Signal
|
|
14
14
|
from evented.event_data import EventData, FunctionResultEventData
|
|
15
|
-
from
|
|
15
|
+
from evented_config import EmailConfig, FileWatchConfig, TimeEventConfig, WebhookConfig
|
|
16
16
|
from pydantic import SecretStr
|
|
17
17
|
|
|
18
18
|
from agentpool.log import get_logger
|
|
@@ -27,8 +27,8 @@ if TYPE_CHECKING:
|
|
|
27
27
|
from types import TracebackType
|
|
28
28
|
|
|
29
29
|
from evented.base import EventSource
|
|
30
|
-
from evented.configs import EventConfig
|
|
31
30
|
from evented.timed_watcher import TimeEventSource
|
|
31
|
+
from evented_config import EventConfig
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
logger = get_logger(__name__)
|
|
@@ -40,7 +40,7 @@ type EventCallback = Callable[[EventData], None | Awaitable[None]]
|
|
|
40
40
|
class EventManager:
|
|
41
41
|
"""Manages multiple event sources and their lifecycles."""
|
|
42
42
|
|
|
43
|
-
event_processed = Signal(
|
|
43
|
+
event_processed = Signal[EventData]()
|
|
44
44
|
|
|
45
45
|
def __init__(
|
|
46
46
|
self,
|
|
@@ -83,7 +83,7 @@ class EventManager:
|
|
|
83
83
|
except Exception:
|
|
84
84
|
logger.exception("Error in event callback", name=get_fn_name(callback))
|
|
85
85
|
|
|
86
|
-
self.event_processed.emit(event)
|
|
86
|
+
await self.event_processed.emit(event)
|
|
87
87
|
|
|
88
88
|
async def add_file_watch(
|
|
89
89
|
self,
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import itertools
|
|
6
5
|
from typing import TYPE_CHECKING, Any, Literal
|
|
7
6
|
|
|
8
7
|
from psygnal.containers import EventedList
|
|
@@ -145,36 +144,13 @@ class ChatMessageList(EventedList[ChatMessage[Any]]):
|
|
|
145
144
|
message: Message to build flow DAG for
|
|
146
145
|
|
|
147
146
|
Returns:
|
|
148
|
-
Root DAGNode of the graph
|
|
147
|
+
Root DAGNode of the graph, or None
|
|
148
|
+
|
|
149
|
+
Note:
|
|
150
|
+
forwarded_from has been removed. This method now returns None.
|
|
151
|
+
Flow tracking can be reconstructed from parent_id chain or pool history.
|
|
149
152
|
"""
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
# Get messages from this conversation
|
|
153
|
-
conv_messages = [m for m in self if m.conversation_id == message.conversation_id]
|
|
154
|
-
nodes: dict[str, DAGNode] = {}
|
|
155
|
-
for msg in conv_messages: # First create all nodes
|
|
156
|
-
if msg.forwarded_from:
|
|
157
|
-
chain = [*msg.forwarded_from, msg.name or "unknown"]
|
|
158
|
-
for name in chain:
|
|
159
|
-
if name not in nodes:
|
|
160
|
-
nodes[name] = DAGNode(name)
|
|
161
|
-
|
|
162
|
-
# Then set up parent relationships
|
|
163
|
-
for msg in conv_messages:
|
|
164
|
-
if msg.forwarded_from:
|
|
165
|
-
chain = [*msg.forwarded_from, msg.name or "unknown"]
|
|
166
|
-
# Connect consecutive nodes
|
|
167
|
-
for parent_name, child_name in itertools.pairwise(chain):
|
|
168
|
-
parent = nodes[parent_name]
|
|
169
|
-
child = nodes[child_name]
|
|
170
|
-
if parent not in child.parents:
|
|
171
|
-
child.add_parent(parent)
|
|
172
|
-
|
|
173
|
-
# Find root nodes (those without parents)
|
|
174
|
-
roots = [node for node in nodes.values() if node.is_root]
|
|
175
|
-
if not roots:
|
|
176
|
-
return None
|
|
177
|
-
return roots[0] # Return first root for now
|
|
153
|
+
return None
|
|
178
154
|
|
|
179
155
|
def to_mermaid_graph(
|
|
180
156
|
self,
|