glaip-sdk 0.2.1__py3-none-any.whl → 0.3.0__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 +8 -0
- glaip_sdk/branding.py +13 -0
- glaip_sdk/cli/commands/agents.py +180 -39
- glaip_sdk/cli/commands/mcps.py +44 -18
- glaip_sdk/cli/commands/models.py +11 -5
- glaip_sdk/cli/commands/tools.py +35 -16
- glaip_sdk/cli/commands/transcripts.py +8 -0
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +8 -0
- glaip_sdk/cli/display.py +34 -19
- glaip_sdk/cli/main.py +14 -7
- glaip_sdk/cli/masking.py +8 -33
- glaip_sdk/cli/pager.py +9 -10
- glaip_sdk/cli/slash/agent_session.py +57 -20
- glaip_sdk/cli/slash/prompt.py +8 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +341 -46
- glaip_sdk/cli/slash/tui/__init__.py +9 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +632 -0
- glaip_sdk/cli/transcript/viewer.py +232 -32
- glaip_sdk/cli/update_notifier.py +2 -2
- glaip_sdk/cli/utils.py +266 -35
- glaip_sdk/cli/validators.py +5 -6
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_agent_payloads.py +30 -0
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +186 -22
- glaip_sdk/client/main.py +23 -6
- glaip_sdk/client/mcps.py +2 -4
- glaip_sdk/client/run_rendering.py +66 -0
- glaip_sdk/client/tools.py +2 -3
- glaip_sdk/config/constants.py +11 -0
- glaip_sdk/models/__init__.py +56 -0
- glaip_sdk/models/agent_runs.py +117 -0
- glaip_sdk/rich_components.py +58 -2
- glaip_sdk/utils/client_utils.py +13 -0
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/import_export.py +6 -9
- glaip_sdk/utils/rendering/__init__.py +122 -1
- glaip_sdk/utils/rendering/renderer/base.py +3 -7
- glaip_sdk/utils/rendering/renderer/debug.py +0 -1
- glaip_sdk/utils/rendering/renderer/stream.py +4 -12
- glaip_sdk/utils/rendering/steps.py +1 -0
- glaip_sdk/utils/resource_refs.py +26 -15
- glaip_sdk/utils/serialization.py +16 -0
- {glaip_sdk-0.2.1.dist-info → glaip_sdk-0.3.0.dist-info}/METADATA +24 -2
- glaip_sdk-0.3.0.dist-info/RECORD +94 -0
- glaip_sdk-0.2.1.dist-info/RECORD +0 -86
- {glaip_sdk-0.2.1.dist-info → glaip_sdk-0.3.0.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.2.1.dist-info → glaip_sdk-0.3.0.dist-info}/entry_points.txt +0 -0
glaip_sdk/client/agents.py
CHANGED
|
@@ -5,9 +5,11 @@ Authors:
|
|
|
5
5
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import asyncio
|
|
8
9
|
import json
|
|
9
10
|
import logging
|
|
10
11
|
from collections.abc import AsyncGenerator, Callable, Iterator, Mapping
|
|
12
|
+
from contextlib import asynccontextmanager
|
|
11
13
|
from os import PathLike
|
|
12
14
|
from pathlib import Path
|
|
13
15
|
from typing import Any, BinaryIO
|
|
@@ -20,6 +22,7 @@ from glaip_sdk.client._agent_payloads import (
|
|
|
20
22
|
AgentListResult,
|
|
21
23
|
AgentUpdateRequest,
|
|
22
24
|
)
|
|
25
|
+
from glaip_sdk.client.agent_runs import AgentRunsClient
|
|
23
26
|
from glaip_sdk.client.base import BaseClient
|
|
24
27
|
from glaip_sdk.client.mcps import MCPClient
|
|
25
28
|
from glaip_sdk.client.run_rendering import (
|
|
@@ -28,6 +31,7 @@ from glaip_sdk.client.run_rendering import (
|
|
|
28
31
|
)
|
|
29
32
|
from glaip_sdk.client.tools import ToolClient
|
|
30
33
|
from glaip_sdk.config.constants import (
|
|
34
|
+
AGENT_CONFIG_FIELDS,
|
|
31
35
|
DEFAULT_AGENT_FRAMEWORK,
|
|
32
36
|
DEFAULT_AGENT_RUN_TIMEOUT,
|
|
33
37
|
DEFAULT_AGENT_TYPE,
|
|
@@ -67,6 +71,19 @@ _MERGED_SEQUENCE_FIELDS = ("tools", "agents", "mcps")
|
|
|
67
71
|
_DEFAULT_METADATA_TYPE = "custom"
|
|
68
72
|
|
|
69
73
|
|
|
74
|
+
@asynccontextmanager
|
|
75
|
+
async def _async_timeout_guard(timeout_seconds: float | None) -> AsyncGenerator[None, None]:
|
|
76
|
+
"""Apply an asyncio timeout when a custom timeout is provided."""
|
|
77
|
+
if timeout_seconds is None:
|
|
78
|
+
yield
|
|
79
|
+
return
|
|
80
|
+
try:
|
|
81
|
+
async with asyncio.timeout(timeout_seconds):
|
|
82
|
+
yield
|
|
83
|
+
except asyncio.TimeoutError as exc:
|
|
84
|
+
raise httpx.TimeoutException(f"Request timed out after {timeout_seconds}s") from exc
|
|
85
|
+
|
|
86
|
+
|
|
70
87
|
def _normalise_sequence(value: Any) -> list[Any] | None:
|
|
71
88
|
"""Normalise optional sequence inputs to plain lists."""
|
|
72
89
|
if value is None:
|
|
@@ -193,19 +210,7 @@ def _extract_original_refs(raw_definition: dict) -> dict[str, list]:
|
|
|
193
210
|
|
|
194
211
|
def _build_cli_args(overrides_dict: dict) -> dict[str, Any]:
|
|
195
212
|
"""Build CLI args from overrides, filtering out None values."""
|
|
196
|
-
cli_args = {
|
|
197
|
-
key: overrides_dict.get(key)
|
|
198
|
-
for key in (
|
|
199
|
-
"name",
|
|
200
|
-
"instruction",
|
|
201
|
-
"model",
|
|
202
|
-
"tools",
|
|
203
|
-
"agents",
|
|
204
|
-
"mcps",
|
|
205
|
-
"timeout",
|
|
206
|
-
)
|
|
207
|
-
if overrides_dict.get(key) is not None
|
|
208
|
-
}
|
|
213
|
+
cli_args = {key: overrides_dict.get(key) for key in AGENT_CONFIG_FIELDS if overrides_dict.get(key) is not None}
|
|
209
214
|
|
|
210
215
|
# Normalize sequence fields
|
|
211
216
|
for field in _MERGED_SEQUENCE_FIELDS:
|
|
@@ -254,6 +259,7 @@ class AgentClient(BaseClient):
|
|
|
254
259
|
self._renderer_manager = AgentRunRenderingManager(logger)
|
|
255
260
|
self._tool_client: ToolClient | None = None
|
|
256
261
|
self._mcp_client: MCPClient | None = None
|
|
262
|
+
self._runs_client: "AgentRunsClient | None" = None
|
|
257
263
|
|
|
258
264
|
def list_agents(
|
|
259
265
|
self,
|
|
@@ -366,6 +372,11 @@ class AgentClient(BaseClient):
|
|
|
366
372
|
# Renderer delegation helpers
|
|
367
373
|
# ------------------------------------------------------------------ #
|
|
368
374
|
def _get_renderer_manager(self) -> AgentRunRenderingManager:
|
|
375
|
+
"""Get or create the renderer manager instance.
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
AgentRunRenderingManager instance.
|
|
379
|
+
"""
|
|
369
380
|
manager = getattr(self, "_renderer_manager", None)
|
|
370
381
|
if manager is None:
|
|
371
382
|
manager = AgentRunRenderingManager(logger)
|
|
@@ -373,6 +384,15 @@ class AgentClient(BaseClient):
|
|
|
373
384
|
return manager
|
|
374
385
|
|
|
375
386
|
def _create_renderer(self, renderer: RichStreamRenderer | str | None, **kwargs: Any) -> RichStreamRenderer:
|
|
387
|
+
"""Create or return a renderer instance.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
renderer: Renderer instance, string identifier, or None.
|
|
391
|
+
**kwargs: Additional keyword arguments (e.g., verbose).
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
RichStreamRenderer instance.
|
|
395
|
+
"""
|
|
376
396
|
manager = self._get_renderer_manager()
|
|
377
397
|
verbose = kwargs.get("verbose", False)
|
|
378
398
|
if isinstance(renderer, RichStreamRenderer) or hasattr(renderer, "on_start"):
|
|
@@ -387,6 +407,18 @@ class AgentClient(BaseClient):
|
|
|
387
407
|
agent_name: str | None,
|
|
388
408
|
meta: dict[str, Any],
|
|
389
409
|
) -> tuple[str, dict[str, Any], float | None, float | None]:
|
|
410
|
+
"""Process stream events from an HTTP response.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
stream_response: HTTP response stream.
|
|
414
|
+
renderer: Renderer to use for displaying events.
|
|
415
|
+
timeout_seconds: Timeout in seconds.
|
|
416
|
+
agent_name: Optional agent name.
|
|
417
|
+
meta: Metadata dictionary.
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
Tuple of (final_text, stats_usage, started_monotonic, finished_monotonic).
|
|
421
|
+
"""
|
|
390
422
|
manager = self._get_renderer_manager()
|
|
391
423
|
return manager.process_stream_events(
|
|
392
424
|
stream_response,
|
|
@@ -404,6 +436,18 @@ class AgentClient(BaseClient):
|
|
|
404
436
|
started_monotonic: float | None,
|
|
405
437
|
finished_monotonic: float | None,
|
|
406
438
|
) -> str:
|
|
439
|
+
"""Finalize the renderer and return the final text.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
renderer: Renderer to finalize.
|
|
443
|
+
final_text: Final text content.
|
|
444
|
+
stats_usage: Usage statistics dictionary.
|
|
445
|
+
started_monotonic: Start time (monotonic).
|
|
446
|
+
finished_monotonic: Finish time (monotonic).
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
Final text string.
|
|
450
|
+
"""
|
|
407
451
|
manager = self._get_renderer_manager()
|
|
408
452
|
return manager.finalize_renderer(
|
|
409
453
|
renderer,
|
|
@@ -414,11 +458,21 @@ class AgentClient(BaseClient):
|
|
|
414
458
|
)
|
|
415
459
|
|
|
416
460
|
def _get_tool_client(self) -> ToolClient:
|
|
461
|
+
"""Get or create the tool client instance.
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
ToolClient instance.
|
|
465
|
+
"""
|
|
417
466
|
if self._tool_client is None:
|
|
418
467
|
self._tool_client = ToolClient(parent_client=self)
|
|
419
468
|
return self._tool_client
|
|
420
469
|
|
|
421
470
|
def _get_mcp_client(self) -> MCPClient:
|
|
471
|
+
"""Get or create the MCP client instance.
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
MCPClient instance.
|
|
475
|
+
"""
|
|
422
476
|
if self._mcp_client is None:
|
|
423
477
|
self._mcp_client = MCPClient(parent_client=self)
|
|
424
478
|
return self._mcp_client
|
|
@@ -428,6 +482,15 @@ class AgentClient(BaseClient):
|
|
|
428
482
|
entry: Any,
|
|
429
483
|
fallback_iter: Iterator[Any] | None,
|
|
430
484
|
) -> tuple[str | None, str | None]:
|
|
485
|
+
"""Normalize a reference entry to extract ID and name.
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
entry: Reference entry (string, dict, or other).
|
|
489
|
+
fallback_iter: Optional iterator for fallback values.
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
Tuple of (entry_id, entry_name).
|
|
493
|
+
"""
|
|
431
494
|
entry_id: str | None = None
|
|
432
495
|
entry_name: str | None = None
|
|
433
496
|
|
|
@@ -464,6 +527,19 @@ class AgentClient(BaseClient):
|
|
|
464
527
|
label: str,
|
|
465
528
|
plural_label: str | None = None,
|
|
466
529
|
) -> list[str] | None:
|
|
530
|
+
"""Resolve a list of resource references to IDs.
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
items: List of resource references to resolve.
|
|
534
|
+
references: Optional list of reference objects for fallback.
|
|
535
|
+
fetch_by_id: Function to fetch resource by ID.
|
|
536
|
+
find_by_name: Function to find resources by name.
|
|
537
|
+
label: Singular label for error messages.
|
|
538
|
+
plural_label: Plural label for error messages.
|
|
539
|
+
|
|
540
|
+
Returns:
|
|
541
|
+
List of resolved resource IDs, or None if items is empty.
|
|
542
|
+
"""
|
|
467
543
|
if not items:
|
|
468
544
|
return None
|
|
469
545
|
|
|
@@ -495,6 +571,22 @@ class AgentClient(BaseClient):
|
|
|
495
571
|
singular: str,
|
|
496
572
|
plural: str,
|
|
497
573
|
) -> str:
|
|
574
|
+
"""Resolve a single resource reference to an ID.
|
|
575
|
+
|
|
576
|
+
Args:
|
|
577
|
+
entry: Resource reference to resolve.
|
|
578
|
+
fallback_iter: Optional iterator for fallback values.
|
|
579
|
+
fetch_by_id: Function to fetch resource by ID.
|
|
580
|
+
find_by_name: Function to find resources by name.
|
|
581
|
+
singular: Singular label for error messages.
|
|
582
|
+
plural: Plural label for error messages.
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
Resolved resource ID string.
|
|
586
|
+
|
|
587
|
+
Raises:
|
|
588
|
+
ValueError: If the resource cannot be resolved.
|
|
589
|
+
"""
|
|
498
590
|
entry_id, entry_name = self._normalise_reference_entry(entry, fallback_iter)
|
|
499
591
|
|
|
500
592
|
validated_id = self._validate_resource_id(fetch_by_id, entry_id)
|
|
@@ -511,6 +603,14 @@ class AgentClient(BaseClient):
|
|
|
511
603
|
|
|
512
604
|
@staticmethod
|
|
513
605
|
def _coerce_reference_value(entry: Any) -> str:
|
|
606
|
+
"""Coerce a reference entry to a string value.
|
|
607
|
+
|
|
608
|
+
Args:
|
|
609
|
+
entry: Reference entry (dict, string, or other).
|
|
610
|
+
|
|
611
|
+
Returns:
|
|
612
|
+
String representation of the reference.
|
|
613
|
+
"""
|
|
514
614
|
if isinstance(entry, dict):
|
|
515
615
|
if entry.get("id"):
|
|
516
616
|
return str(entry["id"])
|
|
@@ -520,6 +620,15 @@ class AgentClient(BaseClient):
|
|
|
520
620
|
|
|
521
621
|
@staticmethod
|
|
522
622
|
def _validate_resource_id(fetch_by_id: Callable[[str], Any], candidate_id: str | None) -> str | None:
|
|
623
|
+
"""Validate a resource ID by attempting to fetch it.
|
|
624
|
+
|
|
625
|
+
Args:
|
|
626
|
+
fetch_by_id: Function to fetch resource by ID.
|
|
627
|
+
candidate_id: Candidate ID to validate.
|
|
628
|
+
|
|
629
|
+
Returns:
|
|
630
|
+
Validated ID if found, None otherwise.
|
|
631
|
+
"""
|
|
523
632
|
if not candidate_id:
|
|
524
633
|
return None
|
|
525
634
|
try:
|
|
@@ -535,6 +644,20 @@ class AgentClient(BaseClient):
|
|
|
535
644
|
singular: str,
|
|
536
645
|
plural: str,
|
|
537
646
|
) -> tuple[str, bool]:
|
|
647
|
+
"""Resolve a resource by name to an ID.
|
|
648
|
+
|
|
649
|
+
Args:
|
|
650
|
+
find_by_name: Function to find resources by name.
|
|
651
|
+
entry_name: Name of the resource to find.
|
|
652
|
+
singular: Singular label for error messages.
|
|
653
|
+
plural: Plural label for error messages.
|
|
654
|
+
|
|
655
|
+
Returns:
|
|
656
|
+
Tuple of (resolved_id, success).
|
|
657
|
+
|
|
658
|
+
Raises:
|
|
659
|
+
ValueError: If resource not found or multiple matches exist.
|
|
660
|
+
"""
|
|
538
661
|
try:
|
|
539
662
|
matches = find_by_name(entry_name)
|
|
540
663
|
except Exception:
|
|
@@ -555,6 +678,15 @@ class AgentClient(BaseClient):
|
|
|
555
678
|
tools: list[Any] | None,
|
|
556
679
|
references: list[Any] | None = None,
|
|
557
680
|
) -> list[str] | None:
|
|
681
|
+
"""Resolve tool references to IDs.
|
|
682
|
+
|
|
683
|
+
Args:
|
|
684
|
+
tools: List of tool references to resolve.
|
|
685
|
+
references: Optional list of reference objects for fallback.
|
|
686
|
+
|
|
687
|
+
Returns:
|
|
688
|
+
List of resolved tool IDs, or None if tools is empty.
|
|
689
|
+
"""
|
|
558
690
|
tool_client = self._get_tool_client()
|
|
559
691
|
return self._resolve_resource_ids(
|
|
560
692
|
tools,
|
|
@@ -570,6 +702,15 @@ class AgentClient(BaseClient):
|
|
|
570
702
|
agents: list[Any] | None,
|
|
571
703
|
references: list[Any] | None = None,
|
|
572
704
|
) -> list[str] | None:
|
|
705
|
+
"""Resolve agent references to IDs.
|
|
706
|
+
|
|
707
|
+
Args:
|
|
708
|
+
agents: List of agent references to resolve.
|
|
709
|
+
references: Optional list of reference objects for fallback.
|
|
710
|
+
|
|
711
|
+
Returns:
|
|
712
|
+
List of resolved agent IDs, or None if agents is empty.
|
|
713
|
+
"""
|
|
573
714
|
return self._resolve_resource_ids(
|
|
574
715
|
agents,
|
|
575
716
|
references,
|
|
@@ -584,6 +725,15 @@ class AgentClient(BaseClient):
|
|
|
584
725
|
mcps: list[Any] | None,
|
|
585
726
|
references: list[Any] | None = None,
|
|
586
727
|
) -> list[str] | None:
|
|
728
|
+
"""Resolve MCP references to IDs.
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
mcps: List of MCP references to resolve.
|
|
732
|
+
references: Optional list of reference objects for fallback.
|
|
733
|
+
|
|
734
|
+
Returns:
|
|
735
|
+
List of resolved MCP IDs, or None if mcps is empty.
|
|
736
|
+
"""
|
|
587
737
|
mcp_client = self._get_mcp_client()
|
|
588
738
|
return self._resolve_resource_ids(
|
|
589
739
|
mcps,
|
|
@@ -1028,7 +1178,7 @@ class AgentClient(BaseClient):
|
|
|
1028
1178
|
message: str,
|
|
1029
1179
|
files: list[str | BinaryIO] | None = None,
|
|
1030
1180
|
*,
|
|
1031
|
-
|
|
1181
|
+
request_timeout: float | None = None,
|
|
1032
1182
|
**kwargs,
|
|
1033
1183
|
) -> AsyncGenerator[dict, None]:
|
|
1034
1184
|
"""Async run an agent with a message, yielding streaming JSON chunks.
|
|
@@ -1037,7 +1187,7 @@ class AgentClient(BaseClient):
|
|
|
1037
1187
|
agent_id: ID of the agent to run
|
|
1038
1188
|
message: Message to send to the agent
|
|
1039
1189
|
files: Optional list of files to include
|
|
1040
|
-
|
|
1190
|
+
request_timeout: Optional request timeout in seconds (defaults to client timeout)
|
|
1041
1191
|
**kwargs: Additional arguments (chat_history, pii_mapping, etc.)
|
|
1042
1192
|
|
|
1043
1193
|
Yields:
|
|
@@ -1048,18 +1198,22 @@ class AgentClient(BaseClient):
|
|
|
1048
1198
|
httpx.TimeoutException: When general timeout occurs
|
|
1049
1199
|
Exception: For other unexpected errors
|
|
1050
1200
|
"""
|
|
1201
|
+
# Derive timeout values for request/control flow
|
|
1202
|
+
legacy_timeout = kwargs.get("timeout")
|
|
1203
|
+
http_timeout_override = request_timeout if request_timeout is not None else legacy_timeout
|
|
1204
|
+
http_timeout = http_timeout_override or self.timeout
|
|
1205
|
+
|
|
1051
1206
|
# Prepare request data
|
|
1052
1207
|
payload, data_payload, files_payload, headers = self._prepare_request_data(message, files, **kwargs)
|
|
1053
1208
|
|
|
1054
1209
|
# Create async client configuration
|
|
1055
|
-
async_client_config = self._create_async_client_config(
|
|
1210
|
+
async_client_config = self._create_async_client_config(http_timeout_override, headers)
|
|
1056
1211
|
|
|
1057
1212
|
# Get execution timeout for streaming control
|
|
1058
1213
|
timeout_seconds = kwargs.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
|
|
1059
1214
|
agent_name = kwargs.get("agent_name")
|
|
1060
1215
|
|
|
1061
|
-
|
|
1062
|
-
# Create async client and stream response
|
|
1216
|
+
async def _chunk_stream() -> AsyncGenerator[dict, None]:
|
|
1063
1217
|
async with httpx.AsyncClient(**async_client_config) as async_client:
|
|
1064
1218
|
async for chunk in self._stream_agent_response(
|
|
1065
1219
|
async_client,
|
|
@@ -1073,7 +1227,17 @@ class AgentClient(BaseClient):
|
|
|
1073
1227
|
):
|
|
1074
1228
|
yield chunk
|
|
1075
1229
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1230
|
+
async with _async_timeout_guard(http_timeout):
|
|
1231
|
+
async for chunk in _chunk_stream():
|
|
1232
|
+
yield chunk
|
|
1233
|
+
|
|
1234
|
+
@property
|
|
1235
|
+
def runs(self) -> "AgentRunsClient":
|
|
1236
|
+
"""Get the agent runs client."""
|
|
1237
|
+
if self._runs_client is None:
|
|
1238
|
+
# Import here to avoid circular dependency with client.main
|
|
1239
|
+
from glaip_sdk.client.main import _build_shared_config
|
|
1240
|
+
|
|
1241
|
+
shared_config = _build_shared_config(self)
|
|
1242
|
+
self._runs_client = AgentRunsClient(**shared_config)
|
|
1243
|
+
return self._runs_client
|
glaip_sdk/client/main.py
CHANGED
|
@@ -14,6 +14,23 @@ from glaip_sdk.client.tools import ToolClient
|
|
|
14
14
|
from glaip_sdk.models import MCP, Agent, Tool
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def _build_shared_config(client: BaseClient) -> dict[str, Any]:
|
|
18
|
+
"""Build shared configuration dictionary for sub-clients.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
client: Base client instance.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Dictionary with shared configuration.
|
|
25
|
+
"""
|
|
26
|
+
return {
|
|
27
|
+
"parent_client": client,
|
|
28
|
+
"api_url": client.api_url,
|
|
29
|
+
"api_key": client.api_key,
|
|
30
|
+
"timeout": client._timeout,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
17
34
|
class Client(BaseClient):
|
|
18
35
|
"""Main client that composes all specialized clients and shares one HTTP session."""
|
|
19
36
|
|
|
@@ -25,12 +42,7 @@ class Client(BaseClient):
|
|
|
25
42
|
"""
|
|
26
43
|
super().__init__(**kwargs)
|
|
27
44
|
# Share the single httpx.Client + config with sub-clients
|
|
28
|
-
shared_config =
|
|
29
|
-
"parent_client": self,
|
|
30
|
-
"api_url": self.api_url,
|
|
31
|
-
"api_key": self.api_key,
|
|
32
|
-
"timeout": self._timeout,
|
|
33
|
-
}
|
|
45
|
+
shared_config = _build_shared_config(self)
|
|
34
46
|
self.agents = AgentClient(**shared_config)
|
|
35
47
|
self.tools = ToolClient(**shared_config)
|
|
36
48
|
self.mcps = MCPClient(**shared_config)
|
|
@@ -217,6 +229,11 @@ class Client(BaseClient):
|
|
|
217
229
|
|
|
218
230
|
@timeout.setter
|
|
219
231
|
def timeout(self, value: float) -> None: # type: ignore[override]
|
|
232
|
+
"""Set the client timeout and propagate to sub-clients.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
value: Timeout value in seconds.
|
|
236
|
+
"""
|
|
220
237
|
# Rebuild the root http client
|
|
221
238
|
BaseClient.timeout.fset(self, value) # call parent setter
|
|
222
239
|
# Propagate the new session to sub-clients so they don't hold a closed client
|
glaip_sdk/client/mcps.py
CHANGED
|
@@ -14,7 +14,7 @@ from glaip_sdk.config.constants import (
|
|
|
14
14
|
DEFAULT_MCP_TYPE,
|
|
15
15
|
)
|
|
16
16
|
from glaip_sdk.models import MCP
|
|
17
|
-
from glaip_sdk.utils.client_utils import create_model_instances, find_by_name
|
|
17
|
+
from glaip_sdk.utils.client_utils import add_kwargs_to_payload, create_model_instances, find_by_name
|
|
18
18
|
|
|
19
19
|
# API endpoints
|
|
20
20
|
MCPS_ENDPOINT = "/mcps/"
|
|
@@ -147,9 +147,7 @@ class MCPClient(BaseClient):
|
|
|
147
147
|
|
|
148
148
|
# Add any other kwargs (excluding already handled ones)
|
|
149
149
|
excluded_keys = {"type"} # type is handled above
|
|
150
|
-
|
|
151
|
-
if key not in excluded_keys:
|
|
152
|
-
payload[key] = value
|
|
150
|
+
add_kwargs_to_payload(payload, kwargs, excluded_keys)
|
|
153
151
|
|
|
154
152
|
return payload
|
|
155
153
|
|
|
@@ -124,6 +124,11 @@ class AgentRunRenderingManager:
|
|
|
124
124
|
renderer.on_start(meta)
|
|
125
125
|
|
|
126
126
|
def _create_silent_renderer(self) -> RichStreamRenderer:
|
|
127
|
+
"""Create a silent renderer that outputs to a string buffer.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
RichStreamRenderer configured for silent output.
|
|
131
|
+
"""
|
|
127
132
|
silent_config = RendererConfig(
|
|
128
133
|
live=False,
|
|
129
134
|
persist_live=False,
|
|
@@ -136,6 +141,11 @@ class AgentRunRenderingManager:
|
|
|
136
141
|
)
|
|
137
142
|
|
|
138
143
|
def _create_minimal_renderer(self) -> RichStreamRenderer:
|
|
144
|
+
"""Create a minimal renderer with reduced output.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
RichStreamRenderer configured for minimal output.
|
|
148
|
+
"""
|
|
139
149
|
minimal_config = RendererConfig(
|
|
140
150
|
live=False,
|
|
141
151
|
persist_live=False,
|
|
@@ -148,6 +158,11 @@ class AgentRunRenderingManager:
|
|
|
148
158
|
)
|
|
149
159
|
|
|
150
160
|
def _create_verbose_renderer(self) -> RichStreamRenderer:
|
|
161
|
+
"""Create a verbose renderer with detailed output.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
RichStreamRenderer configured for verbose output.
|
|
165
|
+
"""
|
|
151
166
|
verbose_config = RendererConfig(
|
|
152
167
|
live=False,
|
|
153
168
|
append_finished_snapshots=False,
|
|
@@ -159,6 +174,14 @@ class AgentRunRenderingManager:
|
|
|
159
174
|
)
|
|
160
175
|
|
|
161
176
|
def _create_default_renderer(self, verbose: bool) -> RichStreamRenderer:
|
|
177
|
+
"""Create the default renderer based on verbosity.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
verbose: Whether to create a verbose renderer.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
RichStreamRenderer instance.
|
|
184
|
+
"""
|
|
162
185
|
if verbose:
|
|
163
186
|
return self._create_verbose_renderer()
|
|
164
187
|
default_config = RendererConfig()
|
|
@@ -214,12 +237,27 @@ class AgentRunRenderingManager:
|
|
|
214
237
|
meta: dict[str, Any],
|
|
215
238
|
renderer: RichStreamRenderer,
|
|
216
239
|
) -> None:
|
|
240
|
+
"""Capture request ID from response headers and update metadata.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
stream_response: HTTP response stream.
|
|
244
|
+
meta: Metadata dictionary to update.
|
|
245
|
+
renderer: Renderer instance.
|
|
246
|
+
"""
|
|
217
247
|
req_id = stream_response.headers.get("x-request-id") or stream_response.headers.get("x-run-id")
|
|
218
248
|
if req_id:
|
|
219
249
|
meta["run_id"] = req_id
|
|
220
250
|
renderer.on_start(meta)
|
|
221
251
|
|
|
222
252
|
def _maybe_start_timer(self, event: dict[str, Any]) -> float | None:
|
|
253
|
+
"""Start timing if this is a content-bearing event.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
event: Event dictionary.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Monotonic time if timer should start, None otherwise.
|
|
260
|
+
"""
|
|
223
261
|
try:
|
|
224
262
|
ev = json.loads(event["data"])
|
|
225
263
|
except json.JSONDecodeError:
|
|
@@ -237,6 +275,18 @@ class AgentRunRenderingManager:
|
|
|
237
275
|
stats_usage: dict[str, Any],
|
|
238
276
|
meta: dict[str, Any],
|
|
239
277
|
) -> tuple[str, dict[str, Any]]:
|
|
278
|
+
"""Process a single streaming event.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
event: Event dictionary.
|
|
282
|
+
renderer: Renderer instance.
|
|
283
|
+
final_text: Accumulated text so far.
|
|
284
|
+
stats_usage: Usage statistics dictionary.
|
|
285
|
+
meta: Metadata dictionary.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Tuple of (updated_final_text, updated_stats_usage).
|
|
289
|
+
"""
|
|
240
290
|
try:
|
|
241
291
|
ev = json.loads(event["data"])
|
|
242
292
|
except json.JSONDecodeError:
|
|
@@ -292,6 +342,15 @@ class AgentRunRenderingManager:
|
|
|
292
342
|
return None
|
|
293
343
|
|
|
294
344
|
def _handle_content_event(self, ev: dict[str, Any], final_text: str) -> str:
|
|
345
|
+
"""Handle a content event and update final text.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
ev: Event dictionary.
|
|
349
|
+
final_text: Current accumulated text.
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
Updated final text.
|
|
353
|
+
"""
|
|
295
354
|
content = ev.get("content", "")
|
|
296
355
|
if not content.startswith("Artifact received:"):
|
|
297
356
|
return content
|
|
@@ -303,6 +362,13 @@ class AgentRunRenderingManager:
|
|
|
303
362
|
meta: dict[str, Any],
|
|
304
363
|
renderer: RichStreamRenderer,
|
|
305
364
|
) -> None:
|
|
365
|
+
"""Handle a run_info event and update metadata.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
ev: Event dictionary.
|
|
369
|
+
meta: Metadata dictionary to update.
|
|
370
|
+
renderer: Renderer instance.
|
|
371
|
+
"""
|
|
306
372
|
if ev.get("model"):
|
|
307
373
|
meta["model"] = ev["model"]
|
|
308
374
|
renderer.on_start(meta)
|
glaip_sdk/client/tools.py
CHANGED
|
@@ -18,6 +18,7 @@ from glaip_sdk.config.constants import (
|
|
|
18
18
|
)
|
|
19
19
|
from glaip_sdk.models import Tool
|
|
20
20
|
from glaip_sdk.utils.client_utils import (
|
|
21
|
+
add_kwargs_to_payload,
|
|
21
22
|
create_model_instances,
|
|
22
23
|
find_by_name,
|
|
23
24
|
)
|
|
@@ -200,9 +201,7 @@ class ToolClient(BaseClient):
|
|
|
200
201
|
|
|
201
202
|
# Add any other kwargs (excluding already handled ones)
|
|
202
203
|
excluded_keys = {"tags", "version"}
|
|
203
|
-
|
|
204
|
-
if key not in excluded_keys:
|
|
205
|
-
payload[key] = value
|
|
204
|
+
add_kwargs_to_payload(payload, kwargs, excluded_keys)
|
|
206
205
|
|
|
207
206
|
return payload
|
|
208
207
|
|
glaip_sdk/config/constants.py
CHANGED
|
@@ -39,3 +39,14 @@ DEFAULT_MCP_TRANSPORT = "stdio"
|
|
|
39
39
|
|
|
40
40
|
# Default error messages
|
|
41
41
|
DEFAULT_ERROR_MESSAGE = "Unknown error"
|
|
42
|
+
|
|
43
|
+
# Agent configuration fields used for CLI args and payload building
|
|
44
|
+
AGENT_CONFIG_FIELDS = (
|
|
45
|
+
"name",
|
|
46
|
+
"instruction",
|
|
47
|
+
"model",
|
|
48
|
+
"tools",
|
|
49
|
+
"agents",
|
|
50
|
+
"mcps",
|
|
51
|
+
"timeout",
|
|
52
|
+
)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Models package for AIP SDK.
|
|
2
|
+
|
|
3
|
+
This package re-exports models from the legacy models.py file for backward compatibility.
|
|
4
|
+
|
|
5
|
+
Authors:
|
|
6
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import importlib.util
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
# Export new agent runs models first (no dependencies on legacy models)
|
|
14
|
+
from glaip_sdk.models.agent_runs import ( # noqa: F401
|
|
15
|
+
RunOutputChunk,
|
|
16
|
+
RunSummary,
|
|
17
|
+
RunsPage,
|
|
18
|
+
RunWithOutput,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Import from the parent models.py file
|
|
22
|
+
_parent_file = Path(__file__).parent.parent / "models.py"
|
|
23
|
+
if _parent_file.exists():
|
|
24
|
+
spec = importlib.util.spec_from_file_location("glaip_sdk.models_legacy", _parent_file)
|
|
25
|
+
models_legacy = importlib.util.module_from_spec(spec)
|
|
26
|
+
sys.modules["glaip_sdk.models_legacy"] = models_legacy
|
|
27
|
+
spec.loader.exec_module(models_legacy)
|
|
28
|
+
|
|
29
|
+
# Re-export all models
|
|
30
|
+
Agent = models_legacy.Agent
|
|
31
|
+
Tool = models_legacy.Tool
|
|
32
|
+
MCP = models_legacy.MCP
|
|
33
|
+
LanguageModelResponse = models_legacy.LanguageModelResponse
|
|
34
|
+
TTYRenderer = models_legacy.TTYRenderer
|
|
35
|
+
else:
|
|
36
|
+
# Fallback: try direct import (won't work if models/ exists)
|
|
37
|
+
# pragma: no cover - defensive fallback path, unlikely to execute
|
|
38
|
+
from glaip_sdk import models as models_legacy # type: ignore # pragma: no cover
|
|
39
|
+
|
|
40
|
+
Agent = models_legacy.Agent # pragma: no cover
|
|
41
|
+
Tool = models_legacy.Tool # pragma: no cover
|
|
42
|
+
MCP = models_legacy.MCP # pragma: no cover
|
|
43
|
+
LanguageModelResponse = models_legacy.LanguageModelResponse # pragma: no cover
|
|
44
|
+
TTYRenderer = models_legacy.TTYRenderer # pragma: no cover
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"Agent",
|
|
48
|
+
"Tool",
|
|
49
|
+
"MCP",
|
|
50
|
+
"LanguageModelResponse",
|
|
51
|
+
"TTYRenderer",
|
|
52
|
+
"RunSummary",
|
|
53
|
+
"RunsPage",
|
|
54
|
+
"RunWithOutput",
|
|
55
|
+
"RunOutputChunk",
|
|
56
|
+
]
|