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.
Files changed (156) hide show
  1. glaip_sdk/__init__.py +42 -5
  2. {glaip_sdk-0.6.12.dist-info → glaip_sdk-0.6.14.dist-info}/METADATA +31 -37
  3. glaip_sdk-0.6.14.dist-info/RECORD +12 -0
  4. {glaip_sdk-0.6.12.dist-info → glaip_sdk-0.6.14.dist-info}/WHEEL +2 -1
  5. glaip_sdk-0.6.14.dist-info/entry_points.txt +2 -0
  6. glaip_sdk-0.6.14.dist-info/top_level.txt +1 -0
  7. glaip_sdk/agents/__init__.py +0 -27
  8. glaip_sdk/agents/base.py +0 -1191
  9. glaip_sdk/cli/__init__.py +0 -9
  10. glaip_sdk/cli/account_store.py +0 -540
  11. glaip_sdk/cli/agent_config.py +0 -78
  12. glaip_sdk/cli/auth.py +0 -699
  13. glaip_sdk/cli/commands/__init__.py +0 -5
  14. glaip_sdk/cli/commands/accounts.py +0 -746
  15. glaip_sdk/cli/commands/agents.py +0 -1509
  16. glaip_sdk/cli/commands/common_config.py +0 -101
  17. glaip_sdk/cli/commands/configure.py +0 -896
  18. glaip_sdk/cli/commands/mcps.py +0 -1356
  19. glaip_sdk/cli/commands/models.py +0 -69
  20. glaip_sdk/cli/commands/tools.py +0 -576
  21. glaip_sdk/cli/commands/transcripts.py +0 -755
  22. glaip_sdk/cli/commands/update.py +0 -61
  23. glaip_sdk/cli/config.py +0 -95
  24. glaip_sdk/cli/constants.py +0 -38
  25. glaip_sdk/cli/context.py +0 -150
  26. glaip_sdk/cli/core/__init__.py +0 -79
  27. glaip_sdk/cli/core/context.py +0 -124
  28. glaip_sdk/cli/core/output.py +0 -846
  29. glaip_sdk/cli/core/prompting.py +0 -649
  30. glaip_sdk/cli/core/rendering.py +0 -187
  31. glaip_sdk/cli/display.py +0 -355
  32. glaip_sdk/cli/hints.py +0 -57
  33. glaip_sdk/cli/io.py +0 -112
  34. glaip_sdk/cli/main.py +0 -604
  35. glaip_sdk/cli/masking.py +0 -136
  36. glaip_sdk/cli/mcp_validators.py +0 -287
  37. glaip_sdk/cli/pager.py +0 -266
  38. glaip_sdk/cli/parsers/__init__.py +0 -7
  39. glaip_sdk/cli/parsers/json_input.py +0 -177
  40. glaip_sdk/cli/resolution.py +0 -67
  41. glaip_sdk/cli/rich_helpers.py +0 -27
  42. glaip_sdk/cli/slash/__init__.py +0 -15
  43. glaip_sdk/cli/slash/accounts_controller.py +0 -578
  44. glaip_sdk/cli/slash/accounts_shared.py +0 -75
  45. glaip_sdk/cli/slash/agent_session.py +0 -285
  46. glaip_sdk/cli/slash/prompt.py +0 -256
  47. glaip_sdk/cli/slash/remote_runs_controller.py +0 -566
  48. glaip_sdk/cli/slash/session.py +0 -1708
  49. glaip_sdk/cli/slash/tui/__init__.py +0 -9
  50. glaip_sdk/cli/slash/tui/accounts_app.py +0 -876
  51. glaip_sdk/cli/slash/tui/background_tasks.py +0 -72
  52. glaip_sdk/cli/slash/tui/loading.py +0 -58
  53. glaip_sdk/cli/slash/tui/remote_runs_app.py +0 -628
  54. glaip_sdk/cli/transcript/__init__.py +0 -31
  55. glaip_sdk/cli/transcript/cache.py +0 -536
  56. glaip_sdk/cli/transcript/capture.py +0 -329
  57. glaip_sdk/cli/transcript/export.py +0 -38
  58. glaip_sdk/cli/transcript/history.py +0 -815
  59. glaip_sdk/cli/transcript/launcher.py +0 -77
  60. glaip_sdk/cli/transcript/viewer.py +0 -374
  61. glaip_sdk/cli/update_notifier.py +0 -290
  62. glaip_sdk/cli/utils.py +0 -263
  63. glaip_sdk/cli/validators.py +0 -238
  64. glaip_sdk/client/__init__.py +0 -11
  65. glaip_sdk/client/_agent_payloads.py +0 -520
  66. glaip_sdk/client/agent_runs.py +0 -147
  67. glaip_sdk/client/agents.py +0 -1335
  68. glaip_sdk/client/base.py +0 -502
  69. glaip_sdk/client/main.py +0 -249
  70. glaip_sdk/client/mcps.py +0 -370
  71. glaip_sdk/client/run_rendering.py +0 -700
  72. glaip_sdk/client/shared.py +0 -21
  73. glaip_sdk/client/tools.py +0 -661
  74. glaip_sdk/client/validators.py +0 -198
  75. glaip_sdk/config/constants.py +0 -52
  76. glaip_sdk/mcps/__init__.py +0 -21
  77. glaip_sdk/mcps/base.py +0 -345
  78. glaip_sdk/models/__init__.py +0 -90
  79. glaip_sdk/models/agent.py +0 -47
  80. glaip_sdk/models/agent_runs.py +0 -116
  81. glaip_sdk/models/common.py +0 -42
  82. glaip_sdk/models/mcp.py +0 -33
  83. glaip_sdk/models/tool.py +0 -33
  84. glaip_sdk/payload_schemas/__init__.py +0 -7
  85. glaip_sdk/payload_schemas/agent.py +0 -85
  86. glaip_sdk/registry/__init__.py +0 -55
  87. glaip_sdk/registry/agent.py +0 -164
  88. glaip_sdk/registry/base.py +0 -139
  89. glaip_sdk/registry/mcp.py +0 -253
  90. glaip_sdk/registry/tool.py +0 -232
  91. glaip_sdk/runner/__init__.py +0 -59
  92. glaip_sdk/runner/base.py +0 -84
  93. glaip_sdk/runner/deps.py +0 -115
  94. glaip_sdk/runner/langgraph.py +0 -782
  95. glaip_sdk/runner/mcp_adapter/__init__.py +0 -13
  96. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +0 -43
  97. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +0 -257
  98. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +0 -95
  99. glaip_sdk/runner/tool_adapter/__init__.py +0 -18
  100. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +0 -44
  101. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +0 -219
  102. glaip_sdk/tools/__init__.py +0 -22
  103. glaip_sdk/tools/base.py +0 -435
  104. glaip_sdk/utils/__init__.py +0 -86
  105. glaip_sdk/utils/a2a/__init__.py +0 -34
  106. glaip_sdk/utils/a2a/event_processor.py +0 -188
  107. glaip_sdk/utils/agent_config.py +0 -194
  108. glaip_sdk/utils/bundler.py +0 -267
  109. glaip_sdk/utils/client.py +0 -111
  110. glaip_sdk/utils/client_utils.py +0 -486
  111. glaip_sdk/utils/datetime_helpers.py +0 -58
  112. glaip_sdk/utils/discovery.py +0 -78
  113. glaip_sdk/utils/display.py +0 -135
  114. glaip_sdk/utils/export.py +0 -143
  115. glaip_sdk/utils/general.py +0 -61
  116. glaip_sdk/utils/import_export.py +0 -168
  117. glaip_sdk/utils/import_resolver.py +0 -492
  118. glaip_sdk/utils/instructions.py +0 -101
  119. glaip_sdk/utils/rendering/__init__.py +0 -115
  120. glaip_sdk/utils/rendering/formatting.py +0 -264
  121. glaip_sdk/utils/rendering/layout/__init__.py +0 -64
  122. glaip_sdk/utils/rendering/layout/panels.py +0 -156
  123. glaip_sdk/utils/rendering/layout/progress.py +0 -202
  124. glaip_sdk/utils/rendering/layout/summary.py +0 -74
  125. glaip_sdk/utils/rendering/layout/transcript.py +0 -606
  126. glaip_sdk/utils/rendering/models.py +0 -85
  127. glaip_sdk/utils/rendering/renderer/__init__.py +0 -55
  128. glaip_sdk/utils/rendering/renderer/base.py +0 -1024
  129. glaip_sdk/utils/rendering/renderer/config.py +0 -27
  130. glaip_sdk/utils/rendering/renderer/console.py +0 -55
  131. glaip_sdk/utils/rendering/renderer/debug.py +0 -178
  132. glaip_sdk/utils/rendering/renderer/factory.py +0 -138
  133. glaip_sdk/utils/rendering/renderer/stream.py +0 -202
  134. glaip_sdk/utils/rendering/renderer/summary_window.py +0 -79
  135. glaip_sdk/utils/rendering/renderer/thinking.py +0 -273
  136. glaip_sdk/utils/rendering/renderer/toggle.py +0 -182
  137. glaip_sdk/utils/rendering/renderer/tool_panels.py +0 -442
  138. glaip_sdk/utils/rendering/renderer/transcript_mode.py +0 -162
  139. glaip_sdk/utils/rendering/state.py +0 -204
  140. glaip_sdk/utils/rendering/step_tree_state.py +0 -100
  141. glaip_sdk/utils/rendering/steps/__init__.py +0 -34
  142. glaip_sdk/utils/rendering/steps/event_processor.py +0 -778
  143. glaip_sdk/utils/rendering/steps/format.py +0 -176
  144. glaip_sdk/utils/rendering/steps/manager.py +0 -387
  145. glaip_sdk/utils/rendering/timing.py +0 -36
  146. glaip_sdk/utils/rendering/viewer/__init__.py +0 -21
  147. glaip_sdk/utils/rendering/viewer/presenter.py +0 -184
  148. glaip_sdk/utils/resource_refs.py +0 -195
  149. glaip_sdk/utils/run_renderer.py +0 -41
  150. glaip_sdk/utils/runtime_config.py +0 -425
  151. glaip_sdk/utils/serialization.py +0 -424
  152. glaip_sdk/utils/sync.py +0 -142
  153. glaip_sdk/utils/tool_detection.py +0 -33
  154. glaip_sdk/utils/validation.py +0 -264
  155. glaip_sdk-0.6.12.dist-info/RECORD +0 -159
  156. 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
- )