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
|
@@ -348,9 +348,7 @@ class AgentCreateRequest:
|
|
|
348
348
|
payload["tool_configs"] = _copy_structure(self.tool_configs)
|
|
349
349
|
|
|
350
350
|
effective_agent_config = _sanitize_agent_config(self.agent_config)
|
|
351
|
-
effective_agent_config = _merge_execution_timeout(
|
|
352
|
-
effective_agent_config, self.timeout
|
|
353
|
-
)
|
|
351
|
+
effective_agent_config = _merge_execution_timeout(effective_agent_config, self.timeout)
|
|
354
352
|
if effective_agent_config:
|
|
355
353
|
payload["agent_config"] = effective_agent_config
|
|
356
354
|
|
|
@@ -407,31 +405,19 @@ __all__ = [
|
|
|
407
405
|
]
|
|
408
406
|
|
|
409
407
|
|
|
410
|
-
def _build_base_update_payload(
|
|
411
|
-
request: AgentUpdateRequest, current_agent: Any
|
|
412
|
-
) -> dict[str, Any]:
|
|
408
|
+
def _build_base_update_payload(request: AgentUpdateRequest, current_agent: Any) -> dict[str, Any]:
|
|
413
409
|
return {
|
|
414
|
-
"name": request.name.strip()
|
|
415
|
-
if request.name is not None
|
|
416
|
-
else getattr(current_agent, "name", None),
|
|
410
|
+
"name": request.name.strip() if request.name is not None else getattr(current_agent, "name", None),
|
|
417
411
|
"instruction": request.instruction.strip()
|
|
418
412
|
if request.instruction is not None
|
|
419
413
|
else getattr(current_agent, "instruction", None),
|
|
420
|
-
"type": request.agent_type
|
|
421
|
-
or getattr(current_agent, "
|
|
422
|
-
or
|
|
423
|
-
"framework": request.framework
|
|
424
|
-
or getattr(current_agent, "framework", None)
|
|
425
|
-
or DEFAULT_AGENT_FRAMEWORK,
|
|
426
|
-
"version": request.version
|
|
427
|
-
or getattr(current_agent, "version", None)
|
|
428
|
-
or DEFAULT_AGENT_VERSION,
|
|
414
|
+
"type": request.agent_type or getattr(current_agent, "type", None) or DEFAULT_AGENT_TYPE,
|
|
415
|
+
"framework": request.framework or getattr(current_agent, "framework", None) or DEFAULT_AGENT_FRAMEWORK,
|
|
416
|
+
"version": request.version or getattr(current_agent, "version", None) or DEFAULT_AGENT_VERSION,
|
|
429
417
|
}
|
|
430
418
|
|
|
431
419
|
|
|
432
|
-
def _resolve_update_language_model_fields(
|
|
433
|
-
request: AgentUpdateRequest, current_agent: Any
|
|
434
|
-
) -> dict[str, Any]:
|
|
420
|
+
def _resolve_update_language_model_fields(request: AgentUpdateRequest, current_agent: Any) -> dict[str, Any]:
|
|
435
421
|
fields = resolve_language_model_fields(
|
|
436
422
|
model=request.model,
|
|
437
423
|
language_model_id=request.language_model_id,
|
|
@@ -443,9 +429,7 @@ def _resolve_update_language_model_fields(
|
|
|
443
429
|
return fields
|
|
444
430
|
|
|
445
431
|
|
|
446
|
-
def _collect_optional_update_fields(
|
|
447
|
-
request: AgentUpdateRequest, current_agent: Any
|
|
448
|
-
) -> dict[str, Any]:
|
|
432
|
+
def _collect_optional_update_fields(request: AgentUpdateRequest, current_agent: Any) -> dict[str, Any]:
|
|
449
433
|
result: dict[str, Any] = {}
|
|
450
434
|
|
|
451
435
|
for field_name, value in (
|
|
@@ -483,9 +467,7 @@ def _collect_optional_update_fields(
|
|
|
483
467
|
return result
|
|
484
468
|
|
|
485
469
|
|
|
486
|
-
def _collect_relationship_fields(
|
|
487
|
-
request: AgentUpdateRequest, current_agent: Any
|
|
488
|
-
) -> dict[str, Any]:
|
|
470
|
+
def _collect_relationship_fields(request: AgentUpdateRequest, current_agent: Any) -> dict[str, Any]:
|
|
489
471
|
return {
|
|
490
472
|
"tools": _resolve_relation_ids(request.tools, current_agent, "tools"),
|
|
491
473
|
"agents": _resolve_relation_ids(request.agents, current_agent, "agents"),
|
|
@@ -493,9 +475,7 @@ def _collect_relationship_fields(
|
|
|
493
475
|
}
|
|
494
476
|
|
|
495
477
|
|
|
496
|
-
def _resolve_agent_config_update(
|
|
497
|
-
request: AgentUpdateRequest, current_agent: Any
|
|
498
|
-
) -> dict[str, Any] | None:
|
|
478
|
+
def _resolve_agent_config_update(request: AgentUpdateRequest, current_agent: Any) -> dict[str, Any] | None:
|
|
499
479
|
effective_agent_config = _sanitize_agent_config(request.agent_config)
|
|
500
480
|
if effective_agent_config is not None:
|
|
501
481
|
return effective_agent_config
|
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
|