glaip-sdk 0.0.20__py3-none-any.whl → 0.1.1__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 +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 -98
- 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 +187 -46
- glaip_sdk/cli/update_notifier.py +165 -21
- glaip_sdk/cli/utils.py +33 -85
- 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 +6 -22
- glaip_sdk/client/mcps.py +1 -3
- glaip_sdk/client/run_rendering.py +121 -24
- 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 +1107 -320
- glaip_sdk/utils/rendering/renderer/config.py +3 -5
- 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 +10 -22
- 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.1.dist-info}/METADATA +12 -1
- glaip_sdk-0.1.1.dist-info/RECORD +82 -0
- glaip_sdk-0.0.20.dist-info/RECORD +0 -80
- {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.1.1.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.1.1.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
|
@@ -151,12 +151,7 @@ class BaseClient:
|
|
|
151
151
|
def timeout(self, value: float) -> None:
|
|
152
152
|
"""Set timeout and rebuild client."""
|
|
153
153
|
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
|
-
):
|
|
154
|
+
if hasattr(self, "http_client") and self.http_client and not self._session_scoped and not self._parent_client:
|
|
160
155
|
self.http_client.close()
|
|
161
156
|
self.http_client = self._build_client(value)
|
|
162
157
|
|
|
@@ -246,18 +241,14 @@ class BaseClient:
|
|
|
246
241
|
client_log.debug(f"Response status: {response.status_code}")
|
|
247
242
|
return response
|
|
248
243
|
except httpx.ConnectError as e:
|
|
249
|
-
client_log.warning(
|
|
250
|
-
f"Connection error on {method} {endpoint}, retrying once: {e}"
|
|
251
|
-
)
|
|
244
|
+
client_log.warning(f"Connection error on {method} {endpoint}, retrying once: {e}")
|
|
252
245
|
try:
|
|
253
246
|
response = self.http_client.request(method, endpoint, **kwargs)
|
|
254
|
-
client_log.debug(
|
|
255
|
-
f"Retry successful, response status: {response.status_code}"
|
|
256
|
-
)
|
|
247
|
+
client_log.debug(f"Retry successful, response status: {response.status_code}")
|
|
257
248
|
return response
|
|
258
249
|
except httpx.ConnectError:
|
|
259
250
|
client_log.error(f"Retry failed for {method} {endpoint}: {e}")
|
|
260
|
-
raise
|
|
251
|
+
raise
|
|
261
252
|
|
|
262
253
|
def _request(self, method: str, endpoint: str, **kwargs) -> Any:
|
|
263
254
|
"""Make HTTP request with error handling and unwrap success envelopes."""
|
|
@@ -381,9 +372,7 @@ class BaseClient:
|
|
|
381
372
|
error_message = self._get_error_message(response)
|
|
382
373
|
# Try to parse response content for payload
|
|
383
374
|
parsed_content = self._parse_response_content(response)
|
|
384
|
-
self._raise_api_error(
|
|
385
|
-
response.status_code, error_message, payload=parsed_content
|
|
386
|
-
)
|
|
375
|
+
self._raise_api_error(response.status_code, error_message, payload=parsed_content)
|
|
387
376
|
return None # Won't be reached but helps with type checking
|
|
388
377
|
|
|
389
378
|
parsed = self._parse_response_content(response)
|
|
@@ -435,12 +424,7 @@ class BaseClient:
|
|
|
435
424
|
|
|
436
425
|
def close(self) -> None:
|
|
437
426
|
"""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
|
-
):
|
|
427
|
+
if hasattr(self, "http_client") and self.http_client and not self._session_scoped and not self._parent_client:
|
|
444
428
|
self.http_client.close()
|
|
445
429
|
|
|
446
430
|
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
|
|
@@ -23,6 +23,54 @@ from glaip_sdk.utils.rendering.renderer import RichStreamRenderer
|
|
|
23
23
|
from glaip_sdk.utils.rendering.renderer.config import RendererConfig
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def _coerce_to_string(value: Any) -> str:
|
|
27
|
+
"""Return a best-effort string representation for transcripts."""
|
|
28
|
+
try:
|
|
29
|
+
return str(value)
|
|
30
|
+
except Exception:
|
|
31
|
+
return f"{value}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _has_visible_text(value: Any) -> bool:
|
|
35
|
+
"""Return True when the value is a non-empty string."""
|
|
36
|
+
return isinstance(value, str) and bool(value.strip())
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _update_state_transcript(state: Any, text_value: str) -> bool:
|
|
40
|
+
"""Inject transcript text into renderer state if possible."""
|
|
41
|
+
if state is None:
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
updated = False
|
|
45
|
+
|
|
46
|
+
if hasattr(state, "final_text") and not _has_visible_text(getattr(state, "final_text", "")):
|
|
47
|
+
try:
|
|
48
|
+
state.final_text = text_value
|
|
49
|
+
updated = True
|
|
50
|
+
except Exception:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
buffer = getattr(state, "buffer", None)
|
|
54
|
+
if isinstance(buffer, list) and not any(_has_visible_text(item) for item in buffer):
|
|
55
|
+
buffer.append(text_value)
|
|
56
|
+
updated = True
|
|
57
|
+
|
|
58
|
+
return updated
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _update_renderer_transcript(renderer: Any, text_value: str) -> None:
|
|
62
|
+
"""Populate the renderer (or its state) with the supplied text."""
|
|
63
|
+
state = getattr(renderer, "state", None)
|
|
64
|
+
if _update_state_transcript(state, text_value):
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
if hasattr(renderer, "final_text") and not _has_visible_text(getattr(renderer, "final_text", "")):
|
|
68
|
+
try:
|
|
69
|
+
renderer.final_text = text_value
|
|
70
|
+
except Exception:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
26
74
|
class AgentRunRenderingManager:
|
|
27
75
|
"""Coordinate renderer creation and streaming event handling."""
|
|
28
76
|
|
|
@@ -79,7 +127,6 @@ class AgentRunRenderingManager:
|
|
|
79
127
|
silent_config = RendererConfig(
|
|
80
128
|
live=False,
|
|
81
129
|
persist_live=False,
|
|
82
|
-
show_delegate_tool_panels=False,
|
|
83
130
|
render_thinking=False,
|
|
84
131
|
)
|
|
85
132
|
return RichStreamRenderer(
|
|
@@ -92,7 +139,6 @@ class AgentRunRenderingManager:
|
|
|
92
139
|
minimal_config = RendererConfig(
|
|
93
140
|
live=False,
|
|
94
141
|
persist_live=False,
|
|
95
|
-
show_delegate_tool_panels=False,
|
|
96
142
|
render_thinking=False,
|
|
97
143
|
)
|
|
98
144
|
return RichStreamRenderer(
|
|
@@ -106,7 +152,6 @@ class AgentRunRenderingManager:
|
|
|
106
152
|
theme="dark",
|
|
107
153
|
style="debug",
|
|
108
154
|
live=False,
|
|
109
|
-
show_delegate_tool_panels=False,
|
|
110
155
|
append_finished_snapshots=False,
|
|
111
156
|
)
|
|
112
157
|
return RichStreamRenderer(
|
|
@@ -139,17 +184,28 @@ class AgentRunRenderingManager:
|
|
|
139
184
|
|
|
140
185
|
self._capture_request_id(stream_response, meta, renderer)
|
|
141
186
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
187
|
+
controller = getattr(renderer, "transcript_controller", None)
|
|
188
|
+
if controller and getattr(controller, "enabled", False):
|
|
189
|
+
controller.on_stream_start(renderer)
|
|
145
190
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
191
|
+
try:
|
|
192
|
+
for event in iter_sse_events(stream_response, timeout_seconds, agent_name):
|
|
193
|
+
if started_monotonic is None:
|
|
194
|
+
started_monotonic = self._maybe_start_timer(event)
|
|
195
|
+
|
|
196
|
+
final_text, stats_usage = self._process_single_event(
|
|
197
|
+
event,
|
|
198
|
+
renderer,
|
|
199
|
+
final_text,
|
|
200
|
+
stats_usage,
|
|
201
|
+
meta,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if controller and getattr(controller, "enabled", False):
|
|
205
|
+
controller.poll(renderer)
|
|
206
|
+
finally:
|
|
207
|
+
if controller and getattr(controller, "enabled", False):
|
|
208
|
+
controller.on_stream_complete()
|
|
153
209
|
|
|
154
210
|
finished_monotonic = monotonic()
|
|
155
211
|
return final_text, stats_usage, started_monotonic, finished_monotonic
|
|
@@ -160,9 +216,7 @@ class AgentRunRenderingManager:
|
|
|
160
216
|
meta: dict[str, Any],
|
|
161
217
|
renderer: RichStreamRenderer,
|
|
162
218
|
) -> None:
|
|
163
|
-
req_id = stream_response.headers.get(
|
|
164
|
-
"x-request-id"
|
|
165
|
-
) 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")
|
|
166
220
|
if req_id:
|
|
167
221
|
meta["run_id"] = req_id
|
|
168
222
|
renderer.on_start(meta)
|
|
@@ -194,19 +248,50 @@ class AgentRunRenderingManager:
|
|
|
194
248
|
kind = (ev.get("metadata") or {}).get("kind")
|
|
195
249
|
renderer.on_event(ev)
|
|
196
250
|
|
|
251
|
+
handled = self._handle_metadata_kind(
|
|
252
|
+
kind,
|
|
253
|
+
ev,
|
|
254
|
+
final_text,
|
|
255
|
+
stats_usage,
|
|
256
|
+
meta,
|
|
257
|
+
renderer,
|
|
258
|
+
)
|
|
259
|
+
if handled is not None:
|
|
260
|
+
return handled
|
|
261
|
+
|
|
262
|
+
if ev.get("content"):
|
|
263
|
+
final_text = self._handle_content_event(ev, final_text)
|
|
264
|
+
|
|
265
|
+
return final_text, stats_usage
|
|
266
|
+
|
|
267
|
+
def _handle_metadata_kind(
|
|
268
|
+
self,
|
|
269
|
+
kind: str | None,
|
|
270
|
+
ev: dict[str, Any],
|
|
271
|
+
final_text: str,
|
|
272
|
+
stats_usage: dict[str, Any],
|
|
273
|
+
meta: dict[str, Any],
|
|
274
|
+
renderer: RichStreamRenderer,
|
|
275
|
+
) -> tuple[str, dict[str, Any]] | None:
|
|
276
|
+
"""Process well-known metadata kinds and return updated state."""
|
|
197
277
|
if kind == "artifact":
|
|
198
278
|
return final_text, stats_usage
|
|
199
279
|
|
|
200
|
-
if kind == "final_response"
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
280
|
+
if kind == "final_response":
|
|
281
|
+
content = ev.get("content")
|
|
282
|
+
if content:
|
|
283
|
+
return content, stats_usage
|
|
284
|
+
return final_text, stats_usage
|
|
285
|
+
|
|
286
|
+
if kind == "usage":
|
|
205
287
|
stats_usage.update(ev.get("usage") or {})
|
|
206
|
-
|
|
288
|
+
return final_text, stats_usage
|
|
289
|
+
|
|
290
|
+
if kind == "run_info":
|
|
207
291
|
self._handle_run_info_event(ev, meta, renderer)
|
|
292
|
+
return final_text, stats_usage
|
|
208
293
|
|
|
209
|
-
return
|
|
294
|
+
return None
|
|
210
295
|
|
|
211
296
|
def _handle_content_event(self, ev: dict[str, Any], final_text: str) -> str:
|
|
212
297
|
content = ev.get("content", "")
|
|
@@ -227,6 +312,14 @@ class AgentRunRenderingManager:
|
|
|
227
312
|
meta["run_id"] = ev["run_id"]
|
|
228
313
|
renderer.on_start(meta)
|
|
229
314
|
|
|
315
|
+
def _ensure_renderer_final_content(self, renderer: RichStreamRenderer, text: str) -> None:
|
|
316
|
+
"""Populate renderer state with final output when the stream omits it."""
|
|
317
|
+
if not text:
|
|
318
|
+
return
|
|
319
|
+
|
|
320
|
+
text_value = _coerce_to_string(text)
|
|
321
|
+
_update_renderer_transcript(renderer, text_value)
|
|
322
|
+
|
|
230
323
|
# --------------------------------------------------------------------- #
|
|
231
324
|
# Finalisation helpers
|
|
232
325
|
# --------------------------------------------------------------------- #
|
|
@@ -250,7 +343,7 @@ class AgentRunRenderingManager:
|
|
|
250
343
|
if hasattr(renderer, "state") and hasattr(renderer.state, "buffer"):
|
|
251
344
|
buffer_values = renderer.state.buffer
|
|
252
345
|
elif hasattr(renderer, "buffer"):
|
|
253
|
-
buffer_values =
|
|
346
|
+
buffer_values = renderer.buffer
|
|
254
347
|
|
|
255
348
|
if buffer_values is not None:
|
|
256
349
|
try:
|
|
@@ -258,6 +351,10 @@ class AgentRunRenderingManager:
|
|
|
258
351
|
except TypeError:
|
|
259
352
|
rendered_text = ""
|
|
260
353
|
|
|
354
|
+
fallback_text = final_text or rendered_text
|
|
355
|
+
if fallback_text:
|
|
356
|
+
self._ensure_renderer_final_content(renderer, fallback_text)
|
|
357
|
+
|
|
261
358
|
renderer.on_complete(st)
|
|
262
359
|
return final_text or rendered_text or "No response content received."
|
|
263
360
|
|
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)
|