glaip-sdk 0.0.20__py3-none-any.whl → 0.1.3__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 (66) hide show
  1. glaip_sdk/_version.py +1 -3
  2. glaip_sdk/branding.py +2 -6
  3. glaip_sdk/cli/agent_config.py +2 -6
  4. glaip_sdk/cli/auth.py +11 -30
  5. glaip_sdk/cli/commands/agents.py +64 -107
  6. glaip_sdk/cli/commands/configure.py +12 -36
  7. glaip_sdk/cli/commands/mcps.py +25 -63
  8. glaip_sdk/cli/commands/models.py +2 -4
  9. glaip_sdk/cli/commands/tools.py +22 -35
  10. glaip_sdk/cli/commands/update.py +3 -8
  11. glaip_sdk/cli/config.py +1 -3
  12. glaip_sdk/cli/display.py +10 -13
  13. glaip_sdk/cli/io.py +8 -14
  14. glaip_sdk/cli/main.py +10 -30
  15. glaip_sdk/cli/mcp_validators.py +5 -15
  16. glaip_sdk/cli/pager.py +3 -9
  17. glaip_sdk/cli/parsers/json_input.py +11 -22
  18. glaip_sdk/cli/resolution.py +3 -9
  19. glaip_sdk/cli/rich_helpers.py +1 -3
  20. glaip_sdk/cli/slash/agent_session.py +5 -10
  21. glaip_sdk/cli/slash/prompt.py +3 -10
  22. glaip_sdk/cli/slash/session.py +46 -98
  23. glaip_sdk/cli/transcript/cache.py +6 -19
  24. glaip_sdk/cli/transcript/capture.py +45 -20
  25. glaip_sdk/cli/transcript/launcher.py +1 -3
  26. glaip_sdk/cli/transcript/viewer.py +224 -47
  27. glaip_sdk/cli/update_notifier.py +165 -21
  28. glaip_sdk/cli/utils.py +33 -91
  29. glaip_sdk/cli/validators.py +11 -12
  30. glaip_sdk/client/_agent_payloads.py +10 -30
  31. glaip_sdk/client/agents.py +33 -63
  32. glaip_sdk/client/base.py +77 -35
  33. glaip_sdk/client/mcps.py +1 -3
  34. glaip_sdk/client/run_rendering.py +121 -26
  35. glaip_sdk/client/tools.py +8 -24
  36. glaip_sdk/client/validators.py +20 -48
  37. glaip_sdk/exceptions.py +1 -3
  38. glaip_sdk/icons.py +9 -3
  39. glaip_sdk/models.py +14 -33
  40. glaip_sdk/payload_schemas/agent.py +1 -3
  41. glaip_sdk/utils/agent_config.py +4 -14
  42. glaip_sdk/utils/client_utils.py +7 -21
  43. glaip_sdk/utils/display.py +2 -6
  44. glaip_sdk/utils/general.py +1 -3
  45. glaip_sdk/utils/import_export.py +3 -9
  46. glaip_sdk/utils/rendering/formatting.py +52 -12
  47. glaip_sdk/utils/rendering/models.py +17 -8
  48. glaip_sdk/utils/rendering/renderer/__init__.py +1 -5
  49. glaip_sdk/utils/rendering/renderer/base.py +1181 -328
  50. glaip_sdk/utils/rendering/renderer/config.py +4 -10
  51. glaip_sdk/utils/rendering/renderer/debug.py +4 -14
  52. glaip_sdk/utils/rendering/renderer/panels.py +1 -3
  53. glaip_sdk/utils/rendering/renderer/progress.py +3 -11
  54. glaip_sdk/utils/rendering/renderer/stream.py +9 -42
  55. glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
  56. glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
  57. glaip_sdk/utils/rendering/step_tree_state.py +100 -0
  58. glaip_sdk/utils/rendering/steps.py +899 -25
  59. glaip_sdk/utils/resource_refs.py +4 -13
  60. glaip_sdk/utils/serialization.py +14 -46
  61. glaip_sdk/utils/validation.py +4 -4
  62. {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.1.3.dist-info}/METADATA +12 -1
  63. glaip_sdk-0.1.3.dist-info/RECORD +83 -0
  64. glaip_sdk-0.0.20.dist-info/RECORD +0 -80
  65. {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.1.3.dist-info}/WHEEL +0 -0
  66. {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.1.3.dist-info}/entry_points.txt +0 -0
@@ -23,6 +23,54 @@ from glaip_sdk.utils.rendering.renderer import RichStreamRenderer
23
23
  from glaip_sdk.utils.rendering.renderer.config import RendererConfig
24
24
 
25
25
 
26
+ def _coerce_to_string(value: Any) -> str:
27
+ """Return a best-effort string representation for transcripts."""
28
+ try:
29
+ return str(value)
30
+ except Exception:
31
+ return f"{value}"
32
+
33
+
34
+ def _has_visible_text(value: Any) -> bool:
35
+ """Return True when the value is a non-empty string."""
36
+ return isinstance(value, str) and bool(value.strip())
37
+
38
+
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
+
26
74
  class AgentRunRenderingManager:
27
75
  """Coordinate renderer creation and streaming event handling."""
28
76
 
@@ -79,7 +127,6 @@ class AgentRunRenderingManager:
79
127
  silent_config = RendererConfig(
80
128
  live=False,
81
129
  persist_live=False,
82
- show_delegate_tool_panels=False,
83
130
  render_thinking=False,
84
131
  )
85
132
  return RichStreamRenderer(
@@ -92,7 +139,6 @@ class AgentRunRenderingManager:
92
139
  minimal_config = RendererConfig(
93
140
  live=False,
94
141
  persist_live=False,
95
- show_delegate_tool_panels=False,
96
142
  render_thinking=False,
97
143
  )
98
144
  return RichStreamRenderer(
@@ -103,10 +149,7 @@ class AgentRunRenderingManager:
103
149
 
104
150
  def _create_verbose_renderer(self) -> RichStreamRenderer:
105
151
  verbose_config = RendererConfig(
106
- theme="dark",
107
- style="debug",
108
152
  live=False,
109
- show_delegate_tool_panels=False,
110
153
  append_finished_snapshots=False,
111
154
  )
112
155
  return RichStreamRenderer(
@@ -139,17 +182,28 @@ class AgentRunRenderingManager:
139
182
 
140
183
  self._capture_request_id(stream_response, meta, renderer)
141
184
 
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)
185
+ controller = getattr(renderer, "transcript_controller", None)
186
+ if controller and getattr(controller, "enabled", False):
187
+ controller.on_stream_start(renderer)
145
188
 
146
- final_text, stats_usage = self._process_single_event(
147
- event,
148
- renderer,
149
- final_text,
150
- stats_usage,
151
- meta,
152
- )
189
+ try:
190
+ for event in iter_sse_events(stream_response, timeout_seconds, agent_name):
191
+ if started_monotonic is None:
192
+ started_monotonic = self._maybe_start_timer(event)
193
+
194
+ final_text, stats_usage = self._process_single_event(
195
+ event,
196
+ renderer,
197
+ final_text,
198
+ stats_usage,
199
+ meta,
200
+ )
201
+
202
+ if controller and getattr(controller, "enabled", False):
203
+ controller.poll(renderer)
204
+ finally:
205
+ if controller and getattr(controller, "enabled", False):
206
+ controller.on_stream_complete()
153
207
 
154
208
  finished_monotonic = monotonic()
155
209
  return final_text, stats_usage, started_monotonic, finished_monotonic
@@ -160,9 +214,7 @@ class AgentRunRenderingManager:
160
214
  meta: dict[str, Any],
161
215
  renderer: RichStreamRenderer,
162
216
  ) -> None:
163
- req_id = stream_response.headers.get(
164
- "x-request-id"
165
- ) or stream_response.headers.get("x-run-id")
217
+ req_id = stream_response.headers.get("x-request-id") or stream_response.headers.get("x-run-id")
166
218
  if req_id:
167
219
  meta["run_id"] = req_id
168
220
  renderer.on_start(meta)
@@ -194,19 +246,50 @@ class AgentRunRenderingManager:
194
246
  kind = (ev.get("metadata") or {}).get("kind")
195
247
  renderer.on_event(ev)
196
248
 
249
+ handled = self._handle_metadata_kind(
250
+ kind,
251
+ ev,
252
+ final_text,
253
+ stats_usage,
254
+ meta,
255
+ renderer,
256
+ )
257
+ if handled is not None:
258
+ return handled
259
+
260
+ if ev.get("content"):
261
+ final_text = self._handle_content_event(ev, final_text)
262
+
263
+ return final_text, stats_usage
264
+
265
+ def _handle_metadata_kind(
266
+ self,
267
+ kind: str | None,
268
+ ev: dict[str, Any],
269
+ final_text: str,
270
+ stats_usage: dict[str, Any],
271
+ meta: dict[str, Any],
272
+ renderer: RichStreamRenderer,
273
+ ) -> tuple[str, dict[str, Any]] | None:
274
+ """Process well-known metadata kinds and return updated state."""
197
275
  if kind == "artifact":
198
276
  return final_text, stats_usage
199
277
 
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":
278
+ if kind == "final_response":
279
+ content = ev.get("content")
280
+ if content:
281
+ return content, stats_usage
282
+ return final_text, stats_usage
283
+
284
+ if kind == "usage":
205
285
  stats_usage.update(ev.get("usage") or {})
206
- elif kind == "run_info":
286
+ return final_text, stats_usage
287
+
288
+ if kind == "run_info":
207
289
  self._handle_run_info_event(ev, meta, renderer)
290
+ return final_text, stats_usage
208
291
 
209
- return final_text, stats_usage
292
+ return None
210
293
 
211
294
  def _handle_content_event(self, ev: dict[str, Any], final_text: str) -> str:
212
295
  content = ev.get("content", "")
@@ -227,6 +310,14 @@ class AgentRunRenderingManager:
227
310
  meta["run_id"] = ev["run_id"]
228
311
  renderer.on_start(meta)
229
312
 
313
+ def _ensure_renderer_final_content(self, renderer: RichStreamRenderer, text: str) -> None:
314
+ """Populate renderer state with final output when the stream omits it."""
315
+ if not text:
316
+ return
317
+
318
+ text_value = _coerce_to_string(text)
319
+ _update_renderer_transcript(renderer, text_value)
320
+
230
321
  # --------------------------------------------------------------------- #
231
322
  # Finalisation helpers
232
323
  # --------------------------------------------------------------------- #
@@ -250,7 +341,7 @@ class AgentRunRenderingManager:
250
341
  if hasattr(renderer, "state") and hasattr(renderer.state, "buffer"):
251
342
  buffer_values = renderer.state.buffer
252
343
  elif hasattr(renderer, "buffer"):
253
- buffer_values = getattr(renderer, "buffer")
344
+ buffer_values = renderer.buffer
254
345
 
255
346
  if buffer_values is not None:
256
347
  try:
@@ -258,6 +349,10 @@ class AgentRunRenderingManager:
258
349
  except TypeError:
259
350
  rendered_text = ""
260
351
 
352
+ fallback_text = final_text or rendered_text
353
+ if fallback_text:
354
+ self._ensure_renderer_final_content(renderer, fallback_text)
355
+
261
356
  renderer.on_complete(st)
262
357
  return final_text or rendered_text or "No response content received."
263
358
 
glaip_sdk/client/tools.py CHANGED
@@ -96,9 +96,7 @@ class ToolClient(BaseClient):
96
96
  """
97
97
  return os.path.splitext(os.path.basename(file_path))[0]
98
98
 
99
- def _prepare_upload_data(
100
- self, name: str, framework: str, description: str | None = None, **kwargs
101
- ) -> dict:
99
+ def _prepare_upload_data(self, name: str, framework: str, description: str | None = None, **kwargs) -> dict:
102
100
  """Prepare upload data dictionary.
103
101
 
104
102
  Args:
@@ -217,29 +215,21 @@ class ToolClient(BaseClient):
217
215
  elif hasattr(current_tool, "description") and current_tool.description:
218
216
  update_data["description"] = current_tool.description
219
217
 
220
- def _handle_tags_update(
221
- self, update_data: dict[str, Any], kwargs: dict[str, Any], current_tool: Tool
222
- ) -> None:
218
+ def _handle_tags_update(self, update_data: dict[str, Any], kwargs: dict[str, Any], current_tool: Tool) -> None:
223
219
  """Handle tags field in update payload."""
224
220
  if kwargs.get("tags"):
225
221
  if isinstance(kwargs["tags"], list):
226
- update_data["tags"] = ",".join(
227
- str(tag).strip() for tag in kwargs["tags"]
228
- )
222
+ update_data["tags"] = ",".join(str(tag).strip() for tag in kwargs["tags"])
229
223
  else:
230
224
  update_data["tags"] = str(kwargs["tags"])
231
225
  elif hasattr(current_tool, "tags") and current_tool.tags:
232
226
  # Preserve existing tags if present
233
227
  if isinstance(current_tool.tags, list):
234
- update_data["tags"] = ",".join(
235
- str(tag).strip() for tag in current_tool.tags
236
- )
228
+ update_data["tags"] = ",".join(str(tag).strip() for tag in current_tool.tags)
237
229
  else:
238
230
  update_data["tags"] = str(current_tool.tags)
239
231
 
240
- def _handle_additional_kwargs(
241
- self, update_data: dict[str, Any], kwargs: dict[str, Any]
242
- ) -> None:
232
+ def _handle_additional_kwargs(self, update_data: dict[str, Any], kwargs: dict[str, Any]) -> None:
243
233
  """Handle additional kwargs in update payload."""
244
234
  excluded_keys = {
245
235
  "tags",
@@ -290,12 +280,8 @@ class ToolClient(BaseClient):
290
280
  update_data = {
291
281
  "name": name if name is not None else current_tool.name,
292
282
  "type": current_type,
293
- "framework": kwargs.get(
294
- "framework", getattr(current_tool, "framework", DEFAULT_TOOL_FRAMEWORK)
295
- ),
296
- "version": kwargs.get(
297
- "version", getattr(current_tool, "version", DEFAULT_TOOL_VERSION)
298
- ),
283
+ "framework": kwargs.get("framework", getattr(current_tool, "framework", DEFAULT_TOOL_FRAMEWORK)),
284
+ "version": kwargs.get("version", getattr(current_tool, "version", DEFAULT_TOOL_VERSION)),
299
285
  }
300
286
 
301
287
  # Handle description update
@@ -355,9 +341,7 @@ class ToolClient(BaseClient):
355
341
 
356
342
  try:
357
343
  # Prepare upload data
358
- upload_data = self._prepare_upload_data(
359
- name=name, framework=framework, description=description, **kwargs
360
- )
344
+ upload_data = self._prepare_upload_data(name=name, framework=framework, description=description, **kwargs)
361
345
 
362
346
  # Upload file
363
347
  return self._upload_tool_file(temp_file_path, upload_data)
@@ -39,9 +39,7 @@ class ResourceValidator:
39
39
  if len(found_tools) == 1:
40
40
  return str(found_tools[0].id)
41
41
  elif len(found_tools) > 1:
42
- raise AmbiguousResourceError(
43
- f"Multiple tools found with name '{tool_name}': {[t.id for t in found_tools]}"
44
- )
42
+ raise AmbiguousResourceError(f"Multiple tools found with name '{tool_name}': {[t.id for t in found_tools]}")
45
43
  else:
46
44
  raise NotFoundError(f"Tool not found: {tool_name}")
47
45
 
@@ -51,9 +49,7 @@ class ResourceValidator:
51
49
  if len(found_tools) == 1:
52
50
  return str(found_tools[0].id)
53
51
  elif len(found_tools) > 1:
54
- raise AmbiguousResourceError(
55
- f"Multiple tools found with name '{tool.name}': {[t.id for t in found_tools]}"
56
- )
52
+ raise AmbiguousResourceError(f"Multiple tools found with name '{tool.name}': {[t.id for t in found_tools]}")
57
53
  else:
58
54
  raise NotFoundError(f"Tool not found: {tool.name}")
59
55
 
@@ -73,9 +69,7 @@ class ResourceValidator:
73
69
  elif hasattr(tool, "name") and tool.name is not None:
74
70
  return self._resolve_tool_by_name_attribute(tool, client)
75
71
  else:
76
- raise ValidationError(
77
- f"Invalid tool reference: {tool} - must have 'id' or 'name' attribute"
78
- )
72
+ raise ValidationError(f"Invalid tool reference: {tool} - must have 'id' or 'name' attribute")
79
73
 
80
74
  def _process_single_tool(self, tool: str | Tool, client: Any) -> str:
81
75
  """Process a single tool reference and return its ID."""
@@ -99,22 +93,14 @@ class ResourceValidator:
99
93
  try:
100
94
  tool_id = cls()._process_single_tool(tool, client)
101
95
  tool_ids.append(tool_id)
102
- except (AmbiguousResourceError, NotFoundError) as e:
96
+ except (AmbiguousResourceError, NotFoundError) as err:
103
97
  # Determine the tool name for the error message
104
- tool_name = (
105
- tool if isinstance(tool, str) else getattr(tool, "name", str(tool))
106
- )
107
- raise ValidationError(
108
- f"Failed to resolve tool name '{tool_name}' to ID: {e}"
109
- )
110
- except Exception as e:
98
+ tool_name = tool if isinstance(tool, str) else getattr(tool, "name", str(tool))
99
+ raise ValidationError(f"Failed to resolve tool name '{tool_name}' to ID: {err}") from err
100
+ except Exception as err:
111
101
  # For other exceptions, wrap them appropriately
112
- tool_name = (
113
- tool if isinstance(tool, str) else getattr(tool, "name", str(tool))
114
- )
115
- raise ValidationError(
116
- f"Failed to resolve tool name '{tool_name}' to ID: {e}"
117
- )
102
+ tool_name = tool if isinstance(tool, str) else getattr(tool, "name", str(tool))
103
+ raise ValidationError(f"Failed to resolve tool name '{tool_name}' to ID: {err}") from err
118
104
 
119
105
  return tool_ids
120
106
 
@@ -158,9 +144,7 @@ class ResourceValidator:
158
144
  elif hasattr(agent, "name") and agent.name is not None:
159
145
  return self._resolve_agent_by_name_attribute(agent, client)
160
146
  else:
161
- raise ValidationError(
162
- f"Invalid agent reference: {agent} - must have 'id' or 'name' attribute"
163
- )
147
+ raise ValidationError(f"Invalid agent reference: {agent} - must have 'id' or 'name' attribute")
164
148
 
165
149
  def _process_single_agent(self, agent: str | Any, client: Any) -> str:
166
150
  """Process a single agent reference and return its ID."""
@@ -184,26 +168,14 @@ class ResourceValidator:
184
168
  try:
185
169
  agent_id = cls()._process_single_agent(agent, client)
186
170
  agent_ids.append(agent_id)
187
- except (AmbiguousResourceError, NotFoundError) as e:
171
+ except (AmbiguousResourceError, NotFoundError) as err:
188
172
  # Determine the agent name for the error message
189
- agent_name = (
190
- agent
191
- if isinstance(agent, str)
192
- else getattr(agent, "name", str(agent))
193
- )
194
- raise ValidationError(
195
- f"Failed to resolve agent name '{agent_name}' to ID: {e}"
196
- )
197
- except Exception as e:
173
+ agent_name = agent if isinstance(agent, str) else getattr(agent, "name", str(agent))
174
+ raise ValidationError(f"Failed to resolve agent name '{agent_name}' to ID: {err}") from err
175
+ except Exception as err:
198
176
  # For other exceptions, wrap them appropriately
199
- agent_name = (
200
- agent
201
- if isinstance(agent, str)
202
- else getattr(agent, "name", str(agent))
203
- )
204
- raise ValidationError(
205
- f"Failed to resolve agent name '{agent_name}' to ID: {e}"
206
- )
177
+ agent_name = agent if isinstance(agent, str) else getattr(agent, "name", str(agent))
178
+ raise ValidationError(f"Failed to resolve agent name '{agent_name}' to ID: {err}") from err
207
179
 
208
180
  return agent_ids
209
181
 
@@ -213,8 +185,8 @@ class ResourceValidator:
213
185
  for tool_id in tool_ids:
214
186
  try:
215
187
  client.get_tool_by_id(tool_id)
216
- except NotFoundError:
217
- raise ValidationError(f"Tool not found: {tool_id}")
188
+ except NotFoundError as err:
189
+ raise ValidationError(f"Tool not found: {tool_id}") from err
218
190
 
219
191
  @classmethod
220
192
  def validate_agents_exist(cls, agent_ids: list[str], client: Any) -> None:
@@ -222,5 +194,5 @@ class ResourceValidator:
222
194
  for agent_id in agent_ids:
223
195
  try:
224
196
  client.get_agent_by_id(agent_id)
225
- except NotFoundError:
226
- raise ValidationError(f"Agent not found: {agent_id}")
197
+ except NotFoundError as err:
198
+ raise ValidationError(f"Agent not found: {agent_id}") from err
glaip_sdk/exceptions.py CHANGED
@@ -107,9 +107,7 @@ class AgentTimeoutError(TimeoutError):
107
107
  agent_name: Optional name of the agent that timed out
108
108
  """
109
109
  agent_info = f" for agent '{agent_name}'" if agent_name else ""
110
- message = (
111
- f"Agent execution timed out after {timeout_seconds} seconds{agent_info}"
112
- )
110
+ message = f"Agent execution timed out after {timeout_seconds} seconds{agent_info}"
113
111
  super().__init__(message)
114
112
  self.timeout_seconds = timeout_seconds
115
113
  self.agent_name = agent_name
glaip_sdk/icons.py CHANGED
@@ -5,10 +5,13 @@ Authors:
5
5
  """
6
6
 
7
7
  ICON_AGENT = "🤖"
8
- ICON_AGENT_STEP = "🧠"
8
+ ICON_AGENT_STEP = "🤖"
9
9
  ICON_TOOL = "🔧"
10
- ICON_TOOL_STEP = "⚙️"
11
- ICON_DELEGATE = ICON_AGENT
10
+ ICON_TOOL_STEP = "🔧"
11
+ ICON_DELEGATE = ICON_AGENT_STEP
12
+ ICON_STATUS_SUCCESS = "✓"
13
+ ICON_STATUS_FAILED = "✗"
14
+ ICON_STATUS_WARNING = "⚠"
12
15
 
13
16
  __all__ = [
14
17
  "ICON_AGENT",
@@ -16,4 +19,7 @@ __all__ = [
16
19
  "ICON_TOOL",
17
20
  "ICON_TOOL_STEP",
18
21
  "ICON_DELEGATE",
22
+ "ICON_STATUS_SUCCESS",
23
+ "ICON_STATUS_FAILED",
24
+ "ICON_STATUS_WARNING",
19
25
  ]
glaip_sdk/models.py CHANGED
@@ -13,6 +13,9 @@ from pydantic import BaseModel
13
13
 
14
14
  from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT
15
15
 
16
+ _AGENT_CLIENT_REQUIRED_MSG = "No client available. Use client.get_agent_by_id() to get a client-connected agent."
17
+ _MCP_CLIENT_REQUIRED_MSG = "No client available. Use client.get_mcp_by_id() to get a client-connected MCP."
18
+
16
19
 
17
20
  class Agent(BaseModel):
18
21
  """Agent model for API responses."""
@@ -27,9 +30,7 @@ class Agent(BaseModel):
27
30
  tools: list[dict[str, Any]] | None = None # Backend returns ToolReference objects
28
31
  agents: list[dict[str, Any]] | None = None # Backend returns AgentReference objects
29
32
  mcps: list[dict[str, Any]] | None = None # Backend returns MCPReference objects
30
- tool_configs: dict[str, Any] | None = (
31
- None # Backend returns tool configurations keyed by tool UUID
32
- )
33
+ tool_configs: dict[str, Any] | None = None # Backend returns tool configurations keyed by tool UUID
33
34
  agent_config: dict[str, Any] | None = None
34
35
  timeout: int = DEFAULT_AGENT_RUN_TIMEOUT
35
36
  metadata: dict[str, Any] | None = None
@@ -53,9 +54,7 @@ class Agent(BaseModel):
53
54
  **kwargs: Additional arguments passed to run_agent
54
55
  """
55
56
  if not self._client:
56
- raise RuntimeError(
57
- "No client available. Use client.get_agent_by_id() to get a client-connected agent."
58
- )
57
+ raise RuntimeError(_AGENT_CLIENT_REQUIRED_MSG)
59
58
  # Automatically pass the agent name for better renderer display
60
59
  kwargs.setdefault("agent_name", self.name)
61
60
  # Pass the agent's configured timeout if not explicitly overridden
@@ -80,9 +79,7 @@ class Agent(BaseModel):
80
79
  Exception: For other unexpected errors
81
80
  """
82
81
  if not self._client:
83
- raise RuntimeError(
84
- "No client available. Use client.get_agent_by_id() to get a client-connected agent."
85
- )
82
+ raise RuntimeError(_AGENT_CLIENT_REQUIRED_MSG)
86
83
  # Automatically pass the agent name for better context
87
84
  kwargs.setdefault("agent_name", self.name)
88
85
  # Pass the agent's configured timeout if not explicitly overridden
@@ -95,9 +92,7 @@ class Agent(BaseModel):
95
92
  def update(self, **kwargs) -> "Agent":
96
93
  """Update agent attributes."""
97
94
  if not self._client:
98
- raise RuntimeError(
99
- "No client available. Use client.get_agent_by_id() to get a client-connected agent."
100
- )
95
+ raise RuntimeError(_AGENT_CLIENT_REQUIRED_MSG)
101
96
  updated_agent = self._client.update_agent(self.id, **kwargs)
102
97
  # Update current instance with new data
103
98
  for key, value in updated_agent.model_dump().items():
@@ -108,9 +103,7 @@ class Agent(BaseModel):
108
103
  def delete(self) -> None:
109
104
  """Delete the agent."""
110
105
  if not self._client:
111
- raise RuntimeError(
112
- "No client available. Use client.get_agent_by_id() to get a client-connected agent."
113
- )
106
+ raise RuntimeError(_AGENT_CLIENT_REQUIRED_MSG)
114
107
  self._client.delete_agent(self.id)
115
108
 
116
109
 
@@ -149,16 +142,12 @@ class Tool(BaseModel):
149
142
  Pass 'file' parameter to update tool code via file upload.
150
143
  """
151
144
  if not self._client:
152
- raise RuntimeError(
153
- "No client available. Use client.get_tool_by_id() to get a client-connected tool."
154
- )
145
+ raise RuntimeError("No client available. Use client.get_tool_by_id() to get a client-connected tool.")
155
146
 
156
147
  # Check if file upload is requested
157
148
  if "file" in kwargs:
158
149
  file_path = kwargs.pop("file") # Remove file from kwargs for metadata
159
- updated_tool = self._client.tools.update_tool_via_file(
160
- self.id, file_path, **kwargs
161
- )
150
+ updated_tool = self._client.tools.update_tool_via_file(self.id, file_path, **kwargs)
162
151
  else:
163
152
  # Regular metadata update
164
153
  updated_tool = self._client.tools.update_tool(self.id, **kwargs)
@@ -172,9 +161,7 @@ class Tool(BaseModel):
172
161
  def delete(self) -> None:
173
162
  """Delete the tool."""
174
163
  if not self._client:
175
- raise RuntimeError(
176
- "No client available. Use client.get_tool_by_id() to get a client-connected tool."
177
- )
164
+ raise RuntimeError("No client available. Use client.get_tool_by_id() to get a client-connected tool.")
178
165
  self._client.delete_tool(self.id)
179
166
 
180
167
 
@@ -198,9 +185,7 @@ class MCP(BaseModel):
198
185
  def get_tools(self) -> list[dict[str, Any]]:
199
186
  """Get tools available from this MCP."""
200
187
  if not self._client:
201
- raise RuntimeError(
202
- "No client available. Use client.get_mcp_by_id() to get a client-connected MCP."
203
- )
188
+ raise RuntimeError(_MCP_CLIENT_REQUIRED_MSG)
204
189
  # This would delegate to the client's MCP tools endpoint
205
190
  # For now, return empty list as placeholder
206
191
  return []
@@ -208,9 +193,7 @@ class MCP(BaseModel):
208
193
  def update(self, **kwargs) -> "MCP":
209
194
  """Update MCP attributes."""
210
195
  if not self._client:
211
- raise RuntimeError(
212
- "No client available. Use client.get_mcp_by_id() to get a client-connected MCP."
213
- )
196
+ raise RuntimeError(_MCP_CLIENT_REQUIRED_MSG)
214
197
  updated_mcp = self._client.update_mcp(self.id, **kwargs)
215
198
  # Update current instance with new data
216
199
  for key, value in updated_mcp.model_dump().items():
@@ -221,9 +204,7 @@ class MCP(BaseModel):
221
204
  def delete(self) -> None:
222
205
  """Delete the MCP."""
223
206
  if not self._client:
224
- raise RuntimeError(
225
- "No client available. Use client.get_mcp_by_id() to get a client-connected MCP."
226
- )
207
+ raise RuntimeError("No client available. Use client.get_mcp_by_id() to get a client-connected MCP.")
227
208
  self._client.delete_mcp(self.id)
228
209
 
229
210
 
@@ -60,9 +60,7 @@ AGENT_FIELD_RULES: Mapping[str, FieldRule] = {
60
60
  }
61
61
 
62
62
 
63
- def get_import_field_plan(
64
- field_name: str, operation: AgentImportOperation
65
- ) -> ImportFieldPlan:
63
+ def get_import_field_plan(field_name: str, operation: AgentImportOperation) -> ImportFieldPlan:
66
64
  """Return the import handling plan for ``field_name`` under ``operation``.
67
65
 
68
66
  Unknown fields default to being copied as-is so new API fields propagate
@@ -54,17 +54,11 @@ def sanitize_agent_config(
54
54
  cfg = agent_config or {}
55
55
 
56
56
  if strip_lm_identity and isinstance(cfg, dict):
57
- cfg = {
58
- k: v
59
- for k, v in cfg.items()
60
- if k not in {"lm_provider", "lm_name", "lm_base_url"}
61
- }
57
+ cfg = {k: v for k, v in cfg.items() if k not in {"lm_provider", "lm_name", "lm_base_url"}}
62
58
  return cfg
63
59
 
64
60
 
65
- def resolve_language_model_selection(
66
- merged_data: dict[str, Any], cli_model: str | None
67
- ) -> tuple[dict[str, Any], bool]:
61
+ def resolve_language_model_selection(merged_data: dict[str, Any], cli_model: str | None) -> tuple[dict[str, Any], bool]:
68
62
  """Resolve language model selection from merged data and CLI args.
69
63
 
70
64
  Implements the LM selection priority:
@@ -98,17 +92,13 @@ def resolve_language_model_selection(
98
92
  # Priority 3: Legacy lm_name from agent_config
99
93
  agent_config = merged_data.get("agent_config") or {}
100
94
  if isinstance(agent_config, dict) and agent_config.get("lm_name"):
101
- return {
102
- "model": agent_config["lm_name"]
103
- }, True # Strip LM identity when extracting from agent_config
95
+ return {"model": agent_config["lm_name"]}, True # Strip LM identity when extracting from agent_config
104
96
 
105
97
  # No LM selection found
106
98
  return {}, False
107
99
 
108
100
 
109
- def normalize_agent_config_for_import(
110
- agent_data: dict[str, Any], cli_model: str | None = None
111
- ) -> dict[str, Any]:
101
+ def normalize_agent_config_for_import(agent_data: dict[str, Any], cli_model: str | None = None) -> dict[str, Any]:
112
102
  """Automatically normalize agent configuration by extracting LM settings from agent_config.
113
103
 
114
104
  This function addresses the common issue where exported agent configurations contain