glaip-sdk 0.6.5b3__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 +42 -5
- glaip_sdk/agents/base.py +362 -39
- glaip_sdk/branding.py +113 -2
- glaip_sdk/cli/account_store.py +15 -0
- glaip_sdk/cli/auth.py +14 -8
- glaip_sdk/cli/commands/accounts.py +1 -1
- 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 +15 -12
- glaip_sdk/cli/commands/configure.py +2 -3
- 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 +2 -4
- 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.py → transcripts_original.py} +2 -1
- glaip_sdk/cli/commands/update.py +163 -17
- glaip_sdk/cli/config.py +1 -0
- glaip_sdk/cli/core/output.py +12 -7
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/main.py +127 -39
- glaip_sdk/cli/pager.py +3 -3
- glaip_sdk/cli/resolution.py +2 -1
- glaip_sdk/cli/slash/accounts_controller.py +112 -32
- glaip_sdk/cli/slash/agent_session.py +5 -2
- glaip_sdk/cli/slash/prompt.py +11 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
- glaip_sdk/cli/slash/session.py +375 -25
- glaip_sdk/cli/slash/tui/__init__.py +28 -1
- glaip_sdk/cli/slash/tui/accounts.tcss +97 -6
- glaip_sdk/cli/slash/tui/accounts_app.py +1107 -126
- 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 +43 -21
- glaip_sdk/cli/slash/tui/remote_runs_app.py +152 -20
- 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/history.py +1 -1
- glaip_sdk/cli/transcript/viewer.py +5 -3
- glaip_sdk/cli/tui_settings.py +125 -0
- glaip_sdk/cli/update_notifier.py +215 -7
- glaip_sdk/cli/validators.py +1 -1
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agents.py +290 -16
- glaip_sdk/client/base.py +25 -0
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +7 -5
- glaip_sdk/client/mcps.py +44 -13
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +28 -48
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +414 -3
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/tools.py +57 -26
- glaip_sdk/config/constants.py +22 -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/models/__init__.py +47 -1
- glaip_sdk/models/_provider_mappings.py +101 -0
- glaip_sdk/models/_validation.py +97 -0
- glaip_sdk/models/agent.py +2 -1
- glaip_sdk/models/agent_runs.py +2 -1
- glaip_sdk/models/constants.py +141 -0
- glaip_sdk/models/model.py +170 -0
- glaip_sdk/models/schedule.py +224 -0
- glaip_sdk/payload_schemas/agent.py +1 -0
- glaip_sdk/payload_schemas/guardrails.py +34 -0
- glaip_sdk/registry/tool.py +273 -66
- 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/base.py +67 -14
- glaip_sdk/utils/__init__.py +1 -0
- 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 +138 -2
- glaip_sdk/utils/import_resolver.py +43 -11
- glaip_sdk/utils/rendering/renderer/base.py +58 -0
- glaip_sdk/utils/runtime_config.py +120 -0
- glaip_sdk/utils/sync.py +31 -11
- glaip_sdk/utils/tool_detection.py +301 -0
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- {glaip_sdk-0.6.5b3.dist-info → glaip_sdk-0.7.17.dist-info}/METADATA +49 -38
- glaip_sdk-0.7.17.dist-info/RECORD +224 -0
- {glaip_sdk-0.6.5b3.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 -1509
- glaip_sdk/cli/commands/mcps.py +0 -1356
- glaip_sdk/cli/commands/tools.py +0 -576
- glaip_sdk/cli/utils.py +0 -263
- glaip_sdk-0.6.5b3.dist-info/RECORD +0 -145
- glaip_sdk-0.6.5b3.dist-info/entry_points.txt +0 -3
glaip_sdk/client/agents.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# pylint: disable=duplicate-code
|
|
2
2
|
"""Agent client for AIP SDK.
|
|
3
3
|
|
|
4
4
|
Authors:
|
|
@@ -9,23 +9,28 @@ Authors:
|
|
|
9
9
|
import asyncio
|
|
10
10
|
import json
|
|
11
11
|
import logging
|
|
12
|
+
import warnings
|
|
12
13
|
from collections.abc import AsyncGenerator, Callable, Iterator, Mapping
|
|
13
14
|
from contextlib import asynccontextmanager
|
|
14
15
|
from os import PathLike
|
|
15
16
|
from pathlib import Path
|
|
16
|
-
from typing import Any, BinaryIO
|
|
17
|
+
from typing import TYPE_CHECKING, Any, BinaryIO
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from glaip_sdk.client.schedules import ScheduleClient
|
|
21
|
+
from glaip_sdk.hitl.remote import RemoteHITLHandler
|
|
17
22
|
|
|
18
23
|
import httpx
|
|
19
24
|
from glaip_sdk.agents import Agent
|
|
20
|
-
from glaip_sdk.client.
|
|
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 (
|
|
21
29
|
AgentCreateRequest,
|
|
22
30
|
AgentListParams,
|
|
23
31
|
AgentListResult,
|
|
24
32
|
AgentUpdateRequest,
|
|
25
33
|
)
|
|
26
|
-
from glaip_sdk.client.agent_runs import AgentRunsClient
|
|
27
|
-
from glaip_sdk.client.base import BaseClient
|
|
28
|
-
from glaip_sdk.client.mcps import MCPClient
|
|
29
34
|
from glaip_sdk.client.run_rendering import (
|
|
30
35
|
AgentRunRenderingManager,
|
|
31
36
|
compute_timeout_seconds,
|
|
@@ -38,10 +43,10 @@ from glaip_sdk.config.constants import (
|
|
|
38
43
|
DEFAULT_AGENT_RUN_TIMEOUT,
|
|
39
44
|
DEFAULT_AGENT_TYPE,
|
|
40
45
|
DEFAULT_AGENT_VERSION,
|
|
41
|
-
DEFAULT_MODEL,
|
|
42
46
|
)
|
|
43
47
|
from glaip_sdk.exceptions import NotFoundError, ValidationError
|
|
44
48
|
from glaip_sdk.models import AgentResponse
|
|
49
|
+
from glaip_sdk.models.constants import DEFAULT_MODEL
|
|
45
50
|
from glaip_sdk.payload_schemas.agent import list_server_only_fields
|
|
46
51
|
from glaip_sdk.utils.agent_config import normalize_agent_config_for_import
|
|
47
52
|
from glaip_sdk.utils.client_utils import (
|
|
@@ -251,19 +256,134 @@ class AgentClient(BaseClient):
|
|
|
251
256
|
self,
|
|
252
257
|
*,
|
|
253
258
|
parent_client: BaseClient | None = None,
|
|
259
|
+
lm_cache_ttl: float = 3600.0,
|
|
254
260
|
**kwargs: Any,
|
|
255
261
|
) -> None:
|
|
256
262
|
"""Initialize the agent client.
|
|
257
263
|
|
|
258
264
|
Args:
|
|
259
|
-
parent_client: Parent client to adopt session/config from
|
|
260
|
-
|
|
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.
|
|
261
269
|
"""
|
|
262
270
|
super().__init__(parent_client=parent_client, **kwargs)
|
|
263
271
|
self._renderer_manager = AgentRunRenderingManager(logger)
|
|
264
272
|
self._tool_client: ToolClient | None = None
|
|
265
273
|
self._mcp_client: MCPClient | None = None
|
|
266
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
|
|
267
387
|
|
|
268
388
|
def list_agents(
|
|
269
389
|
self,
|
|
@@ -411,6 +531,7 @@ class AgentClient(BaseClient):
|
|
|
411
531
|
timeout_seconds: float,
|
|
412
532
|
agent_name: str | None,
|
|
413
533
|
meta: dict[str, Any],
|
|
534
|
+
hitl_handler: "RemoteHITLHandler | None" = None,
|
|
414
535
|
) -> tuple[str, dict[str, Any], float | None, float | None]:
|
|
415
536
|
"""Process stream events from an HTTP response.
|
|
416
537
|
|
|
@@ -420,6 +541,7 @@ class AgentClient(BaseClient):
|
|
|
420
541
|
timeout_seconds: Timeout in seconds.
|
|
421
542
|
agent_name: Optional agent name.
|
|
422
543
|
meta: Metadata dictionary.
|
|
544
|
+
hitl_handler: Optional HITL handler for approval callbacks.
|
|
423
545
|
|
|
424
546
|
Returns:
|
|
425
547
|
Tuple of (final_text, stats_usage, started_monotonic, finished_monotonic).
|
|
@@ -431,6 +553,7 @@ class AgentClient(BaseClient):
|
|
|
431
553
|
timeout_seconds,
|
|
432
554
|
agent_name,
|
|
433
555
|
meta,
|
|
556
|
+
hitl_handler=hitl_handler,
|
|
434
557
|
)
|
|
435
558
|
|
|
436
559
|
def _finalize_renderer(
|
|
@@ -453,8 +576,13 @@ class AgentClient(BaseClient):
|
|
|
453
576
|
Returns:
|
|
454
577
|
Final text string.
|
|
455
578
|
"""
|
|
579
|
+
from glaip_sdk.client.run_rendering import ( # noqa: PLC0415
|
|
580
|
+
finalize_render_manager,
|
|
581
|
+
)
|
|
582
|
+
|
|
456
583
|
manager = self._get_renderer_manager()
|
|
457
|
-
return
|
|
584
|
+
return finalize_render_manager(
|
|
585
|
+
manager,
|
|
458
586
|
renderer,
|
|
459
587
|
final_text,
|
|
460
588
|
stats_usage,
|
|
@@ -482,6 +610,20 @@ class AgentClient(BaseClient):
|
|
|
482
610
|
self._mcp_client = MCPClient(parent_client=self)
|
|
483
611
|
return self._mcp_client
|
|
484
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
|
+
|
|
485
627
|
def _normalise_reference_entry(
|
|
486
628
|
self,
|
|
487
629
|
entry: Any,
|
|
@@ -749,10 +891,18 @@ class AgentClient(BaseClient):
|
|
|
749
891
|
plural_label="MCPs",
|
|
750
892
|
)
|
|
751
893
|
|
|
752
|
-
def
|
|
753
|
-
"""
|
|
754
|
-
|
|
894
|
+
def _validate_agent_basics(self, known: dict[str, Any]) -> tuple[str, str]:
|
|
895
|
+
"""Validate and extract basic agent fields.
|
|
896
|
+
|
|
897
|
+
Args:
|
|
898
|
+
known: Known fields dictionary.
|
|
899
|
+
|
|
900
|
+
Returns:
|
|
901
|
+
Tuple of (name, validated_instruction).
|
|
755
902
|
|
|
903
|
+
Raises:
|
|
904
|
+
ValueError: If name or instruction is empty/whitespace.
|
|
905
|
+
"""
|
|
756
906
|
name = known.pop("name", None)
|
|
757
907
|
instruction = known.pop("instruction", None)
|
|
758
908
|
if not name or not str(name).strip():
|
|
@@ -761,9 +911,20 @@ class AgentClient(BaseClient):
|
|
|
761
911
|
raise ValueError("Agent instruction cannot be empty or whitespace")
|
|
762
912
|
|
|
763
913
|
validated_instruction = validate_agent_instruction(str(instruction))
|
|
764
|
-
|
|
914
|
+
return str(name).strip(), validated_instruction
|
|
765
915
|
|
|
766
|
-
|
|
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
|
+
"""
|
|
767
928
|
tool_refs = extras.pop("_tool_refs", None)
|
|
768
929
|
agent_refs = extras.pop("_agent_refs", None)
|
|
769
930
|
mcp_refs = extras.pop("_mcp_refs", None)
|
|
@@ -776,10 +937,56 @@ class AgentClient(BaseClient):
|
|
|
776
937
|
resolved_agents = self._resolve_agent_ids(agents_raw, agent_refs)
|
|
777
938
|
resolved_mcps = self._resolve_mcp_ids(mcps_raw, mcp_refs)
|
|
778
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
|
+
|
|
779
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
|
+
|
|
780
976
|
provider = known.pop("provider", None)
|
|
781
977
|
model_name = known.pop("model_name", None)
|
|
782
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
|
+
"""
|
|
783
990
|
agent_type_value = known.pop("agent_type", None)
|
|
784
991
|
fallback_type_value = known.pop("type", None)
|
|
785
992
|
if agent_type_value is None:
|
|
@@ -787,6 +994,26 @@ class AgentClient(BaseClient):
|
|
|
787
994
|
|
|
788
995
|
framework_value = known.pop("framework", None) or DEFAULT_AGENT_FRAMEWORK
|
|
789
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)
|
|
790
1017
|
account_id = known.pop("account_id", None)
|
|
791
1018
|
description = known.pop("description", None)
|
|
792
1019
|
metadata = _prepare_agent_metadata(known.pop("metadata", None))
|
|
@@ -799,7 +1026,7 @@ class AgentClient(BaseClient):
|
|
|
799
1026
|
final_extras.setdefault("model", resolved_model)
|
|
800
1027
|
|
|
801
1028
|
request = AgentCreateRequest(
|
|
802
|
-
name=
|
|
1029
|
+
name=name,
|
|
803
1030
|
instruction=validated_instruction,
|
|
804
1031
|
model=resolved_model,
|
|
805
1032
|
language_model_id=language_model_id,
|
|
@@ -877,6 +1104,29 @@ class AgentClient(BaseClient):
|
|
|
877
1104
|
"""Backward-compatible helper to create an agent from a configuration file."""
|
|
878
1105
|
return self.create_agent(file=file_path, **overrides)
|
|
879
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
|
+
|
|
880
1130
|
def _update_agent_from_payload(
|
|
881
1131
|
self,
|
|
882
1132
|
agent_id: str,
|
|
@@ -902,6 +1152,8 @@ class AgentClient(BaseClient):
|
|
|
902
1152
|
if mcps_value is not None:
|
|
903
1153
|
mcps_value = self._resolve_mcp_ids(mcps_value, mcp_refs) # pragma: no cover
|
|
904
1154
|
|
|
1155
|
+
self._resolve_update_model_fields(known)
|
|
1156
|
+
|
|
905
1157
|
request = AgentUpdateRequest(
|
|
906
1158
|
name=known.pop("name", None),
|
|
907
1159
|
instruction=known.pop("instruction", None),
|
|
@@ -1096,6 +1348,7 @@ class AgentClient(BaseClient):
|
|
|
1096
1348
|
*,
|
|
1097
1349
|
renderer: RichStreamRenderer | str | None = "auto",
|
|
1098
1350
|
runtime_config: dict[str, Any] | None = None,
|
|
1351
|
+
hitl_handler: "RemoteHITLHandler | None" = None,
|
|
1099
1352
|
**kwargs,
|
|
1100
1353
|
) -> str:
|
|
1101
1354
|
"""Run an agent with a message, streaming via a renderer.
|
|
@@ -1113,6 +1366,8 @@ class AgentClient(BaseClient):
|
|
|
1113
1366
|
"mcp_configs": {"mcp-id": {"setting": "on"}},
|
|
1114
1367
|
"agent_config": {"planning": True},
|
|
1115
1368
|
}
|
|
1369
|
+
hitl_handler: Optional RemoteHITLHandler for approval callbacks.
|
|
1370
|
+
Set GLAIP_HITL_AUTO_APPROVE=true for auto-approval without handler.
|
|
1116
1371
|
**kwargs: Additional arguments to pass to the run API.
|
|
1117
1372
|
|
|
1118
1373
|
Returns:
|
|
@@ -1169,6 +1424,7 @@ class AgentClient(BaseClient):
|
|
|
1169
1424
|
timeout_seconds,
|
|
1170
1425
|
agent_name,
|
|
1171
1426
|
meta,
|
|
1427
|
+
hitl_handler=hitl_handler,
|
|
1172
1428
|
)
|
|
1173
1429
|
|
|
1174
1430
|
except KeyboardInterrupt:
|
|
@@ -1185,6 +1441,13 @@ class AgentClient(BaseClient):
|
|
|
1185
1441
|
if multipart_data:
|
|
1186
1442
|
multipart_data.close()
|
|
1187
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
|
+
|
|
1188
1451
|
return self._finalize_renderer(
|
|
1189
1452
|
r,
|
|
1190
1453
|
final_text,
|
|
@@ -1266,6 +1529,7 @@ class AgentClient(BaseClient):
|
|
|
1266
1529
|
*,
|
|
1267
1530
|
request_timeout: float | None = None,
|
|
1268
1531
|
runtime_config: dict[str, Any] | None = None,
|
|
1532
|
+
hitl_handler: "RemoteHITLHandler | None" = None,
|
|
1269
1533
|
**kwargs,
|
|
1270
1534
|
) -> AsyncGenerator[dict, None]:
|
|
1271
1535
|
"""Async run an agent with a message, yielding streaming JSON chunks.
|
|
@@ -1282,16 +1546,26 @@ class AgentClient(BaseClient):
|
|
|
1282
1546
|
"mcp_configs": {"mcp-id": {"setting": "on"}},
|
|
1283
1547
|
"agent_config": {"planning": True},
|
|
1284
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.
|
|
1285
1553
|
**kwargs: Additional arguments (chat_history, pii_mapping, etc.)
|
|
1286
1554
|
|
|
1287
1555
|
Yields:
|
|
1288
1556
|
Dictionary containing parsed JSON chunks from the streaming response
|
|
1289
1557
|
|
|
1290
1558
|
Raises:
|
|
1559
|
+
NotImplementedError: If hitl_handler is provided (async HITL not yet supported)
|
|
1291
1560
|
AgentTimeoutError: When agent execution times out
|
|
1292
1561
|
httpx.TimeoutException: When general timeout occurs
|
|
1293
1562
|
Exception: For other unexpected errors
|
|
1294
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
|
+
)
|
|
1295
1569
|
# Include runtime_config in kwargs only when caller hasn't already provided it
|
|
1296
1570
|
if runtime_config is not None and "runtime_config" not in kwargs:
|
|
1297
1571
|
kwargs["runtime_config"] = runtime_config
|
glaip_sdk/client/base.py
CHANGED
|
@@ -8,6 +8,7 @@ Authors:
|
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
10
|
import os
|
|
11
|
+
import time
|
|
11
12
|
from collections.abc import Iterable, Mapping
|
|
12
13
|
from typing import Any, NoReturn, Union
|
|
13
14
|
|
|
@@ -89,6 +90,11 @@ class BaseClient:
|
|
|
89
90
|
client_log.info(f"Initializing client with API URL: {self.api_url}")
|
|
90
91
|
self.http_client = self._build_client(timeout)
|
|
91
92
|
|
|
93
|
+
# Language model cache (shared by all clients)
|
|
94
|
+
self._lm_cache: list[dict[str, Any]] | None = None
|
|
95
|
+
self._lm_cache_time: float = 0.0
|
|
96
|
+
self._lm_cache_ttl: float = 300.0 # 5 minutes TTL
|
|
97
|
+
|
|
92
98
|
def _build_client(self, timeout: float) -> httpx.Client:
|
|
93
99
|
"""Build HTTP client with configuration."""
|
|
94
100
|
# For streaming operations, we need more generous read timeouts
|
|
@@ -481,6 +487,25 @@ class BaseClient:
|
|
|
481
487
|
request_id=request_id,
|
|
482
488
|
)
|
|
483
489
|
|
|
490
|
+
def list_language_models(self, force_refresh: bool = False) -> list[dict[str, Any]]:
|
|
491
|
+
"""List available language models with TTL caching.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
force_refresh: Whether to ignore cache and fetch fresh list.
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
List of available language models.
|
|
498
|
+
"""
|
|
499
|
+
now = time.monotonic()
|
|
500
|
+
if not force_refresh and self._lm_cache is not None:
|
|
501
|
+
if now - self._lm_cache_time < self._lm_cache_ttl:
|
|
502
|
+
return self._lm_cache
|
|
503
|
+
|
|
504
|
+
models = self._request("GET", "/language-models") or []
|
|
505
|
+
self._lm_cache = models
|
|
506
|
+
self._lm_cache_time = now
|
|
507
|
+
return models
|
|
508
|
+
|
|
484
509
|
def close(self) -> None:
|
|
485
510
|
"""Close the HTTP client."""
|
|
486
511
|
if hasattr(self, "http_client") and self.http_client and not self._session_scoped and not self._parent_client:
|
glaip_sdk/client/hitl.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""HITL REST client for manual approval operations.
|
|
3
|
+
|
|
4
|
+
Authors:
|
|
5
|
+
GLAIP SDK Team
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from glaip_sdk.client.base import BaseClient
|
|
11
|
+
from glaip_sdk.hitl.base import HITLDecision
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HITLClient(BaseClient):
|
|
15
|
+
"""Client for HITL REST endpoints.
|
|
16
|
+
|
|
17
|
+
Use for manual approval workflows separate from agent runs.
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
>>> # List pending approvals
|
|
21
|
+
>>> pending = client.hitl.list_pending()
|
|
22
|
+
>>>
|
|
23
|
+
>>> # Approve a request
|
|
24
|
+
>>> client.hitl.approve(
|
|
25
|
+
... request_id="bc4d0a77-7800-470e-a91c-7fd663a66b4d",
|
|
26
|
+
... operator_input="Verified and approved",
|
|
27
|
+
... )
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def approve(
|
|
31
|
+
self,
|
|
32
|
+
request_id: str,
|
|
33
|
+
operator_input: str | None = None,
|
|
34
|
+
run_id: str | None = None,
|
|
35
|
+
) -> dict[str, Any]:
|
|
36
|
+
"""Approve a HITL request.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
request_id: HITL request ID from SSE stream
|
|
40
|
+
operator_input: Optional notes/reason for approval
|
|
41
|
+
run_id: Optional client-side run correlation ID
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Response dict: {"status": "ok", "message": "..."}
|
|
45
|
+
"""
|
|
46
|
+
return self._post_decision(
|
|
47
|
+
request_id,
|
|
48
|
+
HITLDecision.APPROVED,
|
|
49
|
+
operator_input,
|
|
50
|
+
run_id,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def reject(
|
|
54
|
+
self,
|
|
55
|
+
request_id: str,
|
|
56
|
+
operator_input: str | None = None,
|
|
57
|
+
run_id: str | None = None,
|
|
58
|
+
) -> dict[str, Any]:
|
|
59
|
+
"""Reject a HITL request.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
request_id: HITL request ID
|
|
63
|
+
operator_input: Optional reason for rejection
|
|
64
|
+
run_id: Optional run correlation ID
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Response dict
|
|
68
|
+
"""
|
|
69
|
+
return self._post_decision(
|
|
70
|
+
request_id,
|
|
71
|
+
HITLDecision.REJECTED,
|
|
72
|
+
operator_input,
|
|
73
|
+
run_id,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def skip(
|
|
77
|
+
self,
|
|
78
|
+
request_id: str,
|
|
79
|
+
operator_input: str | None = None,
|
|
80
|
+
run_id: str | None = None,
|
|
81
|
+
) -> dict[str, Any]:
|
|
82
|
+
"""Skip a HITL request.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
request_id: HITL request ID
|
|
86
|
+
operator_input: Optional notes
|
|
87
|
+
run_id: Optional run correlation ID
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Response dict
|
|
91
|
+
"""
|
|
92
|
+
return self._post_decision(
|
|
93
|
+
request_id,
|
|
94
|
+
HITLDecision.SKIPPED,
|
|
95
|
+
operator_input,
|
|
96
|
+
run_id,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def _post_decision(
|
|
100
|
+
self,
|
|
101
|
+
request_id: str,
|
|
102
|
+
decision: HITLDecision,
|
|
103
|
+
operator_input: str | None,
|
|
104
|
+
run_id: str | None,
|
|
105
|
+
) -> dict[str, Any]:
|
|
106
|
+
"""Post HITL decision to backend."""
|
|
107
|
+
payload = {
|
|
108
|
+
"request_id": request_id,
|
|
109
|
+
"decision": decision.value,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if operator_input:
|
|
113
|
+
payload["operator_input"] = operator_input
|
|
114
|
+
if run_id:
|
|
115
|
+
payload["run_id"] = run_id
|
|
116
|
+
|
|
117
|
+
return self._request("POST", "/agents/hitl/decision", json=payload)
|
|
118
|
+
|
|
119
|
+
def list_pending(self) -> list[dict[str, Any]]:
|
|
120
|
+
"""List all pending HITL requests.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
List of pending request dicts with metadata:
|
|
124
|
+
[
|
|
125
|
+
{
|
|
126
|
+
"request_id": "...",
|
|
127
|
+
"tool": "...",
|
|
128
|
+
"arguments": {...},
|
|
129
|
+
"created_at": "...",
|
|
130
|
+
"agent_id": "...",
|
|
131
|
+
"hitl_metadata": {...},
|
|
132
|
+
},
|
|
133
|
+
...
|
|
134
|
+
]
|
|
135
|
+
"""
|
|
136
|
+
return self._request("GET", "/agents/hitl/pending")
|