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.
- glaip_sdk/_version.py +1 -3
- glaip_sdk/branding.py +2 -6
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +11 -30
- glaip_sdk/cli/commands/agents.py +64 -107
- glaip_sdk/cli/commands/configure.py +12 -36
- glaip_sdk/cli/commands/mcps.py +25 -63
- glaip_sdk/cli/commands/models.py +2 -4
- glaip_sdk/cli/commands/tools.py +22 -35
- glaip_sdk/cli/commands/update.py +3 -8
- glaip_sdk/cli/config.py +1 -3
- glaip_sdk/cli/display.py +10 -13
- glaip_sdk/cli/io.py +8 -14
- glaip_sdk/cli/main.py +10 -30
- glaip_sdk/cli/mcp_validators.py +5 -15
- glaip_sdk/cli/pager.py +3 -9
- glaip_sdk/cli/parsers/json_input.py +11 -22
- glaip_sdk/cli/resolution.py +3 -9
- glaip_sdk/cli/rich_helpers.py +1 -3
- glaip_sdk/cli/slash/agent_session.py +5 -10
- glaip_sdk/cli/slash/prompt.py +3 -10
- glaip_sdk/cli/slash/session.py +46 -98
- glaip_sdk/cli/transcript/cache.py +6 -19
- glaip_sdk/cli/transcript/capture.py +45 -20
- glaip_sdk/cli/transcript/launcher.py +1 -3
- glaip_sdk/cli/transcript/viewer.py +224 -47
- glaip_sdk/cli/update_notifier.py +165 -21
- glaip_sdk/cli/utils.py +33 -91
- glaip_sdk/cli/validators.py +11 -12
- glaip_sdk/client/_agent_payloads.py +10 -30
- glaip_sdk/client/agents.py +33 -63
- glaip_sdk/client/base.py +77 -35
- glaip_sdk/client/mcps.py +1 -3
- glaip_sdk/client/run_rendering.py +121 -26
- glaip_sdk/client/tools.py +8 -24
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/exceptions.py +1 -3
- glaip_sdk/icons.py +9 -3
- glaip_sdk/models.py +14 -33
- glaip_sdk/payload_schemas/agent.py +1 -3
- glaip_sdk/utils/agent_config.py +4 -14
- glaip_sdk/utils/client_utils.py +7 -21
- glaip_sdk/utils/display.py +2 -6
- glaip_sdk/utils/general.py +1 -3
- glaip_sdk/utils/import_export.py +3 -9
- glaip_sdk/utils/rendering/formatting.py +52 -12
- glaip_sdk/utils/rendering/models.py +17 -8
- glaip_sdk/utils/rendering/renderer/__init__.py +1 -5
- glaip_sdk/utils/rendering/renderer/base.py +1181 -328
- glaip_sdk/utils/rendering/renderer/config.py +4 -10
- glaip_sdk/utils/rendering/renderer/debug.py +4 -14
- glaip_sdk/utils/rendering/renderer/panels.py +1 -3
- glaip_sdk/utils/rendering/renderer/progress.py +3 -11
- glaip_sdk/utils/rendering/renderer/stream.py +9 -42
- glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
- glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
- glaip_sdk/utils/rendering/step_tree_state.py +100 -0
- glaip_sdk/utils/rendering/steps.py +899 -25
- glaip_sdk/utils/resource_refs.py +4 -13
- glaip_sdk/utils/serialization.py +14 -46
- glaip_sdk/utils/validation.py +4 -4
- {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.1.3.dist-info}/METADATA +12 -1
- glaip_sdk-0.1.3.dist-info/RECORD +83 -0
- glaip_sdk-0.0.20.dist-info/RECORD +0 -80
- {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.1.3.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
185
|
+
controller = getattr(renderer, "transcript_controller", None)
|
|
186
|
+
if controller and getattr(controller, "enabled", False):
|
|
187
|
+
controller.on_stream_start(renderer)
|
|
145
188
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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"
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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)
|
glaip_sdk/client/validators.py
CHANGED
|
@@ -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
|
|
96
|
+
except (AmbiguousResourceError, NotFoundError) as err:
|
|
103
97
|
# Determine the tool name for the error message
|
|
104
|
-
tool_name = (
|
|
105
|
-
|
|
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
|
-
|
|
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
|
|
171
|
+
except (AmbiguousResourceError, NotFoundError) as err:
|
|
188
172
|
# Determine the agent name for the error message
|
|
189
|
-
agent_name = (
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
glaip_sdk/utils/agent_config.py
CHANGED
|
@@ -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
|