glaip-sdk 0.0.6a0__py3-none-any.whl → 0.0.8__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 +42 -19
- glaip_sdk/branding.py +3 -3
- glaip_sdk/cli/commands/agents.py +136 -38
- glaip_sdk/cli/commands/configure.py +2 -2
- glaip_sdk/cli/commands/mcps.py +2 -4
- glaip_sdk/cli/commands/tools.py +2 -4
- glaip_sdk/cli/display.py +15 -15
- glaip_sdk/cli/main.py +51 -5
- glaip_sdk/cli/resolution.py +17 -9
- glaip_sdk/cli/slash/__init__.py +25 -0
- glaip_sdk/cli/slash/agent_session.py +146 -0
- glaip_sdk/cli/slash/prompt.py +198 -0
- glaip_sdk/cli/slash/session.py +665 -0
- glaip_sdk/cli/utils.py +162 -38
- glaip_sdk/client/agents.py +101 -73
- glaip_sdk/client/base.py +45 -14
- glaip_sdk/client/tools.py +44 -26
- glaip_sdk/models.py +1 -0
- glaip_sdk/utils/client_utils.py +95 -71
- glaip_sdk/utils/import_export.py +3 -1
- glaip_sdk/utils/rendering/renderer/base.py +170 -125
- glaip_sdk/utils/serialization.py +52 -16
- {glaip_sdk-0.0.6a0.dist-info → glaip_sdk-0.0.8.dist-info}/METADATA +1 -1
- {glaip_sdk-0.0.6a0.dist-info → glaip_sdk-0.0.8.dist-info}/RECORD +26 -22
- {glaip_sdk-0.0.6a0.dist-info → glaip_sdk-0.0.8.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.6a0.dist-info → glaip_sdk-0.0.8.dist-info}/entry_points.txt +0 -0
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/models.py
CHANGED
|
@@ -119,6 +119,7 @@ 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
125
|
def _set_client(self, client: Any) -> "Tool":
|
glaip_sdk/utils/client_utils.py
CHANGED
|
@@ -246,6 +246,33 @@ def _handle_streaming_error(
|
|
|
246
246
|
raise
|
|
247
247
|
|
|
248
248
|
|
|
249
|
+
def _process_sse_line(
|
|
250
|
+
line: str, buf: list[str], event_type: str | None, event_id: str | None
|
|
251
|
+
) -> tuple[list[str], str | None, str | None, dict[str, Any] | None, bool]:
|
|
252
|
+
"""Process a single SSE line and return updated state."""
|
|
253
|
+
result = _parse_sse_line(line, buf, event_type, event_id)
|
|
254
|
+
buf, event_type, event_id, event_data, completed = result
|
|
255
|
+
return buf, event_type, event_id, event_data, completed
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _yield_event_data(event_data: dict[str, Any] | None) -> Iterator[dict[str, Any]]:
|
|
259
|
+
"""Yield event data if available."""
|
|
260
|
+
if event_data:
|
|
261
|
+
yield event_data
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _flush_remaining_buffer(
|
|
265
|
+
buf: list[str], event_type: str | None, event_id: str | None
|
|
266
|
+
) -> Iterator[dict[str, Any]]:
|
|
267
|
+
"""Flush any remaining data in buffer."""
|
|
268
|
+
if buf:
|
|
269
|
+
yield {
|
|
270
|
+
"event": event_type or "message",
|
|
271
|
+
"id": event_id,
|
|
272
|
+
"data": "\n".join(buf),
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
|
|
249
276
|
def iter_sse_events(
|
|
250
277
|
response: httpx.Response,
|
|
251
278
|
timeout_seconds: float | None = None,
|
|
@@ -276,25 +303,16 @@ def iter_sse_events(
|
|
|
276
303
|
if line is None:
|
|
277
304
|
continue
|
|
278
305
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
else: # normal case
|
|
283
|
-
buf, event_type, event_id, event_data = result
|
|
284
|
-
completed = False
|
|
306
|
+
buf, event_type, event_id, event_data, completed = _process_sse_line(
|
|
307
|
+
line, buf, event_type, event_id
|
|
308
|
+
)
|
|
285
309
|
|
|
286
|
-
|
|
287
|
-
yield event_data
|
|
310
|
+
yield from _yield_event_data(event_data)
|
|
288
311
|
if completed:
|
|
289
312
|
return
|
|
290
313
|
|
|
291
314
|
# Flush any remaining data
|
|
292
|
-
|
|
293
|
-
yield {
|
|
294
|
-
"event": event_type or "message",
|
|
295
|
-
"id": event_id,
|
|
296
|
-
"data": "\n".join(buf),
|
|
297
|
-
}
|
|
315
|
+
yield from _flush_remaining_buffer(buf, event_type, event_id)
|
|
298
316
|
|
|
299
317
|
except Exception as e:
|
|
300
318
|
_handle_streaming_error(e, timeout_seconds, agent_name)
|
|
@@ -329,11 +347,7 @@ async def aiter_sse_events(
|
|
|
329
347
|
continue
|
|
330
348
|
|
|
331
349
|
result = _parse_sse_line(line, buf, event_type, event_id)
|
|
332
|
-
|
|
333
|
-
buf, event_type, event_id, event_data, completed = result
|
|
334
|
-
else: # normal case
|
|
335
|
-
buf, event_type, event_id, event_data = result
|
|
336
|
-
completed = False
|
|
350
|
+
buf, event_type, event_id, event_data, completed = result
|
|
337
351
|
|
|
338
352
|
if event_data:
|
|
339
353
|
yield event_data
|
|
@@ -352,6 +366,66 @@ async def aiter_sse_events(
|
|
|
352
366
|
_handle_streaming_error(e, timeout_seconds, agent_name)
|
|
353
367
|
|
|
354
368
|
|
|
369
|
+
def _create_form_data(message: str) -> dict[str, Any]:
|
|
370
|
+
"""Create form data with message and stream flag."""
|
|
371
|
+
return {"input": message, "message": message, "stream": True}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _prepare_file_entry(
|
|
375
|
+
item: str | BinaryIO, stack: ExitStack
|
|
376
|
+
) -> tuple[str, tuple[str, BinaryIO, str]]:
|
|
377
|
+
"""Prepare a single file entry for multipart data."""
|
|
378
|
+
if isinstance(item, str):
|
|
379
|
+
return _prepare_path_entry(item, stack)
|
|
380
|
+
else:
|
|
381
|
+
return _prepare_stream_entry(item)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _prepare_path_entry(
|
|
385
|
+
path_str: str, stack: ExitStack
|
|
386
|
+
) -> tuple[str, tuple[str, BinaryIO, str]]:
|
|
387
|
+
"""Prepare a file path entry."""
|
|
388
|
+
file_path = Path(path_str)
|
|
389
|
+
if not file_path.exists():
|
|
390
|
+
raise FileNotFoundError(f"File not found: {path_str}")
|
|
391
|
+
|
|
392
|
+
handle = stack.enter_context(open(file_path, "rb"))
|
|
393
|
+
return (
|
|
394
|
+
"files",
|
|
395
|
+
(
|
|
396
|
+
file_path.name,
|
|
397
|
+
handle,
|
|
398
|
+
"application/octet-stream",
|
|
399
|
+
),
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _prepare_stream_entry(
|
|
404
|
+
file_obj: BinaryIO,
|
|
405
|
+
) -> tuple[str, tuple[str, BinaryIO, str]]:
|
|
406
|
+
"""Prepare a file object entry."""
|
|
407
|
+
if not hasattr(file_obj, "read"):
|
|
408
|
+
raise ValueError(f"Invalid file object: {file_obj}")
|
|
409
|
+
|
|
410
|
+
raw_name = getattr(file_obj, "name", "file")
|
|
411
|
+
filename = Path(raw_name).name if raw_name else "file"
|
|
412
|
+
|
|
413
|
+
try:
|
|
414
|
+
if hasattr(file_obj, "seek"):
|
|
415
|
+
file_obj.seek(0)
|
|
416
|
+
except (OSError, ValueError):
|
|
417
|
+
pass
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
"files",
|
|
421
|
+
(
|
|
422
|
+
filename,
|
|
423
|
+
file_obj,
|
|
424
|
+
"application/octet-stream",
|
|
425
|
+
),
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
|
|
355
429
|
def prepare_multipart_data(message: str, files: list[str | BinaryIO]) -> MultipartData:
|
|
356
430
|
"""Prepare multipart form data for file uploads.
|
|
357
431
|
|
|
@@ -366,63 +440,13 @@ def prepare_multipart_data(message: str, files: list[str | BinaryIO]) -> Multipa
|
|
|
366
440
|
FileNotFoundError: When a file path doesn't exist
|
|
367
441
|
ValueError: When a file object is invalid
|
|
368
442
|
"""
|
|
369
|
-
|
|
370
|
-
def _prepare_path_entry(
|
|
371
|
-
path_str: str, stack: ExitStack
|
|
372
|
-
) -> tuple[str, tuple[str, BinaryIO, str]]:
|
|
373
|
-
file_path = Path(path_str)
|
|
374
|
-
if not file_path.exists():
|
|
375
|
-
raise FileNotFoundError(f"File not found: {path_str}")
|
|
376
|
-
|
|
377
|
-
handle = stack.enter_context(open(file_path, "rb"))
|
|
378
|
-
return (
|
|
379
|
-
"files",
|
|
380
|
-
(
|
|
381
|
-
file_path.name,
|
|
382
|
-
handle,
|
|
383
|
-
"application/octet-stream",
|
|
384
|
-
),
|
|
385
|
-
)
|
|
386
|
-
|
|
387
|
-
def _prepare_stream_entry(
|
|
388
|
-
file_obj: BinaryIO,
|
|
389
|
-
) -> tuple[str, tuple[str, BinaryIO, str]]:
|
|
390
|
-
if not hasattr(file_obj, "read"):
|
|
391
|
-
raise ValueError(f"Invalid file object: {file_obj}")
|
|
392
|
-
|
|
393
|
-
raw_name = getattr(file_obj, "name", "file")
|
|
394
|
-
filename = Path(raw_name).name if raw_name else "file"
|
|
395
|
-
|
|
396
|
-
try:
|
|
397
|
-
if hasattr(file_obj, "seek"):
|
|
398
|
-
file_obj.seek(0)
|
|
399
|
-
except (OSError, ValueError):
|
|
400
|
-
pass
|
|
401
|
-
|
|
402
|
-
return (
|
|
403
|
-
"files",
|
|
404
|
-
(
|
|
405
|
-
filename,
|
|
406
|
-
file_obj,
|
|
407
|
-
"application/octet-stream",
|
|
408
|
-
),
|
|
409
|
-
)
|
|
410
|
-
|
|
411
|
-
# Backend expects 'input' for the main prompt. Keep 'message' for
|
|
412
|
-
# backward-compatibility with any legacy handlers.
|
|
413
|
-
form_data = {"input": message, "message": message, "stream": True}
|
|
443
|
+
form_data = _create_form_data(message)
|
|
414
444
|
stack = ExitStack()
|
|
415
445
|
multipart_data = MultipartData(form_data, [])
|
|
416
446
|
multipart_data._exit_stack = stack
|
|
417
447
|
|
|
418
448
|
try:
|
|
419
|
-
file_entries = []
|
|
420
|
-
for item in files:
|
|
421
|
-
if isinstance(item, str):
|
|
422
|
-
file_entries.append(_prepare_path_entry(item, stack))
|
|
423
|
-
else:
|
|
424
|
-
file_entries.append(_prepare_stream_entry(item))
|
|
425
|
-
|
|
449
|
+
file_entries = [_prepare_file_entry(item, stack) for item in files]
|
|
426
450
|
multipart_data.files = file_entries
|
|
427
451
|
return multipart_data
|
|
428
452
|
except Exception:
|
glaip_sdk/utils/import_export.py
CHANGED
|
@@ -40,7 +40,9 @@ def extract_ids_from_export(items: list[Any]) -> list[str]:
|
|
|
40
40
|
return ids
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
def convert_export_to_import_format(
|
|
43
|
+
def convert_export_to_import_format(
|
|
44
|
+
data: dict[str, Any],
|
|
45
|
+
) -> dict[str, Any]:
|
|
44
46
|
"""Convert export format to import-compatible format (extract IDs from objects).
|
|
45
47
|
|
|
46
48
|
Args:
|