glaip-sdk 0.0.20__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 (157) hide show
  1. glaip_sdk/__init__.py +5 -2
  2. glaip_sdk/_version.py +10 -3
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1126 -0
  5. glaip_sdk/branding.py +15 -6
  6. glaip_sdk/cli/account_store.py +540 -0
  7. glaip_sdk/cli/agent_config.py +2 -6
  8. glaip_sdk/cli/auth.py +265 -45
  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 +270 -173
  12. glaip_sdk/cli/commands/common_config.py +101 -0
  13. glaip_sdk/cli/commands/configure.py +735 -143
  14. glaip_sdk/cli/commands/mcps.py +265 -134
  15. glaip_sdk/cli/commands/models.py +13 -9
  16. glaip_sdk/cli/commands/tools.py +67 -88
  17. glaip_sdk/cli/commands/transcripts.py +755 -0
  18. glaip_sdk/cli/commands/update.py +3 -8
  19. glaip_sdk/cli/config.py +49 -7
  20. glaip_sdk/cli/constants.py +38 -0
  21. glaip_sdk/cli/context.py +8 -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 +45 -32
  28. glaip_sdk/cli/hints.py +57 -0
  29. glaip_sdk/cli/io.py +14 -17
  30. glaip_sdk/cli/main.py +232 -143
  31. glaip_sdk/cli/masking.py +21 -33
  32. glaip_sdk/cli/mcp_validators.py +5 -15
  33. glaip_sdk/cli/pager.py +12 -19
  34. glaip_sdk/cli/parsers/__init__.py +1 -3
  35. glaip_sdk/cli/parsers/json_input.py +11 -22
  36. glaip_sdk/cli/resolution.py +3 -9
  37. glaip_sdk/cli/rich_helpers.py +1 -3
  38. glaip_sdk/cli/slash/__init__.py +0 -9
  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 +61 -28
  42. glaip_sdk/cli/slash/prompt.py +13 -10
  43. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  44. glaip_sdk/cli/slash/session.py +772 -222
  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 +12 -52
  52. glaip_sdk/cli/transcript/cache.py +258 -60
  53. glaip_sdk/cli/transcript/capture.py +72 -21
  54. glaip_sdk/cli/transcript/history.py +815 -0
  55. glaip_sdk/cli/transcript/launcher.py +1 -3
  56. glaip_sdk/cli/transcript/viewer.py +77 -329
  57. glaip_sdk/cli/update_notifier.py +177 -24
  58. glaip_sdk/cli/utils.py +242 -1309
  59. glaip_sdk/cli/validators.py +16 -18
  60. glaip_sdk/client/__init__.py +2 -1
  61. glaip_sdk/client/_agent_payloads.py +53 -37
  62. glaip_sdk/client/agent_runs.py +147 -0
  63. glaip_sdk/client/agents.py +320 -92
  64. glaip_sdk/client/base.py +78 -35
  65. glaip_sdk/client/main.py +19 -10
  66. glaip_sdk/client/mcps.py +123 -15
  67. glaip_sdk/client/run_rendering.py +218 -78
  68. glaip_sdk/client/shared.py +21 -0
  69. glaip_sdk/client/tools.py +161 -34
  70. glaip_sdk/client/validators.py +20 -48
  71. glaip_sdk/config/constants.py +11 -0
  72. glaip_sdk/exceptions.py +1 -3
  73. glaip_sdk/icons.py +9 -3
  74. glaip_sdk/mcps/__init__.py +21 -0
  75. glaip_sdk/mcps/base.py +345 -0
  76. glaip_sdk/models/__init__.py +90 -0
  77. glaip_sdk/models/agent.py +47 -0
  78. glaip_sdk/models/agent_runs.py +116 -0
  79. glaip_sdk/models/common.py +42 -0
  80. glaip_sdk/models/mcp.py +33 -0
  81. glaip_sdk/models/tool.py +33 -0
  82. glaip_sdk/payload_schemas/__init__.py +1 -13
  83. glaip_sdk/payload_schemas/agent.py +1 -3
  84. glaip_sdk/registry/__init__.py +55 -0
  85. glaip_sdk/registry/agent.py +164 -0
  86. glaip_sdk/registry/base.py +139 -0
  87. glaip_sdk/registry/mcp.py +253 -0
  88. glaip_sdk/registry/tool.py +231 -0
  89. glaip_sdk/rich_components.py +58 -2
  90. glaip_sdk/runner/__init__.py +59 -0
  91. glaip_sdk/runner/base.py +84 -0
  92. glaip_sdk/runner/deps.py +115 -0
  93. glaip_sdk/runner/langgraph.py +597 -0
  94. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  95. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  96. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +158 -0
  97. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  98. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  99. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  100. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +177 -0
  101. glaip_sdk/tools/__init__.py +22 -0
  102. glaip_sdk/tools/base.py +435 -0
  103. glaip_sdk/utils/__init__.py +58 -12
  104. glaip_sdk/utils/a2a/__init__.py +34 -0
  105. glaip_sdk/utils/a2a/event_processor.py +188 -0
  106. glaip_sdk/utils/agent_config.py +4 -14
  107. glaip_sdk/utils/bundler.py +267 -0
  108. glaip_sdk/utils/client.py +111 -0
  109. glaip_sdk/utils/client_utils.py +46 -28
  110. glaip_sdk/utils/datetime_helpers.py +58 -0
  111. glaip_sdk/utils/discovery.py +78 -0
  112. glaip_sdk/utils/display.py +25 -21
  113. glaip_sdk/utils/export.py +143 -0
  114. glaip_sdk/utils/general.py +1 -36
  115. glaip_sdk/utils/import_export.py +15 -16
  116. glaip_sdk/utils/import_resolver.py +492 -0
  117. glaip_sdk/utils/instructions.py +101 -0
  118. glaip_sdk/utils/rendering/__init__.py +115 -1
  119. glaip_sdk/utils/rendering/formatting.py +38 -23
  120. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  121. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +10 -3
  122. glaip_sdk/utils/rendering/{renderer → layout}/progress.py +73 -12
  123. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  124. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  125. glaip_sdk/utils/rendering/models.py +18 -8
  126. glaip_sdk/utils/rendering/renderer/__init__.py +9 -51
  127. glaip_sdk/utils/rendering/renderer/base.py +476 -882
  128. glaip_sdk/utils/rendering/renderer/config.py +4 -10
  129. glaip_sdk/utils/rendering/renderer/debug.py +30 -34
  130. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  131. glaip_sdk/utils/rendering/renderer/stream.py +13 -54
  132. glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
  133. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  134. glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
  135. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  136. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  137. glaip_sdk/utils/rendering/state.py +204 -0
  138. glaip_sdk/utils/rendering/step_tree_state.py +100 -0
  139. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  140. glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
  141. glaip_sdk/utils/rendering/steps/format.py +176 -0
  142. glaip_sdk/utils/rendering/{steps.py → steps/manager.py} +122 -26
  143. glaip_sdk/utils/rendering/timing.py +36 -0
  144. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  145. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  146. glaip_sdk/utils/resource_refs.py +29 -26
  147. glaip_sdk/utils/runtime_config.py +422 -0
  148. glaip_sdk/utils/serialization.py +32 -46
  149. glaip_sdk/utils/sync.py +142 -0
  150. glaip_sdk/utils/tool_detection.py +33 -0
  151. glaip_sdk/utils/validation.py +20 -28
  152. {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.6.5b6.dist-info}/METADATA +49 -4
  153. glaip_sdk-0.6.5b6.dist-info/RECORD +159 -0
  154. {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.6.5b6.dist-info}/WHEEL +1 -1
  155. glaip_sdk/models.py +0 -259
  156. glaip_sdk-0.0.20.dist-info/RECORD +0 -80
  157. {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.6.5b6.dist-info}/entry_points.txt +0 -0
@@ -7,9 +7,9 @@ Authors:
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- import io
11
10
  import json
12
11
  import logging
12
+ from collections.abc import Callable
13
13
  from time import monotonic
14
14
  from typing import Any
15
15
 
@@ -19,8 +19,30 @@ from rich.console import Console as _Console
19
19
  from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT
20
20
  from glaip_sdk.utils.client_utils import iter_sse_events
21
21
  from glaip_sdk.utils.rendering.models import RunStats
22
- from glaip_sdk.utils.rendering.renderer import RichStreamRenderer
23
- from glaip_sdk.utils.rendering.renderer.config import RendererConfig
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())
24
46
 
25
47
 
26
48
  class AgentRunRenderingManager:
@@ -33,6 +55,7 @@ class AgentRunRenderingManager:
33
55
  logger: Optional logger instance, creates default if None
34
56
  """
35
57
  self._logger = logger or logging.getLogger(__name__)
58
+ self._buffer_factory = TranscriptBuffer
36
59
 
37
60
  # --------------------------------------------------------------------- #
38
61
  # Renderer setup helpers
@@ -44,17 +67,38 @@ class AgentRunRenderingManager:
44
67
  verbose: bool = False,
45
68
  ) -> RichStreamRenderer:
46
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)
47
72
  if isinstance(renderer_spec, RichStreamRenderer):
48
73
  return renderer_spec
49
74
 
50
75
  if isinstance(renderer_spec, str):
51
- if renderer_spec == "silent":
52
- return self._create_silent_renderer()
53
- if renderer_spec == "minimal":
54
- return self._create_minimal_renderer()
55
- return self._create_default_renderer(verbose)
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)
56
93
 
57
- return self._create_default_renderer(verbose)
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
58
102
 
59
103
  def build_initial_metadata(
60
104
  self,
@@ -75,52 +119,6 @@ class AgentRunRenderingManager:
75
119
  """Notify renderer that streaming is starting."""
76
120
  renderer.on_start(meta)
77
121
 
78
- def _create_silent_renderer(self) -> RichStreamRenderer:
79
- silent_config = RendererConfig(
80
- live=False,
81
- persist_live=False,
82
- show_delegate_tool_panels=False,
83
- render_thinking=False,
84
- )
85
- return RichStreamRenderer(
86
- console=_Console(file=io.StringIO(), force_terminal=False),
87
- cfg=silent_config,
88
- verbose=False,
89
- )
90
-
91
- def _create_minimal_renderer(self) -> RichStreamRenderer:
92
- minimal_config = RendererConfig(
93
- live=False,
94
- persist_live=False,
95
- show_delegate_tool_panels=False,
96
- render_thinking=False,
97
- )
98
- return RichStreamRenderer(
99
- console=_Console(),
100
- cfg=minimal_config,
101
- verbose=False,
102
- )
103
-
104
- def _create_verbose_renderer(self) -> RichStreamRenderer:
105
- verbose_config = RendererConfig(
106
- theme="dark",
107
- style="debug",
108
- live=False,
109
- show_delegate_tool_panels=False,
110
- append_finished_snapshots=False,
111
- )
112
- return RichStreamRenderer(
113
- console=_Console(),
114
- cfg=verbose_config,
115
- verbose=True,
116
- )
117
-
118
- def _create_default_renderer(self, verbose: bool) -> RichStreamRenderer:
119
- if verbose:
120
- return self._create_verbose_renderer()
121
- default_config = RendererConfig()
122
- return RichStreamRenderer(console=_Console(), cfg=default_config)
123
-
124
122
  # --------------------------------------------------------------------- #
125
123
  # Streaming event handling
126
124
  # --------------------------------------------------------------------- #
@@ -139,17 +137,28 @@ class AgentRunRenderingManager:
139
137
 
140
138
  self._capture_request_id(stream_response, meta, renderer)
141
139
 
142
- for event in iter_sse_events(stream_response, timeout_seconds, agent_name):
143
- if started_monotonic is None:
144
- started_monotonic = self._maybe_start_timer(event)
140
+ controller = getattr(renderer, "transcript_controller", None)
141
+ if controller and getattr(controller, "enabled", False):
142
+ controller.on_stream_start(renderer)
145
143
 
146
- final_text, stats_usage = self._process_single_event(
147
- event,
148
- renderer,
149
- final_text,
150
- stats_usage,
151
- meta,
152
- )
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()
153
162
 
154
163
  finished_monotonic = monotonic()
155
164
  return final_text, stats_usage, started_monotonic, finished_monotonic
@@ -160,14 +169,27 @@ class AgentRunRenderingManager:
160
169
  meta: dict[str, Any],
161
170
  renderer: RichStreamRenderer,
162
171
  ) -> None:
163
- req_id = stream_response.headers.get(
164
- "x-request-id"
165
- ) or stream_response.headers.get("x-run-id")
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")
166
180
  if req_id:
167
181
  meta["run_id"] = req_id
168
182
  renderer.on_start(meta)
169
183
 
170
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
+ """
171
193
  try:
172
194
  ev = json.loads(event["data"])
173
195
  except json.JSONDecodeError:
@@ -185,6 +207,18 @@ class AgentRunRenderingManager:
185
207
  stats_usage: dict[str, Any],
186
208
  meta: dict[str, Any],
187
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
+ """
188
222
  try:
189
223
  ev = json.loads(event["data"])
190
224
  except json.JSONDecodeError:
@@ -194,21 +228,61 @@ class AgentRunRenderingManager:
194
228
  kind = (ev.get("metadata") or {}).get("kind")
195
229
  renderer.on_event(ev)
196
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."""
197
257
  if kind == "artifact":
198
258
  return final_text, stats_usage
199
259
 
200
- if kind == "final_response" and ev.get("content"):
201
- final_text = ev.get("content", "")
202
- elif ev.get("content"):
203
- final_text = self._handle_content_event(ev, final_text)
204
- elif kind == "usage":
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":
205
267
  stats_usage.update(ev.get("usage") or {})
206
- elif kind == "run_info":
268
+ return final_text, stats_usage
269
+
270
+ if kind == "run_info":
207
271
  self._handle_run_info_event(ev, meta, renderer)
272
+ return final_text, stats_usage
208
273
 
209
- return final_text, stats_usage
274
+ return None
210
275
 
211
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
+ """
212
286
  content = ev.get("content", "")
213
287
  if not content.startswith("Artifact received:"):
214
288
  return content
@@ -220,6 +294,13 @@ class AgentRunRenderingManager:
220
294
  meta: dict[str, Any],
221
295
  renderer: RichStreamRenderer,
222
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
+ """
223
304
  if ev.get("model"):
224
305
  meta["model"] = ev["model"]
225
306
  renderer.on_start(meta)
@@ -227,6 +308,59 @@ class AgentRunRenderingManager:
227
308
  meta["run_id"] = ev["run_id"]
228
309
  renderer.on_start(meta)
229
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
+
230
364
  # --------------------------------------------------------------------- #
231
365
  # Finalisation helpers
232
366
  # --------------------------------------------------------------------- #
@@ -250,16 +384,22 @@ class AgentRunRenderingManager:
250
384
  if hasattr(renderer, "state") and hasattr(renderer.state, "buffer"):
251
385
  buffer_values = renderer.state.buffer
252
386
  elif hasattr(renderer, "buffer"):
253
- buffer_values = getattr(renderer, "buffer")
387
+ buffer_values = renderer.buffer
254
388
 
255
- if buffer_values is not None:
389
+ if isinstance(buffer_values, TranscriptBuffer):
390
+ rendered_text = buffer_values.render()
391
+ elif buffer_values is not None:
256
392
  try:
257
393
  rendered_text = "".join(buffer_values)
258
394
  except TypeError:
259
395
  rendered_text = ""
260
396
 
397
+ fallback_text = final_text or rendered_text
398
+ if fallback_text:
399
+ self._ensure_renderer_final_content(renderer, fallback_text)
400
+
261
401
  renderer.on_complete(st)
262
- return final_text or rendered_text or "No response content received."
402
+ return final_text or rendered_text or NO_AGENT_RESPONSE_FALLBACK
263
403
 
264
404
 
265
405
  def compute_timeout_seconds(kwargs: dict[str, Any]) -> float:
@@ -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
+ }