glaip-sdk 0.2.2__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. glaip_sdk/cli/auth.py +2 -1
  2. glaip_sdk/cli/commands/agents.py +51 -36
  3. glaip_sdk/cli/commands/configure.py +2 -1
  4. glaip_sdk/cli/commands/mcps.py +219 -62
  5. glaip_sdk/cli/commands/models.py +3 -5
  6. glaip_sdk/cli/commands/tools.py +27 -16
  7. glaip_sdk/cli/commands/transcripts.py +1 -1
  8. glaip_sdk/cli/constants.py +3 -0
  9. glaip_sdk/cli/display.py +1 -1
  10. glaip_sdk/cli/hints.py +58 -0
  11. glaip_sdk/cli/io.py +6 -3
  12. glaip_sdk/cli/main.py +3 -4
  13. glaip_sdk/cli/slash/agent_session.py +4 -13
  14. glaip_sdk/cli/slash/prompt.py +3 -0
  15. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  16. glaip_sdk/cli/slash/session.py +139 -48
  17. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  18. glaip_sdk/cli/slash/tui/remote_runs_app.py +632 -0
  19. glaip_sdk/cli/transcript/capture.py +1 -1
  20. glaip_sdk/cli/transcript/viewer.py +19 -678
  21. glaip_sdk/cli/update_notifier.py +2 -1
  22. glaip_sdk/cli/utils.py +228 -101
  23. glaip_sdk/cli/validators.py +5 -6
  24. glaip_sdk/client/__init__.py +2 -1
  25. glaip_sdk/client/agent_runs.py +147 -0
  26. glaip_sdk/client/agents.py +40 -22
  27. glaip_sdk/client/main.py +2 -6
  28. glaip_sdk/client/mcps.py +13 -5
  29. glaip_sdk/client/run_rendering.py +90 -111
  30. glaip_sdk/client/shared.py +21 -0
  31. glaip_sdk/client/tools.py +2 -3
  32. glaip_sdk/config/constants.py +11 -0
  33. glaip_sdk/models/__init__.py +56 -0
  34. glaip_sdk/models/agent_runs.py +117 -0
  35. glaip_sdk/models.py +8 -7
  36. glaip_sdk/rich_components.py +58 -2
  37. glaip_sdk/utils/client_utils.py +13 -0
  38. glaip_sdk/utils/display.py +23 -15
  39. glaip_sdk/utils/export.py +143 -0
  40. glaip_sdk/utils/import_export.py +6 -9
  41. glaip_sdk/utils/rendering/__init__.py +115 -1
  42. glaip_sdk/utils/rendering/formatting.py +5 -30
  43. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  44. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
  45. glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
  46. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  47. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  48. glaip_sdk/utils/rendering/models.py +1 -0
  49. glaip_sdk/utils/rendering/renderer/__init__.py +10 -28
  50. glaip_sdk/utils/rendering/renderer/base.py +217 -1476
  51. glaip_sdk/utils/rendering/renderer/debug.py +24 -1
  52. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  53. glaip_sdk/utils/rendering/renderer/stream.py +4 -12
  54. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  55. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  56. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  57. glaip_sdk/utils/rendering/state.py +204 -0
  58. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  59. glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -439
  60. glaip_sdk/utils/rendering/steps/format.py +176 -0
  61. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  62. glaip_sdk/utils/rendering/timing.py +36 -0
  63. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  64. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  65. glaip_sdk/utils/resource_refs.py +26 -15
  66. glaip_sdk/utils/validation.py +13 -21
  67. {glaip_sdk-0.2.2.dist-info → glaip_sdk-0.4.0.dist-info}/METADATA +24 -2
  68. glaip_sdk-0.4.0.dist-info/RECORD +110 -0
  69. glaip_sdk-0.2.2.dist-info/RECORD +0 -87
  70. {glaip_sdk-0.2.2.dist-info → glaip_sdk-0.4.0.dist-info}/WHEEL +0 -0
  71. {glaip_sdk-0.2.2.dist-info → glaip_sdk-0.4.0.dist-info}/entry_points.txt +0 -0
@@ -5,9 +5,11 @@ Authors:
5
5
  Raymond Christopher (raymond.christopher@gdplabs.id)
6
6
  """
7
7
 
8
+ import asyncio
8
9
  import json
9
10
  import logging
10
11
  from collections.abc import AsyncGenerator, Callable, Iterator, Mapping
12
+ from contextlib import asynccontextmanager
11
13
  from os import PathLike
12
14
  from pathlib import Path
13
15
  from typing import Any, BinaryIO
@@ -20,14 +22,17 @@ from glaip_sdk.client._agent_payloads import (
20
22
  AgentListResult,
21
23
  AgentUpdateRequest,
22
24
  )
25
+ from glaip_sdk.client.agent_runs import AgentRunsClient
23
26
  from glaip_sdk.client.base import BaseClient
24
27
  from glaip_sdk.client.mcps import MCPClient
25
28
  from glaip_sdk.client.run_rendering import (
26
29
  AgentRunRenderingManager,
27
30
  compute_timeout_seconds,
28
31
  )
32
+ from glaip_sdk.client.shared import build_shared_config
29
33
  from glaip_sdk.client.tools import ToolClient
30
34
  from glaip_sdk.config.constants import (
35
+ AGENT_CONFIG_FIELDS,
31
36
  DEFAULT_AGENT_FRAMEWORK,
32
37
  DEFAULT_AGENT_RUN_TIMEOUT,
33
38
  DEFAULT_AGENT_TYPE,
@@ -67,6 +72,19 @@ _MERGED_SEQUENCE_FIELDS = ("tools", "agents", "mcps")
67
72
  _DEFAULT_METADATA_TYPE = "custom"
68
73
 
69
74
 
75
+ @asynccontextmanager
76
+ async def _async_timeout_guard(timeout_seconds: float | None) -> AsyncGenerator[None, None]:
77
+ """Apply an asyncio timeout when a custom timeout is provided."""
78
+ if timeout_seconds is None:
79
+ yield
80
+ return
81
+ try:
82
+ async with asyncio.timeout(timeout_seconds):
83
+ yield
84
+ except asyncio.TimeoutError as exc:
85
+ raise httpx.TimeoutException(f"Request timed out after {timeout_seconds}s") from exc
86
+
87
+
70
88
  def _normalise_sequence(value: Any) -> list[Any] | None:
71
89
  """Normalise optional sequence inputs to plain lists."""
72
90
  if value is None:
@@ -193,19 +211,7 @@ def _extract_original_refs(raw_definition: dict) -> dict[str, list]:
193
211
 
194
212
  def _build_cli_args(overrides_dict: dict) -> dict[str, Any]:
195
213
  """Build CLI args from overrides, filtering out None values."""
196
- cli_args = {
197
- key: overrides_dict.get(key)
198
- for key in (
199
- "name",
200
- "instruction",
201
- "model",
202
- "tools",
203
- "agents",
204
- "mcps",
205
- "timeout",
206
- )
207
- if overrides_dict.get(key) is not None
208
- }
214
+ cli_args = {key: overrides_dict.get(key) for key in AGENT_CONFIG_FIELDS if overrides_dict.get(key) is not None}
209
215
 
210
216
  # Normalize sequence fields
211
217
  for field in _MERGED_SEQUENCE_FIELDS:
@@ -254,6 +260,7 @@ class AgentClient(BaseClient):
254
260
  self._renderer_manager = AgentRunRenderingManager(logger)
255
261
  self._tool_client: ToolClient | None = None
256
262
  self._mcp_client: MCPClient | None = None
263
+ self._runs_client: "AgentRunsClient | None" = None
257
264
 
258
265
  def list_agents(
259
266
  self,
@@ -1172,7 +1179,7 @@ class AgentClient(BaseClient):
1172
1179
  message: str,
1173
1180
  files: list[str | BinaryIO] | None = None,
1174
1181
  *,
1175
- timeout: float | None = None,
1182
+ request_timeout: float | None = None,
1176
1183
  **kwargs,
1177
1184
  ) -> AsyncGenerator[dict, None]:
1178
1185
  """Async run an agent with a message, yielding streaming JSON chunks.
@@ -1181,7 +1188,7 @@ class AgentClient(BaseClient):
1181
1188
  agent_id: ID of the agent to run
1182
1189
  message: Message to send to the agent
1183
1190
  files: Optional list of files to include
1184
- timeout: Request timeout in seconds
1191
+ request_timeout: Optional request timeout in seconds (defaults to client timeout)
1185
1192
  **kwargs: Additional arguments (chat_history, pii_mapping, etc.)
1186
1193
 
1187
1194
  Yields:
@@ -1192,18 +1199,22 @@ class AgentClient(BaseClient):
1192
1199
  httpx.TimeoutException: When general timeout occurs
1193
1200
  Exception: For other unexpected errors
1194
1201
  """
1202
+ # Derive timeout values for request/control flow
1203
+ legacy_timeout = kwargs.get("timeout")
1204
+ http_timeout_override = request_timeout if request_timeout is not None else legacy_timeout
1205
+ http_timeout = http_timeout_override or self.timeout
1206
+
1195
1207
  # Prepare request data
1196
1208
  payload, data_payload, files_payload, headers = self._prepare_request_data(message, files, **kwargs)
1197
1209
 
1198
1210
  # Create async client configuration
1199
- async_client_config = self._create_async_client_config(timeout, headers)
1211
+ async_client_config = self._create_async_client_config(http_timeout_override, headers)
1200
1212
 
1201
1213
  # Get execution timeout for streaming control
1202
1214
  timeout_seconds = kwargs.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
1203
1215
  agent_name = kwargs.get("agent_name")
1204
1216
 
1205
- try:
1206
- # Create async client and stream response
1217
+ async def _chunk_stream() -> AsyncGenerator[dict, None]:
1207
1218
  async with httpx.AsyncClient(**async_client_config) as async_client:
1208
1219
  async for chunk in self._stream_agent_response(
1209
1220
  async_client,
@@ -1217,7 +1228,14 @@ class AgentClient(BaseClient):
1217
1228
  ):
1218
1229
  yield chunk
1219
1230
 
1220
- finally:
1221
- # Ensure cleanup - this is handled by the calling context
1222
- # but we keep this for safety in case of future changes
1223
- pass
1231
+ async with _async_timeout_guard(http_timeout):
1232
+ async for chunk in _chunk_stream():
1233
+ yield chunk
1234
+
1235
+ @property
1236
+ def runs(self) -> "AgentRunsClient":
1237
+ """Get the agent runs client."""
1238
+ if self._runs_client is None:
1239
+ shared_config = build_shared_config(self)
1240
+ self._runs_client = AgentRunsClient(**shared_config)
1241
+ return self._runs_client
glaip_sdk/client/main.py CHANGED
@@ -10,6 +10,7 @@ from typing import Any
10
10
  from glaip_sdk.client.agents import AgentClient
11
11
  from glaip_sdk.client.base import BaseClient
12
12
  from glaip_sdk.client.mcps import MCPClient
13
+ from glaip_sdk.client.shared import build_shared_config
13
14
  from glaip_sdk.client.tools import ToolClient
14
15
  from glaip_sdk.models import MCP, Agent, Tool
15
16
 
@@ -25,12 +26,7 @@ class Client(BaseClient):
25
26
  """
26
27
  super().__init__(**kwargs)
27
28
  # Share the single httpx.Client + config with sub-clients
28
- shared_config = {
29
- "parent_client": self,
30
- "api_url": self.api_url,
31
- "api_key": self.api_key,
32
- "timeout": self._timeout,
33
- }
29
+ shared_config = build_shared_config(self)
34
30
  self.agents = AgentClient(**shared_config)
35
31
  self.tools = ToolClient(**shared_config)
36
32
  self.mcps = MCPClient(**shared_config)
glaip_sdk/client/mcps.py CHANGED
@@ -14,7 +14,7 @@ from glaip_sdk.config.constants import (
14
14
  DEFAULT_MCP_TYPE,
15
15
  )
16
16
  from glaip_sdk.models import MCP
17
- from glaip_sdk.utils.client_utils import create_model_instances, find_by_name
17
+ from glaip_sdk.utils.client_utils import add_kwargs_to_payload, create_model_instances, find_by_name
18
18
 
19
19
  # API endpoints
20
20
  MCPS_ENDPOINT = "/mcps/"
@@ -147,9 +147,7 @@ class MCPClient(BaseClient):
147
147
 
148
148
  # Add any other kwargs (excluding already handled ones)
149
149
  excluded_keys = {"type"} # type is handled above
150
- for key, value in kwargs.items():
151
- if key not in excluded_keys:
152
- payload[key] = value
150
+ add_kwargs_to_payload(payload, kwargs, excluded_keys)
153
151
 
154
152
  return payload
155
153
 
@@ -206,7 +204,17 @@ class MCPClient(BaseClient):
206
204
  def get_mcp_tools(self, mcp_id: str) -> list[dict[str, Any]]:
207
205
  """Get tools available from an MCP."""
208
206
  data = self._request("GET", f"{MCPS_ENDPOINT}{mcp_id}/tools")
209
- return data or []
207
+ if data is None:
208
+ return []
209
+ if isinstance(data, list):
210
+ return data
211
+ if isinstance(data, dict):
212
+ if "tools" in data:
213
+ return data.get("tools", []) or []
214
+ logger.warning("Unexpected MCP tools response keys %s; returning empty list", list(data.keys()))
215
+ return []
216
+ logger.warning("Unexpected MCP tools response type %s; returning empty list", type(data).__name__)
217
+ return []
210
218
 
211
219
  def test_mcp_connection(self, config: dict[str, Any]) -> dict[str, Any]:
212
220
  """Test MCP connection using configuration.
@@ -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,17 @@ 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."
24
33
 
25
34
 
26
35
  def _coerce_to_string(value: Any) -> str:
@@ -36,41 +45,6 @@ def _has_visible_text(value: Any) -> bool:
36
45
  return isinstance(value, str) and bool(value.strip())
37
46
 
38
47
 
39
- def _update_state_transcript(state: Any, text_value: str) -> bool:
40
- """Inject transcript text into renderer state if possible."""
41
- if state is None:
42
- return False
43
-
44
- updated = False
45
-
46
- if hasattr(state, "final_text") and not _has_visible_text(getattr(state, "final_text", "")):
47
- try:
48
- state.final_text = text_value
49
- updated = True
50
- except Exception:
51
- pass
52
-
53
- buffer = getattr(state, "buffer", None)
54
- if isinstance(buffer, list) and not any(_has_visible_text(item) for item in buffer):
55
- buffer.append(text_value)
56
- updated = True
57
-
58
- return updated
59
-
60
-
61
- def _update_renderer_transcript(renderer: Any, text_value: str) -> None:
62
- """Populate the renderer (or its state) with the supplied text."""
63
- state = getattr(renderer, "state", None)
64
- if _update_state_transcript(state, text_value):
65
- return
66
-
67
- if hasattr(renderer, "final_text") and not _has_visible_text(getattr(renderer, "final_text", "")):
68
- try:
69
- renderer.final_text = text_value
70
- except Exception:
71
- pass
72
-
73
-
74
48
  class AgentRunRenderingManager:
75
49
  """Coordinate renderer creation and streaming event handling."""
76
50
 
@@ -81,6 +55,7 @@ class AgentRunRenderingManager:
81
55
  logger: Optional logger instance, creates default if None
82
56
  """
83
57
  self._logger = logger or logging.getLogger(__name__)
58
+ self._buffer_factory = TranscriptBuffer
84
59
 
85
60
  # --------------------------------------------------------------------- #
86
61
  # Renderer setup helpers
@@ -92,17 +67,38 @@ class AgentRunRenderingManager:
92
67
  verbose: bool = False,
93
68
  ) -> RichStreamRenderer:
94
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)
95
72
  if isinstance(renderer_spec, RichStreamRenderer):
96
73
  return renderer_spec
97
74
 
98
75
  if isinstance(renderer_spec, str):
99
- if renderer_spec == "silent":
100
- return self._create_silent_renderer()
101
- if renderer_spec == "minimal":
102
- return self._create_minimal_renderer()
103
- 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)
104
86
 
105
- return self._create_default_renderer(verbose)
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
106
102
 
107
103
  def build_initial_metadata(
108
104
  self,
@@ -123,70 +119,6 @@ class AgentRunRenderingManager:
123
119
  """Notify renderer that streaming is starting."""
124
120
  renderer.on_start(meta)
125
121
 
126
- def _create_silent_renderer(self) -> RichStreamRenderer:
127
- """Create a silent renderer that outputs to a string buffer.
128
-
129
- Returns:
130
- RichStreamRenderer configured for silent output.
131
- """
132
- silent_config = RendererConfig(
133
- live=False,
134
- persist_live=False,
135
- render_thinking=False,
136
- )
137
- return RichStreamRenderer(
138
- console=_Console(file=io.StringIO(), force_terminal=False),
139
- cfg=silent_config,
140
- verbose=False,
141
- )
142
-
143
- def _create_minimal_renderer(self) -> RichStreamRenderer:
144
- """Create a minimal renderer with reduced output.
145
-
146
- Returns:
147
- RichStreamRenderer configured for minimal output.
148
- """
149
- minimal_config = RendererConfig(
150
- live=False,
151
- persist_live=False,
152
- render_thinking=False,
153
- )
154
- return RichStreamRenderer(
155
- console=_Console(),
156
- cfg=minimal_config,
157
- verbose=False,
158
- )
159
-
160
- def _create_verbose_renderer(self) -> RichStreamRenderer:
161
- """Create a verbose renderer with detailed output.
162
-
163
- Returns:
164
- RichStreamRenderer configured for verbose output.
165
- """
166
- verbose_config = RendererConfig(
167
- live=False,
168
- append_finished_snapshots=False,
169
- )
170
- return RichStreamRenderer(
171
- console=_Console(),
172
- cfg=verbose_config,
173
- verbose=True,
174
- )
175
-
176
- def _create_default_renderer(self, verbose: bool) -> RichStreamRenderer:
177
- """Create the default renderer based on verbosity.
178
-
179
- Args:
180
- verbose: Whether to create a verbose renderer.
181
-
182
- Returns:
183
- RichStreamRenderer instance.
184
- """
185
- if verbose:
186
- return self._create_verbose_renderer()
187
- default_config = RendererConfig()
188
- return RichStreamRenderer(console=_Console(), cfg=default_config)
189
-
190
122
  # --------------------------------------------------------------------- #
191
123
  # Streaming event handling
192
124
  # --------------------------------------------------------------------- #
@@ -382,7 +314,52 @@ class AgentRunRenderingManager:
382
314
  return
383
315
 
384
316
  text_value = _coerce_to_string(text)
385
- _update_renderer_transcript(renderer, text_value)
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
386
363
 
387
364
  # --------------------------------------------------------------------- #
388
365
  # Finalisation helpers
@@ -409,7 +386,9 @@ class AgentRunRenderingManager:
409
386
  elif hasattr(renderer, "buffer"):
410
387
  buffer_values = renderer.buffer
411
388
 
412
- 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:
413
392
  try:
414
393
  rendered_text = "".join(buffer_values)
415
394
  except TypeError:
@@ -420,7 +399,7 @@ class AgentRunRenderingManager:
420
399
  self._ensure_renderer_final_content(renderer, fallback_text)
421
400
 
422
401
  renderer.on_complete(st)
423
- return final_text or rendered_text or "No response content received."
402
+ return final_text or rendered_text or NO_AGENT_RESPONSE_FALLBACK
424
403
 
425
404
 
426
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
+ }
glaip_sdk/client/tools.py CHANGED
@@ -18,6 +18,7 @@ from glaip_sdk.config.constants import (
18
18
  )
19
19
  from glaip_sdk.models import Tool
20
20
  from glaip_sdk.utils.client_utils import (
21
+ add_kwargs_to_payload,
21
22
  create_model_instances,
22
23
  find_by_name,
23
24
  )
@@ -200,9 +201,7 @@ class ToolClient(BaseClient):
200
201
 
201
202
  # Add any other kwargs (excluding already handled ones)
202
203
  excluded_keys = {"tags", "version"}
203
- for key, value in kwargs.items():
204
- if key not in excluded_keys:
205
- payload[key] = value
204
+ add_kwargs_to_payload(payload, kwargs, excluded_keys)
206
205
 
207
206
  return payload
208
207
 
@@ -39,3 +39,14 @@ DEFAULT_MCP_TRANSPORT = "stdio"
39
39
 
40
40
  # Default error messages
41
41
  DEFAULT_ERROR_MESSAGE = "Unknown error"
42
+
43
+ # Agent configuration fields used for CLI args and payload building
44
+ AGENT_CONFIG_FIELDS = (
45
+ "name",
46
+ "instruction",
47
+ "model",
48
+ "tools",
49
+ "agents",
50
+ "mcps",
51
+ "timeout",
52
+ )
@@ -0,0 +1,56 @@
1
+ """Models package for AIP SDK.
2
+
3
+ This package re-exports models from the legacy models.py file for backward compatibility.
4
+
5
+ Authors:
6
+ Raymond Christopher (raymond.christopher@gdplabs.id)
7
+ """
8
+
9
+ import importlib.util
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ # Export new agent runs models first (no dependencies on legacy models)
14
+ from glaip_sdk.models.agent_runs import ( # noqa: F401
15
+ RunOutputChunk,
16
+ RunSummary,
17
+ RunsPage,
18
+ RunWithOutput,
19
+ )
20
+
21
+ # Import from the parent models.py file
22
+ _parent_file = Path(__file__).parent.parent / "models.py"
23
+ if _parent_file.exists():
24
+ spec = importlib.util.spec_from_file_location("glaip_sdk.models_legacy", _parent_file)
25
+ models_legacy = importlib.util.module_from_spec(spec)
26
+ sys.modules["glaip_sdk.models_legacy"] = models_legacy
27
+ spec.loader.exec_module(models_legacy)
28
+
29
+ # Re-export all models
30
+ Agent = models_legacy.Agent
31
+ Tool = models_legacy.Tool
32
+ MCP = models_legacy.MCP
33
+ LanguageModelResponse = models_legacy.LanguageModelResponse
34
+ TTYRenderer = models_legacy.TTYRenderer
35
+ else:
36
+ # Fallback: try direct import (won't work if models/ exists)
37
+ # pragma: no cover - defensive fallback path, unlikely to execute
38
+ from glaip_sdk import models as models_legacy # type: ignore # pragma: no cover
39
+
40
+ Agent = models_legacy.Agent # pragma: no cover
41
+ Tool = models_legacy.Tool # pragma: no cover
42
+ MCP = models_legacy.MCP # pragma: no cover
43
+ LanguageModelResponse = models_legacy.LanguageModelResponse # pragma: no cover
44
+ TTYRenderer = models_legacy.TTYRenderer # pragma: no cover
45
+
46
+ __all__ = [
47
+ "Agent",
48
+ "Tool",
49
+ "MCP",
50
+ "LanguageModelResponse",
51
+ "TTYRenderer",
52
+ "RunSummary",
53
+ "RunsPage",
54
+ "RunWithOutput",
55
+ "RunOutputChunk",
56
+ ]