glaip-sdk 0.6.12__py3-none-any.whl → 0.6.14__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.
- glaip_sdk/__init__.py +42 -5
- {glaip_sdk-0.6.12.dist-info → glaip_sdk-0.6.14.dist-info}/METADATA +31 -37
- glaip_sdk-0.6.14.dist-info/RECORD +12 -0
- {glaip_sdk-0.6.12.dist-info → glaip_sdk-0.6.14.dist-info}/WHEEL +2 -1
- glaip_sdk-0.6.14.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.6.14.dist-info/top_level.txt +1 -0
- glaip_sdk/agents/__init__.py +0 -27
- glaip_sdk/agents/base.py +0 -1191
- glaip_sdk/cli/__init__.py +0 -9
- glaip_sdk/cli/account_store.py +0 -540
- glaip_sdk/cli/agent_config.py +0 -78
- glaip_sdk/cli/auth.py +0 -699
- glaip_sdk/cli/commands/__init__.py +0 -5
- glaip_sdk/cli/commands/accounts.py +0 -746
- glaip_sdk/cli/commands/agents.py +0 -1509
- glaip_sdk/cli/commands/common_config.py +0 -101
- glaip_sdk/cli/commands/configure.py +0 -896
- glaip_sdk/cli/commands/mcps.py +0 -1356
- glaip_sdk/cli/commands/models.py +0 -69
- glaip_sdk/cli/commands/tools.py +0 -576
- glaip_sdk/cli/commands/transcripts.py +0 -755
- glaip_sdk/cli/commands/update.py +0 -61
- glaip_sdk/cli/config.py +0 -95
- glaip_sdk/cli/constants.py +0 -38
- glaip_sdk/cli/context.py +0 -150
- glaip_sdk/cli/core/__init__.py +0 -79
- glaip_sdk/cli/core/context.py +0 -124
- glaip_sdk/cli/core/output.py +0 -846
- glaip_sdk/cli/core/prompting.py +0 -649
- glaip_sdk/cli/core/rendering.py +0 -187
- glaip_sdk/cli/display.py +0 -355
- glaip_sdk/cli/hints.py +0 -57
- glaip_sdk/cli/io.py +0 -112
- glaip_sdk/cli/main.py +0 -604
- glaip_sdk/cli/masking.py +0 -136
- glaip_sdk/cli/mcp_validators.py +0 -287
- glaip_sdk/cli/pager.py +0 -266
- glaip_sdk/cli/parsers/__init__.py +0 -7
- glaip_sdk/cli/parsers/json_input.py +0 -177
- glaip_sdk/cli/resolution.py +0 -67
- glaip_sdk/cli/rich_helpers.py +0 -27
- glaip_sdk/cli/slash/__init__.py +0 -15
- glaip_sdk/cli/slash/accounts_controller.py +0 -578
- glaip_sdk/cli/slash/accounts_shared.py +0 -75
- glaip_sdk/cli/slash/agent_session.py +0 -285
- glaip_sdk/cli/slash/prompt.py +0 -256
- glaip_sdk/cli/slash/remote_runs_controller.py +0 -566
- glaip_sdk/cli/slash/session.py +0 -1708
- glaip_sdk/cli/slash/tui/__init__.py +0 -9
- glaip_sdk/cli/slash/tui/accounts_app.py +0 -876
- glaip_sdk/cli/slash/tui/background_tasks.py +0 -72
- glaip_sdk/cli/slash/tui/loading.py +0 -58
- glaip_sdk/cli/slash/tui/remote_runs_app.py +0 -628
- glaip_sdk/cli/transcript/__init__.py +0 -31
- glaip_sdk/cli/transcript/cache.py +0 -536
- glaip_sdk/cli/transcript/capture.py +0 -329
- glaip_sdk/cli/transcript/export.py +0 -38
- glaip_sdk/cli/transcript/history.py +0 -815
- glaip_sdk/cli/transcript/launcher.py +0 -77
- glaip_sdk/cli/transcript/viewer.py +0 -374
- glaip_sdk/cli/update_notifier.py +0 -290
- glaip_sdk/cli/utils.py +0 -263
- glaip_sdk/cli/validators.py +0 -238
- glaip_sdk/client/__init__.py +0 -11
- glaip_sdk/client/_agent_payloads.py +0 -520
- glaip_sdk/client/agent_runs.py +0 -147
- glaip_sdk/client/agents.py +0 -1335
- glaip_sdk/client/base.py +0 -502
- glaip_sdk/client/main.py +0 -249
- glaip_sdk/client/mcps.py +0 -370
- glaip_sdk/client/run_rendering.py +0 -700
- glaip_sdk/client/shared.py +0 -21
- glaip_sdk/client/tools.py +0 -661
- glaip_sdk/client/validators.py +0 -198
- glaip_sdk/config/constants.py +0 -52
- glaip_sdk/mcps/__init__.py +0 -21
- glaip_sdk/mcps/base.py +0 -345
- glaip_sdk/models/__init__.py +0 -90
- glaip_sdk/models/agent.py +0 -47
- glaip_sdk/models/agent_runs.py +0 -116
- glaip_sdk/models/common.py +0 -42
- glaip_sdk/models/mcp.py +0 -33
- glaip_sdk/models/tool.py +0 -33
- glaip_sdk/payload_schemas/__init__.py +0 -7
- glaip_sdk/payload_schemas/agent.py +0 -85
- glaip_sdk/registry/__init__.py +0 -55
- glaip_sdk/registry/agent.py +0 -164
- glaip_sdk/registry/base.py +0 -139
- glaip_sdk/registry/mcp.py +0 -253
- glaip_sdk/registry/tool.py +0 -232
- glaip_sdk/runner/__init__.py +0 -59
- glaip_sdk/runner/base.py +0 -84
- glaip_sdk/runner/deps.py +0 -115
- glaip_sdk/runner/langgraph.py +0 -782
- glaip_sdk/runner/mcp_adapter/__init__.py +0 -13
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +0 -43
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +0 -257
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +0 -95
- glaip_sdk/runner/tool_adapter/__init__.py +0 -18
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +0 -44
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +0 -219
- glaip_sdk/tools/__init__.py +0 -22
- glaip_sdk/tools/base.py +0 -435
- glaip_sdk/utils/__init__.py +0 -86
- glaip_sdk/utils/a2a/__init__.py +0 -34
- glaip_sdk/utils/a2a/event_processor.py +0 -188
- glaip_sdk/utils/agent_config.py +0 -194
- glaip_sdk/utils/bundler.py +0 -267
- glaip_sdk/utils/client.py +0 -111
- glaip_sdk/utils/client_utils.py +0 -486
- glaip_sdk/utils/datetime_helpers.py +0 -58
- glaip_sdk/utils/discovery.py +0 -78
- glaip_sdk/utils/display.py +0 -135
- glaip_sdk/utils/export.py +0 -143
- glaip_sdk/utils/general.py +0 -61
- glaip_sdk/utils/import_export.py +0 -168
- glaip_sdk/utils/import_resolver.py +0 -492
- glaip_sdk/utils/instructions.py +0 -101
- glaip_sdk/utils/rendering/__init__.py +0 -115
- glaip_sdk/utils/rendering/formatting.py +0 -264
- glaip_sdk/utils/rendering/layout/__init__.py +0 -64
- glaip_sdk/utils/rendering/layout/panels.py +0 -156
- glaip_sdk/utils/rendering/layout/progress.py +0 -202
- glaip_sdk/utils/rendering/layout/summary.py +0 -74
- glaip_sdk/utils/rendering/layout/transcript.py +0 -606
- glaip_sdk/utils/rendering/models.py +0 -85
- glaip_sdk/utils/rendering/renderer/__init__.py +0 -55
- glaip_sdk/utils/rendering/renderer/base.py +0 -1024
- glaip_sdk/utils/rendering/renderer/config.py +0 -27
- glaip_sdk/utils/rendering/renderer/console.py +0 -55
- glaip_sdk/utils/rendering/renderer/debug.py +0 -178
- glaip_sdk/utils/rendering/renderer/factory.py +0 -138
- glaip_sdk/utils/rendering/renderer/stream.py +0 -202
- glaip_sdk/utils/rendering/renderer/summary_window.py +0 -79
- glaip_sdk/utils/rendering/renderer/thinking.py +0 -273
- glaip_sdk/utils/rendering/renderer/toggle.py +0 -182
- glaip_sdk/utils/rendering/renderer/tool_panels.py +0 -442
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +0 -162
- glaip_sdk/utils/rendering/state.py +0 -204
- glaip_sdk/utils/rendering/step_tree_state.py +0 -100
- glaip_sdk/utils/rendering/steps/__init__.py +0 -34
- glaip_sdk/utils/rendering/steps/event_processor.py +0 -778
- glaip_sdk/utils/rendering/steps/format.py +0 -176
- glaip_sdk/utils/rendering/steps/manager.py +0 -387
- glaip_sdk/utils/rendering/timing.py +0 -36
- glaip_sdk/utils/rendering/viewer/__init__.py +0 -21
- glaip_sdk/utils/rendering/viewer/presenter.py +0 -184
- glaip_sdk/utils/resource_refs.py +0 -195
- glaip_sdk/utils/run_renderer.py +0 -41
- glaip_sdk/utils/runtime_config.py +0 -425
- glaip_sdk/utils/serialization.py +0 -424
- glaip_sdk/utils/sync.py +0 -142
- glaip_sdk/utils/tool_detection.py +0 -33
- glaip_sdk/utils/validation.py +0 -264
- glaip_sdk-0.6.12.dist-info/RECORD +0 -159
- glaip_sdk-0.6.12.dist-info/entry_points.txt +0 -3
|
@@ -1,700 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Rendering helpers for agent streaming flows.
|
|
3
|
-
|
|
4
|
-
Authors:
|
|
5
|
-
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
import json
|
|
11
|
-
import logging
|
|
12
|
-
from collections.abc import AsyncIterable, Callable
|
|
13
|
-
from time import monotonic
|
|
14
|
-
from typing import Any
|
|
15
|
-
|
|
16
|
-
import httpx
|
|
17
|
-
from rich.console import Console as _Console
|
|
18
|
-
|
|
19
|
-
from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT
|
|
20
|
-
from glaip_sdk.utils.client_utils import iter_sse_events
|
|
21
|
-
from glaip_sdk.utils.rendering.models import RunStats
|
|
22
|
-
from glaip_sdk.utils.rendering.renderer import (
|
|
23
|
-
RendererFactoryOptions,
|
|
24
|
-
RichStreamRenderer,
|
|
25
|
-
make_default_renderer,
|
|
26
|
-
make_minimal_renderer,
|
|
27
|
-
make_silent_renderer,
|
|
28
|
-
make_verbose_renderer,
|
|
29
|
-
)
|
|
30
|
-
from glaip_sdk.utils.rendering.state import TranscriptBuffer
|
|
31
|
-
|
|
32
|
-
NO_AGENT_RESPONSE_FALLBACK = "No agent response received."
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _coerce_to_string(value: Any) -> str:
|
|
36
|
-
"""Return a best-effort string representation for transcripts."""
|
|
37
|
-
try:
|
|
38
|
-
return str(value)
|
|
39
|
-
except Exception:
|
|
40
|
-
return f"{value}"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _has_visible_text(value: Any) -> bool:
|
|
44
|
-
"""Return True when the value is a non-empty string."""
|
|
45
|
-
return isinstance(value, str) and bool(value.strip())
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class AgentRunRenderingManager:
|
|
49
|
-
"""Coordinate renderer creation and streaming event handling."""
|
|
50
|
-
|
|
51
|
-
def __init__(self, logger: logging.Logger | None = None) -> None:
|
|
52
|
-
"""Initialize the rendering manager.
|
|
53
|
-
|
|
54
|
-
Args:
|
|
55
|
-
logger: Optional logger instance, creates default if None
|
|
56
|
-
"""
|
|
57
|
-
self._logger = logger or logging.getLogger(__name__)
|
|
58
|
-
self._buffer_factory = TranscriptBuffer
|
|
59
|
-
|
|
60
|
-
# --------------------------------------------------------------------- #
|
|
61
|
-
# Renderer setup helpers
|
|
62
|
-
# --------------------------------------------------------------------- #
|
|
63
|
-
def create_renderer(
|
|
64
|
-
self,
|
|
65
|
-
renderer_spec: RichStreamRenderer | str | None,
|
|
66
|
-
*,
|
|
67
|
-
verbose: bool = False,
|
|
68
|
-
) -> RichStreamRenderer:
|
|
69
|
-
"""Create an appropriate renderer based on the supplied spec."""
|
|
70
|
-
transcript_buffer = self._buffer_factory()
|
|
71
|
-
base_options = RendererFactoryOptions(console=_Console(), transcript_buffer=transcript_buffer)
|
|
72
|
-
if isinstance(renderer_spec, RichStreamRenderer):
|
|
73
|
-
return renderer_spec
|
|
74
|
-
|
|
75
|
-
if isinstance(renderer_spec, str):
|
|
76
|
-
lowered = renderer_spec.lower()
|
|
77
|
-
if lowered == "silent":
|
|
78
|
-
return self._attach_buffer(base_options.build(make_silent_renderer), transcript_buffer)
|
|
79
|
-
if lowered == "minimal":
|
|
80
|
-
return self._attach_buffer(base_options.build(make_minimal_renderer), transcript_buffer)
|
|
81
|
-
if lowered == "verbose":
|
|
82
|
-
return self._attach_buffer(base_options.build(make_verbose_renderer), transcript_buffer)
|
|
83
|
-
|
|
84
|
-
if verbose:
|
|
85
|
-
return self._attach_buffer(base_options.build(make_verbose_renderer), transcript_buffer)
|
|
86
|
-
|
|
87
|
-
default_options = RendererFactoryOptions(
|
|
88
|
-
console=_Console(),
|
|
89
|
-
transcript_buffer=transcript_buffer,
|
|
90
|
-
verbose=verbose,
|
|
91
|
-
)
|
|
92
|
-
return self._attach_buffer(default_options.build(make_default_renderer), transcript_buffer)
|
|
93
|
-
|
|
94
|
-
@staticmethod
|
|
95
|
-
def _attach_buffer(renderer: RichStreamRenderer, buffer: TranscriptBuffer) -> RichStreamRenderer:
|
|
96
|
-
"""Attach a captured transcript buffer to a renderer for later inspection."""
|
|
97
|
-
try:
|
|
98
|
-
renderer._captured_transcript_buffer = buffer # type: ignore[attr-defined]
|
|
99
|
-
except Exception:
|
|
100
|
-
pass
|
|
101
|
-
return renderer
|
|
102
|
-
|
|
103
|
-
def build_initial_metadata(
|
|
104
|
-
self,
|
|
105
|
-
agent_id: str,
|
|
106
|
-
message: str,
|
|
107
|
-
kwargs: dict[str, Any],
|
|
108
|
-
) -> dict[str, Any]:
|
|
109
|
-
"""Construct the initial renderer metadata payload."""
|
|
110
|
-
return {
|
|
111
|
-
"agent_name": kwargs.get("agent_name", agent_id),
|
|
112
|
-
"model": kwargs.get("model"),
|
|
113
|
-
"run_id": None,
|
|
114
|
-
"input_message": message,
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
@staticmethod
|
|
118
|
-
def start_renderer(renderer: RichStreamRenderer, meta: dict[str, Any]) -> None:
|
|
119
|
-
"""Notify renderer that streaming is starting."""
|
|
120
|
-
renderer.on_start(meta)
|
|
121
|
-
|
|
122
|
-
# --------------------------------------------------------------------- #
|
|
123
|
-
# Streaming event handling
|
|
124
|
-
# --------------------------------------------------------------------- #
|
|
125
|
-
def process_stream_events(
|
|
126
|
-
self,
|
|
127
|
-
stream_response: httpx.Response,
|
|
128
|
-
renderer: RichStreamRenderer,
|
|
129
|
-
timeout_seconds: float,
|
|
130
|
-
agent_name: str | None,
|
|
131
|
-
meta: dict[str, Any],
|
|
132
|
-
) -> tuple[str, dict[str, Any], float | None, float | None]:
|
|
133
|
-
"""Process streaming events and accumulate response."""
|
|
134
|
-
final_text = ""
|
|
135
|
-
stats_usage: dict[str, Any] = {}
|
|
136
|
-
started_monotonic: float | None = None
|
|
137
|
-
|
|
138
|
-
self._capture_request_id(stream_response, meta, renderer)
|
|
139
|
-
|
|
140
|
-
controller = getattr(renderer, "transcript_controller", None)
|
|
141
|
-
if controller and getattr(controller, "enabled", False):
|
|
142
|
-
controller.on_stream_start(renderer)
|
|
143
|
-
|
|
144
|
-
try:
|
|
145
|
-
for event in iter_sse_events(stream_response, timeout_seconds, agent_name):
|
|
146
|
-
if started_monotonic is None:
|
|
147
|
-
started_monotonic = self._maybe_start_timer(event)
|
|
148
|
-
|
|
149
|
-
final_text, stats_usage = self._process_single_event(
|
|
150
|
-
event,
|
|
151
|
-
renderer,
|
|
152
|
-
final_text,
|
|
153
|
-
stats_usage,
|
|
154
|
-
meta,
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
if controller and getattr(controller, "enabled", False):
|
|
158
|
-
controller.poll(renderer)
|
|
159
|
-
finally:
|
|
160
|
-
if controller and getattr(controller, "enabled", False):
|
|
161
|
-
controller.on_stream_complete()
|
|
162
|
-
|
|
163
|
-
finished_monotonic = monotonic()
|
|
164
|
-
return final_text, stats_usage, started_monotonic, finished_monotonic
|
|
165
|
-
|
|
166
|
-
async def async_process_stream_events(
|
|
167
|
-
self,
|
|
168
|
-
event_stream: AsyncIterable[dict[str, Any]],
|
|
169
|
-
renderer: RichStreamRenderer,
|
|
170
|
-
meta: dict[str, Any],
|
|
171
|
-
*,
|
|
172
|
-
skip_final_render: bool = True,
|
|
173
|
-
) -> tuple[str, dict[str, Any], float | None, float | None]:
|
|
174
|
-
"""Process streaming events from an async event source.
|
|
175
|
-
|
|
176
|
-
This method provides unified stream processing for both remote (HTTP)
|
|
177
|
-
and local (LangGraph) agent execution, ensuring consistent behavior.
|
|
178
|
-
|
|
179
|
-
Args:
|
|
180
|
-
event_stream: Async iterable yielding SSE-like event dicts.
|
|
181
|
-
Each event should have a "data" key with JSON string, or be
|
|
182
|
-
a pre-parsed dict with "content", "metadata", etc.
|
|
183
|
-
renderer: Renderer to use for displaying events.
|
|
184
|
-
meta: Metadata dictionary for renderer context.
|
|
185
|
-
skip_final_render: If True, skip rendering final_response events
|
|
186
|
-
(they are rendered separately via finalize_renderer).
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
Tuple of (final_text, stats_usage, started_monotonic, finished_monotonic).
|
|
190
|
-
"""
|
|
191
|
-
final_text = ""
|
|
192
|
-
stats_usage: dict[str, Any] = {}
|
|
193
|
-
started_monotonic: float | None = None
|
|
194
|
-
last_rendered_content: str | None = None
|
|
195
|
-
|
|
196
|
-
controller = getattr(renderer, "transcript_controller", None)
|
|
197
|
-
if controller and getattr(controller, "enabled", False):
|
|
198
|
-
controller.on_stream_start(renderer)
|
|
199
|
-
|
|
200
|
-
try:
|
|
201
|
-
async for event in event_stream:
|
|
202
|
-
if started_monotonic is None:
|
|
203
|
-
started_monotonic = monotonic()
|
|
204
|
-
|
|
205
|
-
# Parse event if needed (handles both raw SSE and pre-parsed dicts)
|
|
206
|
-
parsed_event = self._parse_event(event)
|
|
207
|
-
if parsed_event is None:
|
|
208
|
-
continue
|
|
209
|
-
|
|
210
|
-
# Process the event and update accumulators
|
|
211
|
-
final_text, stats_usage = self._handle_parsed_event(
|
|
212
|
-
parsed_event,
|
|
213
|
-
renderer,
|
|
214
|
-
final_text,
|
|
215
|
-
stats_usage,
|
|
216
|
-
meta,
|
|
217
|
-
skip_final_render=skip_final_render,
|
|
218
|
-
last_rendered_content=last_rendered_content,
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
# Track last rendered content to avoid duplicates
|
|
222
|
-
content_str = self._extract_content_string(parsed_event)
|
|
223
|
-
if content_str:
|
|
224
|
-
last_rendered_content = content_str
|
|
225
|
-
|
|
226
|
-
if controller and getattr(controller, "enabled", False):
|
|
227
|
-
controller.poll(renderer)
|
|
228
|
-
finally:
|
|
229
|
-
if controller and getattr(controller, "enabled", False):
|
|
230
|
-
controller.on_stream_complete()
|
|
231
|
-
|
|
232
|
-
finished_monotonic = monotonic()
|
|
233
|
-
return final_text, stats_usage, started_monotonic, finished_monotonic
|
|
234
|
-
|
|
235
|
-
def _parse_event(self, event: dict[str, Any]) -> dict[str, Any] | None:
|
|
236
|
-
"""Parse an SSE event dict into a usable format.
|
|
237
|
-
|
|
238
|
-
Args:
|
|
239
|
-
event: Raw event dict, either with "data" key (SSE format) or
|
|
240
|
-
pre-parsed with "content", "metadata", etc.
|
|
241
|
-
|
|
242
|
-
Returns:
|
|
243
|
-
Parsed event dict, or None if parsing fails.
|
|
244
|
-
"""
|
|
245
|
-
if "data" in event:
|
|
246
|
-
try:
|
|
247
|
-
return json.loads(event["data"])
|
|
248
|
-
except json.JSONDecodeError:
|
|
249
|
-
self._logger.debug("Non-JSON SSE fragment skipped")
|
|
250
|
-
return None
|
|
251
|
-
# Already parsed (e.g., from local runner)
|
|
252
|
-
return event if event else None
|
|
253
|
-
|
|
254
|
-
def _handle_parsed_event(
|
|
255
|
-
self,
|
|
256
|
-
ev: dict[str, Any],
|
|
257
|
-
renderer: RichStreamRenderer,
|
|
258
|
-
final_text: str,
|
|
259
|
-
stats_usage: dict[str, Any],
|
|
260
|
-
meta: dict[str, Any],
|
|
261
|
-
*,
|
|
262
|
-
skip_final_render: bool = True,
|
|
263
|
-
last_rendered_content: str | None = None,
|
|
264
|
-
) -> tuple[str, dict[str, Any]]:
|
|
265
|
-
"""Handle a parsed event and update accumulators.
|
|
266
|
-
|
|
267
|
-
Args:
|
|
268
|
-
ev: Parsed event dictionary.
|
|
269
|
-
renderer: Renderer instance.
|
|
270
|
-
final_text: Current accumulated final text.
|
|
271
|
-
stats_usage: Usage statistics dictionary.
|
|
272
|
-
meta: Metadata dictionary.
|
|
273
|
-
skip_final_render: If True, skip rendering final_response events.
|
|
274
|
-
last_rendered_content: Last rendered content to avoid duplicates.
|
|
275
|
-
|
|
276
|
-
Returns:
|
|
277
|
-
Tuple of (updated_final_text, updated_stats_usage).
|
|
278
|
-
"""
|
|
279
|
-
kind = self._get_event_kind(ev)
|
|
280
|
-
|
|
281
|
-
# Dispatch to specialized handlers based on event kind
|
|
282
|
-
handler = self._get_event_handler(kind, ev)
|
|
283
|
-
if handler:
|
|
284
|
-
return handler(ev, renderer, final_text, stats_usage, meta, skip_final_render)
|
|
285
|
-
|
|
286
|
-
# Default: handle content events
|
|
287
|
-
return self._handle_content_event_async(ev, renderer, final_text, stats_usage, last_rendered_content)
|
|
288
|
-
|
|
289
|
-
def _get_event_handler(
|
|
290
|
-
self,
|
|
291
|
-
kind: str | None,
|
|
292
|
-
ev: dict[str, Any],
|
|
293
|
-
) -> Callable[..., tuple[str, dict[str, Any]]] | None:
|
|
294
|
-
"""Get the appropriate handler for an event kind.
|
|
295
|
-
|
|
296
|
-
Args:
|
|
297
|
-
kind: Event kind string.
|
|
298
|
-
ev: Event dictionary (for checking is_final flag).
|
|
299
|
-
|
|
300
|
-
Returns:
|
|
301
|
-
Handler function or None for default content handling.
|
|
302
|
-
"""
|
|
303
|
-
if kind == "usage":
|
|
304
|
-
return self._handle_usage_event
|
|
305
|
-
if kind == "final_response" or ev.get("is_final"):
|
|
306
|
-
return self._handle_final_response_event
|
|
307
|
-
if kind == "run_info":
|
|
308
|
-
return self._handle_run_info_event_wrapper
|
|
309
|
-
if kind in ("artifact", "status_update"):
|
|
310
|
-
return self._handle_render_only_event
|
|
311
|
-
return None
|
|
312
|
-
|
|
313
|
-
def _handle_usage_event(
|
|
314
|
-
self,
|
|
315
|
-
ev: dict[str, Any],
|
|
316
|
-
_renderer: RichStreamRenderer,
|
|
317
|
-
final_text: str,
|
|
318
|
-
stats_usage: dict[str, Any],
|
|
319
|
-
_meta: dict[str, Any],
|
|
320
|
-
_skip_final_render: bool,
|
|
321
|
-
) -> tuple[str, dict[str, Any]]:
|
|
322
|
-
"""Handle usage events."""
|
|
323
|
-
stats_usage.update(ev.get("usage") or {})
|
|
324
|
-
return final_text, stats_usage
|
|
325
|
-
|
|
326
|
-
def _handle_final_response_event(
|
|
327
|
-
self,
|
|
328
|
-
ev: dict[str, Any],
|
|
329
|
-
renderer: RichStreamRenderer,
|
|
330
|
-
final_text: str,
|
|
331
|
-
stats_usage: dict[str, Any],
|
|
332
|
-
_meta: dict[str, Any],
|
|
333
|
-
skip_final_render: bool,
|
|
334
|
-
) -> tuple[str, dict[str, Any]]:
|
|
335
|
-
"""Handle final_response events."""
|
|
336
|
-
content = ev.get("content")
|
|
337
|
-
if content:
|
|
338
|
-
final_text = str(content)
|
|
339
|
-
if not skip_final_render:
|
|
340
|
-
renderer.on_event(ev)
|
|
341
|
-
return final_text, stats_usage
|
|
342
|
-
|
|
343
|
-
def _handle_run_info_event_wrapper(
|
|
344
|
-
self,
|
|
345
|
-
ev: dict[str, Any],
|
|
346
|
-
renderer: RichStreamRenderer,
|
|
347
|
-
final_text: str,
|
|
348
|
-
stats_usage: dict[str, Any],
|
|
349
|
-
meta: dict[str, Any],
|
|
350
|
-
_skip_final_render: bool,
|
|
351
|
-
) -> tuple[str, dict[str, Any]]:
|
|
352
|
-
"""Handle run_info events."""
|
|
353
|
-
self._handle_run_info_event(ev, meta, renderer)
|
|
354
|
-
return final_text, stats_usage
|
|
355
|
-
|
|
356
|
-
def _handle_render_only_event(
|
|
357
|
-
self,
|
|
358
|
-
ev: dict[str, Any],
|
|
359
|
-
renderer: RichStreamRenderer,
|
|
360
|
-
final_text: str,
|
|
361
|
-
stats_usage: dict[str, Any],
|
|
362
|
-
_meta: dict[str, Any],
|
|
363
|
-
_skip_final_render: bool,
|
|
364
|
-
) -> tuple[str, dict[str, Any]]:
|
|
365
|
-
"""Handle events that only need rendering (artifact, status_update)."""
|
|
366
|
-
renderer.on_event(ev)
|
|
367
|
-
return final_text, stats_usage
|
|
368
|
-
|
|
369
|
-
def _handle_content_event_async(
|
|
370
|
-
self,
|
|
371
|
-
ev: dict[str, Any],
|
|
372
|
-
renderer: RichStreamRenderer,
|
|
373
|
-
final_text: str,
|
|
374
|
-
stats_usage: dict[str, Any],
|
|
375
|
-
last_rendered_content: str | None,
|
|
376
|
-
) -> tuple[str, dict[str, Any]]:
|
|
377
|
-
"""Handle content events with deduplication."""
|
|
378
|
-
content = ev.get("content")
|
|
379
|
-
if content:
|
|
380
|
-
content_str = str(content)
|
|
381
|
-
if not content_str.startswith("Artifact received:"):
|
|
382
|
-
if content_str != last_rendered_content:
|
|
383
|
-
renderer.on_event(ev)
|
|
384
|
-
final_text = content_str
|
|
385
|
-
else:
|
|
386
|
-
renderer.on_event(ev)
|
|
387
|
-
return final_text, stats_usage
|
|
388
|
-
|
|
389
|
-
def _get_event_kind(self, ev: dict[str, Any]) -> str | None:
|
|
390
|
-
"""Extract normalized event kind from parsed event.
|
|
391
|
-
|
|
392
|
-
Args:
|
|
393
|
-
ev: Parsed event dictionary.
|
|
394
|
-
|
|
395
|
-
Returns:
|
|
396
|
-
Event kind string or None.
|
|
397
|
-
"""
|
|
398
|
-
metadata = ev.get("metadata") or {}
|
|
399
|
-
kind = metadata.get("kind")
|
|
400
|
-
if kind:
|
|
401
|
-
return str(kind)
|
|
402
|
-
event_type = ev.get("event_type")
|
|
403
|
-
return str(event_type) if event_type else None
|
|
404
|
-
|
|
405
|
-
def _extract_content_string(self, event: dict[str, Any]) -> str | None:
|
|
406
|
-
"""Extract textual content from a parsed event.
|
|
407
|
-
|
|
408
|
-
Args:
|
|
409
|
-
event: Parsed event dictionary.
|
|
410
|
-
|
|
411
|
-
Returns:
|
|
412
|
-
Content string or None.
|
|
413
|
-
"""
|
|
414
|
-
if not event:
|
|
415
|
-
return None
|
|
416
|
-
content = event.get("content")
|
|
417
|
-
if content:
|
|
418
|
-
return str(content)
|
|
419
|
-
return None
|
|
420
|
-
|
|
421
|
-
def _capture_request_id(
|
|
422
|
-
self,
|
|
423
|
-
stream_response: httpx.Response,
|
|
424
|
-
meta: dict[str, Any],
|
|
425
|
-
renderer: RichStreamRenderer,
|
|
426
|
-
) -> None:
|
|
427
|
-
"""Capture request ID from response headers and update metadata.
|
|
428
|
-
|
|
429
|
-
Args:
|
|
430
|
-
stream_response: HTTP response stream.
|
|
431
|
-
meta: Metadata dictionary to update.
|
|
432
|
-
renderer: Renderer instance.
|
|
433
|
-
"""
|
|
434
|
-
req_id = stream_response.headers.get("x-request-id") or stream_response.headers.get("x-run-id")
|
|
435
|
-
if req_id:
|
|
436
|
-
meta["run_id"] = req_id
|
|
437
|
-
renderer.on_start(meta)
|
|
438
|
-
|
|
439
|
-
def _maybe_start_timer(self, event: dict[str, Any]) -> float | None:
|
|
440
|
-
"""Start timing if this is a content-bearing event.
|
|
441
|
-
|
|
442
|
-
Args:
|
|
443
|
-
event: Event dictionary.
|
|
444
|
-
|
|
445
|
-
Returns:
|
|
446
|
-
Monotonic time if timer should start, None otherwise.
|
|
447
|
-
"""
|
|
448
|
-
try:
|
|
449
|
-
ev = json.loads(event["data"])
|
|
450
|
-
except json.JSONDecodeError:
|
|
451
|
-
return None
|
|
452
|
-
|
|
453
|
-
if "content" in ev or "status" in ev or ev.get("metadata"):
|
|
454
|
-
return monotonic()
|
|
455
|
-
return None
|
|
456
|
-
|
|
457
|
-
def _process_single_event(
|
|
458
|
-
self,
|
|
459
|
-
event: dict[str, Any],
|
|
460
|
-
renderer: RichStreamRenderer,
|
|
461
|
-
final_text: str,
|
|
462
|
-
stats_usage: dict[str, Any],
|
|
463
|
-
meta: dict[str, Any],
|
|
464
|
-
) -> tuple[str, dict[str, Any]]:
|
|
465
|
-
"""Process a single streaming event.
|
|
466
|
-
|
|
467
|
-
Args:
|
|
468
|
-
event: Event dictionary.
|
|
469
|
-
renderer: Renderer instance.
|
|
470
|
-
final_text: Accumulated text so far.
|
|
471
|
-
stats_usage: Usage statistics dictionary.
|
|
472
|
-
meta: Metadata dictionary.
|
|
473
|
-
|
|
474
|
-
Returns:
|
|
475
|
-
Tuple of (updated_final_text, updated_stats_usage).
|
|
476
|
-
"""
|
|
477
|
-
try:
|
|
478
|
-
ev = json.loads(event["data"])
|
|
479
|
-
except json.JSONDecodeError:
|
|
480
|
-
self._logger.debug("Non-JSON SSE fragment skipped")
|
|
481
|
-
return final_text, stats_usage
|
|
482
|
-
|
|
483
|
-
kind = (ev.get("metadata") or {}).get("kind")
|
|
484
|
-
renderer.on_event(ev)
|
|
485
|
-
|
|
486
|
-
handled = self._handle_metadata_kind(
|
|
487
|
-
kind,
|
|
488
|
-
ev,
|
|
489
|
-
final_text,
|
|
490
|
-
stats_usage,
|
|
491
|
-
meta,
|
|
492
|
-
renderer,
|
|
493
|
-
)
|
|
494
|
-
if handled is not None:
|
|
495
|
-
return handled
|
|
496
|
-
|
|
497
|
-
if ev.get("content"):
|
|
498
|
-
final_text = self._handle_content_event(ev, final_text)
|
|
499
|
-
|
|
500
|
-
return final_text, stats_usage
|
|
501
|
-
|
|
502
|
-
def _handle_metadata_kind(
|
|
503
|
-
self,
|
|
504
|
-
kind: str | None,
|
|
505
|
-
ev: dict[str, Any],
|
|
506
|
-
final_text: str,
|
|
507
|
-
stats_usage: dict[str, Any],
|
|
508
|
-
meta: dict[str, Any],
|
|
509
|
-
renderer: RichStreamRenderer,
|
|
510
|
-
) -> tuple[str, dict[str, Any]] | None:
|
|
511
|
-
"""Process well-known metadata kinds and return updated state."""
|
|
512
|
-
if kind == "artifact":
|
|
513
|
-
return final_text, stats_usage
|
|
514
|
-
|
|
515
|
-
if kind == "final_response":
|
|
516
|
-
content = ev.get("content")
|
|
517
|
-
if content:
|
|
518
|
-
return content, stats_usage
|
|
519
|
-
return final_text, stats_usage
|
|
520
|
-
|
|
521
|
-
if kind == "usage":
|
|
522
|
-
stats_usage.update(ev.get("usage") or {})
|
|
523
|
-
return final_text, stats_usage
|
|
524
|
-
|
|
525
|
-
if kind == "run_info":
|
|
526
|
-
self._handle_run_info_event(ev, meta, renderer)
|
|
527
|
-
return final_text, stats_usage
|
|
528
|
-
|
|
529
|
-
return None
|
|
530
|
-
|
|
531
|
-
def _handle_content_event(self, ev: dict[str, Any], final_text: str) -> str:
|
|
532
|
-
"""Handle a content event and update final text.
|
|
533
|
-
|
|
534
|
-
Args:
|
|
535
|
-
ev: Event dictionary.
|
|
536
|
-
final_text: Current accumulated text.
|
|
537
|
-
|
|
538
|
-
Returns:
|
|
539
|
-
Updated final text.
|
|
540
|
-
"""
|
|
541
|
-
content = ev.get("content", "")
|
|
542
|
-
if not content.startswith("Artifact received:"):
|
|
543
|
-
return content
|
|
544
|
-
return final_text
|
|
545
|
-
|
|
546
|
-
def _handle_run_info_event(
|
|
547
|
-
self,
|
|
548
|
-
ev: dict[str, Any],
|
|
549
|
-
meta: dict[str, Any],
|
|
550
|
-
renderer: RichStreamRenderer,
|
|
551
|
-
) -> None:
|
|
552
|
-
"""Handle a run_info event and update metadata.
|
|
553
|
-
|
|
554
|
-
Args:
|
|
555
|
-
ev: Event dictionary.
|
|
556
|
-
meta: Metadata dictionary to update.
|
|
557
|
-
renderer: Renderer instance.
|
|
558
|
-
"""
|
|
559
|
-
if ev.get("model"):
|
|
560
|
-
meta["model"] = ev["model"]
|
|
561
|
-
renderer.on_start(meta)
|
|
562
|
-
if ev.get("run_id"):
|
|
563
|
-
meta["run_id"] = ev["run_id"]
|
|
564
|
-
renderer.on_start(meta)
|
|
565
|
-
|
|
566
|
-
def _ensure_renderer_final_content(self, renderer: RichStreamRenderer, text: str) -> None:
|
|
567
|
-
"""Populate renderer state with final output when the stream omits it."""
|
|
568
|
-
if not text:
|
|
569
|
-
return
|
|
570
|
-
|
|
571
|
-
text_value = _coerce_to_string(text)
|
|
572
|
-
state = getattr(renderer, "state", None)
|
|
573
|
-
if state is None:
|
|
574
|
-
self._ensure_renderer_text(renderer, text_value)
|
|
575
|
-
return
|
|
576
|
-
|
|
577
|
-
self._ensure_state_final_text(state, text_value)
|
|
578
|
-
self._ensure_state_buffer(state, text_value)
|
|
579
|
-
|
|
580
|
-
def _ensure_renderer_text(self, renderer: RichStreamRenderer, text_value: str) -> None:
|
|
581
|
-
"""Best-effort assignment for renderer.final_text."""
|
|
582
|
-
if not hasattr(renderer, "final_text"):
|
|
583
|
-
return
|
|
584
|
-
current_text = getattr(renderer, "final_text", "")
|
|
585
|
-
if _has_visible_text(current_text):
|
|
586
|
-
return
|
|
587
|
-
self._safe_set_attr(renderer, "final_text", text_value)
|
|
588
|
-
|
|
589
|
-
def _ensure_state_final_text(self, state: Any, text_value: str) -> None:
|
|
590
|
-
"""Best-effort assignment for renderer.state.final_text."""
|
|
591
|
-
current_text = getattr(state, "final_text", "")
|
|
592
|
-
if _has_visible_text(current_text):
|
|
593
|
-
return
|
|
594
|
-
self._safe_set_attr(state, "final_text", text_value)
|
|
595
|
-
|
|
596
|
-
def _ensure_state_buffer(self, state: Any, text_value: str) -> None:
|
|
597
|
-
"""Append fallback text to the state buffer when available."""
|
|
598
|
-
buffer = getattr(state, "buffer", None)
|
|
599
|
-
if not hasattr(buffer, "append"):
|
|
600
|
-
return
|
|
601
|
-
self._safe_append(buffer.append, text_value)
|
|
602
|
-
|
|
603
|
-
@staticmethod
|
|
604
|
-
def _safe_set_attr(target: Any, attr: str, value: str) -> None:
|
|
605
|
-
"""Assign attribute while masking renderer-specific failures."""
|
|
606
|
-
try:
|
|
607
|
-
setattr(target, attr, value)
|
|
608
|
-
except Exception:
|
|
609
|
-
pass
|
|
610
|
-
|
|
611
|
-
@staticmethod
|
|
612
|
-
def _safe_append(appender: Callable[[str], Any], value: str) -> None:
|
|
613
|
-
"""Invoke append-like functions without leaking renderer errors."""
|
|
614
|
-
try:
|
|
615
|
-
appender(value)
|
|
616
|
-
except Exception:
|
|
617
|
-
pass
|
|
618
|
-
|
|
619
|
-
# --------------------------------------------------------------------- #
|
|
620
|
-
# Finalisation helpers
|
|
621
|
-
# --------------------------------------------------------------------- #
|
|
622
|
-
def finalize_renderer(
|
|
623
|
-
self,
|
|
624
|
-
renderer: RichStreamRenderer,
|
|
625
|
-
final_text: str,
|
|
626
|
-
stats_usage: dict[str, Any],
|
|
627
|
-
started_monotonic: float | None,
|
|
628
|
-
finished_monotonic: float | None,
|
|
629
|
-
) -> str:
|
|
630
|
-
"""Complete rendering and return the textual result."""
|
|
631
|
-
st = RunStats()
|
|
632
|
-
st.started_at = started_monotonic or st.started_at
|
|
633
|
-
st.finished_at = finished_monotonic or st.started_at
|
|
634
|
-
st.usage = stats_usage
|
|
635
|
-
|
|
636
|
-
rendered_text = ""
|
|
637
|
-
buffer_values: Any | None = None
|
|
638
|
-
|
|
639
|
-
if hasattr(renderer, "state") and hasattr(renderer.state, "buffer"):
|
|
640
|
-
buffer_values = renderer.state.buffer
|
|
641
|
-
elif hasattr(renderer, "buffer"):
|
|
642
|
-
buffer_values = renderer.buffer
|
|
643
|
-
|
|
644
|
-
if isinstance(buffer_values, TranscriptBuffer):
|
|
645
|
-
rendered_text = buffer_values.render()
|
|
646
|
-
elif buffer_values is not None:
|
|
647
|
-
try:
|
|
648
|
-
rendered_text = "".join(buffer_values)
|
|
649
|
-
except TypeError:
|
|
650
|
-
rendered_text = ""
|
|
651
|
-
|
|
652
|
-
fallback_text = final_text or rendered_text
|
|
653
|
-
if fallback_text:
|
|
654
|
-
self._ensure_renderer_final_content(renderer, fallback_text)
|
|
655
|
-
|
|
656
|
-
renderer.on_complete(st)
|
|
657
|
-
return final_text or rendered_text or NO_AGENT_RESPONSE_FALLBACK
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
def compute_timeout_seconds(kwargs: dict[str, Any]) -> float:
|
|
661
|
-
"""Determine the execution timeout for agent runs.
|
|
662
|
-
|
|
663
|
-
Args:
|
|
664
|
-
kwargs: Dictionary containing execution parameters, including timeout.
|
|
665
|
-
|
|
666
|
-
Returns:
|
|
667
|
-
The timeout value in seconds, defaulting to DEFAULT_AGENT_RUN_TIMEOUT
|
|
668
|
-
if not specified in kwargs.
|
|
669
|
-
"""
|
|
670
|
-
return kwargs.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
def finalize_render_manager(
|
|
674
|
-
manager: AgentRunRenderingManager,
|
|
675
|
-
renderer: RichStreamRenderer,
|
|
676
|
-
final_text: str,
|
|
677
|
-
stats_usage: dict[str, Any],
|
|
678
|
-
started_monotonic: float | None,
|
|
679
|
-
finished_monotonic: float | None,
|
|
680
|
-
) -> str:
|
|
681
|
-
"""Helper to finalize renderer via manager and return final text.
|
|
682
|
-
|
|
683
|
-
Args:
|
|
684
|
-
manager: The rendering manager instance.
|
|
685
|
-
renderer: Renderer to finalize.
|
|
686
|
-
final_text: Final text content.
|
|
687
|
-
stats_usage: Usage statistics dictionary.
|
|
688
|
-
started_monotonic: Start time (monotonic).
|
|
689
|
-
finished_monotonic: Finish time (monotonic).
|
|
690
|
-
|
|
691
|
-
Returns:
|
|
692
|
-
Final text string.
|
|
693
|
-
"""
|
|
694
|
-
return manager.finalize_renderer(
|
|
695
|
-
renderer,
|
|
696
|
-
final_text,
|
|
697
|
-
stats_usage,
|
|
698
|
-
started_monotonic,
|
|
699
|
-
finished_monotonic,
|
|
700
|
-
)
|