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
|
@@ -120,6 +120,71 @@ async def detailed_print_handler(ctx: RunContext, event: RichAgentStreamEvent[An
|
|
|
120
120
|
print(file=sys.stderr) # Final newline
|
|
121
121
|
|
|
122
122
|
|
|
123
|
+
def create_file_stream_handler(
|
|
124
|
+
path: str,
|
|
125
|
+
mode: str = "a",
|
|
126
|
+
include_tools: bool = False,
|
|
127
|
+
include_thinking: bool = False,
|
|
128
|
+
) -> IndividualEventHandler:
|
|
129
|
+
"""Create an event handler that streams text output to a file.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
path: Path to the output file
|
|
133
|
+
mode: File open mode ('w' for overwrite, 'a' for append)
|
|
134
|
+
include_tools: Whether to include tool call/result information
|
|
135
|
+
include_thinking: Whether to include thinking content
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Event handler function that writes to the specified file
|
|
139
|
+
"""
|
|
140
|
+
from pathlib import Path
|
|
141
|
+
|
|
142
|
+
file_path = Path(path).expanduser()
|
|
143
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
144
|
+
|
|
145
|
+
# Open file handle that persists across calls
|
|
146
|
+
file_handle = file_path.open(mode, encoding="utf-8")
|
|
147
|
+
|
|
148
|
+
async def file_stream_handler(ctx: RunContext, event: RichAgentStreamEvent[Any]) -> None:
|
|
149
|
+
"""Stream agent output to file."""
|
|
150
|
+
match event:
|
|
151
|
+
case (
|
|
152
|
+
PartStartEvent(part=TextPart(content=delta))
|
|
153
|
+
| PartDeltaEvent(delta=TextPartDelta(content_delta=delta))
|
|
154
|
+
):
|
|
155
|
+
file_handle.write(delta)
|
|
156
|
+
file_handle.flush()
|
|
157
|
+
|
|
158
|
+
case (
|
|
159
|
+
PartStartEvent(part=ThinkingPart(content=delta))
|
|
160
|
+
| PartDeltaEvent(delta=ThinkingPartDelta(content_delta=delta))
|
|
161
|
+
):
|
|
162
|
+
if include_thinking and delta:
|
|
163
|
+
file_handle.write(f"\n[thinking] {delta}")
|
|
164
|
+
file_handle.flush()
|
|
165
|
+
|
|
166
|
+
case FunctionToolCallEvent(part=ToolCallPart() as part):
|
|
167
|
+
if include_tools:
|
|
168
|
+
kwargs_str = ", ".join(f"{k}={v!r}" for k, v in safe_args_as_dict(part).items())
|
|
169
|
+
file_handle.write(f"\n[tool] {part.tool_name}({kwargs_str})\n")
|
|
170
|
+
file_handle.flush()
|
|
171
|
+
|
|
172
|
+
case FunctionToolResultEvent(result=ToolReturnPart() as return_part):
|
|
173
|
+
if include_tools:
|
|
174
|
+
file_handle.write(f"[result] {return_part.content}\n")
|
|
175
|
+
file_handle.flush()
|
|
176
|
+
|
|
177
|
+
case RunErrorEvent(message=message):
|
|
178
|
+
file_handle.write(f"\n[error] {message}\n")
|
|
179
|
+
file_handle.flush()
|
|
180
|
+
|
|
181
|
+
case StreamCompleteEvent():
|
|
182
|
+
file_handle.write("\n")
|
|
183
|
+
file_handle.flush()
|
|
184
|
+
|
|
185
|
+
return file_stream_handler
|
|
186
|
+
|
|
187
|
+
|
|
123
188
|
def resolve_event_handlers(
|
|
124
189
|
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None,
|
|
125
190
|
) -> list[IndividualEventHandler]:
|
|
@@ -146,6 +146,7 @@ class StreamEventEmitter:
|
|
|
146
146
|
path: str,
|
|
147
147
|
success: bool,
|
|
148
148
|
error: str | None = None,
|
|
149
|
+
line: int = 0,
|
|
149
150
|
) -> None:
|
|
150
151
|
"""Emit file operation event.
|
|
151
152
|
|
|
@@ -154,6 +155,7 @@ class StreamEventEmitter:
|
|
|
154
155
|
path: The file/directory path that was operated on
|
|
155
156
|
success: Whether the operation completed successfully
|
|
156
157
|
error: Error message if operation failed
|
|
158
|
+
line: Line number for navigation (0 = beginning)
|
|
157
159
|
"""
|
|
158
160
|
event = ToolCallProgressEvent.file_operation(
|
|
159
161
|
tool_call_id=self._context.tool_call_id or "",
|
|
@@ -162,6 +164,7 @@ class StreamEventEmitter:
|
|
|
162
164
|
path=path,
|
|
163
165
|
success=success,
|
|
164
166
|
error=error,
|
|
167
|
+
line=line,
|
|
165
168
|
)
|
|
166
169
|
await self._emit(event)
|
|
167
170
|
|
|
@@ -20,7 +20,15 @@ from __future__ import annotations
|
|
|
20
20
|
from dataclasses import dataclass, field
|
|
21
21
|
from typing import TYPE_CHECKING, Any, Literal
|
|
22
22
|
|
|
23
|
-
from pydantic_ai import
|
|
23
|
+
from pydantic_ai import (
|
|
24
|
+
AgentStreamEvent,
|
|
25
|
+
PartDeltaEvent as PyAIPartDeltaEvent,
|
|
26
|
+
PartStartEvent as PyAIPartStartEvent,
|
|
27
|
+
TextPart,
|
|
28
|
+
TextPartDelta,
|
|
29
|
+
ThinkingPart,
|
|
30
|
+
ThinkingPartDelta,
|
|
31
|
+
)
|
|
24
32
|
|
|
25
33
|
from agentpool.messaging import ChatMessage # noqa: TC001
|
|
26
34
|
|
|
@@ -35,6 +43,30 @@ if TYPE_CHECKING:
|
|
|
35
43
|
# Lifecycle events (aligned with AG-UI protocol)
|
|
36
44
|
|
|
37
45
|
|
|
46
|
+
class PartStartEvent(PyAIPartStartEvent):
|
|
47
|
+
"""Part start event."""
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def thinking(cls, index: int, content: str) -> PartStartEvent:
|
|
51
|
+
return cls(index=index, part=ThinkingPart(content=content))
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def text(cls, index: int, content: str) -> PartStartEvent:
|
|
55
|
+
return cls(index=index, part=TextPart(content=content))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class PartDeltaEvent(PyAIPartDeltaEvent):
|
|
59
|
+
"""Part start event."""
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def thinking(cls, index: int, content: str) -> PartDeltaEvent:
|
|
63
|
+
return cls(index=index, delta=ThinkingPartDelta(content_delta=content))
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def text(cls, index: int, content: str) -> PartDeltaEvent:
|
|
67
|
+
return cls(index=index, delta=TextPartDelta(content_delta=content))
|
|
68
|
+
|
|
69
|
+
|
|
38
70
|
@dataclass(kw_only=True)
|
|
39
71
|
class RunStartedEvent:
|
|
40
72
|
"""Signals the start of an agent run."""
|
|
@@ -409,6 +441,7 @@ class ToolCallProgressEvent:
|
|
|
409
441
|
success: bool,
|
|
410
442
|
error: str | None = None,
|
|
411
443
|
tool_name: str | None = None,
|
|
444
|
+
line: int = 0,
|
|
412
445
|
) -> ToolCallProgressEvent:
|
|
413
446
|
"""Create event for file operation.
|
|
414
447
|
|
|
@@ -419,13 +452,14 @@ class ToolCallProgressEvent:
|
|
|
419
452
|
success: Whether operation succeeded
|
|
420
453
|
error: Error message if failed
|
|
421
454
|
tool_name: Optional tool name
|
|
455
|
+
line: Line number for navigation (0 = beginning)
|
|
422
456
|
"""
|
|
423
457
|
status: Literal["completed", "failed"] = "completed" if success else "failed"
|
|
424
458
|
title = f"{operation.capitalize()}: {path}"
|
|
425
459
|
if error:
|
|
426
460
|
title = f"{title} - {error}"
|
|
427
461
|
|
|
428
|
-
items: list[ToolCallContentItem] = [LocationContentItem(path=path)]
|
|
462
|
+
items: list[ToolCallContentItem] = [LocationContentItem(path=path, line=line)]
|
|
429
463
|
if error:
|
|
430
464
|
items.append(TextContentItem(text=f"Error: {error}"))
|
|
431
465
|
|
|
@@ -460,7 +494,6 @@ class ToolCallProgressEvent:
|
|
|
460
494
|
"""
|
|
461
495
|
items: list[ToolCallContentItem] = [
|
|
462
496
|
DiffContentItem(path=path, old_text=old_text, new_text=new_text),
|
|
463
|
-
LocationContentItem(path=path),
|
|
464
497
|
]
|
|
465
498
|
|
|
466
499
|
return cls(
|
|
@@ -513,10 +546,36 @@ class ToolCallCompleteEvent:
|
|
|
513
546
|
"""The name of the agent that made the tool call."""
|
|
514
547
|
message_id: str
|
|
515
548
|
"""The message ID associated with this tool call."""
|
|
549
|
+
metadata: dict[str, Any] | None = None
|
|
550
|
+
"""Optional metadata for UI/client use (diffs, diagnostics, etc.)."""
|
|
516
551
|
event_kind: Literal["tool_call_complete"] = "tool_call_complete"
|
|
517
552
|
"""Event type identifier."""
|
|
518
553
|
|
|
519
554
|
|
|
555
|
+
@dataclass(kw_only=True)
|
|
556
|
+
class ToolResultMetadataEvent:
|
|
557
|
+
"""Sidechannel event carrying tool result metadata stripped by Claude SDK.
|
|
558
|
+
|
|
559
|
+
The Claude SDK strips the `_meta` field from MCP CallToolResult when converting
|
|
560
|
+
to ToolResultBlock, losing UI-only metadata (diffs, diagnostics, etc.).
|
|
561
|
+
|
|
562
|
+
This event provides a sidechannel to preserve that metadata:
|
|
563
|
+
- Tool returns ToolResult with metadata
|
|
564
|
+
- ToolManagerBridge emits this event with metadata before converting
|
|
565
|
+
- ClaudeCodeAgent correlates by tool_call_id and enriches ToolCallCompleteEvent
|
|
566
|
+
- Downstream consumers (OpenCode, ACP) receive complete events with metadata
|
|
567
|
+
|
|
568
|
+
This avoids polluting LLM context with UI-only data while preserving it for clients.
|
|
569
|
+
"""
|
|
570
|
+
|
|
571
|
+
tool_call_id: str
|
|
572
|
+
"""The ID of the tool call this metadata belongs to."""
|
|
573
|
+
metadata: dict[str, Any]
|
|
574
|
+
"""Metadata for UI/client use (diffs, diagnostics, etc.)."""
|
|
575
|
+
event_kind: Literal["tool_result_metadata"] = "tool_result_metadata"
|
|
576
|
+
"""Event type identifier."""
|
|
577
|
+
|
|
578
|
+
|
|
520
579
|
@dataclass(kw_only=True)
|
|
521
580
|
class CustomEvent[T]:
|
|
522
581
|
"""Generic custom event that can be emitted during tool execution."""
|
|
@@ -543,6 +602,26 @@ class PlanUpdateEvent:
|
|
|
543
602
|
"""Event type identifier."""
|
|
544
603
|
|
|
545
604
|
|
|
605
|
+
@dataclass(kw_only=True)
|
|
606
|
+
class SubAgentEvent:
|
|
607
|
+
"""Event wrapping activity from a subagent or team member.
|
|
608
|
+
|
|
609
|
+
Used to propagate events from delegated agents/teams into the parent stream,
|
|
610
|
+
allowing the consumer (UI/server) to decide how to render nested activity.
|
|
611
|
+
"""
|
|
612
|
+
|
|
613
|
+
source_name: str
|
|
614
|
+
"""Name of the agent or team that produced this event."""
|
|
615
|
+
source_type: Literal["agent", "team_parallel", "team_sequential"]
|
|
616
|
+
"""Type of source: agent, parallel team, or sequential team."""
|
|
617
|
+
event: RichAgentStreamEvent[Any]
|
|
618
|
+
"""The actual event from the subagent/team."""
|
|
619
|
+
depth: int = 1
|
|
620
|
+
"""Nesting depth (1 = direct child, 2 = grandchild, etc.)."""
|
|
621
|
+
event_kind: Literal["subagent"] = "subagent"
|
|
622
|
+
"""Event type identifier."""
|
|
623
|
+
|
|
624
|
+
|
|
546
625
|
@dataclass(kw_only=True)
|
|
547
626
|
class CompactionEvent:
|
|
548
627
|
"""Event indicating context compaction is starting or completed.
|
|
@@ -572,6 +651,8 @@ type RichAgentStreamEvent[OutputDataT] = (
|
|
|
572
651
|
| ToolCallCompleteEvent
|
|
573
652
|
| PlanUpdateEvent
|
|
574
653
|
| CompactionEvent
|
|
654
|
+
| SubAgentEvent
|
|
655
|
+
| ToolResultMetadataEvent
|
|
575
656
|
| CustomEvent[Any]
|
|
576
657
|
)
|
|
577
658
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""AgentPool event helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
from agentpool.agents.events import DiffContentItem, LocationContentItem
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from agentpool.agents.events import ToolCallContentItem
|
|
13
|
+
from agentpool.tools.base import ToolKind
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class RichToolInfo:
|
|
18
|
+
"""Rich display information derived from tool name and input."""
|
|
19
|
+
|
|
20
|
+
title: str
|
|
21
|
+
"""Human-readable title for the tool call."""
|
|
22
|
+
kind: ToolKind = "other"
|
|
23
|
+
"""Category of tool operation."""
|
|
24
|
+
locations: list[LocationContentItem] = field(default_factory=list)
|
|
25
|
+
"""File locations involved in the operation."""
|
|
26
|
+
content: list[ToolCallContentItem] = field(default_factory=list)
|
|
27
|
+
"""Rich content items (diffs, text, etc.)."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def derive_rich_tool_info(name: str, input_data: dict[str, Any]) -> RichToolInfo: # noqa: PLR0911, PLR0915
|
|
31
|
+
"""Derive rich display info from tool name and input arguments.
|
|
32
|
+
|
|
33
|
+
Maps MCP tool names and their inputs to human-readable titles, kinds,
|
|
34
|
+
and location information for rich UI display. Handles both Claude Code
|
|
35
|
+
built-in tools and MCP bridge tools.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
name: The tool name (e.g., "Read", "mcp__server__read")
|
|
39
|
+
input_data: The tool input arguments
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
RichToolInfo with derived display information
|
|
43
|
+
"""
|
|
44
|
+
# Extract the actual tool name if it's an MCP bridge tool
|
|
45
|
+
# Format: mcp__{server_name}__{tool_name}
|
|
46
|
+
actual_name = name
|
|
47
|
+
if name.startswith("mcp__") and "__" in name[5:]:
|
|
48
|
+
parts = name.split("__")
|
|
49
|
+
if len(parts) >= 3: # noqa: PLR2004
|
|
50
|
+
actual_name = parts[-1] # Get the last part (actual tool name)
|
|
51
|
+
|
|
52
|
+
# Normalize to lowercase for matching
|
|
53
|
+
tool_lower = actual_name.lower()
|
|
54
|
+
# Read operations
|
|
55
|
+
if tool_lower in ("read", "read_file"):
|
|
56
|
+
path = input_data.get("file_path") or input_data.get("path", "")
|
|
57
|
+
offset = input_data.get("offset") or input_data.get("line")
|
|
58
|
+
limit = input_data.get("limit")
|
|
59
|
+
|
|
60
|
+
suffix = ""
|
|
61
|
+
if limit:
|
|
62
|
+
start = (offset or 0) + 1
|
|
63
|
+
end = (offset or 0) + limit
|
|
64
|
+
suffix = f" ({start}-{end})"
|
|
65
|
+
elif offset:
|
|
66
|
+
suffix = f" (from line {offset + 1})"
|
|
67
|
+
title = f"Read {path}{suffix}" if path else "Read File"
|
|
68
|
+
locations = [LocationContentItem(path=path, line=offset or 0)] if path else []
|
|
69
|
+
return RichToolInfo(title=title, kind="read", locations=locations)
|
|
70
|
+
|
|
71
|
+
# Write operations
|
|
72
|
+
if tool_lower in ("write", "write_file"):
|
|
73
|
+
path = input_data.get("file_path") or input_data.get("path", "")
|
|
74
|
+
content = input_data.get("content", "")
|
|
75
|
+
return RichToolInfo(
|
|
76
|
+
title=f"Write {path}" if path else "Write File",
|
|
77
|
+
kind="edit",
|
|
78
|
+
locations=[LocationContentItem(path=path)] if path else [],
|
|
79
|
+
content=[DiffContentItem(path=path, old_text=None, new_text=content)] if path else [],
|
|
80
|
+
)
|
|
81
|
+
# Edit operations
|
|
82
|
+
if tool_lower in ("edit", "edit_file"):
|
|
83
|
+
path = input_data.get("file_path") or input_data.get("path", "")
|
|
84
|
+
old_string = input_data.get("old_string") or input_data.get("old_text", "")
|
|
85
|
+
new_string = input_data.get("new_string") or input_data.get("new_text", "")
|
|
86
|
+
return RichToolInfo(
|
|
87
|
+
title=f"Edit {path}" if path else "Edit File",
|
|
88
|
+
kind="edit",
|
|
89
|
+
locations=[LocationContentItem(path=path)] if path else [],
|
|
90
|
+
content=[DiffContentItem(path=path, old_text=old_string, new_text=new_string)]
|
|
91
|
+
if path
|
|
92
|
+
else [],
|
|
93
|
+
)
|
|
94
|
+
# Delete operations
|
|
95
|
+
if tool_lower in ("delete", "delete_path", "delete_file"):
|
|
96
|
+
path = input_data.get("file_path") or input_data.get("path", "")
|
|
97
|
+
locations = [LocationContentItem(path=path)] if path else []
|
|
98
|
+
title = f"Delete {path}" if path else "Delete"
|
|
99
|
+
return RichToolInfo(title=title, kind="delete", locations=locations)
|
|
100
|
+
# Bash/terminal operations
|
|
101
|
+
if tool_lower in ("bash", "execute", "run_command", "execute_command", "execute_code"):
|
|
102
|
+
command = input_data.get("command") or input_data.get("code", "")
|
|
103
|
+
# Escape backticks in command
|
|
104
|
+
escaped_cmd = command.replace("`", "\\`") if command else ""
|
|
105
|
+
title = f"`{escaped_cmd}`" if escaped_cmd else "Terminal"
|
|
106
|
+
return RichToolInfo(title=title, kind="execute")
|
|
107
|
+
# Search operations
|
|
108
|
+
if tool_lower in ("grep", "search", "glob", "find"):
|
|
109
|
+
pattern = input_data.get("pattern") or input_data.get("query", "")
|
|
110
|
+
path = input_data.get("path", "")
|
|
111
|
+
title = f"Search for '{pattern}'" if pattern else "Search"
|
|
112
|
+
if path:
|
|
113
|
+
title += f" in {path}"
|
|
114
|
+
locations = [LocationContentItem(path=path)] if path else []
|
|
115
|
+
return RichToolInfo(title=title, kind="search", locations=locations)
|
|
116
|
+
# List directory
|
|
117
|
+
if tool_lower in ("ls", "list", "list_directory"):
|
|
118
|
+
path = input_data.get("path", ".")
|
|
119
|
+
title = f"List {path}" if path != "." else "List current directory"
|
|
120
|
+
locations = [LocationContentItem(path=path)] if path else []
|
|
121
|
+
return RichToolInfo(title=title, kind="search", locations=locations)
|
|
122
|
+
# Web operations
|
|
123
|
+
if tool_lower in ("webfetch", "web_fetch", "fetch"):
|
|
124
|
+
url = input_data.get("url", "")
|
|
125
|
+
return RichToolInfo(title=f"Fetch {url}" if url else "Web Fetch", kind="fetch")
|
|
126
|
+
if tool_lower in ("websearch", "web_search", "search_web"):
|
|
127
|
+
query = input_data.get("query", "")
|
|
128
|
+
return RichToolInfo(title=f"Search: {query}" if query else "Web Search", kind="fetch")
|
|
129
|
+
# Task/subagent operations
|
|
130
|
+
if tool_lower == "task":
|
|
131
|
+
description = input_data.get("description", "")
|
|
132
|
+
return RichToolInfo(title=description if description else "Task", kind="think")
|
|
133
|
+
# Notebook operations
|
|
134
|
+
if tool_lower in ("notebookread", "notebook_read"):
|
|
135
|
+
path = input_data.get("notebook_path", "")
|
|
136
|
+
title = f"Read Notebook {path}" if path else "Read Notebook"
|
|
137
|
+
locations = [LocationContentItem(path=path)] if path else []
|
|
138
|
+
return RichToolInfo(title=title, kind="read", locations=locations)
|
|
139
|
+
if tool_lower in ("notebookedit", "notebook_edit"):
|
|
140
|
+
path = input_data.get("notebook_path", "")
|
|
141
|
+
title = f"Edit Notebook {path}" if path else "Edit Notebook"
|
|
142
|
+
locations = [LocationContentItem(path=path)] if path else []
|
|
143
|
+
return RichToolInfo(title=title, kind="edit", locations=locations)
|
|
144
|
+
# Default: use the tool name as title
|
|
145
|
+
return RichToolInfo(title=actual_name, kind="other")
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""Stream processors for event pipelines.
|
|
2
|
+
|
|
3
|
+
This module provides composable processors that can transform, filter, or observe
|
|
4
|
+
event streams. Processors wrap AsyncIterators and can be chained together.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
```python
|
|
8
|
+
# Simple function processor
|
|
9
|
+
async def log_events(stream):
|
|
10
|
+
async for event in stream:
|
|
11
|
+
print(f"Event: {type(event).__name__}")
|
|
12
|
+
yield event
|
|
13
|
+
|
|
14
|
+
# Class-based processor with state
|
|
15
|
+
tracker = FileTrackingProcessor()
|
|
16
|
+
|
|
17
|
+
# Compose into pipeline
|
|
18
|
+
pipeline = StreamPipeline([tracker, log_events])
|
|
19
|
+
|
|
20
|
+
async for event in pipeline(raw_events):
|
|
21
|
+
yield event
|
|
22
|
+
|
|
23
|
+
# Access state directly
|
|
24
|
+
print(tracker.get_metadata())
|
|
25
|
+
```
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from collections.abc import Callable
|
|
31
|
+
from dataclasses import dataclass, field
|
|
32
|
+
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from collections.abc import AsyncIterator, Coroutine
|
|
37
|
+
|
|
38
|
+
from agentpool.agents.events.events import RichAgentStreamEvent
|
|
39
|
+
from agentpool.common_types import SimpleJsonType
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Type alias for processor callables
|
|
43
|
+
type StreamProcessorCallable = Callable[
|
|
44
|
+
[AsyncIterator[RichAgentStreamEvent[Any]]], AsyncIterator[RichAgentStreamEvent[Any]]
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@runtime_checkable
|
|
49
|
+
class StreamProcessor(Protocol):
|
|
50
|
+
"""Protocol for stream processors.
|
|
51
|
+
|
|
52
|
+
Processors can be:
|
|
53
|
+
- Callables: `(AsyncIterator[RichAgentStreamEvent]) -> AsyncIterator[RichAgentStreamEvent]`
|
|
54
|
+
- Classes with `__call__`: Same signature, but can hold state
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __call__(
|
|
58
|
+
self, stream: AsyncIterator[RichAgentStreamEvent[Any]]
|
|
59
|
+
) -> AsyncIterator[RichAgentStreamEvent[Any]]:
|
|
60
|
+
"""Process an event stream.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
stream: Input event stream
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Transformed/filtered event stream
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class StreamPipeline:
|
|
73
|
+
"""Composable pipeline for processing event streams.
|
|
74
|
+
|
|
75
|
+
Chains multiple processors together, passing the output of each
|
|
76
|
+
to the input of the next.
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
```python
|
|
80
|
+
tracker = FileTrackingProcessor()
|
|
81
|
+
pipeline = StreamPipeline([
|
|
82
|
+
tracker,
|
|
83
|
+
event_handler_processor(handler),
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
async for event in pipeline(raw_events):
|
|
87
|
+
yield event
|
|
88
|
+
|
|
89
|
+
# Access state directly from processor instances
|
|
90
|
+
print(tracker.get_metadata())
|
|
91
|
+
```
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
processors: list[StreamProcessorCallable | StreamProcessor] = field(default_factory=list)
|
|
95
|
+
|
|
96
|
+
def __call__(
|
|
97
|
+
self, stream: AsyncIterator[RichAgentStreamEvent[Any]]
|
|
98
|
+
) -> AsyncIterator[RichAgentStreamEvent[Any]]:
|
|
99
|
+
"""Run events through all processors in sequence.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
stream: Input event stream
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Processed event stream
|
|
106
|
+
"""
|
|
107
|
+
result = stream
|
|
108
|
+
for processor in self.processors:
|
|
109
|
+
result = processor(result)
|
|
110
|
+
return result
|
|
111
|
+
|
|
112
|
+
def add(self, processor: StreamProcessorCallable | StreamProcessor) -> None:
|
|
113
|
+
"""Add a processor to the pipeline.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
processor: Processor to add
|
|
117
|
+
"""
|
|
118
|
+
self.processors.append(processor)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def extract_file_path_from_tool_call(tool_name: str, raw_input: dict[str, Any]) -> str | None:
|
|
122
|
+
"""Extract file path from a tool call if it's a file-writing tool.
|
|
123
|
+
|
|
124
|
+
Uses simple heuristics:
|
|
125
|
+
- Tool name contains 'write' or 'edit' (case-insensitive)
|
|
126
|
+
- Input contains 'path' or 'file_path' key
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
tool_name: Name of the tool being called
|
|
130
|
+
raw_input: Tool call arguments
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
File path if this is a file-writing tool, None otherwise
|
|
134
|
+
"""
|
|
135
|
+
name_lower = tool_name.lower()
|
|
136
|
+
if "write" not in name_lower and "edit" not in name_lower:
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
# Try common path argument names
|
|
140
|
+
for key in ("file_path", "path", "filepath", "filename", "file"):
|
|
141
|
+
if key in raw_input and isinstance(val := raw_input[key], str):
|
|
142
|
+
return val
|
|
143
|
+
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@dataclass
|
|
148
|
+
class FileTrackingProcessor:
|
|
149
|
+
"""Tracks files modified during a stream of events.
|
|
150
|
+
|
|
151
|
+
Observes ToolCallStartEvent and extracts file paths from write/edit operations.
|
|
152
|
+
Does not modify events - just passes them through while collecting metadata.
|
|
153
|
+
|
|
154
|
+
Example:
|
|
155
|
+
```python
|
|
156
|
+
tracker = FileTrackingProcessor()
|
|
157
|
+
|
|
158
|
+
async for event in tracker(events):
|
|
159
|
+
yield event
|
|
160
|
+
|
|
161
|
+
print(f"Modified files: {tracker.touched_files}")
|
|
162
|
+
print(f"Metadata: {tracker.get_metadata()}")
|
|
163
|
+
```
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
touched_files: set[str] = field(default_factory=set)
|
|
167
|
+
"""Set of file paths that were modified by tool calls."""
|
|
168
|
+
|
|
169
|
+
extractor: Callable[[str, dict[str, Any]], str | None] = extract_file_path_from_tool_call
|
|
170
|
+
"""Function to extract file path from tool call. Can be customized."""
|
|
171
|
+
|
|
172
|
+
def process_event(self, event: RichAgentStreamEvent[Any]) -> None:
|
|
173
|
+
"""Process an event and track any file modifications.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
event: The event to process (checks for ToolCallStartEvent)
|
|
177
|
+
"""
|
|
178
|
+
from agentpool.agents.events import ToolCallStartEvent
|
|
179
|
+
|
|
180
|
+
if isinstance(event, ToolCallStartEvent) and (
|
|
181
|
+
file_path := self.extractor(event.tool_name or "", event.raw_input or {})
|
|
182
|
+
):
|
|
183
|
+
self.touched_files.add(file_path)
|
|
184
|
+
|
|
185
|
+
def __call__(
|
|
186
|
+
self, stream: AsyncIterator[RichAgentStreamEvent[Any]]
|
|
187
|
+
) -> AsyncIterator[RichAgentStreamEvent[Any]]:
|
|
188
|
+
"""Wrap a stream to track file modifications.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
stream: Input event stream
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Same events, unmodified
|
|
195
|
+
"""
|
|
196
|
+
return self._process(stream)
|
|
197
|
+
|
|
198
|
+
async def _process(
|
|
199
|
+
self, stream: AsyncIterator[RichAgentStreamEvent[Any]]
|
|
200
|
+
) -> AsyncIterator[RichAgentStreamEvent[Any]]:
|
|
201
|
+
"""Internal async generator for processing."""
|
|
202
|
+
async for event in stream:
|
|
203
|
+
self.process_event(event)
|
|
204
|
+
yield event
|
|
205
|
+
|
|
206
|
+
def get_metadata(self) -> SimpleJsonType:
|
|
207
|
+
"""Get metadata dict with touched files (if any).
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Dict with 'touched_files' key if files were modified, else empty dict
|
|
211
|
+
"""
|
|
212
|
+
if self.touched_files:
|
|
213
|
+
return {"touched_files": sorted(self.touched_files)}
|
|
214
|
+
return {}
|
|
215
|
+
|
|
216
|
+
def reset(self) -> None:
|
|
217
|
+
"""Clear tracked files for reuse."""
|
|
218
|
+
self.touched_files.clear()
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def event_handler_processor(
|
|
222
|
+
handler: Callable[[Any, RichAgentStreamEvent[Any]], Coroutine[Any, Any, None]],
|
|
223
|
+
) -> StreamProcessorCallable:
|
|
224
|
+
"""Create a processor that calls an event handler for each event.
|
|
225
|
+
|
|
226
|
+
The handler is called with (None, event) to match the existing
|
|
227
|
+
MultiEventHandler signature.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
handler: Async callable with signature (ctx, event) -> None
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Processor function that calls the handler
|
|
234
|
+
|
|
235
|
+
Example:
|
|
236
|
+
```python
|
|
237
|
+
pipeline = StreamPipeline([
|
|
238
|
+
event_handler_processor(self.event_handler),
|
|
239
|
+
])
|
|
240
|
+
```
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
async def process(
|
|
244
|
+
stream: AsyncIterator[RichAgentStreamEvent[Any]],
|
|
245
|
+
) -> AsyncIterator[RichAgentStreamEvent[Any]]:
|
|
246
|
+
async for event in stream:
|
|
247
|
+
await handler(None, event)
|
|
248
|
+
yield event
|
|
249
|
+
|
|
250
|
+
return process
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# Convenience alias for backwards compatibility with existing FileTracker usage
|
|
254
|
+
FileTracker = FileTrackingProcessor
|