glaip-sdk 0.1.2__py3-none-any.whl → 0.7.17__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 +1413 -0
- glaip_sdk/branding.py +126 -2
- glaip_sdk/cli/account_store.py +555 -0
- glaip_sdk/cli/auth.py +260 -15
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents/__init__.py +116 -0
- glaip_sdk/cli/commands/agents/_common.py +562 -0
- glaip_sdk/cli/commands/agents/create.py +155 -0
- glaip_sdk/cli/commands/agents/delete.py +64 -0
- glaip_sdk/cli/commands/agents/get.py +89 -0
- glaip_sdk/cli/commands/agents/list.py +129 -0
- glaip_sdk/cli/commands/agents/run.py +264 -0
- glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
- glaip_sdk/cli/commands/agents/update.py +112 -0
- glaip_sdk/cli/commands/common_config.py +104 -0
- glaip_sdk/cli/commands/configure.py +728 -113
- glaip_sdk/cli/commands/mcps/__init__.py +94 -0
- glaip_sdk/cli/commands/mcps/_common.py +459 -0
- glaip_sdk/cli/commands/mcps/connect.py +82 -0
- glaip_sdk/cli/commands/mcps/create.py +152 -0
- glaip_sdk/cli/commands/mcps/delete.py +73 -0
- glaip_sdk/cli/commands/mcps/get.py +212 -0
- glaip_sdk/cli/commands/mcps/list.py +69 -0
- glaip_sdk/cli/commands/mcps/tools.py +235 -0
- glaip_sdk/cli/commands/mcps/update.py +190 -0
- glaip_sdk/cli/commands/models.py +12 -8
- glaip_sdk/cli/commands/shared/__init__.py +21 -0
- glaip_sdk/cli/commands/shared/formatters.py +91 -0
- glaip_sdk/cli/commands/tools/__init__.py +69 -0
- glaip_sdk/cli/commands/tools/_common.py +80 -0
- glaip_sdk/cli/commands/tools/create.py +228 -0
- glaip_sdk/cli/commands/tools/delete.py +61 -0
- glaip_sdk/cli/commands/tools/get.py +103 -0
- glaip_sdk/cli/commands/tools/list.py +69 -0
- glaip_sdk/cli/commands/tools/script.py +49 -0
- glaip_sdk/cli/commands/tools/update.py +102 -0
- glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
- glaip_sdk/cli/commands/transcripts/_common.py +9 -0
- glaip_sdk/cli/commands/transcripts/clear.py +5 -0
- glaip_sdk/cli/commands/transcripts/detail.py +5 -0
- glaip_sdk/cli/commands/transcripts_original.py +756 -0
- glaip_sdk/cli/commands/update.py +163 -17
- glaip_sdk/cli/config.py +49 -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 +41 -20
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +6 -3
- glaip_sdk/cli/main.py +340 -143
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/pager.py +12 -13
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/resolution.py +2 -1
- glaip_sdk/cli/slash/__init__.py +0 -9
- glaip_sdk/cli/slash/accounts_controller.py +580 -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 +568 -0
- glaip_sdk/cli/slash/session.py +1105 -153
- glaip_sdk/cli/slash/tui/__init__.py +36 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +177 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +1853 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/clipboard.py +195 -0
- glaip_sdk/cli/slash/tui/context.py +92 -0
- glaip_sdk/cli/slash/tui/indicators.py +341 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
- glaip_sdk/cli/slash/tui/layouts/harlequin.py +184 -0
- glaip_sdk/cli/slash/tui/loading.py +80 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +760 -0
- glaip_sdk/cli/slash/tui/terminal.py +407 -0
- glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
- glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
- glaip_sdk/cli/slash/tui/theme/manager.py +112 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +388 -0
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +255 -44
- glaip_sdk/cli/transcript/capture.py +66 -1
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/viewer.py +72 -463
- glaip_sdk/cli/tui_settings.py +125 -0
- glaip_sdk/cli/update_notifier.py +227 -10
- glaip_sdk/cli/validators.py +5 -6
- glaip_sdk/client/__init__.py +3 -1
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +576 -44
- glaip_sdk/client/base.py +26 -0
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +25 -14
- glaip_sdk/client/mcps.py +165 -24
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +63 -47
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +546 -92
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +206 -32
- glaip_sdk/config/constants.py +33 -2
- glaip_sdk/guardrails/__init__.py +80 -0
- glaip_sdk/guardrails/serializer.py +89 -0
- glaip_sdk/hitl/__init__.py +48 -0
- glaip_sdk/hitl/base.py +64 -0
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +121 -0
- glaip_sdk/hitl/remote.py +523 -0
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +136 -0
- glaip_sdk/models/_provider_mappings.py +101 -0
- glaip_sdk/models/_validation.py +97 -0
- glaip_sdk/models/agent.py +48 -0
- glaip_sdk/models/agent_runs.py +117 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/constants.py +141 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/model.py +170 -0
- glaip_sdk/models/schedule.py +224 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +1 -13
- glaip_sdk/payload_schemas/agent.py +1 -0
- glaip_sdk/payload_schemas/guardrails.py +34 -0
- 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 +445 -0
- glaip_sdk/rich_components.py +58 -2
- glaip_sdk/runner/__init__.py +76 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +115 -0
- glaip_sdk/runner/langgraph.py +1055 -0
- glaip_sdk/runner/logging_config.py +77 -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 +116 -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 +242 -0
- glaip_sdk/schedules/__init__.py +22 -0
- glaip_sdk/schedules/base.py +291 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +488 -0
- glaip_sdk/utils/__init__.py +59 -12
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +8 -2
- glaip_sdk/utils/bundler.py +403 -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 +524 -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 +299 -1434
- glaip_sdk/utils/rendering/renderer/config.py +1 -5
- 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 -33
- glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
- 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 +426 -0
- glaip_sdk/utils/serialization.py +18 -0
- glaip_sdk/utils/sync.py +162 -0
- glaip_sdk/utils/tool_detection.py +301 -0
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- glaip_sdk/utils/validation.py +16 -24
- {glaip_sdk-0.1.2.dist-info → glaip_sdk-0.7.17.dist-info}/METADATA +69 -23
- glaip_sdk-0.7.17.dist-info/RECORD +224 -0
- {glaip_sdk-0.1.2.dist-info → glaip_sdk-0.7.17.dist-info}/WHEEL +2 -1
- glaip_sdk-0.7.17.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.7.17.dist-info/top_level.txt +1 -0
- glaip_sdk/cli/commands/agents.py +0 -1369
- glaip_sdk/cli/commands/mcps.py +0 -1187
- glaip_sdk/cli/commands/tools.py +0 -584
- glaip_sdk/cli/utils.py +0 -1278
- glaip_sdk/models.py +0 -240
- glaip_sdk-0.1.2.dist-info/RECORD +0 -82
- glaip_sdk-0.1.2.dist-info/entry_points.txt +0 -3
glaip_sdk/client/agents.py
CHANGED
|
@@ -1,41 +1,52 @@
|
|
|
1
|
-
|
|
1
|
+
# pylint: disable=duplicate-code
|
|
2
2
|
"""Agent client for AIP SDK.
|
|
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
|
|
12
|
+
import warnings
|
|
10
13
|
from collections.abc import AsyncGenerator, Callable, Iterator, Mapping
|
|
14
|
+
from contextlib import asynccontextmanager
|
|
11
15
|
from os import PathLike
|
|
12
16
|
from pathlib import Path
|
|
13
|
-
from typing import Any, BinaryIO
|
|
17
|
+
from typing import TYPE_CHECKING, Any, BinaryIO
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from glaip_sdk.client.schedules import ScheduleClient
|
|
21
|
+
from glaip_sdk.hitl.remote import RemoteHITLHandler
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
import httpx
|
|
24
|
+
from glaip_sdk.agents import Agent
|
|
25
|
+
from glaip_sdk.client.agent_runs import AgentRunsClient
|
|
26
|
+
from glaip_sdk.client.base import BaseClient
|
|
27
|
+
from glaip_sdk.client.mcps import MCPClient
|
|
28
|
+
from glaip_sdk.client.payloads.agent import (
|
|
18
29
|
AgentCreateRequest,
|
|
19
30
|
AgentListParams,
|
|
20
31
|
AgentListResult,
|
|
21
32
|
AgentUpdateRequest,
|
|
22
33
|
)
|
|
23
|
-
from glaip_sdk.client.base import BaseClient
|
|
24
|
-
from glaip_sdk.client.mcps import MCPClient
|
|
25
34
|
from glaip_sdk.client.run_rendering import (
|
|
26
35
|
AgentRunRenderingManager,
|
|
27
36
|
compute_timeout_seconds,
|
|
28
37
|
)
|
|
38
|
+
from glaip_sdk.client.shared import build_shared_config
|
|
29
39
|
from glaip_sdk.client.tools import ToolClient
|
|
30
40
|
from glaip_sdk.config.constants import (
|
|
41
|
+
AGENT_CONFIG_FIELDS,
|
|
31
42
|
DEFAULT_AGENT_FRAMEWORK,
|
|
32
43
|
DEFAULT_AGENT_RUN_TIMEOUT,
|
|
33
44
|
DEFAULT_AGENT_TYPE,
|
|
34
45
|
DEFAULT_AGENT_VERSION,
|
|
35
|
-
DEFAULT_MODEL,
|
|
36
46
|
)
|
|
37
47
|
from glaip_sdk.exceptions import NotFoundError, ValidationError
|
|
38
|
-
from glaip_sdk.models import
|
|
48
|
+
from glaip_sdk.models import AgentResponse
|
|
49
|
+
from glaip_sdk.models.constants import DEFAULT_MODEL
|
|
39
50
|
from glaip_sdk.payload_schemas.agent import list_server_only_fields
|
|
40
51
|
from glaip_sdk.utils.agent_config import normalize_agent_config_for_import
|
|
41
52
|
from glaip_sdk.utils.client_utils import (
|
|
@@ -67,6 +78,21 @@ _MERGED_SEQUENCE_FIELDS = ("tools", "agents", "mcps")
|
|
|
67
78
|
_DEFAULT_METADATA_TYPE = "custom"
|
|
68
79
|
|
|
69
80
|
|
|
81
|
+
@asynccontextmanager
|
|
82
|
+
async def _async_timeout_guard(
|
|
83
|
+
timeout_seconds: float | None,
|
|
84
|
+
) -> AsyncGenerator[None, None]:
|
|
85
|
+
"""Apply an asyncio timeout when a custom timeout is provided."""
|
|
86
|
+
if timeout_seconds is None:
|
|
87
|
+
yield
|
|
88
|
+
return
|
|
89
|
+
try:
|
|
90
|
+
async with asyncio.timeout(timeout_seconds):
|
|
91
|
+
yield
|
|
92
|
+
except asyncio.TimeoutError as exc:
|
|
93
|
+
raise httpx.TimeoutException(f"Request timed out after {timeout_seconds}s") from exc
|
|
94
|
+
|
|
95
|
+
|
|
70
96
|
def _normalise_sequence(value: Any) -> list[Any] | None:
|
|
71
97
|
"""Normalise optional sequence inputs to plain lists."""
|
|
72
98
|
if value is None:
|
|
@@ -193,19 +219,7 @@ def _extract_original_refs(raw_definition: dict) -> dict[str, list]:
|
|
|
193
219
|
|
|
194
220
|
def _build_cli_args(overrides_dict: dict) -> dict[str, Any]:
|
|
195
221
|
"""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
|
-
}
|
|
222
|
+
cli_args = {key: overrides_dict.get(key) for key in AGENT_CONFIG_FIELDS if overrides_dict.get(key) is not None}
|
|
209
223
|
|
|
210
224
|
# Normalize sequence fields
|
|
211
225
|
for field in _MERGED_SEQUENCE_FIELDS:
|
|
@@ -242,18 +256,134 @@ class AgentClient(BaseClient):
|
|
|
242
256
|
self,
|
|
243
257
|
*,
|
|
244
258
|
parent_client: BaseClient | None = None,
|
|
259
|
+
lm_cache_ttl: float = 3600.0,
|
|
245
260
|
**kwargs: Any,
|
|
246
261
|
) -> None:
|
|
247
262
|
"""Initialize the agent client.
|
|
248
263
|
|
|
249
264
|
Args:
|
|
250
|
-
parent_client: Parent client to adopt session/config from
|
|
251
|
-
|
|
265
|
+
parent_client: Parent client to adopt session/config from.
|
|
266
|
+
lm_cache_ttl: TTL for the language model list cache in seconds.
|
|
267
|
+
Defaults to 3600 (1 hour).
|
|
268
|
+
**kwargs: Additional arguments for standalone initialization.
|
|
252
269
|
"""
|
|
253
270
|
super().__init__(parent_client=parent_client, **kwargs)
|
|
254
271
|
self._renderer_manager = AgentRunRenderingManager(logger)
|
|
255
272
|
self._tool_client: ToolClient | None = None
|
|
256
273
|
self._mcp_client: MCPClient | None = None
|
|
274
|
+
self._runs_client: AgentRunsClient | None = None
|
|
275
|
+
self._schedule_client: ScheduleClient | None = None
|
|
276
|
+
|
|
277
|
+
self._lm_cache: list[dict[str, Any]] | None = None
|
|
278
|
+
self._lm_cache_time: float = 0.0
|
|
279
|
+
self._lm_cache_ttl: float = lm_cache_ttl
|
|
280
|
+
|
|
281
|
+
def clear_language_model_cache(self) -> None:
|
|
282
|
+
"""Invalidate the language model list cache.
|
|
283
|
+
|
|
284
|
+
Forces the next call to list_language_models() to fetch a fresh list
|
|
285
|
+
from the server.
|
|
286
|
+
"""
|
|
287
|
+
self._lm_cache = None
|
|
288
|
+
self._lm_cache_time = 0.0
|
|
289
|
+
logger.debug("Language model cache invalidated.")
|
|
290
|
+
|
|
291
|
+
def _resolve_language_model_id(self, model_str: str | None) -> str | None:
|
|
292
|
+
"""Resolve a friendly model name to a server language model ID.
|
|
293
|
+
|
|
294
|
+
Handles provider name mapping (e.g., 'deepinfra/model' → 'openai-compatible/model')
|
|
295
|
+
by checking both the original provider name and its driver equivalent.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
model_str: The model string to resolve (e.g., 'openai/gpt-4o', 'deepinfra/Qwen3-30B').
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
The resolved server model ID (UUID), or None if not found.
|
|
302
|
+
|
|
303
|
+
Examples:
|
|
304
|
+
>>> _resolve_language_model_id("openai/gpt-4o")
|
|
305
|
+
"uuid-1234-..."
|
|
306
|
+
>>> _resolve_language_model_id("deepinfra/Qwen3-30B") # Maps to openai-compatible
|
|
307
|
+
"uuid-5678-..."
|
|
308
|
+
"""
|
|
309
|
+
if not model_str:
|
|
310
|
+
return None
|
|
311
|
+
|
|
312
|
+
# If resolution is explicitly disabled (e.g. in unit tests to avoid extra API calls), skip it
|
|
313
|
+
if getattr(self, "_skip_model_resolution", False):
|
|
314
|
+
return None
|
|
315
|
+
|
|
316
|
+
try:
|
|
317
|
+
models = self.list_language_models()
|
|
318
|
+
|
|
319
|
+
# Try exact match first
|
|
320
|
+
model_id = self._find_exact_model_match(model_str, models)
|
|
321
|
+
if model_id:
|
|
322
|
+
return model_id
|
|
323
|
+
|
|
324
|
+
# Try with provider-to-driver mapping
|
|
325
|
+
return self._try_resolve_with_driver_mapping(model_str, models)
|
|
326
|
+
except Exception:
|
|
327
|
+
pass
|
|
328
|
+
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
def _find_exact_model_match(self, model_str: str, models: list[dict[str, Any]]) -> str | None:
|
|
332
|
+
"""Find exact model match in models list.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
model_str: Model string to match.
|
|
336
|
+
models: List of language model dictionaries from server.
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Model ID (UUID) if found, None otherwise.
|
|
340
|
+
"""
|
|
341
|
+
for model_info in models:
|
|
342
|
+
provider = model_info.get("provider")
|
|
343
|
+
name = model_info.get("name")
|
|
344
|
+
if provider and name:
|
|
345
|
+
full_name = f"{provider}/{name}"
|
|
346
|
+
if full_name == model_str:
|
|
347
|
+
return model_info.get("id")
|
|
348
|
+
if name == model_str:
|
|
349
|
+
return model_info.get("id")
|
|
350
|
+
return None
|
|
351
|
+
|
|
352
|
+
def _try_resolve_with_driver_mapping(self, model_str: str, models: list[dict[str, Any]]) -> str | None:
|
|
353
|
+
"""Try to resolve model using provider-to-driver mapping.
|
|
354
|
+
|
|
355
|
+
Maps provider names to their driver implementations (e.g., deepinfra → openai-compatible)
|
|
356
|
+
and searches the models list with the driver name.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
model_str: Model string in provider/model format (e.g., "deepinfra/Qwen3-30B").
|
|
360
|
+
models: List of language model dictionaries from server.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
Model ID (UUID) if found, None otherwise.
|
|
364
|
+
"""
|
|
365
|
+
if "/" not in model_str:
|
|
366
|
+
return None
|
|
367
|
+
|
|
368
|
+
from glaip_sdk.models._provider_mappings import get_driver # noqa: PLC0415
|
|
369
|
+
|
|
370
|
+
provider, model_name = model_str.split("/", 1)
|
|
371
|
+
driver = get_driver(provider)
|
|
372
|
+
|
|
373
|
+
# Only try with driver if it's different from provider
|
|
374
|
+
if driver == provider:
|
|
375
|
+
return None
|
|
376
|
+
|
|
377
|
+
driver_model_str = f"{driver}/{model_name}"
|
|
378
|
+
for model_info in models:
|
|
379
|
+
provider_field = model_info.get("provider")
|
|
380
|
+
name_field = model_info.get("name")
|
|
381
|
+
if provider_field and name_field:
|
|
382
|
+
full_name = f"{provider_field}/{name_field}"
|
|
383
|
+
if full_name == driver_model_str:
|
|
384
|
+
return model_info.get("id")
|
|
385
|
+
|
|
386
|
+
return None
|
|
257
387
|
|
|
258
388
|
def list_agents(
|
|
259
389
|
self,
|
|
@@ -352,7 +482,8 @@ class AgentClient(BaseClient):
|
|
|
352
482
|
status_code=404,
|
|
353
483
|
)
|
|
354
484
|
|
|
355
|
-
|
|
485
|
+
response = AgentResponse(**data)
|
|
486
|
+
return Agent.from_response(response, client=self)
|
|
356
487
|
|
|
357
488
|
def find_agents(self, name: str | None = None) -> list[Agent]:
|
|
358
489
|
"""Find agents by name."""
|
|
@@ -366,6 +497,11 @@ class AgentClient(BaseClient):
|
|
|
366
497
|
# Renderer delegation helpers
|
|
367
498
|
# ------------------------------------------------------------------ #
|
|
368
499
|
def _get_renderer_manager(self) -> AgentRunRenderingManager:
|
|
500
|
+
"""Get or create the renderer manager instance.
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
AgentRunRenderingManager instance.
|
|
504
|
+
"""
|
|
369
505
|
manager = getattr(self, "_renderer_manager", None)
|
|
370
506
|
if manager is None:
|
|
371
507
|
manager = AgentRunRenderingManager(logger)
|
|
@@ -373,6 +509,15 @@ class AgentClient(BaseClient):
|
|
|
373
509
|
return manager
|
|
374
510
|
|
|
375
511
|
def _create_renderer(self, renderer: RichStreamRenderer | str | None, **kwargs: Any) -> RichStreamRenderer:
|
|
512
|
+
"""Create or return a renderer instance.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
renderer: Renderer instance, string identifier, or None.
|
|
516
|
+
**kwargs: Additional keyword arguments (e.g., verbose).
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
RichStreamRenderer instance.
|
|
520
|
+
"""
|
|
376
521
|
manager = self._get_renderer_manager()
|
|
377
522
|
verbose = kwargs.get("verbose", False)
|
|
378
523
|
if isinstance(renderer, RichStreamRenderer) or hasattr(renderer, "on_start"):
|
|
@@ -386,7 +531,21 @@ class AgentClient(BaseClient):
|
|
|
386
531
|
timeout_seconds: float,
|
|
387
532
|
agent_name: str | None,
|
|
388
533
|
meta: dict[str, Any],
|
|
534
|
+
hitl_handler: "RemoteHITLHandler | None" = None,
|
|
389
535
|
) -> tuple[str, dict[str, Any], float | None, float | None]:
|
|
536
|
+
"""Process stream events from an HTTP response.
|
|
537
|
+
|
|
538
|
+
Args:
|
|
539
|
+
stream_response: HTTP response stream.
|
|
540
|
+
renderer: Renderer to use for displaying events.
|
|
541
|
+
timeout_seconds: Timeout in seconds.
|
|
542
|
+
agent_name: Optional agent name.
|
|
543
|
+
meta: Metadata dictionary.
|
|
544
|
+
hitl_handler: Optional HITL handler for approval callbacks.
|
|
545
|
+
|
|
546
|
+
Returns:
|
|
547
|
+
Tuple of (final_text, stats_usage, started_monotonic, finished_monotonic).
|
|
548
|
+
"""
|
|
390
549
|
manager = self._get_renderer_manager()
|
|
391
550
|
return manager.process_stream_events(
|
|
392
551
|
stream_response,
|
|
@@ -394,6 +553,7 @@ class AgentClient(BaseClient):
|
|
|
394
553
|
timeout_seconds,
|
|
395
554
|
agent_name,
|
|
396
555
|
meta,
|
|
556
|
+
hitl_handler=hitl_handler,
|
|
397
557
|
)
|
|
398
558
|
|
|
399
559
|
def _finalize_renderer(
|
|
@@ -404,8 +564,25 @@ class AgentClient(BaseClient):
|
|
|
404
564
|
started_monotonic: float | None,
|
|
405
565
|
finished_monotonic: float | None,
|
|
406
566
|
) -> str:
|
|
567
|
+
"""Finalize the renderer and return the final response text.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
renderer: Renderer to finalize.
|
|
571
|
+
final_text: Final text content.
|
|
572
|
+
stats_usage: Usage statistics dictionary.
|
|
573
|
+
started_monotonic: Start time (monotonic).
|
|
574
|
+
finished_monotonic: Finish time (monotonic).
|
|
575
|
+
|
|
576
|
+
Returns:
|
|
577
|
+
Final text string.
|
|
578
|
+
"""
|
|
579
|
+
from glaip_sdk.client.run_rendering import ( # noqa: PLC0415
|
|
580
|
+
finalize_render_manager,
|
|
581
|
+
)
|
|
582
|
+
|
|
407
583
|
manager = self._get_renderer_manager()
|
|
408
|
-
return
|
|
584
|
+
return finalize_render_manager(
|
|
585
|
+
manager,
|
|
409
586
|
renderer,
|
|
410
587
|
final_text,
|
|
411
588
|
stats_usage,
|
|
@@ -414,20 +591,53 @@ class AgentClient(BaseClient):
|
|
|
414
591
|
)
|
|
415
592
|
|
|
416
593
|
def _get_tool_client(self) -> ToolClient:
|
|
594
|
+
"""Get or create the tool client instance.
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
ToolClient instance.
|
|
598
|
+
"""
|
|
417
599
|
if self._tool_client is None:
|
|
418
600
|
self._tool_client = ToolClient(parent_client=self)
|
|
419
601
|
return self._tool_client
|
|
420
602
|
|
|
421
603
|
def _get_mcp_client(self) -> MCPClient:
|
|
604
|
+
"""Get or create the MCP client instance.
|
|
605
|
+
|
|
606
|
+
Returns:
|
|
607
|
+
MCPClient instance.
|
|
608
|
+
"""
|
|
422
609
|
if self._mcp_client is None:
|
|
423
610
|
self._mcp_client = MCPClient(parent_client=self)
|
|
424
611
|
return self._mcp_client
|
|
425
612
|
|
|
613
|
+
@property
|
|
614
|
+
def schedules(self) -> "ScheduleClient":
|
|
615
|
+
"""Get or create the schedule client instance.
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
ScheduleClient instance.
|
|
619
|
+
"""
|
|
620
|
+
if self._schedule_client is None:
|
|
621
|
+
# Import here to avoid circular import
|
|
622
|
+
from glaip_sdk.client.schedules import ScheduleClient # noqa: PLC0415
|
|
623
|
+
|
|
624
|
+
self._schedule_client = ScheduleClient(parent_client=self)
|
|
625
|
+
return self._schedule_client
|
|
626
|
+
|
|
426
627
|
def _normalise_reference_entry(
|
|
427
628
|
self,
|
|
428
629
|
entry: Any,
|
|
429
630
|
fallback_iter: Iterator[Any] | None,
|
|
430
631
|
) -> tuple[str | None, str | None]:
|
|
632
|
+
"""Normalize a reference entry to extract ID and name.
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
entry: Reference entry (string, dict, or other).
|
|
636
|
+
fallback_iter: Optional iterator for fallback values.
|
|
637
|
+
|
|
638
|
+
Returns:
|
|
639
|
+
Tuple of (entry_id, entry_name).
|
|
640
|
+
"""
|
|
431
641
|
entry_id: str | None = None
|
|
432
642
|
entry_name: str | None = None
|
|
433
643
|
|
|
@@ -464,6 +674,19 @@ class AgentClient(BaseClient):
|
|
|
464
674
|
label: str,
|
|
465
675
|
plural_label: str | None = None,
|
|
466
676
|
) -> list[str] | None:
|
|
677
|
+
"""Resolve a list of resource references to IDs.
|
|
678
|
+
|
|
679
|
+
Args:
|
|
680
|
+
items: List of resource references to resolve.
|
|
681
|
+
references: Optional list of reference objects for fallback.
|
|
682
|
+
fetch_by_id: Function to fetch resource by ID.
|
|
683
|
+
find_by_name: Function to find resources by name.
|
|
684
|
+
label: Singular label for error messages.
|
|
685
|
+
plural_label: Plural label for error messages.
|
|
686
|
+
|
|
687
|
+
Returns:
|
|
688
|
+
List of resolved resource IDs, or None if items is empty.
|
|
689
|
+
"""
|
|
467
690
|
if not items:
|
|
468
691
|
return None
|
|
469
692
|
|
|
@@ -495,6 +718,22 @@ class AgentClient(BaseClient):
|
|
|
495
718
|
singular: str,
|
|
496
719
|
plural: str,
|
|
497
720
|
) -> str:
|
|
721
|
+
"""Resolve a single resource reference to an ID.
|
|
722
|
+
|
|
723
|
+
Args:
|
|
724
|
+
entry: Resource reference to resolve.
|
|
725
|
+
fallback_iter: Optional iterator for fallback values.
|
|
726
|
+
fetch_by_id: Function to fetch resource by ID.
|
|
727
|
+
find_by_name: Function to find resources by name.
|
|
728
|
+
singular: Singular label for error messages.
|
|
729
|
+
plural: Plural label for error messages.
|
|
730
|
+
|
|
731
|
+
Returns:
|
|
732
|
+
Resolved resource ID string.
|
|
733
|
+
|
|
734
|
+
Raises:
|
|
735
|
+
ValueError: If the resource cannot be resolved.
|
|
736
|
+
"""
|
|
498
737
|
entry_id, entry_name = self._normalise_reference_entry(entry, fallback_iter)
|
|
499
738
|
|
|
500
739
|
validated_id = self._validate_resource_id(fetch_by_id, entry_id)
|
|
@@ -511,6 +750,14 @@ class AgentClient(BaseClient):
|
|
|
511
750
|
|
|
512
751
|
@staticmethod
|
|
513
752
|
def _coerce_reference_value(entry: Any) -> str:
|
|
753
|
+
"""Coerce a reference entry to a string value.
|
|
754
|
+
|
|
755
|
+
Args:
|
|
756
|
+
entry: Reference entry (dict, string, or other).
|
|
757
|
+
|
|
758
|
+
Returns:
|
|
759
|
+
String representation of the reference.
|
|
760
|
+
"""
|
|
514
761
|
if isinstance(entry, dict):
|
|
515
762
|
if entry.get("id"):
|
|
516
763
|
return str(entry["id"])
|
|
@@ -520,6 +767,15 @@ class AgentClient(BaseClient):
|
|
|
520
767
|
|
|
521
768
|
@staticmethod
|
|
522
769
|
def _validate_resource_id(fetch_by_id: Callable[[str], Any], candidate_id: str | None) -> str | None:
|
|
770
|
+
"""Validate a resource ID by attempting to fetch it.
|
|
771
|
+
|
|
772
|
+
Args:
|
|
773
|
+
fetch_by_id: Function to fetch resource by ID.
|
|
774
|
+
candidate_id: Candidate ID to validate.
|
|
775
|
+
|
|
776
|
+
Returns:
|
|
777
|
+
Validated ID if found, None otherwise.
|
|
778
|
+
"""
|
|
523
779
|
if not candidate_id:
|
|
524
780
|
return None
|
|
525
781
|
try:
|
|
@@ -535,6 +791,20 @@ class AgentClient(BaseClient):
|
|
|
535
791
|
singular: str,
|
|
536
792
|
plural: str,
|
|
537
793
|
) -> tuple[str, bool]:
|
|
794
|
+
"""Resolve a resource by name to an ID.
|
|
795
|
+
|
|
796
|
+
Args:
|
|
797
|
+
find_by_name: Function to find resources by name.
|
|
798
|
+
entry_name: Name of the resource to find.
|
|
799
|
+
singular: Singular label for error messages.
|
|
800
|
+
plural: Plural label for error messages.
|
|
801
|
+
|
|
802
|
+
Returns:
|
|
803
|
+
Tuple of (resolved_id, success).
|
|
804
|
+
|
|
805
|
+
Raises:
|
|
806
|
+
ValueError: If resource not found or multiple matches exist.
|
|
807
|
+
"""
|
|
538
808
|
try:
|
|
539
809
|
matches = find_by_name(entry_name)
|
|
540
810
|
except Exception:
|
|
@@ -555,6 +825,15 @@ class AgentClient(BaseClient):
|
|
|
555
825
|
tools: list[Any] | None,
|
|
556
826
|
references: list[Any] | None = None,
|
|
557
827
|
) -> list[str] | None:
|
|
828
|
+
"""Resolve tool references to IDs.
|
|
829
|
+
|
|
830
|
+
Args:
|
|
831
|
+
tools: List of tool references to resolve.
|
|
832
|
+
references: Optional list of reference objects for fallback.
|
|
833
|
+
|
|
834
|
+
Returns:
|
|
835
|
+
List of resolved tool IDs, or None if tools is empty.
|
|
836
|
+
"""
|
|
558
837
|
tool_client = self._get_tool_client()
|
|
559
838
|
return self._resolve_resource_ids(
|
|
560
839
|
tools,
|
|
@@ -570,6 +849,15 @@ class AgentClient(BaseClient):
|
|
|
570
849
|
agents: list[Any] | None,
|
|
571
850
|
references: list[Any] | None = None,
|
|
572
851
|
) -> list[str] | None:
|
|
852
|
+
"""Resolve agent references to IDs.
|
|
853
|
+
|
|
854
|
+
Args:
|
|
855
|
+
agents: List of agent references to resolve.
|
|
856
|
+
references: Optional list of reference objects for fallback.
|
|
857
|
+
|
|
858
|
+
Returns:
|
|
859
|
+
List of resolved agent IDs, or None if agents is empty.
|
|
860
|
+
"""
|
|
573
861
|
return self._resolve_resource_ids(
|
|
574
862
|
agents,
|
|
575
863
|
references,
|
|
@@ -584,6 +872,15 @@ class AgentClient(BaseClient):
|
|
|
584
872
|
mcps: list[Any] | None,
|
|
585
873
|
references: list[Any] | None = None,
|
|
586
874
|
) -> list[str] | None:
|
|
875
|
+
"""Resolve MCP references to IDs.
|
|
876
|
+
|
|
877
|
+
Args:
|
|
878
|
+
mcps: List of MCP references to resolve.
|
|
879
|
+
references: Optional list of reference objects for fallback.
|
|
880
|
+
|
|
881
|
+
Returns:
|
|
882
|
+
List of resolved MCP IDs, or None if mcps is empty.
|
|
883
|
+
"""
|
|
587
884
|
mcp_client = self._get_mcp_client()
|
|
588
885
|
return self._resolve_resource_ids(
|
|
589
886
|
mcps,
|
|
@@ -594,10 +891,18 @@ class AgentClient(BaseClient):
|
|
|
594
891
|
plural_label="MCPs",
|
|
595
892
|
)
|
|
596
893
|
|
|
597
|
-
def
|
|
598
|
-
"""
|
|
599
|
-
known, extras = _split_known_and_extra(payload, AgentCreateRequest.__dataclass_fields__)
|
|
894
|
+
def _validate_agent_basics(self, known: dict[str, Any]) -> tuple[str, str]:
|
|
895
|
+
"""Validate and extract basic agent fields.
|
|
600
896
|
|
|
897
|
+
Args:
|
|
898
|
+
known: Known fields dictionary.
|
|
899
|
+
|
|
900
|
+
Returns:
|
|
901
|
+
Tuple of (name, validated_instruction).
|
|
902
|
+
|
|
903
|
+
Raises:
|
|
904
|
+
ValueError: If name or instruction is empty/whitespace.
|
|
905
|
+
"""
|
|
601
906
|
name = known.pop("name", None)
|
|
602
907
|
instruction = known.pop("instruction", None)
|
|
603
908
|
if not name or not str(name).strip():
|
|
@@ -606,9 +911,20 @@ class AgentClient(BaseClient):
|
|
|
606
911
|
raise ValueError("Agent instruction cannot be empty or whitespace")
|
|
607
912
|
|
|
608
913
|
validated_instruction = validate_agent_instruction(str(instruction))
|
|
609
|
-
|
|
914
|
+
return str(name).strip(), validated_instruction
|
|
610
915
|
|
|
611
|
-
|
|
916
|
+
def _resolve_all_resources(
|
|
917
|
+
self, known: dict[str, Any], extras: dict[str, Any]
|
|
918
|
+
) -> tuple[list[str] | None, list[str] | None, list[str] | None]:
|
|
919
|
+
"""Resolve all resource IDs (tools, agents, mcps).
|
|
920
|
+
|
|
921
|
+
Args:
|
|
922
|
+
known: Known fields dictionary.
|
|
923
|
+
extras: Extra fields dictionary.
|
|
924
|
+
|
|
925
|
+
Returns:
|
|
926
|
+
Tuple of (resolved_tools, resolved_agents, resolved_mcps).
|
|
927
|
+
"""
|
|
612
928
|
tool_refs = extras.pop("_tool_refs", None)
|
|
613
929
|
agent_refs = extras.pop("_agent_refs", None)
|
|
614
930
|
mcp_refs = extras.pop("_mcp_refs", None)
|
|
@@ -621,10 +937,56 @@ class AgentClient(BaseClient):
|
|
|
621
937
|
resolved_agents = self._resolve_agent_ids(agents_raw, agent_refs)
|
|
622
938
|
resolved_mcps = self._resolve_mcp_ids(mcps_raw, mcp_refs)
|
|
623
939
|
|
|
940
|
+
return resolved_tools, resolved_agents, resolved_mcps
|
|
941
|
+
|
|
942
|
+
def _process_model_fields(
|
|
943
|
+
self, resolved_model: Any, known: dict[str, Any]
|
|
944
|
+
) -> tuple[str, str | None, str | None, str | None]:
|
|
945
|
+
"""Process model fields and extract language model ID.
|
|
946
|
+
|
|
947
|
+
Args:
|
|
948
|
+
resolved_model: Resolved model (string or Model object).
|
|
949
|
+
known: Known fields dictionary.
|
|
950
|
+
|
|
951
|
+
Returns:
|
|
952
|
+
Tuple of (resolved_model_str, language_model_id, provider, model_name).
|
|
953
|
+
"""
|
|
954
|
+
from glaip_sdk.models import Model # noqa: PLC0415
|
|
955
|
+
|
|
956
|
+
if isinstance(resolved_model, Model):
|
|
957
|
+
if resolved_model.credentials or resolved_model.hyperparameters or resolved_model.base_url:
|
|
958
|
+
warnings.warn(
|
|
959
|
+
"Model object contains local configuration (credentials, hyperparameters, or base_url) "
|
|
960
|
+
"which is ignored for remote deployment. These fields are only used for local execution.",
|
|
961
|
+
UserWarning,
|
|
962
|
+
stacklevel=2,
|
|
963
|
+
)
|
|
964
|
+
resolved_model = resolved_model.id
|
|
965
|
+
|
|
966
|
+
# Validate and normalize string models (handles bare name deprecation)
|
|
967
|
+
if isinstance(resolved_model, str):
|
|
968
|
+
from glaip_sdk.models._validation import _validate_model # noqa: PLC0415
|
|
969
|
+
|
|
970
|
+
resolved_model = _validate_model(resolved_model)
|
|
971
|
+
|
|
624
972
|
language_model_id = known.pop("language_model_id", None)
|
|
973
|
+
if not language_model_id and isinstance(resolved_model, str):
|
|
974
|
+
language_model_id = self._resolve_language_model_id(resolved_model)
|
|
975
|
+
|
|
625
976
|
provider = known.pop("provider", None)
|
|
626
977
|
model_name = known.pop("model_name", None)
|
|
627
978
|
|
|
979
|
+
return resolved_model, language_model_id, provider, model_name
|
|
980
|
+
|
|
981
|
+
def _extract_agent_metadata(self, known: dict[str, Any]) -> tuple[str, str, str]:
|
|
982
|
+
"""Extract agent type, framework, and version.
|
|
983
|
+
|
|
984
|
+
Args:
|
|
985
|
+
known: Known fields dictionary.
|
|
986
|
+
|
|
987
|
+
Returns:
|
|
988
|
+
Tuple of (agent_type, framework, version).
|
|
989
|
+
"""
|
|
628
990
|
agent_type_value = known.pop("agent_type", None)
|
|
629
991
|
fallback_type_value = known.pop("type", None)
|
|
630
992
|
if agent_type_value is None:
|
|
@@ -632,6 +994,26 @@ class AgentClient(BaseClient):
|
|
|
632
994
|
|
|
633
995
|
framework_value = known.pop("framework", None) or DEFAULT_AGENT_FRAMEWORK
|
|
634
996
|
version_value = known.pop("version", None) or DEFAULT_AGENT_VERSION
|
|
997
|
+
|
|
998
|
+
return agent_type_value, framework_value, version_value
|
|
999
|
+
|
|
1000
|
+
def _create_agent_from_payload(self, payload: Mapping[str, Any]) -> "Agent":
|
|
1001
|
+
"""Create an agent using a fully prepared payload mapping."""
|
|
1002
|
+
known, extras = _split_known_and_extra(payload, AgentCreateRequest.__dataclass_fields__)
|
|
1003
|
+
|
|
1004
|
+
# Validate and extract basic fields
|
|
1005
|
+
name, validated_instruction = self._validate_agent_basics(known)
|
|
1006
|
+
_normalise_sequence_fields(known)
|
|
1007
|
+
|
|
1008
|
+
# Resolve model and resources
|
|
1009
|
+
resolved_model = known.pop("model", None) or DEFAULT_MODEL
|
|
1010
|
+
resolved_tools, resolved_agents, resolved_mcps = self._resolve_all_resources(known, extras)
|
|
1011
|
+
|
|
1012
|
+
# Process model and language model ID
|
|
1013
|
+
resolved_model, language_model_id, provider, model_name = self._process_model_fields(resolved_model, known)
|
|
1014
|
+
|
|
1015
|
+
# Extract agent type, framework, version
|
|
1016
|
+
agent_type_value, framework_value, version_value = self._extract_agent_metadata(known)
|
|
635
1017
|
account_id = known.pop("account_id", None)
|
|
636
1018
|
description = known.pop("description", None)
|
|
637
1019
|
metadata = _prepare_agent_metadata(known.pop("metadata", None))
|
|
@@ -644,7 +1026,7 @@ class AgentClient(BaseClient):
|
|
|
644
1026
|
final_extras.setdefault("model", resolved_model)
|
|
645
1027
|
|
|
646
1028
|
request = AgentCreateRequest(
|
|
647
|
-
name=
|
|
1029
|
+
name=name,
|
|
648
1030
|
instruction=validated_instruction,
|
|
649
1031
|
model=resolved_model,
|
|
650
1032
|
language_model_id=language_model_id,
|
|
@@ -675,7 +1057,8 @@ class AgentClient(BaseClient):
|
|
|
675
1057
|
get_endpoint_fmt=f"{AGENTS_ENDPOINT}{{id}}",
|
|
676
1058
|
json=payload_dict,
|
|
677
1059
|
)
|
|
678
|
-
|
|
1060
|
+
response = AgentResponse(**full_agent_data)
|
|
1061
|
+
return Agent.from_response(response, client=self)
|
|
679
1062
|
|
|
680
1063
|
def create_agent(
|
|
681
1064
|
self,
|
|
@@ -721,6 +1104,29 @@ class AgentClient(BaseClient):
|
|
|
721
1104
|
"""Backward-compatible helper to create an agent from a configuration file."""
|
|
722
1105
|
return self.create_agent(file=file_path, **overrides)
|
|
723
1106
|
|
|
1107
|
+
def _resolve_update_model_fields(self, known: dict[str, Any]) -> None:
|
|
1108
|
+
"""Resolve model fields in-place for update payload if present.
|
|
1109
|
+
|
|
1110
|
+
If 'model' or 'language_model_id' keys exist in the known fields dict,
|
|
1111
|
+
this method resolves them into 'language_model_id', 'provider', and 'model_name'
|
|
1112
|
+
using the standard resolution logic, ensuring consistency with create_agent.
|
|
1113
|
+
|
|
1114
|
+
Args:
|
|
1115
|
+
known: The dictionary of known fields to check and update in-place.
|
|
1116
|
+
"""
|
|
1117
|
+
if "model" in known or "language_model_id" in known:
|
|
1118
|
+
model_val = known.pop("model", None)
|
|
1119
|
+
r_model, r_id, r_prov, r_name = self._process_model_fields(model_val, known)
|
|
1120
|
+
|
|
1121
|
+
if r_model is not None:
|
|
1122
|
+
known["model"] = r_model
|
|
1123
|
+
if r_id is not None:
|
|
1124
|
+
known["language_model_id"] = r_id
|
|
1125
|
+
if r_prov is not None:
|
|
1126
|
+
known["provider"] = r_prov
|
|
1127
|
+
if r_name is not None:
|
|
1128
|
+
known["model_name"] = r_name
|
|
1129
|
+
|
|
724
1130
|
def _update_agent_from_payload(
|
|
725
1131
|
self,
|
|
726
1132
|
agent_id: str,
|
|
@@ -746,6 +1152,8 @@ class AgentClient(BaseClient):
|
|
|
746
1152
|
if mcps_value is not None:
|
|
747
1153
|
mcps_value = self._resolve_mcp_ids(mcps_value, mcp_refs) # pragma: no cover
|
|
748
1154
|
|
|
1155
|
+
self._resolve_update_model_fields(known)
|
|
1156
|
+
|
|
749
1157
|
request = AgentUpdateRequest(
|
|
750
1158
|
name=known.pop("name", None),
|
|
751
1159
|
instruction=known.pop("instruction", None),
|
|
@@ -770,8 +1178,9 @@ class AgentClient(BaseClient):
|
|
|
770
1178
|
|
|
771
1179
|
payload_dict = request.to_payload(current_agent)
|
|
772
1180
|
|
|
773
|
-
|
|
774
|
-
|
|
1181
|
+
api_response = self._request("PUT", f"/agents/{agent_id}", json=payload_dict)
|
|
1182
|
+
response = AgentResponse(**api_response)
|
|
1183
|
+
return Agent.from_response(response, client=self)
|
|
775
1184
|
|
|
776
1185
|
def update_agent(
|
|
777
1186
|
self,
|
|
@@ -818,6 +1227,62 @@ class AgentClient(BaseClient):
|
|
|
818
1227
|
"""Delete an agent."""
|
|
819
1228
|
self._request("DELETE", f"/agents/{agent_id}")
|
|
820
1229
|
|
|
1230
|
+
def upsert_agent(self, identifier: str | Agent, **kwargs) -> Agent:
|
|
1231
|
+
"""Create or update an agent by instance, ID, or name.
|
|
1232
|
+
|
|
1233
|
+
Args:
|
|
1234
|
+
identifier: Agent instance, ID (UUID string), or name
|
|
1235
|
+
**kwargs: Agent configuration (instruction, description, tools, etc.)
|
|
1236
|
+
|
|
1237
|
+
Returns:
|
|
1238
|
+
The created or updated agent.
|
|
1239
|
+
|
|
1240
|
+
Example:
|
|
1241
|
+
>>> # By name (creates if not exists)
|
|
1242
|
+
>>> agent = client.agents.upsert_agent(
|
|
1243
|
+
... "hello_agent",
|
|
1244
|
+
... instruction="You are a helpful assistant.",
|
|
1245
|
+
... description="A friendly agent",
|
|
1246
|
+
... )
|
|
1247
|
+
>>> # By instance
|
|
1248
|
+
>>> agent = client.agents.upsert_agent(existing_agent, description="Updated")
|
|
1249
|
+
>>> # By ID
|
|
1250
|
+
>>> agent = client.agents.upsert_agent("uuid-here", description="Updated")
|
|
1251
|
+
"""
|
|
1252
|
+
# Handle Agent instance
|
|
1253
|
+
if isinstance(identifier, Agent):
|
|
1254
|
+
if identifier.id:
|
|
1255
|
+
logger.info("Updating agent by instance: %s", identifier.name)
|
|
1256
|
+
return self.update_agent(identifier.id, name=identifier.name, **kwargs)
|
|
1257
|
+
identifier = identifier.name
|
|
1258
|
+
|
|
1259
|
+
# Handle string (ID or name)
|
|
1260
|
+
if isinstance(identifier, str):
|
|
1261
|
+
# Check if it's a UUID
|
|
1262
|
+
if is_uuid(identifier):
|
|
1263
|
+
logger.info("Updating agent by ID: %s", identifier)
|
|
1264
|
+
return self.update_agent(identifier, **kwargs)
|
|
1265
|
+
|
|
1266
|
+
# It's a name - find or create
|
|
1267
|
+
return self._upsert_agent_by_name(identifier, **kwargs)
|
|
1268
|
+
|
|
1269
|
+
raise ValueError(f"Invalid identifier type: {type(identifier)}")
|
|
1270
|
+
|
|
1271
|
+
def _upsert_agent_by_name(self, name: str, **kwargs) -> Agent:
|
|
1272
|
+
"""Find agent by name and update, or create if not found."""
|
|
1273
|
+
existing = self.find_agents(name)
|
|
1274
|
+
|
|
1275
|
+
if len(existing) == 1:
|
|
1276
|
+
logger.info("Updating existing agent: %s", name)
|
|
1277
|
+
return self.update_agent(existing[0].id, name=name, **kwargs)
|
|
1278
|
+
|
|
1279
|
+
if len(existing) > 1:
|
|
1280
|
+
raise ValueError(f"Multiple agents found with name '{name}'")
|
|
1281
|
+
|
|
1282
|
+
# Create new agent
|
|
1283
|
+
logger.info("Creating new agent: %s", name)
|
|
1284
|
+
return self.create_agent(name=name, **kwargs)
|
|
1285
|
+
|
|
821
1286
|
def _prepare_sync_request_data(
|
|
822
1287
|
self,
|
|
823
1288
|
message: str,
|
|
@@ -882,9 +1347,35 @@ class AgentClient(BaseClient):
|
|
|
882
1347
|
tty: bool = False,
|
|
883
1348
|
*,
|
|
884
1349
|
renderer: RichStreamRenderer | str | None = "auto",
|
|
1350
|
+
runtime_config: dict[str, Any] | None = None,
|
|
1351
|
+
hitl_handler: "RemoteHITLHandler | None" = None,
|
|
885
1352
|
**kwargs,
|
|
886
1353
|
) -> str:
|
|
887
|
-
"""Run an agent with a message, streaming via a renderer.
|
|
1354
|
+
"""Run an agent with a message, streaming via a renderer.
|
|
1355
|
+
|
|
1356
|
+
Args:
|
|
1357
|
+
agent_id: The ID of the agent to run.
|
|
1358
|
+
message: The message to send to the agent.
|
|
1359
|
+
files: Optional list of files to include with the request.
|
|
1360
|
+
tty: Whether to enable TTY mode.
|
|
1361
|
+
renderer: Renderer for streaming output.
|
|
1362
|
+
runtime_config: Optional runtime configuration for tools, MCPs, and agents.
|
|
1363
|
+
Keys should be platform IDs. Example:
|
|
1364
|
+
{
|
|
1365
|
+
"tool_configs": {"tool-id": {"param": "value"}},
|
|
1366
|
+
"mcp_configs": {"mcp-id": {"setting": "on"}},
|
|
1367
|
+
"agent_config": {"planning": True},
|
|
1368
|
+
}
|
|
1369
|
+
hitl_handler: Optional RemoteHITLHandler for approval callbacks.
|
|
1370
|
+
Set GLAIP_HITL_AUTO_APPROVE=true for auto-approval without handler.
|
|
1371
|
+
**kwargs: Additional arguments to pass to the run API.
|
|
1372
|
+
|
|
1373
|
+
Returns:
|
|
1374
|
+
The agent's response as a string.
|
|
1375
|
+
"""
|
|
1376
|
+
# Include runtime_config in kwargs only when caller hasn't already provided it
|
|
1377
|
+
if runtime_config is not None and "runtime_config" not in kwargs:
|
|
1378
|
+
kwargs["runtime_config"] = runtime_config
|
|
888
1379
|
(
|
|
889
1380
|
payload,
|
|
890
1381
|
data_payload,
|
|
@@ -933,6 +1424,7 @@ class AgentClient(BaseClient):
|
|
|
933
1424
|
timeout_seconds,
|
|
934
1425
|
agent_name,
|
|
935
1426
|
meta,
|
|
1427
|
+
hitl_handler=hitl_handler,
|
|
936
1428
|
)
|
|
937
1429
|
|
|
938
1430
|
except KeyboardInterrupt:
|
|
@@ -949,6 +1441,13 @@ class AgentClient(BaseClient):
|
|
|
949
1441
|
if multipart_data:
|
|
950
1442
|
multipart_data.close()
|
|
951
1443
|
|
|
1444
|
+
# Wait for pending HITL decisions before returning
|
|
1445
|
+
if hitl_handler and hasattr(hitl_handler, "wait_for_pending_decisions"):
|
|
1446
|
+
try:
|
|
1447
|
+
hitl_handler.wait_for_pending_decisions(timeout=30)
|
|
1448
|
+
except Exception as e:
|
|
1449
|
+
logger.warning(f"Error waiting for HITL decisions: {e}")
|
|
1450
|
+
|
|
952
1451
|
return self._finalize_renderer(
|
|
953
1452
|
r,
|
|
954
1453
|
final_text,
|
|
@@ -1028,7 +1527,9 @@ class AgentClient(BaseClient):
|
|
|
1028
1527
|
message: str,
|
|
1029
1528
|
files: list[str | BinaryIO] | None = None,
|
|
1030
1529
|
*,
|
|
1031
|
-
|
|
1530
|
+
request_timeout: float | None = None,
|
|
1531
|
+
runtime_config: dict[str, Any] | None = None,
|
|
1532
|
+
hitl_handler: "RemoteHITLHandler | None" = None,
|
|
1032
1533
|
**kwargs,
|
|
1033
1534
|
) -> AsyncGenerator[dict, None]:
|
|
1034
1535
|
"""Async run an agent with a message, yielding streaming JSON chunks.
|
|
@@ -1037,29 +1538,53 @@ class AgentClient(BaseClient):
|
|
|
1037
1538
|
agent_id: ID of the agent to run
|
|
1038
1539
|
message: Message to send to the agent
|
|
1039
1540
|
files: Optional list of files to include
|
|
1040
|
-
|
|
1541
|
+
request_timeout: Optional request timeout in seconds (defaults to client timeout)
|
|
1542
|
+
runtime_config: Optional runtime configuration for tools, MCPs, and agents.
|
|
1543
|
+
Keys should be platform IDs. Example:
|
|
1544
|
+
{
|
|
1545
|
+
"tool_configs": {"tool-id": {"param": "value"}},
|
|
1546
|
+
"mcp_configs": {"mcp-id": {"setting": "on"}},
|
|
1547
|
+
"agent_config": {"planning": True},
|
|
1548
|
+
}
|
|
1549
|
+
hitl_handler: Optional HITL handler for remote approval requests.
|
|
1550
|
+
Note: Async HITL support is currently deferred. This parameter
|
|
1551
|
+
is accepted for API consistency but will raise NotImplementedError
|
|
1552
|
+
if provided.
|
|
1041
1553
|
**kwargs: Additional arguments (chat_history, pii_mapping, etc.)
|
|
1042
1554
|
|
|
1043
1555
|
Yields:
|
|
1044
1556
|
Dictionary containing parsed JSON chunks from the streaming response
|
|
1045
1557
|
|
|
1046
1558
|
Raises:
|
|
1559
|
+
NotImplementedError: If hitl_handler is provided (async HITL not yet supported)
|
|
1047
1560
|
AgentTimeoutError: When agent execution times out
|
|
1048
1561
|
httpx.TimeoutException: When general timeout occurs
|
|
1049
1562
|
Exception: For other unexpected errors
|
|
1050
1563
|
"""
|
|
1564
|
+
if hitl_handler is not None:
|
|
1565
|
+
raise NotImplementedError(
|
|
1566
|
+
"Async HITL support is currently deferred. "
|
|
1567
|
+
"Please use the synchronous run_agent() method with hitl_handler."
|
|
1568
|
+
)
|
|
1569
|
+
# Include runtime_config in kwargs only when caller hasn't already provided it
|
|
1570
|
+
if runtime_config is not None and "runtime_config" not in kwargs:
|
|
1571
|
+
kwargs["runtime_config"] = runtime_config
|
|
1572
|
+
# Derive timeout values for request/control flow
|
|
1573
|
+
legacy_timeout = kwargs.get("timeout")
|
|
1574
|
+
http_timeout_override = request_timeout if request_timeout is not None else legacy_timeout
|
|
1575
|
+
http_timeout = http_timeout_override or self.timeout
|
|
1576
|
+
|
|
1051
1577
|
# Prepare request data
|
|
1052
1578
|
payload, data_payload, files_payload, headers = self._prepare_request_data(message, files, **kwargs)
|
|
1053
1579
|
|
|
1054
1580
|
# Create async client configuration
|
|
1055
|
-
async_client_config = self._create_async_client_config(
|
|
1581
|
+
async_client_config = self._create_async_client_config(http_timeout_override, headers)
|
|
1056
1582
|
|
|
1057
1583
|
# Get execution timeout for streaming control
|
|
1058
1584
|
timeout_seconds = kwargs.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
|
|
1059
1585
|
agent_name = kwargs.get("agent_name")
|
|
1060
1586
|
|
|
1061
|
-
|
|
1062
|
-
# Create async client and stream response
|
|
1587
|
+
async def _chunk_stream() -> AsyncGenerator[dict, None]:
|
|
1063
1588
|
async with httpx.AsyncClient(**async_client_config) as async_client:
|
|
1064
1589
|
async for chunk in self._stream_agent_response(
|
|
1065
1590
|
async_client,
|
|
@@ -1073,7 +1598,14 @@ class AgentClient(BaseClient):
|
|
|
1073
1598
|
):
|
|
1074
1599
|
yield chunk
|
|
1075
1600
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1601
|
+
async with _async_timeout_guard(http_timeout):
|
|
1602
|
+
async for chunk in _chunk_stream():
|
|
1603
|
+
yield chunk
|
|
1604
|
+
|
|
1605
|
+
@property
|
|
1606
|
+
def runs(self) -> "AgentRunsClient":
|
|
1607
|
+
"""Get the agent runs client."""
|
|
1608
|
+
if self._runs_client is None:
|
|
1609
|
+
shared_config = build_shared_config(self)
|
|
1610
|
+
self._runs_client = AgentRunsClient(**shared_config)
|
|
1611
|
+
return self._runs_client
|