glaip-sdk 0.0.5b1__py3-none-any.whl → 0.0.7__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/__init__.py +1 -1
- glaip_sdk/_version.py +42 -19
- glaip_sdk/branding.py +3 -2
- glaip_sdk/cli/commands/__init__.py +1 -1
- glaip_sdk/cli/commands/agents.py +452 -285
- glaip_sdk/cli/commands/configure.py +14 -13
- glaip_sdk/cli/commands/mcps.py +30 -20
- glaip_sdk/cli/commands/models.py +5 -3
- glaip_sdk/cli/commands/tools.py +111 -106
- glaip_sdk/cli/display.py +48 -27
- glaip_sdk/cli/io.py +1 -1
- glaip_sdk/cli/main.py +26 -5
- glaip_sdk/cli/resolution.py +5 -4
- glaip_sdk/cli/utils.py +437 -188
- glaip_sdk/cli/validators.py +7 -2
- glaip_sdk/client/agents.py +276 -153
- glaip_sdk/client/base.py +69 -27
- glaip_sdk/client/tools.py +44 -26
- glaip_sdk/client/validators.py +154 -94
- glaip_sdk/config/constants.py +0 -2
- glaip_sdk/models.py +5 -4
- glaip_sdk/utils/__init__.py +7 -7
- glaip_sdk/utils/client_utils.py +191 -101
- glaip_sdk/utils/display.py +4 -2
- glaip_sdk/utils/general.py +8 -6
- glaip_sdk/utils/import_export.py +58 -25
- glaip_sdk/utils/rendering/formatting.py +12 -6
- glaip_sdk/utils/rendering/models.py +1 -1
- glaip_sdk/utils/rendering/renderer/base.py +523 -332
- glaip_sdk/utils/rendering/renderer/console.py +6 -5
- glaip_sdk/utils/rendering/renderer/debug.py +94 -52
- glaip_sdk/utils/rendering/renderer/stream.py +93 -48
- glaip_sdk/utils/rendering/steps.py +103 -39
- glaip_sdk/utils/rich_utils.py +1 -1
- glaip_sdk/utils/run_renderer.py +1 -1
- glaip_sdk/utils/serialization.py +9 -3
- glaip_sdk/utils/validation.py +2 -2
- glaip_sdk-0.0.7.dist-info/METADATA +183 -0
- glaip_sdk-0.0.7.dist-info/RECORD +55 -0
- glaip_sdk-0.0.5b1.dist-info/METADATA +0 -645
- glaip_sdk-0.0.5b1.dist-info/RECORD +0 -55
- {glaip_sdk-0.0.5b1.dist-info → glaip_sdk-0.0.7.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.5b1.dist-info → glaip_sdk-0.0.7.dist-info}/entry_points.txt +0 -0
glaip_sdk/client/base.py
CHANGED
|
@@ -7,13 +7,14 @@ Authors:
|
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
9
|
import os
|
|
10
|
-
from typing import Any, Union
|
|
10
|
+
from typing import Any, NoReturn, Union
|
|
11
11
|
|
|
12
12
|
import httpx
|
|
13
13
|
from dotenv import load_dotenv
|
|
14
14
|
|
|
15
15
|
import glaip_sdk
|
|
16
|
-
from glaip_sdk.
|
|
16
|
+
from glaip_sdk._version import __version__ as SDK_VERSION
|
|
17
|
+
from glaip_sdk.config.constants import SDK_NAME
|
|
17
18
|
from glaip_sdk.exceptions import (
|
|
18
19
|
AuthenticationError,
|
|
19
20
|
ConflictError,
|
|
@@ -147,7 +148,7 @@ class BaseClient:
|
|
|
147
148
|
return self._timeout
|
|
148
149
|
|
|
149
150
|
@timeout.setter
|
|
150
|
-
def timeout(self, value: float):
|
|
151
|
+
def timeout(self, value: float) -> None:
|
|
151
152
|
"""Set timeout and rebuild client."""
|
|
152
153
|
self._timeout = value
|
|
153
154
|
if (
|
|
@@ -165,10 +166,10 @@ class BaseClient:
|
|
|
165
166
|
post_endpoint: str,
|
|
166
167
|
get_endpoint_fmt: str,
|
|
167
168
|
*,
|
|
168
|
-
json=None,
|
|
169
|
-
data=None,
|
|
170
|
-
files=None,
|
|
171
|
-
**kwargs,
|
|
169
|
+
json: Any | None = None,
|
|
170
|
+
data: Any | None = None,
|
|
171
|
+
files: Any | None = None,
|
|
172
|
+
**kwargs: Any,
|
|
172
173
|
) -> Any:
|
|
173
174
|
"""Helper for POST-then-GET pattern used in create methods.
|
|
174
175
|
|
|
@@ -210,7 +211,7 @@ class BaseClient:
|
|
|
210
211
|
get_endpoint = get_endpoint_fmt.format(id=resource_id)
|
|
211
212
|
return self._request("GET", get_endpoint)
|
|
212
213
|
|
|
213
|
-
def _ensure_client_alive(self):
|
|
214
|
+
def _ensure_client_alive(self) -> None:
|
|
214
215
|
"""Ensure HTTP client is alive, recreate if needed."""
|
|
215
216
|
if not hasattr(self, "http_client") or self.http_client is None:
|
|
216
217
|
if not self._parent_client:
|
|
@@ -253,25 +254,25 @@ class BaseClient:
|
|
|
253
254
|
client_log.error(f"Retry failed for {method} {endpoint}: {e}")
|
|
254
255
|
raise e
|
|
255
256
|
|
|
256
|
-
def
|
|
257
|
-
"""
|
|
257
|
+
def _parse_response_content(self, response: httpx.Response) -> Any | None:
|
|
258
|
+
"""Parse response content based on content type."""
|
|
258
259
|
if response.status_code == 204:
|
|
259
260
|
return None
|
|
260
261
|
|
|
261
|
-
parsed = None
|
|
262
262
|
content_type = response.headers.get("content-type", "").lower()
|
|
263
263
|
if "json" in content_type:
|
|
264
264
|
try:
|
|
265
|
-
|
|
265
|
+
return response.json()
|
|
266
266
|
except ValueError:
|
|
267
267
|
pass
|
|
268
268
|
|
|
269
|
-
if
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
self._raise_api_error(response.status_code, response.text)
|
|
269
|
+
if 200 <= response.status_code < 300:
|
|
270
|
+
return response.text
|
|
271
|
+
else:
|
|
272
|
+
return None # Let _handle_response deal with error status codes
|
|
274
273
|
|
|
274
|
+
def _handle_success_response(self, parsed: Any) -> Any:
|
|
275
|
+
"""Handle successful response with success flag."""
|
|
275
276
|
if isinstance(parsed, dict) and "success" in parsed:
|
|
276
277
|
if parsed.get("success"):
|
|
277
278
|
return parsed.get("data", parsed)
|
|
@@ -279,18 +280,54 @@ class BaseClient:
|
|
|
279
280
|
error_type = parsed.get("error", "UnknownError")
|
|
280
281
|
message = parsed.get("message", "Unknown error")
|
|
281
282
|
self._raise_api_error(
|
|
282
|
-
|
|
283
|
+
400,
|
|
284
|
+
message,
|
|
285
|
+
error_type,
|
|
286
|
+
payload=parsed, # Using 400 as status since original response had error
|
|
283
287
|
)
|
|
284
288
|
|
|
285
|
-
|
|
286
|
-
|
|
289
|
+
return parsed
|
|
290
|
+
|
|
291
|
+
def _get_error_message(self, response: httpx.Response) -> str:
|
|
292
|
+
"""Extract error message from response, preferring parsed content."""
|
|
293
|
+
# Try to get error message from parsed content if available
|
|
294
|
+
error_message = response.text
|
|
295
|
+
try:
|
|
296
|
+
parsed = response.json()
|
|
297
|
+
if isinstance(parsed, dict) and "message" in parsed:
|
|
298
|
+
error_message = parsed["message"]
|
|
299
|
+
elif isinstance(parsed, str):
|
|
300
|
+
error_message = parsed
|
|
301
|
+
except (ValueError, TypeError):
|
|
302
|
+
pass # Use response.text as fallback
|
|
303
|
+
return error_message
|
|
304
|
+
|
|
305
|
+
def _handle_response(self, response: httpx.Response) -> Any:
|
|
306
|
+
"""Handle HTTP response with proper error handling."""
|
|
307
|
+
# Handle no-content success before general error handling
|
|
308
|
+
if response.status_code == 204:
|
|
309
|
+
return None
|
|
287
310
|
|
|
288
|
-
|
|
289
|
-
|
|
311
|
+
# Handle error status codes
|
|
312
|
+
if not (200 <= response.status_code < 300):
|
|
313
|
+
error_message = self._get_error_message(response)
|
|
314
|
+
self._raise_api_error(response.status_code, error_message)
|
|
315
|
+
return None # Won't be reached but helps with type checking
|
|
316
|
+
|
|
317
|
+
parsed = self._parse_response_content(response)
|
|
318
|
+
if parsed is None:
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
return self._handle_success_response(parsed)
|
|
290
322
|
|
|
291
323
|
def _raise_api_error(
|
|
292
|
-
self,
|
|
293
|
-
|
|
324
|
+
self,
|
|
325
|
+
status: int,
|
|
326
|
+
message: str,
|
|
327
|
+
error_type: str | None = None,
|
|
328
|
+
*,
|
|
329
|
+
payload: Any | None = None,
|
|
330
|
+
) -> NoReturn:
|
|
294
331
|
"""Raise appropriate exception with rich context."""
|
|
295
332
|
request_id = None
|
|
296
333
|
try:
|
|
@@ -324,7 +361,7 @@ class BaseClient:
|
|
|
324
361
|
request_id=request_id,
|
|
325
362
|
)
|
|
326
363
|
|
|
327
|
-
def close(self):
|
|
364
|
+
def close(self) -> None:
|
|
328
365
|
"""Close the HTTP client."""
|
|
329
366
|
if (
|
|
330
367
|
hasattr(self, "http_client")
|
|
@@ -334,11 +371,16 @@ class BaseClient:
|
|
|
334
371
|
):
|
|
335
372
|
self.http_client.close()
|
|
336
373
|
|
|
337
|
-
def __enter__(self):
|
|
374
|
+
def __enter__(self) -> "BaseClient":
|
|
338
375
|
"""Context manager entry."""
|
|
339
376
|
return self
|
|
340
377
|
|
|
341
|
-
def __exit__(
|
|
378
|
+
def __exit__(
|
|
379
|
+
self,
|
|
380
|
+
_exc_type: type[BaseException] | None,
|
|
381
|
+
_exc_val: BaseException | None,
|
|
382
|
+
_exc_tb: Any,
|
|
383
|
+
) -> None:
|
|
342
384
|
"""Context manager exit."""
|
|
343
385
|
# Only close if this is not session-scoped
|
|
344
386
|
if not self._session_scoped:
|
glaip_sdk/client/tools.py
CHANGED
|
@@ -208,6 +208,44 @@ class ToolClient(BaseClient):
|
|
|
208
208
|
|
|
209
209
|
return payload
|
|
210
210
|
|
|
211
|
+
def _handle_description_update(
|
|
212
|
+
self, update_data: dict[str, Any], description: str | None, current_tool: Tool
|
|
213
|
+
) -> None:
|
|
214
|
+
"""Handle description field in update payload."""
|
|
215
|
+
if description is not None:
|
|
216
|
+
update_data["description"] = description.strip()
|
|
217
|
+
elif hasattr(current_tool, "description") and current_tool.description:
|
|
218
|
+
update_data["description"] = current_tool.description
|
|
219
|
+
|
|
220
|
+
def _handle_tags_update(
|
|
221
|
+
self, update_data: dict[str, Any], kwargs: dict[str, Any], current_tool: Tool
|
|
222
|
+
) -> None:
|
|
223
|
+
"""Handle tags field in update payload."""
|
|
224
|
+
if kwargs.get("tags"):
|
|
225
|
+
if isinstance(kwargs["tags"], list):
|
|
226
|
+
update_data["tags"] = ",".join(
|
|
227
|
+
str(tag).strip() for tag in kwargs["tags"]
|
|
228
|
+
)
|
|
229
|
+
else:
|
|
230
|
+
update_data["tags"] = str(kwargs["tags"])
|
|
231
|
+
elif hasattr(current_tool, "tags") and current_tool.tags:
|
|
232
|
+
# Preserve existing tags if present
|
|
233
|
+
if isinstance(current_tool.tags, list):
|
|
234
|
+
update_data["tags"] = ",".join(
|
|
235
|
+
str(tag).strip() for tag in current_tool.tags
|
|
236
|
+
)
|
|
237
|
+
else:
|
|
238
|
+
update_data["tags"] = str(current_tool.tags)
|
|
239
|
+
|
|
240
|
+
def _handle_additional_kwargs(
|
|
241
|
+
self, update_data: dict[str, Any], kwargs: dict[str, Any]
|
|
242
|
+
) -> None:
|
|
243
|
+
"""Handle additional kwargs in update payload."""
|
|
244
|
+
excluded_keys = {"tags", "framework", "version"}
|
|
245
|
+
for key, value in kwargs.items():
|
|
246
|
+
if key not in excluded_keys:
|
|
247
|
+
update_data[key] = value
|
|
248
|
+
|
|
211
249
|
def _build_update_payload(
|
|
212
250
|
self,
|
|
213
251
|
current_tool: Tool,
|
|
@@ -242,34 +280,14 @@ class ToolClient(BaseClient):
|
|
|
242
280
|
),
|
|
243
281
|
}
|
|
244
282
|
|
|
245
|
-
# Handle description
|
|
246
|
-
|
|
247
|
-
update_data["description"] = description.strip()
|
|
248
|
-
elif hasattr(current_tool, "description") and current_tool.description:
|
|
249
|
-
update_data["description"] = current_tool.description
|
|
283
|
+
# Handle description update
|
|
284
|
+
self._handle_description_update(update_data, description, current_tool)
|
|
250
285
|
|
|
251
|
-
# Handle tags
|
|
252
|
-
|
|
253
|
-
if isinstance(kwargs["tags"], list):
|
|
254
|
-
update_data["tags"] = ",".join(
|
|
255
|
-
str(tag).strip() for tag in kwargs["tags"]
|
|
256
|
-
)
|
|
257
|
-
else:
|
|
258
|
-
update_data["tags"] = str(kwargs["tags"])
|
|
259
|
-
elif hasattr(current_tool, "tags") and current_tool.tags:
|
|
260
|
-
# Preserve existing tags if present
|
|
261
|
-
if isinstance(current_tool.tags, list):
|
|
262
|
-
update_data["tags"] = ",".join(
|
|
263
|
-
str(tag).strip() for tag in current_tool.tags
|
|
264
|
-
)
|
|
265
|
-
else:
|
|
266
|
-
update_data["tags"] = str(current_tool.tags)
|
|
286
|
+
# Handle tags update
|
|
287
|
+
self._handle_tags_update(update_data, kwargs, current_tool)
|
|
267
288
|
|
|
268
|
-
#
|
|
269
|
-
|
|
270
|
-
for key, value in kwargs.items():
|
|
271
|
-
if key not in excluded_keys:
|
|
272
|
-
update_data[key] = value
|
|
289
|
+
# Handle additional kwargs
|
|
290
|
+
self._handle_additional_kwargs(update_data, kwargs)
|
|
273
291
|
|
|
274
292
|
return update_data
|
|
275
293
|
|
glaip_sdk/client/validators.py
CHANGED
|
@@ -25,8 +25,67 @@ class ResourceValidator:
|
|
|
25
25
|
"""Check if a name is reserved."""
|
|
26
26
|
return name in cls.RESERVED_NAMES
|
|
27
27
|
|
|
28
|
+
def _is_uuid_string(self, value: str) -> bool:
|
|
29
|
+
"""Check if a string is a valid UUID."""
|
|
30
|
+
try:
|
|
31
|
+
UUID(value)
|
|
32
|
+
return True
|
|
33
|
+
except ValueError:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
def _resolve_tool_by_name(self, tool_name: str, client: Any) -> str:
|
|
37
|
+
"""Resolve tool name to ID."""
|
|
38
|
+
found_tools = client.find_tools(name=tool_name)
|
|
39
|
+
if len(found_tools) == 1:
|
|
40
|
+
return str(found_tools[0].id)
|
|
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
|
+
)
|
|
45
|
+
else:
|
|
46
|
+
raise NotFoundError(f"Tool not found: {tool_name}")
|
|
47
|
+
|
|
48
|
+
def _resolve_tool_by_name_attribute(self, tool: Tool, client: Any) -> str:
|
|
49
|
+
"""Resolve tool by name attribute."""
|
|
50
|
+
found_tools = client.find_tools(name=tool.name)
|
|
51
|
+
if len(found_tools) == 1:
|
|
52
|
+
return str(found_tools[0].id)
|
|
53
|
+
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
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
raise NotFoundError(f"Tool not found: {tool.name}")
|
|
59
|
+
|
|
60
|
+
def _process_tool_string(self, tool: str, client: Any) -> str:
|
|
61
|
+
"""Process a string tool reference."""
|
|
62
|
+
if self._is_uuid_string(tool):
|
|
63
|
+
return tool # Already a UUID string
|
|
64
|
+
else:
|
|
65
|
+
return self._resolve_tool_by_name(tool, client)
|
|
66
|
+
|
|
67
|
+
def _process_tool_object(self, tool: Tool, client: Any) -> str:
|
|
68
|
+
"""Process a Tool object reference."""
|
|
69
|
+
if hasattr(tool, "id") and tool.id is not None:
|
|
70
|
+
return str(tool.id)
|
|
71
|
+
elif isinstance(tool, UUID):
|
|
72
|
+
return str(tool)
|
|
73
|
+
elif hasattr(tool, "name") and tool.name is not None:
|
|
74
|
+
return self._resolve_tool_by_name_attribute(tool, client)
|
|
75
|
+
else:
|
|
76
|
+
raise ValidationError(
|
|
77
|
+
f"Invalid tool reference: {tool} - must have 'id' or 'name' attribute"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def _process_single_tool(self, tool: str | Tool, client: Any) -> str:
|
|
81
|
+
"""Process a single tool reference and return its ID."""
|
|
82
|
+
if isinstance(tool, str):
|
|
83
|
+
return self._process_tool_string(tool, client)
|
|
84
|
+
else:
|
|
85
|
+
return self._process_tool_object(tool, client)
|
|
86
|
+
|
|
28
87
|
@classmethod
|
|
29
|
-
def extract_tool_ids(cls, tools: list[str | Tool], client) -> list[str]:
|
|
88
|
+
def extract_tool_ids(cls, tools: list[str | Tool], client: Any) -> list[str]:
|
|
30
89
|
"""Extract tool IDs from a list of tool names, IDs, or Tool objects.
|
|
31
90
|
|
|
32
91
|
For agent creation, the backend expects tool IDs (UUIDs).
|
|
@@ -37,57 +96,81 @@ class ResourceValidator:
|
|
|
37
96
|
"""
|
|
38
97
|
tool_ids = []
|
|
39
98
|
for tool in tools:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
raise NotFoundError(f"Tool not found: {tool}")
|
|
57
|
-
except Exception as e:
|
|
58
|
-
raise ValidationError(
|
|
59
|
-
f"Failed to resolve tool name '{tool}' to ID: {e}"
|
|
60
|
-
)
|
|
61
|
-
elif hasattr(tool, "id") and tool.id is not None: # Tool object with ID
|
|
62
|
-
tool_ids.append(str(tool.id))
|
|
63
|
-
elif isinstance(tool, UUID): # UUID object
|
|
64
|
-
tool_ids.append(str(tool))
|
|
65
|
-
elif (
|
|
66
|
-
hasattr(tool, "name") and tool.name is not None
|
|
67
|
-
): # Tool object with name but no ID
|
|
68
|
-
# Try to find the tool by name and get its ID
|
|
69
|
-
try:
|
|
70
|
-
found_tools = client.find_tools(name=tool.name)
|
|
71
|
-
if len(found_tools) == 1:
|
|
72
|
-
tool_ids.append(str(found_tools[0].id))
|
|
73
|
-
elif len(found_tools) > 1:
|
|
74
|
-
raise AmbiguousResourceError(
|
|
75
|
-
f"Multiple tools found with name '{tool.name}': {[t.id for t in found_tools]}"
|
|
76
|
-
)
|
|
77
|
-
else:
|
|
78
|
-
raise NotFoundError(f"Tool not found: {tool.name}")
|
|
79
|
-
except Exception as e:
|
|
80
|
-
raise ValidationError(
|
|
81
|
-
f"Failed to resolve tool name '{tool.name}' to ID: {e}"
|
|
82
|
-
)
|
|
83
|
-
else:
|
|
99
|
+
try:
|
|
100
|
+
tool_id = cls()._process_single_tool(tool, client)
|
|
101
|
+
tool_ids.append(tool_id)
|
|
102
|
+
except (AmbiguousResourceError, NotFoundError) as e:
|
|
103
|
+
# 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:
|
|
111
|
+
# For other exceptions, wrap them appropriately
|
|
112
|
+
tool_name = (
|
|
113
|
+
tool if isinstance(tool, str) else getattr(tool, "name", str(tool))
|
|
114
|
+
)
|
|
84
115
|
raise ValidationError(
|
|
85
|
-
f"
|
|
116
|
+
f"Failed to resolve tool name '{tool_name}' to ID: {e}"
|
|
86
117
|
)
|
|
118
|
+
|
|
87
119
|
return tool_ids
|
|
88
120
|
|
|
121
|
+
def _resolve_agent_by_name(self, agent_name: str, client: Any) -> str:
|
|
122
|
+
"""Resolve agent name to ID."""
|
|
123
|
+
found_agents = client.find_agents(name=agent_name)
|
|
124
|
+
if len(found_agents) == 1:
|
|
125
|
+
return str(found_agents[0].id)
|
|
126
|
+
elif len(found_agents) > 1:
|
|
127
|
+
raise AmbiguousResourceError(
|
|
128
|
+
f"Multiple agents found with name '{agent_name}': {[a.id for a in found_agents]}"
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
raise NotFoundError(f"Agent not found: {agent_name}")
|
|
132
|
+
|
|
133
|
+
def _resolve_agent_by_name_attribute(self, agent: Any, client: Any) -> str:
|
|
134
|
+
"""Resolve agent by name attribute."""
|
|
135
|
+
found_agents = client.find_agents(name=agent.name)
|
|
136
|
+
if len(found_agents) == 1:
|
|
137
|
+
return str(found_agents[0].id)
|
|
138
|
+
elif len(found_agents) > 1:
|
|
139
|
+
raise AmbiguousResourceError(
|
|
140
|
+
f"Multiple agents found with name '{agent.name}': {[a.id for a in found_agents]}"
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
raise NotFoundError(f"Agent not found: {agent.name}")
|
|
144
|
+
|
|
145
|
+
def _process_agent_string(self, agent: str, client: Any) -> str:
|
|
146
|
+
"""Process a string agent reference."""
|
|
147
|
+
if self._is_uuid_string(agent):
|
|
148
|
+
return agent # Already a UUID string
|
|
149
|
+
else:
|
|
150
|
+
return self._resolve_agent_by_name(agent, client)
|
|
151
|
+
|
|
152
|
+
def _process_agent_object(self, agent: Any, client: Any) -> str:
|
|
153
|
+
"""Process an Agent object reference."""
|
|
154
|
+
if hasattr(agent, "id") and agent.id is not None:
|
|
155
|
+
return str(agent.id)
|
|
156
|
+
elif isinstance(agent, UUID):
|
|
157
|
+
return str(agent)
|
|
158
|
+
elif hasattr(agent, "name") and agent.name is not None:
|
|
159
|
+
return self._resolve_agent_by_name_attribute(agent, client)
|
|
160
|
+
else:
|
|
161
|
+
raise ValidationError(
|
|
162
|
+
f"Invalid agent reference: {agent} - must have 'id' or 'name' attribute"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def _process_single_agent(self, agent: str | Any, client: Any) -> str:
|
|
166
|
+
"""Process a single agent reference and return its ID."""
|
|
167
|
+
if isinstance(agent, str):
|
|
168
|
+
return self._process_agent_string(agent, client)
|
|
169
|
+
else:
|
|
170
|
+
return self._process_agent_object(agent, client)
|
|
171
|
+
|
|
89
172
|
@classmethod
|
|
90
|
-
def extract_agent_ids(cls, agents: list[str | Any], client) -> list[str]:
|
|
173
|
+
def extract_agent_ids(cls, agents: list[str | Any], client: Any) -> list[str]:
|
|
91
174
|
"""Extract agent IDs from a list of agent names, IDs, or agent objects.
|
|
92
175
|
|
|
93
176
|
For agent creation, the backend expects agent IDs (UUIDs).
|
|
@@ -98,57 +181,34 @@ class ResourceValidator:
|
|
|
98
181
|
"""
|
|
99
182
|
agent_ids = []
|
|
100
183
|
for agent in agents:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
agent_ids.append(str(found_agents[0].id))
|
|
112
|
-
elif len(found_agents) > 1:
|
|
113
|
-
raise AmbiguousResourceError(
|
|
114
|
-
f"Multiple agents found with name '{agent}': {[a.id for a in found_agents]}"
|
|
115
|
-
)
|
|
116
|
-
else:
|
|
117
|
-
raise NotFoundError(f"Agent not found: {agent}")
|
|
118
|
-
except Exception as e:
|
|
119
|
-
raise ValidationError(
|
|
120
|
-
f"Failed to resolve agent name '{agent}' to ID: {e}"
|
|
121
|
-
)
|
|
122
|
-
elif hasattr(agent, "id") and agent.id is not None: # Agent object with ID
|
|
123
|
-
agent_ids.append(str(agent.id))
|
|
124
|
-
elif isinstance(agent, UUID): # UUID object
|
|
125
|
-
agent_ids.append(str(agent))
|
|
126
|
-
elif (
|
|
127
|
-
hasattr(agent, "name") and agent.name is not None
|
|
128
|
-
): # Agent object with name but no ID
|
|
129
|
-
# Try to find the agent by name and get its ID
|
|
130
|
-
try:
|
|
131
|
-
found_agents = client.find_agents(name=agent.name)
|
|
132
|
-
if len(found_agents) == 1:
|
|
133
|
-
agent_ids.append(str(found_agents[0].id))
|
|
134
|
-
elif len(found_agents) > 1:
|
|
135
|
-
raise AmbiguousResourceError(
|
|
136
|
-
f"Multiple agents found with name '{agent.name}': {[a.id for a in found_agents]}"
|
|
137
|
-
)
|
|
138
|
-
else:
|
|
139
|
-
raise NotFoundError(f"Agent not found: {agent.name}")
|
|
140
|
-
except Exception as e:
|
|
141
|
-
raise ValidationError(
|
|
142
|
-
f"Failed to resolve agent name '{agent.name}' to ID: {e}"
|
|
143
|
-
)
|
|
144
|
-
else:
|
|
184
|
+
try:
|
|
185
|
+
agent_id = cls()._process_single_agent(agent, client)
|
|
186
|
+
agent_ids.append(agent_id)
|
|
187
|
+
except (AmbiguousResourceError, NotFoundError) as e:
|
|
188
|
+
# 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
|
+
)
|
|
145
194
|
raise ValidationError(
|
|
146
|
-
f"
|
|
195
|
+
f"Failed to resolve agent name '{agent_name}' to ID: {e}"
|
|
196
|
+
)
|
|
197
|
+
except Exception as e:
|
|
198
|
+
# For other exceptions, wrap them appropriately
|
|
199
|
+
agent_name = (
|
|
200
|
+
agent
|
|
201
|
+
if isinstance(agent, str)
|
|
202
|
+
else getattr(agent, "name", str(agent))
|
|
147
203
|
)
|
|
204
|
+
raise ValidationError(
|
|
205
|
+
f"Failed to resolve agent name '{agent_name}' to ID: {e}"
|
|
206
|
+
)
|
|
207
|
+
|
|
148
208
|
return agent_ids
|
|
149
209
|
|
|
150
210
|
@classmethod
|
|
151
|
-
def validate_tools_exist(cls, tool_ids: list[str], client) -> None:
|
|
211
|
+
def validate_tools_exist(cls, tool_ids: list[str], client: Any) -> None:
|
|
152
212
|
"""Validate that all tool IDs exist."""
|
|
153
213
|
for tool_id in tool_ids:
|
|
154
214
|
try:
|
|
@@ -157,7 +217,7 @@ class ResourceValidator:
|
|
|
157
217
|
raise ValidationError(f"Tool not found: {tool_id}")
|
|
158
218
|
|
|
159
219
|
@classmethod
|
|
160
|
-
def validate_agents_exist(cls, agent_ids: list[str], client) -> None:
|
|
220
|
+
def validate_agents_exist(cls, agent_ids: list[str], client: Any) -> None:
|
|
161
221
|
"""Validate that all agent IDs exist."""
|
|
162
222
|
for agent_id in agent_ids:
|
|
163
223
|
try:
|
glaip_sdk/config/constants.py
CHANGED
glaip_sdk/models.py
CHANGED
|
@@ -39,7 +39,7 @@ class Agent(BaseModel):
|
|
|
39
39
|
updated_at: datetime | None = None # Backend returns last update timestamp
|
|
40
40
|
_client: Any = None
|
|
41
41
|
|
|
42
|
-
def _set_client(self, client):
|
|
42
|
+
def _set_client(self, client: Any) -> "Agent":
|
|
43
43
|
"""Set the client reference for this resource."""
|
|
44
44
|
self._client = client
|
|
45
45
|
return self
|
|
@@ -119,9 +119,10 @@ class Tool(BaseModel):
|
|
|
119
119
|
version: str | None = None
|
|
120
120
|
tool_script: str | None = None
|
|
121
121
|
tool_file: str | None = None
|
|
122
|
+
tags: str | list[str] | None = None
|
|
122
123
|
_client: Any = None # Will hold client reference
|
|
123
124
|
|
|
124
|
-
def _set_client(self, client):
|
|
125
|
+
def _set_client(self, client: Any) -> "Tool":
|
|
125
126
|
"""Set the client reference for this resource."""
|
|
126
127
|
self._client = client
|
|
127
128
|
return self
|
|
@@ -185,7 +186,7 @@ class MCP(BaseModel):
|
|
|
185
186
|
metadata: dict[str, Any] | None = None
|
|
186
187
|
_client: Any = None # Will hold client reference
|
|
187
188
|
|
|
188
|
-
def _set_client(self, client):
|
|
189
|
+
def _set_client(self, client: Any) -> "MCP":
|
|
189
190
|
"""Set the client reference for this resource."""
|
|
190
191
|
self._client = client
|
|
191
192
|
return self
|
|
@@ -239,7 +240,7 @@ class TTYRenderer:
|
|
|
239
240
|
def __init__(self, use_color: bool = True):
|
|
240
241
|
self.use_color = use_color
|
|
241
242
|
|
|
242
|
-
def render_message(self, message: str, event_type: str = "message"):
|
|
243
|
+
def render_message(self, message: str, event_type: str = "message") -> None:
|
|
243
244
|
"""Render a message with optional color."""
|
|
244
245
|
if event_type == "error":
|
|
245
246
|
print(f"ERROR: {message}", flush=True)
|
glaip_sdk/utils/__init__.py
CHANGED
|
@@ -23,18 +23,18 @@ from glaip_sdk.utils.rich_utils import RICH_AVAILABLE
|
|
|
23
23
|
from glaip_sdk.utils.run_renderer import RichStreamRenderer
|
|
24
24
|
|
|
25
25
|
__all__ = [
|
|
26
|
+
"RICH_AVAILABLE",
|
|
26
27
|
"RichStreamRenderer",
|
|
27
28
|
"RunStats",
|
|
28
29
|
"Step",
|
|
29
30
|
"StepManager",
|
|
30
|
-
"RICH_AVAILABLE",
|
|
31
|
-
"is_uuid",
|
|
32
|
-
"sanitize_name",
|
|
33
|
-
"format_file_size",
|
|
34
31
|
"format_datetime",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
32
|
+
"format_file_size",
|
|
33
|
+
"is_uuid",
|
|
37
34
|
"print_agent_created",
|
|
38
|
-
"print_agent_updated",
|
|
39
35
|
"print_agent_deleted",
|
|
36
|
+
"print_agent_output",
|
|
37
|
+
"print_agent_updated",
|
|
38
|
+
"progress_bar",
|
|
39
|
+
"sanitize_name",
|
|
40
40
|
]
|