glaip-sdk 0.1.3__py3-none-any.whl → 0.6.19__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/__init__.py +44 -4
- glaip_sdk/_version.py +9 -0
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1196 -0
- glaip_sdk/branding.py +13 -0
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/auth.py +254 -15
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents.py +213 -73
- glaip_sdk/cli/commands/common_config.py +104 -0
- glaip_sdk/cli/commands/configure.py +729 -113
- glaip_sdk/cli/commands/mcps.py +241 -72
- glaip_sdk/cli/commands/models.py +11 -5
- glaip_sdk/cli/commands/tools.py +49 -57
- glaip_sdk/cli/commands/transcripts.py +755 -0
- glaip_sdk/cli/config.py +48 -4
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +8 -0
- glaip_sdk/cli/core/__init__.py +79 -0
- glaip_sdk/cli/core/context.py +124 -0
- glaip_sdk/cli/core/output.py +851 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +35 -19
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +6 -3
- glaip_sdk/cli/main.py +241 -121
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/pager.py +9 -10
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/slash/__init__.py +0 -9
- glaip_sdk/cli/slash/accounts_controller.py +578 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +62 -21
- glaip_sdk/cli/slash/prompt.py +21 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +771 -140
- glaip_sdk/cli/slash/tui/__init__.py +9 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +876 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +255 -44
- glaip_sdk/cli/transcript/capture.py +27 -1
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/viewer.py +72 -499
- glaip_sdk/cli/update_notifier.py +14 -5
- glaip_sdk/cli/utils.py +243 -1252
- glaip_sdk/cli/validators.py +5 -6
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_agent_payloads.py +45 -9
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +291 -35
- glaip_sdk/client/base.py +1 -0
- glaip_sdk/client/main.py +19 -10
- glaip_sdk/client/mcps.py +122 -12
- glaip_sdk/client/run_rendering.py +466 -89
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +155 -10
- glaip_sdk/config/constants.py +11 -0
- glaip_sdk/hitl/__init__.py +15 -0
- glaip_sdk/hitl/local.py +151 -0
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +90 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +116 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +1 -13
- glaip_sdk/registry/__init__.py +55 -0
- glaip_sdk/registry/agent.py +164 -0
- glaip_sdk/registry/base.py +139 -0
- glaip_sdk/registry/mcp.py +253 -0
- glaip_sdk/registry/tool.py +232 -0
- glaip_sdk/rich_components.py +58 -2
- glaip_sdk/runner/__init__.py +59 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +112 -0
- glaip_sdk/runner/langgraph.py +870 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +435 -0
- glaip_sdk/utils/__init__.py +58 -12
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/bundler.py +267 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +39 -7
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +23 -15
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +0 -33
- glaip_sdk/utils/import_export.py +12 -7
- glaip_sdk/utils/import_resolver.py +492 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -1
- glaip_sdk/utils/rendering/formatting.py +5 -30
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +1 -0
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -47
- glaip_sdk/utils/rendering/renderer/base.py +275 -1476
- glaip_sdk/utils/rendering/renderer/debug.py +26 -20
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +4 -12
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
- glaip_sdk/utils/rendering/state.py +204 -0
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -440
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/steps/manager.py +387 -0
- glaip_sdk/utils/rendering/timing.py +36 -0
- glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
- glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
- glaip_sdk/utils/resource_refs.py +25 -13
- glaip_sdk/utils/runtime_config.py +425 -0
- glaip_sdk/utils/serialization.py +18 -0
- glaip_sdk/utils/sync.py +142 -0
- glaip_sdk/utils/tool_detection.py +33 -0
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- glaip_sdk/utils/validation.py +16 -24
- {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.19.dist-info}/METADATA +56 -21
- glaip_sdk-0.6.19.dist-info/RECORD +163 -0
- {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.19.dist-info}/WHEEL +2 -1
- glaip_sdk-0.6.19.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.6.19.dist-info/top_level.txt +1 -0
- glaip_sdk/models.py +0 -240
- glaip_sdk-0.1.3.dist-info/RECORD +0 -83
- glaip_sdk-0.1.3.dist-info/entry_points.txt +0 -3
glaip_sdk/client/agents.py
CHANGED
|
@@ -3,31 +3,37 @@
|
|
|
3
3
|
|
|
4
4
|
Authors:
|
|
5
5
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
6
7
|
"""
|
|
7
8
|
|
|
9
|
+
import asyncio
|
|
8
10
|
import json
|
|
9
11
|
import logging
|
|
10
12
|
from collections.abc import AsyncGenerator, Callable, Iterator, Mapping
|
|
13
|
+
from contextlib import asynccontextmanager
|
|
11
14
|
from os import PathLike
|
|
12
15
|
from pathlib import Path
|
|
13
16
|
from typing import Any, BinaryIO
|
|
14
17
|
|
|
15
18
|
import httpx
|
|
16
|
-
|
|
19
|
+
from glaip_sdk.agents import Agent
|
|
17
20
|
from glaip_sdk.client._agent_payloads import (
|
|
18
21
|
AgentCreateRequest,
|
|
19
22
|
AgentListParams,
|
|
20
23
|
AgentListResult,
|
|
21
24
|
AgentUpdateRequest,
|
|
22
25
|
)
|
|
26
|
+
from glaip_sdk.client.agent_runs import AgentRunsClient
|
|
23
27
|
from glaip_sdk.client.base import BaseClient
|
|
24
28
|
from glaip_sdk.client.mcps import MCPClient
|
|
25
29
|
from glaip_sdk.client.run_rendering import (
|
|
26
30
|
AgentRunRenderingManager,
|
|
27
31
|
compute_timeout_seconds,
|
|
28
32
|
)
|
|
33
|
+
from glaip_sdk.client.shared import build_shared_config
|
|
29
34
|
from glaip_sdk.client.tools import ToolClient
|
|
30
35
|
from glaip_sdk.config.constants import (
|
|
36
|
+
AGENT_CONFIG_FIELDS,
|
|
31
37
|
DEFAULT_AGENT_FRAMEWORK,
|
|
32
38
|
DEFAULT_AGENT_RUN_TIMEOUT,
|
|
33
39
|
DEFAULT_AGENT_TYPE,
|
|
@@ -35,7 +41,7 @@ from glaip_sdk.config.constants import (
|
|
|
35
41
|
DEFAULT_MODEL,
|
|
36
42
|
)
|
|
37
43
|
from glaip_sdk.exceptions import NotFoundError, ValidationError
|
|
38
|
-
from glaip_sdk.models import
|
|
44
|
+
from glaip_sdk.models import AgentResponse
|
|
39
45
|
from glaip_sdk.payload_schemas.agent import list_server_only_fields
|
|
40
46
|
from glaip_sdk.utils.agent_config import normalize_agent_config_for_import
|
|
41
47
|
from glaip_sdk.utils.client_utils import (
|
|
@@ -67,6 +73,21 @@ _MERGED_SEQUENCE_FIELDS = ("tools", "agents", "mcps")
|
|
|
67
73
|
_DEFAULT_METADATA_TYPE = "custom"
|
|
68
74
|
|
|
69
75
|
|
|
76
|
+
@asynccontextmanager
|
|
77
|
+
async def _async_timeout_guard(
|
|
78
|
+
timeout_seconds: float | None,
|
|
79
|
+
) -> AsyncGenerator[None, None]:
|
|
80
|
+
"""Apply an asyncio timeout when a custom timeout is provided."""
|
|
81
|
+
if timeout_seconds is None:
|
|
82
|
+
yield
|
|
83
|
+
return
|
|
84
|
+
try:
|
|
85
|
+
async with asyncio.timeout(timeout_seconds):
|
|
86
|
+
yield
|
|
87
|
+
except asyncio.TimeoutError as exc:
|
|
88
|
+
raise httpx.TimeoutException(f"Request timed out after {timeout_seconds}s") from exc
|
|
89
|
+
|
|
90
|
+
|
|
70
91
|
def _normalise_sequence(value: Any) -> list[Any] | None:
|
|
71
92
|
"""Normalise optional sequence inputs to plain lists."""
|
|
72
93
|
if value is None:
|
|
@@ -193,19 +214,7 @@ def _extract_original_refs(raw_definition: dict) -> dict[str, list]:
|
|
|
193
214
|
|
|
194
215
|
def _build_cli_args(overrides_dict: dict) -> dict[str, Any]:
|
|
195
216
|
"""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
|
-
}
|
|
217
|
+
cli_args = {key: overrides_dict.get(key) for key in AGENT_CONFIG_FIELDS if overrides_dict.get(key) is not None}
|
|
209
218
|
|
|
210
219
|
# Normalize sequence fields
|
|
211
220
|
for field in _MERGED_SEQUENCE_FIELDS:
|
|
@@ -254,6 +263,7 @@ class AgentClient(BaseClient):
|
|
|
254
263
|
self._renderer_manager = AgentRunRenderingManager(logger)
|
|
255
264
|
self._tool_client: ToolClient | None = None
|
|
256
265
|
self._mcp_client: MCPClient | None = None
|
|
266
|
+
self._runs_client: AgentRunsClient | None = None
|
|
257
267
|
|
|
258
268
|
def list_agents(
|
|
259
269
|
self,
|
|
@@ -352,7 +362,8 @@ class AgentClient(BaseClient):
|
|
|
352
362
|
status_code=404,
|
|
353
363
|
)
|
|
354
364
|
|
|
355
|
-
|
|
365
|
+
response = AgentResponse(**data)
|
|
366
|
+
return Agent.from_response(response, client=self)
|
|
356
367
|
|
|
357
368
|
def find_agents(self, name: str | None = None) -> list[Agent]:
|
|
358
369
|
"""Find agents by name."""
|
|
@@ -366,6 +377,11 @@ class AgentClient(BaseClient):
|
|
|
366
377
|
# Renderer delegation helpers
|
|
367
378
|
# ------------------------------------------------------------------ #
|
|
368
379
|
def _get_renderer_manager(self) -> AgentRunRenderingManager:
|
|
380
|
+
"""Get or create the renderer manager instance.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
AgentRunRenderingManager instance.
|
|
384
|
+
"""
|
|
369
385
|
manager = getattr(self, "_renderer_manager", None)
|
|
370
386
|
if manager is None:
|
|
371
387
|
manager = AgentRunRenderingManager(logger)
|
|
@@ -373,6 +389,15 @@ class AgentClient(BaseClient):
|
|
|
373
389
|
return manager
|
|
374
390
|
|
|
375
391
|
def _create_renderer(self, renderer: RichStreamRenderer | str | None, **kwargs: Any) -> RichStreamRenderer:
|
|
392
|
+
"""Create or return a renderer instance.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
renderer: Renderer instance, string identifier, or None.
|
|
396
|
+
**kwargs: Additional keyword arguments (e.g., verbose).
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
RichStreamRenderer instance.
|
|
400
|
+
"""
|
|
376
401
|
manager = self._get_renderer_manager()
|
|
377
402
|
verbose = kwargs.get("verbose", False)
|
|
378
403
|
if isinstance(renderer, RichStreamRenderer) or hasattr(renderer, "on_start"):
|
|
@@ -387,6 +412,18 @@ class AgentClient(BaseClient):
|
|
|
387
412
|
agent_name: str | None,
|
|
388
413
|
meta: dict[str, Any],
|
|
389
414
|
) -> tuple[str, dict[str, Any], float | None, float | None]:
|
|
415
|
+
"""Process stream events from an HTTP response.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
stream_response: HTTP response stream.
|
|
419
|
+
renderer: Renderer to use for displaying events.
|
|
420
|
+
timeout_seconds: Timeout in seconds.
|
|
421
|
+
agent_name: Optional agent name.
|
|
422
|
+
meta: Metadata dictionary.
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
Tuple of (final_text, stats_usage, started_monotonic, finished_monotonic).
|
|
426
|
+
"""
|
|
390
427
|
manager = self._get_renderer_manager()
|
|
391
428
|
return manager.process_stream_events(
|
|
392
429
|
stream_response,
|
|
@@ -404,21 +441,41 @@ class AgentClient(BaseClient):
|
|
|
404
441
|
started_monotonic: float | None,
|
|
405
442
|
finished_monotonic: float | None,
|
|
406
443
|
) -> str:
|
|
444
|
+
"""Finalize the renderer and return the final response text.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
renderer: Renderer to finalize.
|
|
448
|
+
final_text: Final text content.
|
|
449
|
+
stats_usage: Usage statistics dictionary.
|
|
450
|
+
started_monotonic: Start time (monotonic).
|
|
451
|
+
finished_monotonic: Finish time (monotonic).
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
Final text string.
|
|
455
|
+
"""
|
|
456
|
+
from glaip_sdk.client.run_rendering import finalize_render_manager # noqa: PLC0415
|
|
457
|
+
|
|
407
458
|
manager = self._get_renderer_manager()
|
|
408
|
-
return
|
|
409
|
-
renderer,
|
|
410
|
-
final_text,
|
|
411
|
-
stats_usage,
|
|
412
|
-
started_monotonic,
|
|
413
|
-
finished_monotonic,
|
|
459
|
+
return finalize_render_manager(
|
|
460
|
+
manager, renderer, final_text, stats_usage, started_monotonic, finished_monotonic
|
|
414
461
|
)
|
|
415
462
|
|
|
416
463
|
def _get_tool_client(self) -> ToolClient:
|
|
464
|
+
"""Get or create the tool client instance.
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
ToolClient instance.
|
|
468
|
+
"""
|
|
417
469
|
if self._tool_client is None:
|
|
418
470
|
self._tool_client = ToolClient(parent_client=self)
|
|
419
471
|
return self._tool_client
|
|
420
472
|
|
|
421
473
|
def _get_mcp_client(self) -> MCPClient:
|
|
474
|
+
"""Get or create the MCP client instance.
|
|
475
|
+
|
|
476
|
+
Returns:
|
|
477
|
+
MCPClient instance.
|
|
478
|
+
"""
|
|
422
479
|
if self._mcp_client is None:
|
|
423
480
|
self._mcp_client = MCPClient(parent_client=self)
|
|
424
481
|
return self._mcp_client
|
|
@@ -428,6 +485,15 @@ class AgentClient(BaseClient):
|
|
|
428
485
|
entry: Any,
|
|
429
486
|
fallback_iter: Iterator[Any] | None,
|
|
430
487
|
) -> tuple[str | None, str | None]:
|
|
488
|
+
"""Normalize a reference entry to extract ID and name.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
entry: Reference entry (string, dict, or other).
|
|
492
|
+
fallback_iter: Optional iterator for fallback values.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
Tuple of (entry_id, entry_name).
|
|
496
|
+
"""
|
|
431
497
|
entry_id: str | None = None
|
|
432
498
|
entry_name: str | None = None
|
|
433
499
|
|
|
@@ -464,6 +530,19 @@ class AgentClient(BaseClient):
|
|
|
464
530
|
label: str,
|
|
465
531
|
plural_label: str | None = None,
|
|
466
532
|
) -> list[str] | None:
|
|
533
|
+
"""Resolve a list of resource references to IDs.
|
|
534
|
+
|
|
535
|
+
Args:
|
|
536
|
+
items: List of resource references to resolve.
|
|
537
|
+
references: Optional list of reference objects for fallback.
|
|
538
|
+
fetch_by_id: Function to fetch resource by ID.
|
|
539
|
+
find_by_name: Function to find resources by name.
|
|
540
|
+
label: Singular label for error messages.
|
|
541
|
+
plural_label: Plural label for error messages.
|
|
542
|
+
|
|
543
|
+
Returns:
|
|
544
|
+
List of resolved resource IDs, or None if items is empty.
|
|
545
|
+
"""
|
|
467
546
|
if not items:
|
|
468
547
|
return None
|
|
469
548
|
|
|
@@ -495,6 +574,22 @@ class AgentClient(BaseClient):
|
|
|
495
574
|
singular: str,
|
|
496
575
|
plural: str,
|
|
497
576
|
) -> str:
|
|
577
|
+
"""Resolve a single resource reference to an ID.
|
|
578
|
+
|
|
579
|
+
Args:
|
|
580
|
+
entry: Resource reference to resolve.
|
|
581
|
+
fallback_iter: Optional iterator for fallback values.
|
|
582
|
+
fetch_by_id: Function to fetch resource by ID.
|
|
583
|
+
find_by_name: Function to find resources by name.
|
|
584
|
+
singular: Singular label for error messages.
|
|
585
|
+
plural: Plural label for error messages.
|
|
586
|
+
|
|
587
|
+
Returns:
|
|
588
|
+
Resolved resource ID string.
|
|
589
|
+
|
|
590
|
+
Raises:
|
|
591
|
+
ValueError: If the resource cannot be resolved.
|
|
592
|
+
"""
|
|
498
593
|
entry_id, entry_name = self._normalise_reference_entry(entry, fallback_iter)
|
|
499
594
|
|
|
500
595
|
validated_id = self._validate_resource_id(fetch_by_id, entry_id)
|
|
@@ -511,6 +606,14 @@ class AgentClient(BaseClient):
|
|
|
511
606
|
|
|
512
607
|
@staticmethod
|
|
513
608
|
def _coerce_reference_value(entry: Any) -> str:
|
|
609
|
+
"""Coerce a reference entry to a string value.
|
|
610
|
+
|
|
611
|
+
Args:
|
|
612
|
+
entry: Reference entry (dict, string, or other).
|
|
613
|
+
|
|
614
|
+
Returns:
|
|
615
|
+
String representation of the reference.
|
|
616
|
+
"""
|
|
514
617
|
if isinstance(entry, dict):
|
|
515
618
|
if entry.get("id"):
|
|
516
619
|
return str(entry["id"])
|
|
@@ -520,6 +623,15 @@ class AgentClient(BaseClient):
|
|
|
520
623
|
|
|
521
624
|
@staticmethod
|
|
522
625
|
def _validate_resource_id(fetch_by_id: Callable[[str], Any], candidate_id: str | None) -> str | None:
|
|
626
|
+
"""Validate a resource ID by attempting to fetch it.
|
|
627
|
+
|
|
628
|
+
Args:
|
|
629
|
+
fetch_by_id: Function to fetch resource by ID.
|
|
630
|
+
candidate_id: Candidate ID to validate.
|
|
631
|
+
|
|
632
|
+
Returns:
|
|
633
|
+
Validated ID if found, None otherwise.
|
|
634
|
+
"""
|
|
523
635
|
if not candidate_id:
|
|
524
636
|
return None
|
|
525
637
|
try:
|
|
@@ -535,6 +647,20 @@ class AgentClient(BaseClient):
|
|
|
535
647
|
singular: str,
|
|
536
648
|
plural: str,
|
|
537
649
|
) -> tuple[str, bool]:
|
|
650
|
+
"""Resolve a resource by name to an ID.
|
|
651
|
+
|
|
652
|
+
Args:
|
|
653
|
+
find_by_name: Function to find resources by name.
|
|
654
|
+
entry_name: Name of the resource to find.
|
|
655
|
+
singular: Singular label for error messages.
|
|
656
|
+
plural: Plural label for error messages.
|
|
657
|
+
|
|
658
|
+
Returns:
|
|
659
|
+
Tuple of (resolved_id, success).
|
|
660
|
+
|
|
661
|
+
Raises:
|
|
662
|
+
ValueError: If resource not found or multiple matches exist.
|
|
663
|
+
"""
|
|
538
664
|
try:
|
|
539
665
|
matches = find_by_name(entry_name)
|
|
540
666
|
except Exception:
|
|
@@ -555,6 +681,15 @@ class AgentClient(BaseClient):
|
|
|
555
681
|
tools: list[Any] | None,
|
|
556
682
|
references: list[Any] | None = None,
|
|
557
683
|
) -> list[str] | None:
|
|
684
|
+
"""Resolve tool references to IDs.
|
|
685
|
+
|
|
686
|
+
Args:
|
|
687
|
+
tools: List of tool references to resolve.
|
|
688
|
+
references: Optional list of reference objects for fallback.
|
|
689
|
+
|
|
690
|
+
Returns:
|
|
691
|
+
List of resolved tool IDs, or None if tools is empty.
|
|
692
|
+
"""
|
|
558
693
|
tool_client = self._get_tool_client()
|
|
559
694
|
return self._resolve_resource_ids(
|
|
560
695
|
tools,
|
|
@@ -570,6 +705,15 @@ class AgentClient(BaseClient):
|
|
|
570
705
|
agents: list[Any] | None,
|
|
571
706
|
references: list[Any] | None = None,
|
|
572
707
|
) -> list[str] | None:
|
|
708
|
+
"""Resolve agent references to IDs.
|
|
709
|
+
|
|
710
|
+
Args:
|
|
711
|
+
agents: List of agent references to resolve.
|
|
712
|
+
references: Optional list of reference objects for fallback.
|
|
713
|
+
|
|
714
|
+
Returns:
|
|
715
|
+
List of resolved agent IDs, or None if agents is empty.
|
|
716
|
+
"""
|
|
573
717
|
return self._resolve_resource_ids(
|
|
574
718
|
agents,
|
|
575
719
|
references,
|
|
@@ -584,6 +728,15 @@ class AgentClient(BaseClient):
|
|
|
584
728
|
mcps: list[Any] | None,
|
|
585
729
|
references: list[Any] | None = None,
|
|
586
730
|
) -> list[str] | None:
|
|
731
|
+
"""Resolve MCP references to IDs.
|
|
732
|
+
|
|
733
|
+
Args:
|
|
734
|
+
mcps: List of MCP references to resolve.
|
|
735
|
+
references: Optional list of reference objects for fallback.
|
|
736
|
+
|
|
737
|
+
Returns:
|
|
738
|
+
List of resolved MCP IDs, or None if mcps is empty.
|
|
739
|
+
"""
|
|
587
740
|
mcp_client = self._get_mcp_client()
|
|
588
741
|
return self._resolve_resource_ids(
|
|
589
742
|
mcps,
|
|
@@ -675,7 +828,8 @@ class AgentClient(BaseClient):
|
|
|
675
828
|
get_endpoint_fmt=f"{AGENTS_ENDPOINT}{{id}}",
|
|
676
829
|
json=payload_dict,
|
|
677
830
|
)
|
|
678
|
-
|
|
831
|
+
response = AgentResponse(**full_agent_data)
|
|
832
|
+
return Agent.from_response(response, client=self)
|
|
679
833
|
|
|
680
834
|
def create_agent(
|
|
681
835
|
self,
|
|
@@ -770,8 +924,9 @@ class AgentClient(BaseClient):
|
|
|
770
924
|
|
|
771
925
|
payload_dict = request.to_payload(current_agent)
|
|
772
926
|
|
|
773
|
-
|
|
774
|
-
|
|
927
|
+
api_response = self._request("PUT", f"/agents/{agent_id}", json=payload_dict)
|
|
928
|
+
response = AgentResponse(**api_response)
|
|
929
|
+
return Agent.from_response(response, client=self)
|
|
775
930
|
|
|
776
931
|
def update_agent(
|
|
777
932
|
self,
|
|
@@ -818,6 +973,62 @@ class AgentClient(BaseClient):
|
|
|
818
973
|
"""Delete an agent."""
|
|
819
974
|
self._request("DELETE", f"/agents/{agent_id}")
|
|
820
975
|
|
|
976
|
+
def upsert_agent(self, identifier: str | Agent, **kwargs) -> Agent:
|
|
977
|
+
"""Create or update an agent by instance, ID, or name.
|
|
978
|
+
|
|
979
|
+
Args:
|
|
980
|
+
identifier: Agent instance, ID (UUID string), or name
|
|
981
|
+
**kwargs: Agent configuration (instruction, description, tools, etc.)
|
|
982
|
+
|
|
983
|
+
Returns:
|
|
984
|
+
The created or updated agent.
|
|
985
|
+
|
|
986
|
+
Example:
|
|
987
|
+
>>> # By name (creates if not exists)
|
|
988
|
+
>>> agent = client.agents.upsert_agent(
|
|
989
|
+
... "hello_agent",
|
|
990
|
+
... instruction="You are a helpful assistant.",
|
|
991
|
+
... description="A friendly agent",
|
|
992
|
+
... )
|
|
993
|
+
>>> # By instance
|
|
994
|
+
>>> agent = client.agents.upsert_agent(existing_agent, description="Updated")
|
|
995
|
+
>>> # By ID
|
|
996
|
+
>>> agent = client.agents.upsert_agent("uuid-here", description="Updated")
|
|
997
|
+
"""
|
|
998
|
+
# Handle Agent instance
|
|
999
|
+
if isinstance(identifier, Agent):
|
|
1000
|
+
if identifier.id:
|
|
1001
|
+
logger.info("Updating agent by instance: %s", identifier.name)
|
|
1002
|
+
return self.update_agent(identifier.id, name=identifier.name, **kwargs)
|
|
1003
|
+
identifier = identifier.name
|
|
1004
|
+
|
|
1005
|
+
# Handle string (ID or name)
|
|
1006
|
+
if isinstance(identifier, str):
|
|
1007
|
+
# Check if it's a UUID
|
|
1008
|
+
if is_uuid(identifier):
|
|
1009
|
+
logger.info("Updating agent by ID: %s", identifier)
|
|
1010
|
+
return self.update_agent(identifier, **kwargs)
|
|
1011
|
+
|
|
1012
|
+
# It's a name - find or create
|
|
1013
|
+
return self._upsert_agent_by_name(identifier, **kwargs)
|
|
1014
|
+
|
|
1015
|
+
raise ValueError(f"Invalid identifier type: {type(identifier)}")
|
|
1016
|
+
|
|
1017
|
+
def _upsert_agent_by_name(self, name: str, **kwargs) -> Agent:
|
|
1018
|
+
"""Find agent by name and update, or create if not found."""
|
|
1019
|
+
existing = self.find_agents(name)
|
|
1020
|
+
|
|
1021
|
+
if len(existing) == 1:
|
|
1022
|
+
logger.info("Updating existing agent: %s", name)
|
|
1023
|
+
return self.update_agent(existing[0].id, name=name, **kwargs)
|
|
1024
|
+
|
|
1025
|
+
if len(existing) > 1:
|
|
1026
|
+
raise ValueError(f"Multiple agents found with name '{name}'")
|
|
1027
|
+
|
|
1028
|
+
# Create new agent
|
|
1029
|
+
logger.info("Creating new agent: %s", name)
|
|
1030
|
+
return self.create_agent(name=name, **kwargs)
|
|
1031
|
+
|
|
821
1032
|
def _prepare_sync_request_data(
|
|
822
1033
|
self,
|
|
823
1034
|
message: str,
|
|
@@ -882,9 +1093,32 @@ class AgentClient(BaseClient):
|
|
|
882
1093
|
tty: bool = False,
|
|
883
1094
|
*,
|
|
884
1095
|
renderer: RichStreamRenderer | str | None = "auto",
|
|
1096
|
+
runtime_config: dict[str, Any] | None = None,
|
|
885
1097
|
**kwargs,
|
|
886
1098
|
) -> str:
|
|
887
|
-
"""Run an agent with a message, streaming via a renderer.
|
|
1099
|
+
"""Run an agent with a message, streaming via a renderer.
|
|
1100
|
+
|
|
1101
|
+
Args:
|
|
1102
|
+
agent_id: The ID of the agent to run.
|
|
1103
|
+
message: The message to send to the agent.
|
|
1104
|
+
files: Optional list of files to include with the request.
|
|
1105
|
+
tty: Whether to enable TTY mode.
|
|
1106
|
+
renderer: Renderer for streaming output.
|
|
1107
|
+
runtime_config: Optional runtime configuration for tools, MCPs, and agents.
|
|
1108
|
+
Keys should be platform IDs. Example:
|
|
1109
|
+
{
|
|
1110
|
+
"tool_configs": {"tool-id": {"param": "value"}},
|
|
1111
|
+
"mcp_configs": {"mcp-id": {"setting": "on"}},
|
|
1112
|
+
"agent_config": {"planning": True},
|
|
1113
|
+
}
|
|
1114
|
+
**kwargs: Additional arguments to pass to the run API.
|
|
1115
|
+
|
|
1116
|
+
Returns:
|
|
1117
|
+
The agent's response as a string.
|
|
1118
|
+
"""
|
|
1119
|
+
# Include runtime_config in kwargs only when caller hasn't already provided it
|
|
1120
|
+
if runtime_config is not None and "runtime_config" not in kwargs:
|
|
1121
|
+
kwargs["runtime_config"] = runtime_config
|
|
888
1122
|
(
|
|
889
1123
|
payload,
|
|
890
1124
|
data_payload,
|
|
@@ -1028,7 +1262,8 @@ class AgentClient(BaseClient):
|
|
|
1028
1262
|
message: str,
|
|
1029
1263
|
files: list[str | BinaryIO] | None = None,
|
|
1030
1264
|
*,
|
|
1031
|
-
|
|
1265
|
+
request_timeout: float | None = None,
|
|
1266
|
+
runtime_config: dict[str, Any] | None = None,
|
|
1032
1267
|
**kwargs,
|
|
1033
1268
|
) -> AsyncGenerator[dict, None]:
|
|
1034
1269
|
"""Async run an agent with a message, yielding streaming JSON chunks.
|
|
@@ -1037,7 +1272,14 @@ class AgentClient(BaseClient):
|
|
|
1037
1272
|
agent_id: ID of the agent to run
|
|
1038
1273
|
message: Message to send to the agent
|
|
1039
1274
|
files: Optional list of files to include
|
|
1040
|
-
|
|
1275
|
+
request_timeout: Optional request timeout in seconds (defaults to client timeout)
|
|
1276
|
+
runtime_config: Optional runtime configuration for tools, MCPs, and agents.
|
|
1277
|
+
Keys should be platform IDs. Example:
|
|
1278
|
+
{
|
|
1279
|
+
"tool_configs": {"tool-id": {"param": "value"}},
|
|
1280
|
+
"mcp_configs": {"mcp-id": {"setting": "on"}},
|
|
1281
|
+
"agent_config": {"planning": True},
|
|
1282
|
+
}
|
|
1041
1283
|
**kwargs: Additional arguments (chat_history, pii_mapping, etc.)
|
|
1042
1284
|
|
|
1043
1285
|
Yields:
|
|
@@ -1048,18 +1290,25 @@ class AgentClient(BaseClient):
|
|
|
1048
1290
|
httpx.TimeoutException: When general timeout occurs
|
|
1049
1291
|
Exception: For other unexpected errors
|
|
1050
1292
|
"""
|
|
1293
|
+
# Include runtime_config in kwargs only when caller hasn't already provided it
|
|
1294
|
+
if runtime_config is not None and "runtime_config" not in kwargs:
|
|
1295
|
+
kwargs["runtime_config"] = runtime_config
|
|
1296
|
+
# Derive timeout values for request/control flow
|
|
1297
|
+
legacy_timeout = kwargs.get("timeout")
|
|
1298
|
+
http_timeout_override = request_timeout if request_timeout is not None else legacy_timeout
|
|
1299
|
+
http_timeout = http_timeout_override or self.timeout
|
|
1300
|
+
|
|
1051
1301
|
# Prepare request data
|
|
1052
1302
|
payload, data_payload, files_payload, headers = self._prepare_request_data(message, files, **kwargs)
|
|
1053
1303
|
|
|
1054
1304
|
# Create async client configuration
|
|
1055
|
-
async_client_config = self._create_async_client_config(
|
|
1305
|
+
async_client_config = self._create_async_client_config(http_timeout_override, headers)
|
|
1056
1306
|
|
|
1057
1307
|
# Get execution timeout for streaming control
|
|
1058
1308
|
timeout_seconds = kwargs.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
|
|
1059
1309
|
agent_name = kwargs.get("agent_name")
|
|
1060
1310
|
|
|
1061
|
-
|
|
1062
|
-
# Create async client and stream response
|
|
1311
|
+
async def _chunk_stream() -> AsyncGenerator[dict, None]:
|
|
1063
1312
|
async with httpx.AsyncClient(**async_client_config) as async_client:
|
|
1064
1313
|
async for chunk in self._stream_agent_response(
|
|
1065
1314
|
async_client,
|
|
@@ -1073,7 +1322,14 @@ class AgentClient(BaseClient):
|
|
|
1073
1322
|
):
|
|
1074
1323
|
yield chunk
|
|
1075
1324
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1325
|
+
async with _async_timeout_guard(http_timeout):
|
|
1326
|
+
async for chunk in _chunk_stream():
|
|
1327
|
+
yield chunk
|
|
1328
|
+
|
|
1329
|
+
@property
|
|
1330
|
+
def runs(self) -> "AgentRunsClient":
|
|
1331
|
+
"""Get the agent runs client."""
|
|
1332
|
+
if self._runs_client is None:
|
|
1333
|
+
shared_config = build_shared_config(self)
|
|
1334
|
+
self._runs_client = AgentRunsClient(**shared_config)
|
|
1335
|
+
return self._runs_client
|
glaip_sdk/client/base.py
CHANGED
glaip_sdk/client/main.py
CHANGED
|
@@ -3,15 +3,24 @@
|
|
|
3
3
|
|
|
4
4
|
Authors:
|
|
5
5
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
|
-
from
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
9
12
|
|
|
10
13
|
from glaip_sdk.client.agents import AgentClient
|
|
11
14
|
from glaip_sdk.client.base import BaseClient
|
|
12
15
|
from glaip_sdk.client.mcps import MCPClient
|
|
16
|
+
from glaip_sdk.client.shared import build_shared_config
|
|
13
17
|
from glaip_sdk.client.tools import ToolClient
|
|
14
|
-
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
20
|
+
from glaip_sdk.agents import Agent
|
|
21
|
+
from glaip_sdk.client._agent_payloads import AgentListResult
|
|
22
|
+
from glaip_sdk.mcps import MCP
|
|
23
|
+
from glaip_sdk.tools import Tool
|
|
15
24
|
|
|
16
25
|
|
|
17
26
|
class Client(BaseClient):
|
|
@@ -25,12 +34,7 @@ class Client(BaseClient):
|
|
|
25
34
|
"""
|
|
26
35
|
super().__init__(**kwargs)
|
|
27
36
|
# 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
|
-
}
|
|
37
|
+
shared_config = build_shared_config(self)
|
|
34
38
|
self.agents = AgentClient(**shared_config)
|
|
35
39
|
self.tools = ToolClient(**shared_config)
|
|
36
40
|
self.mcps = MCPClient(**shared_config)
|
|
@@ -53,7 +57,7 @@ class Client(BaseClient):
|
|
|
53
57
|
name: str | None = None,
|
|
54
58
|
version: str | None = None,
|
|
55
59
|
sync_langflow_agents: bool = False,
|
|
56
|
-
) ->
|
|
60
|
+
) -> AgentListResult:
|
|
57
61
|
"""List agents with optional filtering.
|
|
58
62
|
|
|
59
63
|
Args:
|
|
@@ -64,7 +68,7 @@ class Client(BaseClient):
|
|
|
64
68
|
sync_langflow_agents: Sync with LangFlow server before listing (only applies when agent_type=langflow)
|
|
65
69
|
|
|
66
70
|
Returns:
|
|
67
|
-
|
|
71
|
+
AgentListResult with agents and pagination metadata. Supports iteration and indexing.
|
|
68
72
|
"""
|
|
69
73
|
return self.agents.list_agents(
|
|
70
74
|
agent_type=agent_type,
|
|
@@ -217,6 +221,11 @@ class Client(BaseClient):
|
|
|
217
221
|
|
|
218
222
|
@timeout.setter
|
|
219
223
|
def timeout(self, value: float) -> None: # type: ignore[override]
|
|
224
|
+
"""Set the client timeout and propagate to sub-clients.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
value: Timeout value in seconds.
|
|
228
|
+
"""
|
|
220
229
|
# Rebuild the root http client
|
|
221
230
|
BaseClient.timeout.fset(self, value) # call parent setter
|
|
222
231
|
# Propagate the new session to sub-clients so they don't hold a closed client
|