glaip-sdk 0.0.7__py3-none-any.whl → 0.6.5b6__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 (161) hide show
  1. glaip_sdk/__init__.py +6 -3
  2. glaip_sdk/_version.py +12 -5
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1126 -0
  5. glaip_sdk/branding.py +79 -15
  6. glaip_sdk/cli/account_store.py +540 -0
  7. glaip_sdk/cli/agent_config.py +2 -6
  8. glaip_sdk/cli/auth.py +699 -0
  9. glaip_sdk/cli/commands/__init__.py +2 -2
  10. glaip_sdk/cli/commands/accounts.py +746 -0
  11. glaip_sdk/cli/commands/agents.py +503 -183
  12. glaip_sdk/cli/commands/common_config.py +101 -0
  13. glaip_sdk/cli/commands/configure.py +774 -137
  14. glaip_sdk/cli/commands/mcps.py +1124 -181
  15. glaip_sdk/cli/commands/models.py +25 -10
  16. glaip_sdk/cli/commands/tools.py +144 -92
  17. glaip_sdk/cli/commands/transcripts.py +755 -0
  18. glaip_sdk/cli/commands/update.py +61 -0
  19. glaip_sdk/cli/config.py +95 -0
  20. glaip_sdk/cli/constants.py +38 -0
  21. glaip_sdk/cli/context.py +150 -0
  22. glaip_sdk/cli/core/__init__.py +79 -0
  23. glaip_sdk/cli/core/context.py +124 -0
  24. glaip_sdk/cli/core/output.py +846 -0
  25. glaip_sdk/cli/core/prompting.py +649 -0
  26. glaip_sdk/cli/core/rendering.py +187 -0
  27. glaip_sdk/cli/display.py +143 -53
  28. glaip_sdk/cli/hints.py +57 -0
  29. glaip_sdk/cli/io.py +24 -18
  30. glaip_sdk/cli/main.py +420 -145
  31. glaip_sdk/cli/masking.py +136 -0
  32. glaip_sdk/cli/mcp_validators.py +287 -0
  33. glaip_sdk/cli/pager.py +266 -0
  34. glaip_sdk/cli/parsers/__init__.py +7 -0
  35. glaip_sdk/cli/parsers/json_input.py +177 -0
  36. glaip_sdk/cli/resolution.py +28 -21
  37. glaip_sdk/cli/rich_helpers.py +27 -0
  38. glaip_sdk/cli/slash/__init__.py +15 -0
  39. glaip_sdk/cli/slash/accounts_controller.py +500 -0
  40. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  41. glaip_sdk/cli/slash/agent_session.py +282 -0
  42. glaip_sdk/cli/slash/prompt.py +245 -0
  43. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  44. glaip_sdk/cli/slash/session.py +1679 -0
  45. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  46. glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
  47. glaip_sdk/cli/slash/tui/accounts_app.py +872 -0
  48. glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
  49. glaip_sdk/cli/slash/tui/loading.py +58 -0
  50. glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
  51. glaip_sdk/cli/transcript/__init__.py +31 -0
  52. glaip_sdk/cli/transcript/cache.py +536 -0
  53. glaip_sdk/cli/transcript/capture.py +329 -0
  54. glaip_sdk/cli/transcript/export.py +38 -0
  55. glaip_sdk/cli/transcript/history.py +815 -0
  56. glaip_sdk/cli/transcript/launcher.py +77 -0
  57. glaip_sdk/cli/transcript/viewer.py +372 -0
  58. glaip_sdk/cli/update_notifier.py +290 -0
  59. glaip_sdk/cli/utils.py +247 -1238
  60. glaip_sdk/cli/validators.py +16 -18
  61. glaip_sdk/client/__init__.py +2 -1
  62. glaip_sdk/client/_agent_payloads.py +520 -0
  63. glaip_sdk/client/agent_runs.py +147 -0
  64. glaip_sdk/client/agents.py +940 -574
  65. glaip_sdk/client/base.py +163 -48
  66. glaip_sdk/client/main.py +35 -12
  67. glaip_sdk/client/mcps.py +126 -18
  68. glaip_sdk/client/run_rendering.py +415 -0
  69. glaip_sdk/client/shared.py +21 -0
  70. glaip_sdk/client/tools.py +195 -37
  71. glaip_sdk/client/validators.py +20 -48
  72. glaip_sdk/config/constants.py +15 -5
  73. glaip_sdk/exceptions.py +16 -9
  74. glaip_sdk/icons.py +25 -0
  75. glaip_sdk/mcps/__init__.py +21 -0
  76. glaip_sdk/mcps/base.py +345 -0
  77. glaip_sdk/models/__init__.py +90 -0
  78. glaip_sdk/models/agent.py +47 -0
  79. glaip_sdk/models/agent_runs.py +116 -0
  80. glaip_sdk/models/common.py +42 -0
  81. glaip_sdk/models/mcp.py +33 -0
  82. glaip_sdk/models/tool.py +33 -0
  83. glaip_sdk/payload_schemas/__init__.py +7 -0
  84. glaip_sdk/payload_schemas/agent.py +85 -0
  85. glaip_sdk/registry/__init__.py +55 -0
  86. glaip_sdk/registry/agent.py +164 -0
  87. glaip_sdk/registry/base.py +139 -0
  88. glaip_sdk/registry/mcp.py +253 -0
  89. glaip_sdk/registry/tool.py +231 -0
  90. glaip_sdk/rich_components.py +98 -2
  91. glaip_sdk/runner/__init__.py +59 -0
  92. glaip_sdk/runner/base.py +84 -0
  93. glaip_sdk/runner/deps.py +115 -0
  94. glaip_sdk/runner/langgraph.py +597 -0
  95. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  96. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  97. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +158 -0
  98. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  99. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  100. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  101. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +177 -0
  102. glaip_sdk/tools/__init__.py +22 -0
  103. glaip_sdk/tools/base.py +435 -0
  104. glaip_sdk/utils/__init__.py +59 -13
  105. glaip_sdk/utils/a2a/__init__.py +34 -0
  106. glaip_sdk/utils/a2a/event_processor.py +188 -0
  107. glaip_sdk/utils/agent_config.py +53 -40
  108. glaip_sdk/utils/bundler.py +267 -0
  109. glaip_sdk/utils/client.py +111 -0
  110. glaip_sdk/utils/client_utils.py +58 -26
  111. glaip_sdk/utils/datetime_helpers.py +58 -0
  112. glaip_sdk/utils/discovery.py +78 -0
  113. glaip_sdk/utils/display.py +65 -32
  114. glaip_sdk/utils/export.py +143 -0
  115. glaip_sdk/utils/general.py +1 -36
  116. glaip_sdk/utils/import_export.py +20 -25
  117. glaip_sdk/utils/import_resolver.py +492 -0
  118. glaip_sdk/utils/instructions.py +101 -0
  119. glaip_sdk/utils/rendering/__init__.py +115 -1
  120. glaip_sdk/utils/rendering/formatting.py +85 -43
  121. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  122. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +51 -19
  123. glaip_sdk/utils/rendering/layout/progress.py +202 -0
  124. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  125. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  126. glaip_sdk/utils/rendering/models.py +39 -7
  127. glaip_sdk/utils/rendering/renderer/__init__.py +9 -51
  128. glaip_sdk/utils/rendering/renderer/base.py +672 -759
  129. glaip_sdk/utils/rendering/renderer/config.py +4 -10
  130. glaip_sdk/utils/rendering/renderer/debug.py +75 -22
  131. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  132. glaip_sdk/utils/rendering/renderer/stream.py +13 -54
  133. glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
  134. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  135. glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
  136. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  137. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  138. glaip_sdk/utils/rendering/state.py +204 -0
  139. glaip_sdk/utils/rendering/step_tree_state.py +100 -0
  140. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  141. glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
  142. glaip_sdk/utils/rendering/steps/format.py +176 -0
  143. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  144. glaip_sdk/utils/rendering/timing.py +36 -0
  145. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  146. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  147. glaip_sdk/utils/resource_refs.py +29 -26
  148. glaip_sdk/utils/runtime_config.py +422 -0
  149. glaip_sdk/utils/serialization.py +184 -51
  150. glaip_sdk/utils/sync.py +142 -0
  151. glaip_sdk/utils/tool_detection.py +33 -0
  152. glaip_sdk/utils/validation.py +21 -30
  153. {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/METADATA +58 -12
  154. glaip_sdk-0.6.5b6.dist-info/RECORD +159 -0
  155. {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/WHEEL +1 -1
  156. glaip_sdk/models.py +0 -250
  157. glaip_sdk/utils/rendering/renderer/progress.py +0 -118
  158. glaip_sdk/utils/rendering/steps.py +0 -232
  159. glaip_sdk/utils/rich_utils.py +0 -29
  160. glaip_sdk-0.0.7.dist-info/RECORD +0 -55
  161. {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,415 @@
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 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
+ def _capture_request_id(
167
+ self,
168
+ stream_response: httpx.Response,
169
+ meta: dict[str, Any],
170
+ renderer: RichStreamRenderer,
171
+ ) -> None:
172
+ """Capture request ID from response headers and update metadata.
173
+
174
+ Args:
175
+ stream_response: HTTP response stream.
176
+ meta: Metadata dictionary to update.
177
+ renderer: Renderer instance.
178
+ """
179
+ req_id = stream_response.headers.get("x-request-id") or stream_response.headers.get("x-run-id")
180
+ if req_id:
181
+ meta["run_id"] = req_id
182
+ renderer.on_start(meta)
183
+
184
+ def _maybe_start_timer(self, event: dict[str, Any]) -> float | None:
185
+ """Start timing if this is a content-bearing event.
186
+
187
+ Args:
188
+ event: Event dictionary.
189
+
190
+ Returns:
191
+ Monotonic time if timer should start, None otherwise.
192
+ """
193
+ try:
194
+ ev = json.loads(event["data"])
195
+ except json.JSONDecodeError:
196
+ return None
197
+
198
+ if "content" in ev or "status" in ev or ev.get("metadata"):
199
+ return monotonic()
200
+ return None
201
+
202
+ def _process_single_event(
203
+ self,
204
+ event: dict[str, Any],
205
+ renderer: RichStreamRenderer,
206
+ final_text: str,
207
+ stats_usage: dict[str, Any],
208
+ meta: dict[str, Any],
209
+ ) -> tuple[str, dict[str, Any]]:
210
+ """Process a single streaming event.
211
+
212
+ Args:
213
+ event: Event dictionary.
214
+ renderer: Renderer instance.
215
+ final_text: Accumulated text so far.
216
+ stats_usage: Usage statistics dictionary.
217
+ meta: Metadata dictionary.
218
+
219
+ Returns:
220
+ Tuple of (updated_final_text, updated_stats_usage).
221
+ """
222
+ try:
223
+ ev = json.loads(event["data"])
224
+ except json.JSONDecodeError:
225
+ self._logger.debug("Non-JSON SSE fragment skipped")
226
+ return final_text, stats_usage
227
+
228
+ kind = (ev.get("metadata") or {}).get("kind")
229
+ renderer.on_event(ev)
230
+
231
+ handled = self._handle_metadata_kind(
232
+ kind,
233
+ ev,
234
+ final_text,
235
+ stats_usage,
236
+ meta,
237
+ renderer,
238
+ )
239
+ if handled is not None:
240
+ return handled
241
+
242
+ if ev.get("content"):
243
+ final_text = self._handle_content_event(ev, final_text)
244
+
245
+ return final_text, stats_usage
246
+
247
+ def _handle_metadata_kind(
248
+ self,
249
+ kind: str | None,
250
+ ev: dict[str, Any],
251
+ final_text: str,
252
+ stats_usage: dict[str, Any],
253
+ meta: dict[str, Any],
254
+ renderer: RichStreamRenderer,
255
+ ) -> tuple[str, dict[str, Any]] | None:
256
+ """Process well-known metadata kinds and return updated state."""
257
+ if kind == "artifact":
258
+ return final_text, stats_usage
259
+
260
+ if kind == "final_response":
261
+ content = ev.get("content")
262
+ if content:
263
+ return content, stats_usage
264
+ return final_text, stats_usage
265
+
266
+ if kind == "usage":
267
+ stats_usage.update(ev.get("usage") or {})
268
+ return final_text, stats_usage
269
+
270
+ if kind == "run_info":
271
+ self._handle_run_info_event(ev, meta, renderer)
272
+ return final_text, stats_usage
273
+
274
+ return None
275
+
276
+ def _handle_content_event(self, ev: dict[str, Any], final_text: str) -> str:
277
+ """Handle a content event and update final text.
278
+
279
+ Args:
280
+ ev: Event dictionary.
281
+ final_text: Current accumulated text.
282
+
283
+ Returns:
284
+ Updated final text.
285
+ """
286
+ content = ev.get("content", "")
287
+ if not content.startswith("Artifact received:"):
288
+ return content
289
+ return final_text
290
+
291
+ def _handle_run_info_event(
292
+ self,
293
+ ev: dict[str, Any],
294
+ meta: dict[str, Any],
295
+ renderer: RichStreamRenderer,
296
+ ) -> None:
297
+ """Handle a run_info event and update metadata.
298
+
299
+ Args:
300
+ ev: Event dictionary.
301
+ meta: Metadata dictionary to update.
302
+ renderer: Renderer instance.
303
+ """
304
+ if ev.get("model"):
305
+ meta["model"] = ev["model"]
306
+ renderer.on_start(meta)
307
+ if ev.get("run_id"):
308
+ meta["run_id"] = ev["run_id"]
309
+ renderer.on_start(meta)
310
+
311
+ def _ensure_renderer_final_content(self, renderer: RichStreamRenderer, text: str) -> None:
312
+ """Populate renderer state with final output when the stream omits it."""
313
+ if not text:
314
+ return
315
+
316
+ text_value = _coerce_to_string(text)
317
+ state = getattr(renderer, "state", None)
318
+ if state is None:
319
+ self._ensure_renderer_text(renderer, text_value)
320
+ return
321
+
322
+ self._ensure_state_final_text(state, text_value)
323
+ self._ensure_state_buffer(state, text_value)
324
+
325
+ def _ensure_renderer_text(self, renderer: RichStreamRenderer, text_value: str) -> None:
326
+ """Best-effort assignment for renderer.final_text."""
327
+ if not hasattr(renderer, "final_text"):
328
+ return
329
+ current_text = getattr(renderer, "final_text", "")
330
+ if _has_visible_text(current_text):
331
+ return
332
+ self._safe_set_attr(renderer, "final_text", text_value)
333
+
334
+ def _ensure_state_final_text(self, state: Any, text_value: str) -> None:
335
+ """Best-effort assignment for renderer.state.final_text."""
336
+ current_text = getattr(state, "final_text", "")
337
+ if _has_visible_text(current_text):
338
+ return
339
+ self._safe_set_attr(state, "final_text", text_value)
340
+
341
+ def _ensure_state_buffer(self, state: Any, text_value: str) -> None:
342
+ """Append fallback text to the state buffer when available."""
343
+ buffer = getattr(state, "buffer", None)
344
+ if not hasattr(buffer, "append"):
345
+ return
346
+ self._safe_append(buffer.append, text_value)
347
+
348
+ @staticmethod
349
+ def _safe_set_attr(target: Any, attr: str, value: str) -> None:
350
+ """Assign attribute while masking renderer-specific failures."""
351
+ try:
352
+ setattr(target, attr, value)
353
+ except Exception:
354
+ pass
355
+
356
+ @staticmethod
357
+ def _safe_append(appender: Callable[[str], Any], value: str) -> None:
358
+ """Invoke append-like functions without leaking renderer errors."""
359
+ try:
360
+ appender(value)
361
+ except Exception:
362
+ pass
363
+
364
+ # --------------------------------------------------------------------- #
365
+ # Finalisation helpers
366
+ # --------------------------------------------------------------------- #
367
+ def finalize_renderer(
368
+ self,
369
+ renderer: RichStreamRenderer,
370
+ final_text: str,
371
+ stats_usage: dict[str, Any],
372
+ started_monotonic: float | None,
373
+ finished_monotonic: float | None,
374
+ ) -> str:
375
+ """Complete rendering and return the textual result."""
376
+ st = RunStats()
377
+ st.started_at = started_monotonic or st.started_at
378
+ st.finished_at = finished_monotonic or st.started_at
379
+ st.usage = stats_usage
380
+
381
+ rendered_text = ""
382
+ buffer_values: Any | None = None
383
+
384
+ if hasattr(renderer, "state") and hasattr(renderer.state, "buffer"):
385
+ buffer_values = renderer.state.buffer
386
+ elif hasattr(renderer, "buffer"):
387
+ buffer_values = renderer.buffer
388
+
389
+ if isinstance(buffer_values, TranscriptBuffer):
390
+ rendered_text = buffer_values.render()
391
+ elif buffer_values is not None:
392
+ try:
393
+ rendered_text = "".join(buffer_values)
394
+ except TypeError:
395
+ rendered_text = ""
396
+
397
+ fallback_text = final_text or rendered_text
398
+ if fallback_text:
399
+ self._ensure_renderer_final_content(renderer, fallback_text)
400
+
401
+ renderer.on_complete(st)
402
+ return final_text or rendered_text or NO_AGENT_RESPONSE_FALLBACK
403
+
404
+
405
+ def compute_timeout_seconds(kwargs: dict[str, Any]) -> float:
406
+ """Determine the execution timeout for agent runs.
407
+
408
+ Args:
409
+ kwargs: Dictionary containing execution parameters, including timeout.
410
+
411
+ Returns:
412
+ The timeout value in seconds, defaulting to DEFAULT_AGENT_RUN_TIMEOUT
413
+ if not specified in kwargs.
414
+ """
415
+ return kwargs.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
@@ -0,0 +1,21 @@
1
+ """Shared helpers for client configuration wiring.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ from glaip_sdk.client.base import BaseClient
12
+
13
+
14
+ def build_shared_config(client: BaseClient) -> dict[str, Any]:
15
+ """Return the keyword arguments used to initialize sub-clients."""
16
+ return {
17
+ "parent_client": client,
18
+ "api_url": client.api_url,
19
+ "api_key": client.api_key,
20
+ "timeout": client._timeout,
21
+ }