glaip-sdk 0.1.0__py3-none-any.whl → 0.1.2__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 +45 -107
- glaip_sdk/cli/commands/configure.py +12 -36
- glaip_sdk/cli/commands/mcps.py +26 -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 +4 -12
- 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 -95
- glaip_sdk/cli/transcript/cache.py +6 -19
- glaip_sdk/cli/transcript/capture.py +6 -20
- glaip_sdk/cli/transcript/launcher.py +1 -3
- glaip_sdk/cli/transcript/viewer.py +11 -40
- glaip_sdk/cli/update_notifier.py +165 -21
- glaip_sdk/cli/utils.py +33 -84
- 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 +6 -14
- glaip_sdk/client/tools.py +8 -24
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/exceptions.py +1 -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 +2 -5
- glaip_sdk/utils/rendering/models.py +2 -6
- glaip_sdk/utils/rendering/renderer/__init__.py +1 -3
- glaip_sdk/utils/rendering/renderer/base.py +63 -189
- 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 +7 -19
- glaip_sdk/utils/rendering/renderer/toggle.py +1 -3
- glaip_sdk/utils/rendering/step_tree_state.py +1 -3
- glaip_sdk/utils/rendering/steps.py +29 -83
- 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.1.0.dist-info → glaip_sdk-0.1.2.dist-info}/METADATA +1 -1
- glaip_sdk-0.1.2.dist-info/RECORD +82 -0
- glaip_sdk-0.1.0.dist-info/RECORD +0 -82
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.2.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.2.dist-info}/entry_points.txt +0 -0
glaip_sdk/client/agents.py
CHANGED
|
@@ -34,7 +34,7 @@ from glaip_sdk.config.constants import (
|
|
|
34
34
|
DEFAULT_AGENT_VERSION,
|
|
35
35
|
DEFAULT_MODEL,
|
|
36
36
|
)
|
|
37
|
-
from glaip_sdk.exceptions import NotFoundError
|
|
37
|
+
from glaip_sdk.exceptions import NotFoundError, ValidationError
|
|
38
38
|
from glaip_sdk.models import Agent
|
|
39
39
|
from glaip_sdk.payload_schemas.agent import list_server_only_fields
|
|
40
40
|
from glaip_sdk.utils.agent_config import normalize_agent_config_for_import
|
|
@@ -97,9 +97,7 @@ def _merge_override_maps(
|
|
|
97
97
|
for key, value in source.items():
|
|
98
98
|
if value is None:
|
|
99
99
|
continue
|
|
100
|
-
merged[key] = (
|
|
101
|
-
_normalise_sequence(value) if key in _MERGED_SEQUENCE_FIELDS else value
|
|
102
|
-
)
|
|
100
|
+
merged[key] = _normalise_sequence(value) if key in _MERGED_SEQUENCE_FIELDS else value
|
|
103
101
|
return merged
|
|
104
102
|
|
|
105
103
|
|
|
@@ -134,9 +132,7 @@ def _prepare_agent_metadata(value: Any) -> dict[str, Any]:
|
|
|
134
132
|
return prepared
|
|
135
133
|
|
|
136
134
|
|
|
137
|
-
def _load_agent_file_payload(
|
|
138
|
-
file_path: Path, *, model_override: str | None
|
|
139
|
-
) -> dict[str, Any]:
|
|
135
|
+
def _load_agent_file_payload(file_path: Path, *, model_override: str | None) -> dict[str, Any]:
|
|
140
136
|
"""Load agent configuration from disk and normalise legacy fields."""
|
|
141
137
|
if not file_path.exists():
|
|
142
138
|
raise FileNotFoundError(f"Agent configuration file not found: {file_path}")
|
|
@@ -168,9 +164,7 @@ def _prepare_import_payload(
|
|
|
168
164
|
raw_definition = load_resource_from_file(file_path)
|
|
169
165
|
original_refs = _extract_original_refs(raw_definition)
|
|
170
166
|
|
|
171
|
-
base_payload = _load_agent_file_payload(
|
|
172
|
-
file_path, model_override=overrides_dict.get("model")
|
|
173
|
-
)
|
|
167
|
+
base_payload = _load_agent_file_payload(file_path, model_override=overrides_dict.get("model"))
|
|
174
168
|
|
|
175
169
|
cli_args = _build_cli_args(overrides_dict)
|
|
176
170
|
|
|
@@ -223,11 +217,7 @@ def _build_cli_args(overrides_dict: dict) -> dict[str, Any]:
|
|
|
223
217
|
|
|
224
218
|
def _build_additional_args(overrides_dict: dict, cli_args: dict) -> dict[str, Any]:
|
|
225
219
|
"""Build additional args not already in CLI args."""
|
|
226
|
-
return {
|
|
227
|
-
key: value
|
|
228
|
-
for key, value in overrides_dict.items()
|
|
229
|
-
if value is not None and key not in cli_args
|
|
230
|
-
}
|
|
220
|
+
return {key: value for key, value in overrides_dict.items() if value is not None and key not in cli_args}
|
|
231
221
|
|
|
232
222
|
|
|
233
223
|
def _remove_model_fields_if_needed(merged: dict, overrides_dict: dict) -> None:
|
|
@@ -278,9 +268,7 @@ class AgentClient(BaseClient):
|
|
|
278
268
|
"""
|
|
279
269
|
if query is not None and kwargs:
|
|
280
270
|
# Both query object and individual parameters provided
|
|
281
|
-
raise ValueError(
|
|
282
|
-
"Provide either `query` or individual filter arguments, not both."
|
|
283
|
-
)
|
|
271
|
+
raise ValueError("Provide either `query` or individual filter arguments, not both.")
|
|
284
272
|
|
|
285
273
|
if query is None:
|
|
286
274
|
# Create query from individual parameters for backward compatibility
|
|
@@ -339,7 +327,19 @@ class AgentClient(BaseClient):
|
|
|
339
327
|
|
|
340
328
|
def get_agent_by_id(self, agent_id: str) -> Agent:
|
|
341
329
|
"""Get agent by ID."""
|
|
342
|
-
|
|
330
|
+
try:
|
|
331
|
+
data = self._request("GET", f"/agents/{agent_id}")
|
|
332
|
+
except ValidationError as exc:
|
|
333
|
+
if exc.status_code == 422:
|
|
334
|
+
message = f"Agent '{agent_id}' not found"
|
|
335
|
+
raise NotFoundError(
|
|
336
|
+
message,
|
|
337
|
+
status_code=404,
|
|
338
|
+
error_type=exc.error_type,
|
|
339
|
+
payload=exc.payload,
|
|
340
|
+
request_id=exc.request_id,
|
|
341
|
+
) from exc
|
|
342
|
+
raise
|
|
343
343
|
|
|
344
344
|
if isinstance(data, str):
|
|
345
345
|
# Some backends may respond with plain text for missing agents.
|
|
@@ -372,9 +372,7 @@ class AgentClient(BaseClient):
|
|
|
372
372
|
self._renderer_manager = manager
|
|
373
373
|
return manager
|
|
374
374
|
|
|
375
|
-
def _create_renderer(
|
|
376
|
-
self, renderer: RichStreamRenderer | str | None, **kwargs: Any
|
|
377
|
-
) -> RichStreamRenderer:
|
|
375
|
+
def _create_renderer(self, renderer: RichStreamRenderer | str | None, **kwargs: Any) -> RichStreamRenderer:
|
|
378
376
|
manager = self._get_renderer_manager()
|
|
379
377
|
verbose = kwargs.get("verbose", False)
|
|
380
378
|
if isinstance(renderer, RichStreamRenderer) or hasattr(renderer, "on_start"):
|
|
@@ -506,9 +504,7 @@ class AgentClient(BaseClient):
|
|
|
506
504
|
return entry_id
|
|
507
505
|
|
|
508
506
|
if entry_name:
|
|
509
|
-
resolved, success = self._resolve_resource_by_name(
|
|
510
|
-
find_by_name, entry_name, singular, plural
|
|
511
|
-
)
|
|
507
|
+
resolved, success = self._resolve_resource_by_name(find_by_name, entry_name, singular, plural)
|
|
512
508
|
return resolved if success else entry_name
|
|
513
509
|
|
|
514
510
|
raise ValueError(f"{singular} references must include a valid ID or name.")
|
|
@@ -523,9 +519,7 @@ class AgentClient(BaseClient):
|
|
|
523
519
|
return str(entry)
|
|
524
520
|
|
|
525
521
|
@staticmethod
|
|
526
|
-
def _validate_resource_id(
|
|
527
|
-
fetch_by_id: Callable[[str], Any], candidate_id: str | None
|
|
528
|
-
) -> str | None:
|
|
522
|
+
def _validate_resource_id(fetch_by_id: Callable[[str], Any], candidate_id: str | None) -> str | None:
|
|
529
523
|
if not candidate_id:
|
|
530
524
|
return None
|
|
531
525
|
try:
|
|
@@ -547,21 +541,13 @@ class AgentClient(BaseClient):
|
|
|
547
541
|
return entry_name, False
|
|
548
542
|
|
|
549
543
|
if not matches:
|
|
550
|
-
raise ValueError(
|
|
551
|
-
f"{singular} '{entry_name}' not found in current workspace."
|
|
552
|
-
)
|
|
544
|
+
raise ValueError(f"{singular} '{entry_name}' not found in current workspace.")
|
|
553
545
|
if len(matches) > 1:
|
|
554
|
-
exact = [
|
|
555
|
-
m
|
|
556
|
-
for m in matches
|
|
557
|
-
if getattr(m, "name", "").lower() == entry_name.lower()
|
|
558
|
-
]
|
|
546
|
+
exact = [m for m in matches if getattr(m, "name", "").lower() == entry_name.lower()]
|
|
559
547
|
if len(exact) == 1:
|
|
560
548
|
matches = exact
|
|
561
549
|
else:
|
|
562
|
-
raise ValueError(
|
|
563
|
-
f"Multiple {plural} named '{entry_name}'. Please disambiguate."
|
|
564
|
-
)
|
|
550
|
+
raise ValueError(f"Multiple {plural} named '{entry_name}'. Please disambiguate.")
|
|
565
551
|
return str(matches[0].id), True
|
|
566
552
|
|
|
567
553
|
def _resolve_tool_ids(
|
|
@@ -610,9 +596,7 @@ class AgentClient(BaseClient):
|
|
|
610
596
|
|
|
611
597
|
def _create_agent_from_payload(self, payload: Mapping[str, Any]) -> "Agent":
|
|
612
598
|
"""Create an agent using a fully prepared payload mapping."""
|
|
613
|
-
known, extras = _split_known_and_extra(
|
|
614
|
-
payload, AgentCreateRequest.__dataclass_fields__
|
|
615
|
-
)
|
|
599
|
+
known, extras = _split_known_and_extra(payload, AgentCreateRequest.__dataclass_fields__)
|
|
616
600
|
|
|
617
601
|
name = known.pop("name", None)
|
|
618
602
|
instruction = known.pop("instruction", None)
|
|
@@ -721,9 +705,7 @@ class AgentClient(BaseClient):
|
|
|
721
705
|
overrides = _merge_override_maps(base_overrides, kwargs)
|
|
722
706
|
|
|
723
707
|
if file is not None:
|
|
724
|
-
payload = _prepare_import_payload(
|
|
725
|
-
Path(file).expanduser(), overrides, drop_model_fields=True
|
|
726
|
-
)
|
|
708
|
+
payload = _prepare_import_payload(Path(file).expanduser(), overrides, drop_model_fields=True)
|
|
727
709
|
if overrides.get("model") is None:
|
|
728
710
|
payload.pop("model", None)
|
|
729
711
|
else:
|
|
@@ -746,9 +728,7 @@ class AgentClient(BaseClient):
|
|
|
746
728
|
payload: Mapping[str, Any],
|
|
747
729
|
) -> "Agent":
|
|
748
730
|
"""Update an agent using a prepared payload mapping."""
|
|
749
|
-
known, extras = _split_known_and_extra(
|
|
750
|
-
payload, AgentUpdateRequest.__dataclass_fields__
|
|
751
|
-
)
|
|
731
|
+
known, extras = _split_known_and_extra(payload, AgentUpdateRequest.__dataclass_fields__)
|
|
752
732
|
_normalise_sequence_fields(known)
|
|
753
733
|
|
|
754
734
|
tool_refs = extras.pop("_tool_refs", None)
|
|
@@ -818,9 +798,7 @@ class AgentClient(BaseClient):
|
|
|
818
798
|
overrides = _merge_override_maps(base_overrides, kwargs)
|
|
819
799
|
|
|
820
800
|
if file is not None:
|
|
821
|
-
payload = _prepare_import_payload(
|
|
822
|
-
Path(file).expanduser(), overrides, drop_model_fields=True
|
|
823
|
-
)
|
|
801
|
+
payload = _prepare_import_payload(Path(file).expanduser(), overrides, drop_model_fields=True)
|
|
824
802
|
else:
|
|
825
803
|
payload = overrides
|
|
826
804
|
|
|
@@ -882,9 +860,7 @@ class AgentClient(BaseClient):
|
|
|
882
860
|
payload["tty"] = True
|
|
883
861
|
return payload, None, None, headers, None
|
|
884
862
|
|
|
885
|
-
def _get_timeout_values(
|
|
886
|
-
self, timeout: float | None, **kwargs: Any
|
|
887
|
-
) -> tuple[float, float]:
|
|
863
|
+
def _get_timeout_values(self, timeout: float | None, **kwargs: Any) -> tuple[float, float]:
|
|
888
864
|
"""Get request timeout and execution timeout values.
|
|
889
865
|
|
|
890
866
|
Args:
|
|
@@ -1009,9 +985,7 @@ class AgentClient(BaseClient):
|
|
|
1009
985
|
headers = {"Accept": SSE_CONTENT_TYPE}
|
|
1010
986
|
return payload, None, None, headers
|
|
1011
987
|
|
|
1012
|
-
def _create_async_client_config(
|
|
1013
|
-
self, timeout: float | None, headers: dict | None
|
|
1014
|
-
) -> dict:
|
|
988
|
+
def _create_async_client_config(self, timeout: float | None, headers: dict | None) -> dict:
|
|
1015
989
|
"""Create async client configuration with proper headers and timeout."""
|
|
1016
990
|
config = self._build_async_client(timeout or self.timeout)
|
|
1017
991
|
if headers:
|
|
@@ -1040,9 +1014,7 @@ class AgentClient(BaseClient):
|
|
|
1040
1014
|
) as stream_response:
|
|
1041
1015
|
stream_response.raise_for_status()
|
|
1042
1016
|
|
|
1043
|
-
async for event in aiter_sse_events(
|
|
1044
|
-
stream_response, timeout_seconds, agent_name
|
|
1045
|
-
):
|
|
1017
|
+
async for event in aiter_sse_events(stream_response, timeout_seconds, agent_name):
|
|
1046
1018
|
try:
|
|
1047
1019
|
chunk = json.loads(event["data"])
|
|
1048
1020
|
yield chunk
|
|
@@ -1077,9 +1049,7 @@ class AgentClient(BaseClient):
|
|
|
1077
1049
|
Exception: For other unexpected errors
|
|
1078
1050
|
"""
|
|
1079
1051
|
# Prepare request data
|
|
1080
|
-
payload, data_payload, files_payload, headers = self._prepare_request_data(
|
|
1081
|
-
message, files, **kwargs
|
|
1082
|
-
)
|
|
1052
|
+
payload, data_payload, files_payload, headers = self._prepare_request_data(message, files, **kwargs)
|
|
1083
1053
|
|
|
1084
1054
|
# Create async client configuration
|
|
1085
1055
|
async_client_config = self._create_async_client_config(timeout, headers)
|
glaip_sdk/client/base.py
CHANGED
|
@@ -7,6 +7,7 @@ Authors:
|
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
9
|
import os
|
|
10
|
+
from collections.abc import Iterable, Mapping
|
|
10
11
|
from typing import Any, NoReturn, Union
|
|
11
12
|
|
|
12
13
|
import httpx
|
|
@@ -151,12 +152,7 @@ class BaseClient:
|
|
|
151
152
|
def timeout(self, value: float) -> None:
|
|
152
153
|
"""Set timeout and rebuild client."""
|
|
153
154
|
self._timeout = value
|
|
154
|
-
if (
|
|
155
|
-
hasattr(self, "http_client")
|
|
156
|
-
and self.http_client
|
|
157
|
-
and not self._session_scoped
|
|
158
|
-
and not self._parent_client
|
|
159
|
-
):
|
|
155
|
+
if hasattr(self, "http_client") and self.http_client and not self._session_scoped and not self._parent_client:
|
|
160
156
|
self.http_client.close()
|
|
161
157
|
self.http_client = self._build_client(value)
|
|
162
158
|
|
|
@@ -246,18 +242,14 @@ class BaseClient:
|
|
|
246
242
|
client_log.debug(f"Response status: {response.status_code}")
|
|
247
243
|
return response
|
|
248
244
|
except httpx.ConnectError as e:
|
|
249
|
-
client_log.warning(
|
|
250
|
-
f"Connection error on {method} {endpoint}, retrying once: {e}"
|
|
251
|
-
)
|
|
245
|
+
client_log.warning(f"Connection error on {method} {endpoint}, retrying once: {e}")
|
|
252
246
|
try:
|
|
253
247
|
response = self.http_client.request(method, endpoint, **kwargs)
|
|
254
|
-
client_log.debug(
|
|
255
|
-
f"Retry successful, response status: {response.status_code}"
|
|
256
|
-
)
|
|
248
|
+
client_log.debug(f"Retry successful, response status: {response.status_code}")
|
|
257
249
|
return response
|
|
258
250
|
except httpx.ConnectError:
|
|
259
251
|
client_log.error(f"Retry failed for {method} {endpoint}: {e}")
|
|
260
|
-
raise
|
|
252
|
+
raise
|
|
261
253
|
|
|
262
254
|
def _request(self, method: str, endpoint: str, **kwargs) -> Any:
|
|
263
255
|
"""Make HTTP request with error handling and unwrap success envelopes."""
|
|
@@ -298,7 +290,7 @@ class BaseClient:
|
|
|
298
290
|
return parsed.get("data", parsed) if unwrap else parsed
|
|
299
291
|
else:
|
|
300
292
|
error_type = parsed.get("error", "UnknownError")
|
|
301
|
-
message = parsed.
|
|
293
|
+
message = self._format_error_dict({key: value for key, value in parsed.items() if key != "success"})
|
|
302
294
|
self._raise_api_error(
|
|
303
295
|
400,
|
|
304
296
|
message,
|
|
@@ -341,12 +333,58 @@ class BaseClient:
|
|
|
341
333
|
return validation_message
|
|
342
334
|
return f"Validation error: {parsed}"
|
|
343
335
|
|
|
336
|
+
formatted_details = None
|
|
337
|
+
if "details" in parsed:
|
|
338
|
+
formatted_details = self._format_error_details(parsed["details"])
|
|
339
|
+
|
|
344
340
|
message = parsed.get("message")
|
|
345
341
|
if message:
|
|
342
|
+
if formatted_details:
|
|
343
|
+
return f"{message}\n{formatted_details}"
|
|
346
344
|
return message
|
|
347
345
|
|
|
346
|
+
if formatted_details:
|
|
347
|
+
return formatted_details
|
|
348
|
+
|
|
348
349
|
return str(parsed) if parsed else DEFAULT_ERROR_MESSAGE
|
|
349
350
|
|
|
351
|
+
def _format_error_details(self, details: Any) -> str | None:
|
|
352
|
+
"""Render generic error details into a human-readable string."""
|
|
353
|
+
if details is None:
|
|
354
|
+
return None
|
|
355
|
+
|
|
356
|
+
if isinstance(details, dict):
|
|
357
|
+
return self._format_detail_mapping(details)
|
|
358
|
+
|
|
359
|
+
if isinstance(details, (list, tuple, set)):
|
|
360
|
+
return self._format_detail_iterable(details)
|
|
361
|
+
|
|
362
|
+
return f"Details: {details}"
|
|
363
|
+
|
|
364
|
+
@staticmethod
|
|
365
|
+
def _format_detail_mapping(details: Mapping[str, Any]) -> str | None:
|
|
366
|
+
"""Format details provided as a mapping."""
|
|
367
|
+
entries = [f" {key}: {value}" for key, value in details.items()]
|
|
368
|
+
if not entries:
|
|
369
|
+
return None
|
|
370
|
+
return "Details:\n" + "\n".join(entries)
|
|
371
|
+
|
|
372
|
+
@staticmethod
|
|
373
|
+
def _format_detail_iterable(details: Iterable[Any]) -> str | None:
|
|
374
|
+
"""Format details provided as an iterable collection."""
|
|
375
|
+
entries: list[str] = []
|
|
376
|
+
for item in details:
|
|
377
|
+
if isinstance(item, Mapping):
|
|
378
|
+
inner = ", ".join(f"{k}={v}" for k, v in item.items())
|
|
379
|
+
entries.append(f" - {inner if inner else '{}'}")
|
|
380
|
+
else:
|
|
381
|
+
entries.append(f" - {item}")
|
|
382
|
+
|
|
383
|
+
if not entries:
|
|
384
|
+
return None
|
|
385
|
+
|
|
386
|
+
return "Details:\n" + "\n".join(entries)
|
|
387
|
+
|
|
350
388
|
def _format_validation_errors(self, errors: list[Any]) -> str | None:
|
|
351
389
|
"""Render validation errors into a human-readable string."""
|
|
352
390
|
entries: list[str] = []
|
|
@@ -365,6 +403,22 @@ class BaseClient:
|
|
|
365
403
|
|
|
366
404
|
return "Validation errors:\n" + "\n".join(entries)
|
|
367
405
|
|
|
406
|
+
@staticmethod
|
|
407
|
+
def _is_no_content_response(response: httpx.Response) -> bool:
|
|
408
|
+
"""Return True when the response contains no content."""
|
|
409
|
+
return response.status_code == 204
|
|
410
|
+
|
|
411
|
+
@staticmethod
|
|
412
|
+
def _is_success_status(response: httpx.Response) -> bool:
|
|
413
|
+
"""Return True for successful HTTP status codes."""
|
|
414
|
+
return 200 <= response.status_code < 300
|
|
415
|
+
|
|
416
|
+
def _handle_error_response(self, response: httpx.Response) -> None:
|
|
417
|
+
"""Raise an API error for non-success responses."""
|
|
418
|
+
error_message = self._get_error_message(response)
|
|
419
|
+
parsed_content = self._parse_response_content(response)
|
|
420
|
+
self._raise_api_error(response.status_code, error_message, payload=parsed_content)
|
|
421
|
+
|
|
368
422
|
def _handle_response(
|
|
369
423
|
self,
|
|
370
424
|
response: httpx.Response,
|
|
@@ -373,24 +427,17 @@ class BaseClient:
|
|
|
373
427
|
) -> Any:
|
|
374
428
|
"""Handle HTTP response with proper error handling."""
|
|
375
429
|
# Handle no-content success before general error handling
|
|
376
|
-
if response
|
|
430
|
+
if self._is_no_content_response(response):
|
|
377
431
|
return None
|
|
378
432
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
self._raise_api_error(
|
|
385
|
-
response.status_code, error_message, payload=parsed_content
|
|
386
|
-
)
|
|
387
|
-
return None # Won't be reached but helps with type checking
|
|
388
|
-
|
|
389
|
-
parsed = self._parse_response_content(response)
|
|
390
|
-
if parsed is None:
|
|
391
|
-
return None
|
|
433
|
+
if self._is_success_status(response):
|
|
434
|
+
parsed = self._parse_response_content(response)
|
|
435
|
+
if parsed is None:
|
|
436
|
+
return None
|
|
437
|
+
return self._handle_success_response(parsed, unwrap=unwrap)
|
|
392
438
|
|
|
393
|
-
|
|
439
|
+
self._handle_error_response(response)
|
|
440
|
+
return None
|
|
394
441
|
|
|
395
442
|
def _raise_api_error(
|
|
396
443
|
self,
|
|
@@ -435,12 +482,7 @@ class BaseClient:
|
|
|
435
482
|
|
|
436
483
|
def close(self) -> None:
|
|
437
484
|
"""Close the HTTP client."""
|
|
438
|
-
if (
|
|
439
|
-
hasattr(self, "http_client")
|
|
440
|
-
and self.http_client
|
|
441
|
-
and not self._session_scoped
|
|
442
|
-
and not self._parent_client
|
|
443
|
-
):
|
|
485
|
+
if hasattr(self, "http_client") and self.http_client and not self._session_scoped and not self._parent_client:
|
|
444
486
|
self.http_client.close()
|
|
445
487
|
|
|
446
488
|
def __enter__(self) -> "BaseClient":
|
glaip_sdk/client/mcps.py
CHANGED
|
@@ -179,9 +179,7 @@ class MCPClient(BaseClient):
|
|
|
179
179
|
update_data = {
|
|
180
180
|
"name": name if name is not None else current_mcp.name,
|
|
181
181
|
"type": DEFAULT_MCP_TYPE, # Required by backend, MCPs are always server type
|
|
182
|
-
"transport": kwargs.get(
|
|
183
|
-
"transport", getattr(current_mcp, "transport", DEFAULT_MCP_TRANSPORT)
|
|
184
|
-
),
|
|
182
|
+
"transport": kwargs.get("transport", getattr(current_mcp, "transport", DEFAULT_MCP_TRANSPORT)),
|
|
185
183
|
}
|
|
186
184
|
|
|
187
185
|
# Handle description with proper None handling
|
|
@@ -43,9 +43,7 @@ def _update_state_transcript(state: Any, text_value: str) -> bool:
|
|
|
43
43
|
|
|
44
44
|
updated = False
|
|
45
45
|
|
|
46
|
-
if hasattr(state, "final_text") and not _has_visible_text(
|
|
47
|
-
getattr(state, "final_text", "")
|
|
48
|
-
):
|
|
46
|
+
if hasattr(state, "final_text") and not _has_visible_text(getattr(state, "final_text", "")):
|
|
49
47
|
try:
|
|
50
48
|
state.final_text = text_value
|
|
51
49
|
updated = True
|
|
@@ -66,11 +64,9 @@ def _update_renderer_transcript(renderer: Any, text_value: str) -> None:
|
|
|
66
64
|
if _update_state_transcript(state, text_value):
|
|
67
65
|
return
|
|
68
66
|
|
|
69
|
-
if hasattr(renderer, "final_text") and not _has_visible_text(
|
|
70
|
-
getattr(renderer, "final_text", "")
|
|
71
|
-
):
|
|
67
|
+
if hasattr(renderer, "final_text") and not _has_visible_text(getattr(renderer, "final_text", "")):
|
|
72
68
|
try:
|
|
73
|
-
|
|
69
|
+
renderer.final_text = text_value
|
|
74
70
|
except Exception:
|
|
75
71
|
pass
|
|
76
72
|
|
|
@@ -220,9 +216,7 @@ class AgentRunRenderingManager:
|
|
|
220
216
|
meta: dict[str, Any],
|
|
221
217
|
renderer: RichStreamRenderer,
|
|
222
218
|
) -> None:
|
|
223
|
-
req_id = stream_response.headers.get(
|
|
224
|
-
"x-request-id"
|
|
225
|
-
) or stream_response.headers.get("x-run-id")
|
|
219
|
+
req_id = stream_response.headers.get("x-request-id") or stream_response.headers.get("x-run-id")
|
|
226
220
|
if req_id:
|
|
227
221
|
meta["run_id"] = req_id
|
|
228
222
|
renderer.on_start(meta)
|
|
@@ -318,9 +312,7 @@ class AgentRunRenderingManager:
|
|
|
318
312
|
meta["run_id"] = ev["run_id"]
|
|
319
313
|
renderer.on_start(meta)
|
|
320
314
|
|
|
321
|
-
def _ensure_renderer_final_content(
|
|
322
|
-
self, renderer: RichStreamRenderer, text: str
|
|
323
|
-
) -> None:
|
|
315
|
+
def _ensure_renderer_final_content(self, renderer: RichStreamRenderer, text: str) -> None:
|
|
324
316
|
"""Populate renderer state with final output when the stream omits it."""
|
|
325
317
|
if not text:
|
|
326
318
|
return
|
|
@@ -351,7 +343,7 @@ class AgentRunRenderingManager:
|
|
|
351
343
|
if hasattr(renderer, "state") and hasattr(renderer.state, "buffer"):
|
|
352
344
|
buffer_values = renderer.state.buffer
|
|
353
345
|
elif hasattr(renderer, "buffer"):
|
|
354
|
-
buffer_values =
|
|
346
|
+
buffer_values = renderer.buffer
|
|
355
347
|
|
|
356
348
|
if buffer_values is not None:
|
|
357
349
|
try:
|
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
|