glaip-sdk 0.0.20__py3-none-any.whl → 0.7.7__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 +10 -3
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1250 -0
- glaip_sdk/branding.py +15 -6
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +271 -45
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents/__init__.py +119 -0
- glaip_sdk/cli/commands/agents/_common.py +561 -0
- glaip_sdk/cli/commands/agents/create.py +151 -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 +734 -143
- 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 +14 -12
- 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 +164 -23
- glaip_sdk/cli/config.py +49 -7
- 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 +45 -32
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +14 -17
- glaip_sdk/cli/main.py +344 -167
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/mcp_validators.py +5 -15
- glaip_sdk/cli/pager.py +15 -22
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/parsers/json_input.py +11 -22
- glaip_sdk/cli/resolution.py +5 -10
- glaip_sdk/cli/rich_helpers.py +1 -3
- 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 +65 -29
- glaip_sdk/cli/slash/prompt.py +24 -10
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +827 -232
- glaip_sdk/cli/slash/tui/__init__.py +34 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +88 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +933 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/clipboard.py +147 -0
- glaip_sdk/cli/slash/tui/context.py +59 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/slash/tui/terminal.py +402 -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 +86 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +123 -0
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +258 -60
- glaip_sdk/cli/transcript/capture.py +72 -21
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/launcher.py +1 -3
- glaip_sdk/cli/transcript/viewer.py +79 -329
- glaip_sdk/cli/update_notifier.py +385 -24
- glaip_sdk/cli/validators.py +16 -18
- 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 +370 -100
- glaip_sdk/client/base.py +78 -35
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +25 -10
- glaip_sdk/client/mcps.py +166 -27
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +65 -74
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +583 -79
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +214 -56
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/config/constants.py +11 -0
- glaip_sdk/exceptions.py +1 -3
- 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/icons.py +9 -3
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +107 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +117 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -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 -3
- 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 +112 -0
- glaip_sdk/runner/langgraph.py +872 -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 +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 +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 +468 -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 +4 -14
- glaip_sdk/utils/bundler.py +403 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +46 -28
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +25 -21
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +1 -36
- glaip_sdk/utils/import_export.py +15 -16
- 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 +38 -23
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +10 -3
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +73 -12
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +18 -8
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -51
- glaip_sdk/utils/rendering/renderer/base.py +534 -882
- glaip_sdk/utils/rendering/renderer/config.py +4 -10
- glaip_sdk/utils/rendering/renderer/debug.py +30 -34
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +13 -54
- glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/toggle.py +182 -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/step_tree_state.py +100 -0
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/{steps.py → steps/manager.py} +122 -26
- 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 +29 -26
- glaip_sdk/utils/runtime_config.py +425 -0
- glaip_sdk/utils/serialization.py +32 -46
- 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 +20 -28
- {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.7.7.dist-info}/METADATA +78 -23
- glaip_sdk-0.7.7.dist-info/RECORD +213 -0
- {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.7.7.dist-info}/WHEEL +2 -1
- glaip_sdk-0.7.7.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.7.7.dist-info/top_level.txt +1 -0
- glaip_sdk/cli/commands/agents.py +0 -1412
- glaip_sdk/cli/commands/mcps.py +0 -1225
- glaip_sdk/cli/commands/tools.py +0 -597
- glaip_sdk/cli/utils.py +0 -1330
- glaip_sdk/models.py +0 -259
- glaip_sdk-0.0.20.dist-info/RECORD +0 -80
- glaip_sdk-0.0.20.dist-info/entry_points.txt +0 -3
glaip_sdk/client/agents.py
CHANGED
|
@@ -3,39 +3,49 @@
|
|
|
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
|
-
from typing import Any, BinaryIO
|
|
16
|
+
from typing import TYPE_CHECKING, Any, BinaryIO
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from glaip_sdk.client.schedules import ScheduleClient
|
|
20
|
+
from glaip_sdk.hitl.remote import RemoteHITLHandler
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
import httpx
|
|
23
|
+
from glaip_sdk.agents import Agent
|
|
24
|
+
from glaip_sdk.client.payloads.agent import (
|
|
18
25
|
AgentCreateRequest,
|
|
19
26
|
AgentListParams,
|
|
20
27
|
AgentListResult,
|
|
21
28
|
AgentUpdateRequest,
|
|
22
29
|
)
|
|
30
|
+
from glaip_sdk.client.agent_runs import AgentRunsClient
|
|
23
31
|
from glaip_sdk.client.base import BaseClient
|
|
24
32
|
from glaip_sdk.client.mcps import MCPClient
|
|
25
33
|
from glaip_sdk.client.run_rendering import (
|
|
26
34
|
AgentRunRenderingManager,
|
|
27
35
|
compute_timeout_seconds,
|
|
28
36
|
)
|
|
37
|
+
from glaip_sdk.client.shared import build_shared_config
|
|
29
38
|
from glaip_sdk.client.tools import ToolClient
|
|
30
39
|
from glaip_sdk.config.constants import (
|
|
40
|
+
AGENT_CONFIG_FIELDS,
|
|
31
41
|
DEFAULT_AGENT_FRAMEWORK,
|
|
32
42
|
DEFAULT_AGENT_RUN_TIMEOUT,
|
|
33
43
|
DEFAULT_AGENT_TYPE,
|
|
34
44
|
DEFAULT_AGENT_VERSION,
|
|
35
45
|
DEFAULT_MODEL,
|
|
36
46
|
)
|
|
37
|
-
from glaip_sdk.exceptions import NotFoundError
|
|
38
|
-
from glaip_sdk.models import
|
|
47
|
+
from glaip_sdk.exceptions import NotFoundError, ValidationError
|
|
48
|
+
from glaip_sdk.models import AgentResponse
|
|
39
49
|
from glaip_sdk.payload_schemas.agent import list_server_only_fields
|
|
40
50
|
from glaip_sdk.utils.agent_config import normalize_agent_config_for_import
|
|
41
51
|
from glaip_sdk.utils.client_utils import (
|
|
@@ -67,6 +77,21 @@ _MERGED_SEQUENCE_FIELDS = ("tools", "agents", "mcps")
|
|
|
67
77
|
_DEFAULT_METADATA_TYPE = "custom"
|
|
68
78
|
|
|
69
79
|
|
|
80
|
+
@asynccontextmanager
|
|
81
|
+
async def _async_timeout_guard(
|
|
82
|
+
timeout_seconds: float | None,
|
|
83
|
+
) -> AsyncGenerator[None, None]:
|
|
84
|
+
"""Apply an asyncio timeout when a custom timeout is provided."""
|
|
85
|
+
if timeout_seconds is None:
|
|
86
|
+
yield
|
|
87
|
+
return
|
|
88
|
+
try:
|
|
89
|
+
async with asyncio.timeout(timeout_seconds):
|
|
90
|
+
yield
|
|
91
|
+
except asyncio.TimeoutError as exc:
|
|
92
|
+
raise httpx.TimeoutException(f"Request timed out after {timeout_seconds}s") from exc
|
|
93
|
+
|
|
94
|
+
|
|
70
95
|
def _normalise_sequence(value: Any) -> list[Any] | None:
|
|
71
96
|
"""Normalise optional sequence inputs to plain lists."""
|
|
72
97
|
if value is None:
|
|
@@ -97,9 +122,7 @@ def _merge_override_maps(
|
|
|
97
122
|
for key, value in source.items():
|
|
98
123
|
if value is None:
|
|
99
124
|
continue
|
|
100
|
-
merged[key] = (
|
|
101
|
-
_normalise_sequence(value) if key in _MERGED_SEQUENCE_FIELDS else value
|
|
102
|
-
)
|
|
125
|
+
merged[key] = _normalise_sequence(value) if key in _MERGED_SEQUENCE_FIELDS else value
|
|
103
126
|
return merged
|
|
104
127
|
|
|
105
128
|
|
|
@@ -134,9 +157,7 @@ def _prepare_agent_metadata(value: Any) -> dict[str, Any]:
|
|
|
134
157
|
return prepared
|
|
135
158
|
|
|
136
159
|
|
|
137
|
-
def _load_agent_file_payload(
|
|
138
|
-
file_path: Path, *, model_override: str | None
|
|
139
|
-
) -> dict[str, Any]:
|
|
160
|
+
def _load_agent_file_payload(file_path: Path, *, model_override: str | None) -> dict[str, Any]:
|
|
140
161
|
"""Load agent configuration from disk and normalise legacy fields."""
|
|
141
162
|
if not file_path.exists():
|
|
142
163
|
raise FileNotFoundError(f"Agent configuration file not found: {file_path}")
|
|
@@ -168,9 +189,7 @@ def _prepare_import_payload(
|
|
|
168
189
|
raw_definition = load_resource_from_file(file_path)
|
|
169
190
|
original_refs = _extract_original_refs(raw_definition)
|
|
170
191
|
|
|
171
|
-
base_payload = _load_agent_file_payload(
|
|
172
|
-
file_path, model_override=overrides_dict.get("model")
|
|
173
|
-
)
|
|
192
|
+
base_payload = _load_agent_file_payload(file_path, model_override=overrides_dict.get("model"))
|
|
174
193
|
|
|
175
194
|
cli_args = _build_cli_args(overrides_dict)
|
|
176
195
|
|
|
@@ -199,19 +218,7 @@ def _extract_original_refs(raw_definition: dict) -> dict[str, list]:
|
|
|
199
218
|
|
|
200
219
|
def _build_cli_args(overrides_dict: dict) -> dict[str, Any]:
|
|
201
220
|
"""Build CLI args from overrides, filtering out None values."""
|
|
202
|
-
cli_args = {
|
|
203
|
-
key: overrides_dict.get(key)
|
|
204
|
-
for key in (
|
|
205
|
-
"name",
|
|
206
|
-
"instruction",
|
|
207
|
-
"model",
|
|
208
|
-
"tools",
|
|
209
|
-
"agents",
|
|
210
|
-
"mcps",
|
|
211
|
-
"timeout",
|
|
212
|
-
)
|
|
213
|
-
if overrides_dict.get(key) is not None
|
|
214
|
-
}
|
|
221
|
+
cli_args = {key: overrides_dict.get(key) for key in AGENT_CONFIG_FIELDS if overrides_dict.get(key) is not None}
|
|
215
222
|
|
|
216
223
|
# Normalize sequence fields
|
|
217
224
|
for field in _MERGED_SEQUENCE_FIELDS:
|
|
@@ -223,11 +230,7 @@ def _build_cli_args(overrides_dict: dict) -> dict[str, Any]:
|
|
|
223
230
|
|
|
224
231
|
def _build_additional_args(overrides_dict: dict, cli_args: dict) -> dict[str, Any]:
|
|
225
232
|
"""Build additional args not already in CLI args."""
|
|
226
|
-
return {
|
|
227
|
-
key: value
|
|
228
|
-
for key, value in overrides_dict.items()
|
|
229
|
-
if value is not None and key not in cli_args
|
|
230
|
-
}
|
|
233
|
+
return {key: value for key, value in overrides_dict.items() if value is not None and key not in cli_args}
|
|
231
234
|
|
|
232
235
|
|
|
233
236
|
def _remove_model_fields_if_needed(merged: dict, overrides_dict: dict) -> None:
|
|
@@ -264,6 +267,8 @@ class AgentClient(BaseClient):
|
|
|
264
267
|
self._renderer_manager = AgentRunRenderingManager(logger)
|
|
265
268
|
self._tool_client: ToolClient | None = None
|
|
266
269
|
self._mcp_client: MCPClient | None = None
|
|
270
|
+
self._runs_client: AgentRunsClient | None = None
|
|
271
|
+
self._schedule_client: ScheduleClient | None = None
|
|
267
272
|
|
|
268
273
|
def list_agents(
|
|
269
274
|
self,
|
|
@@ -278,9 +283,7 @@ class AgentClient(BaseClient):
|
|
|
278
283
|
"""
|
|
279
284
|
if query is not None and kwargs:
|
|
280
285
|
# Both query object and individual parameters provided
|
|
281
|
-
raise ValueError(
|
|
282
|
-
"Provide either `query` or individual filter arguments, not both."
|
|
283
|
-
)
|
|
286
|
+
raise ValueError("Provide either `query` or individual filter arguments, not both.")
|
|
284
287
|
|
|
285
288
|
if query is None:
|
|
286
289
|
# Create query from individual parameters for backward compatibility
|
|
@@ -339,7 +342,19 @@ class AgentClient(BaseClient):
|
|
|
339
342
|
|
|
340
343
|
def get_agent_by_id(self, agent_id: str) -> Agent:
|
|
341
344
|
"""Get agent by ID."""
|
|
342
|
-
|
|
345
|
+
try:
|
|
346
|
+
data = self._request("GET", f"/agents/{agent_id}")
|
|
347
|
+
except ValidationError as exc:
|
|
348
|
+
if exc.status_code == 422:
|
|
349
|
+
message = f"Agent '{agent_id}' not found"
|
|
350
|
+
raise NotFoundError(
|
|
351
|
+
message,
|
|
352
|
+
status_code=404,
|
|
353
|
+
error_type=exc.error_type,
|
|
354
|
+
payload=exc.payload,
|
|
355
|
+
request_id=exc.request_id,
|
|
356
|
+
) from exc
|
|
357
|
+
raise
|
|
343
358
|
|
|
344
359
|
if isinstance(data, str):
|
|
345
360
|
# Some backends may respond with plain text for missing agents.
|
|
@@ -352,7 +367,8 @@ class AgentClient(BaseClient):
|
|
|
352
367
|
status_code=404,
|
|
353
368
|
)
|
|
354
369
|
|
|
355
|
-
|
|
370
|
+
response = AgentResponse(**data)
|
|
371
|
+
return Agent.from_response(response, client=self)
|
|
356
372
|
|
|
357
373
|
def find_agents(self, name: str | None = None) -> list[Agent]:
|
|
358
374
|
"""Find agents by name."""
|
|
@@ -366,15 +382,27 @@ class AgentClient(BaseClient):
|
|
|
366
382
|
# Renderer delegation helpers
|
|
367
383
|
# ------------------------------------------------------------------ #
|
|
368
384
|
def _get_renderer_manager(self) -> AgentRunRenderingManager:
|
|
385
|
+
"""Get or create the renderer manager instance.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
AgentRunRenderingManager instance.
|
|
389
|
+
"""
|
|
369
390
|
manager = getattr(self, "_renderer_manager", None)
|
|
370
391
|
if manager is None:
|
|
371
392
|
manager = AgentRunRenderingManager(logger)
|
|
372
393
|
self._renderer_manager = manager
|
|
373
394
|
return manager
|
|
374
395
|
|
|
375
|
-
def _create_renderer(
|
|
376
|
-
|
|
377
|
-
|
|
396
|
+
def _create_renderer(self, renderer: RichStreamRenderer | str | None, **kwargs: Any) -> RichStreamRenderer:
|
|
397
|
+
"""Create or return a renderer instance.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
renderer: Renderer instance, string identifier, or None.
|
|
401
|
+
**kwargs: Additional keyword arguments (e.g., verbose).
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
RichStreamRenderer instance.
|
|
405
|
+
"""
|
|
378
406
|
manager = self._get_renderer_manager()
|
|
379
407
|
verbose = kwargs.get("verbose", False)
|
|
380
408
|
if isinstance(renderer, RichStreamRenderer) or hasattr(renderer, "on_start"):
|
|
@@ -388,7 +416,21 @@ class AgentClient(BaseClient):
|
|
|
388
416
|
timeout_seconds: float,
|
|
389
417
|
agent_name: str | None,
|
|
390
418
|
meta: dict[str, Any],
|
|
419
|
+
hitl_handler: "RemoteHITLHandler | None" = None,
|
|
391
420
|
) -> tuple[str, dict[str, Any], float | None, float | None]:
|
|
421
|
+
"""Process stream events from an HTTP response.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
stream_response: HTTP response stream.
|
|
425
|
+
renderer: Renderer to use for displaying events.
|
|
426
|
+
timeout_seconds: Timeout in seconds.
|
|
427
|
+
agent_name: Optional agent name.
|
|
428
|
+
meta: Metadata dictionary.
|
|
429
|
+
hitl_handler: Optional HITL handler for approval callbacks.
|
|
430
|
+
|
|
431
|
+
Returns:
|
|
432
|
+
Tuple of (final_text, stats_usage, started_monotonic, finished_monotonic).
|
|
433
|
+
"""
|
|
392
434
|
manager = self._get_renderer_manager()
|
|
393
435
|
return manager.process_stream_events(
|
|
394
436
|
stream_response,
|
|
@@ -396,6 +438,7 @@ class AgentClient(BaseClient):
|
|
|
396
438
|
timeout_seconds,
|
|
397
439
|
agent_name,
|
|
398
440
|
meta,
|
|
441
|
+
hitl_handler=hitl_handler,
|
|
399
442
|
)
|
|
400
443
|
|
|
401
444
|
def _finalize_renderer(
|
|
@@ -406,30 +449,73 @@ class AgentClient(BaseClient):
|
|
|
406
449
|
started_monotonic: float | None,
|
|
407
450
|
finished_monotonic: float | None,
|
|
408
451
|
) -> str:
|
|
452
|
+
"""Finalize the renderer and return the final response text.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
renderer: Renderer to finalize.
|
|
456
|
+
final_text: Final text content.
|
|
457
|
+
stats_usage: Usage statistics dictionary.
|
|
458
|
+
started_monotonic: Start time (monotonic).
|
|
459
|
+
finished_monotonic: Finish time (monotonic).
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
Final text string.
|
|
463
|
+
"""
|
|
464
|
+
from glaip_sdk.client.run_rendering import finalize_render_manager # noqa: PLC0415
|
|
465
|
+
|
|
409
466
|
manager = self._get_renderer_manager()
|
|
410
|
-
return
|
|
411
|
-
renderer,
|
|
412
|
-
final_text,
|
|
413
|
-
stats_usage,
|
|
414
|
-
started_monotonic,
|
|
415
|
-
finished_monotonic,
|
|
467
|
+
return finalize_render_manager(
|
|
468
|
+
manager, renderer, final_text, stats_usage, started_monotonic, finished_monotonic
|
|
416
469
|
)
|
|
417
470
|
|
|
418
471
|
def _get_tool_client(self) -> ToolClient:
|
|
472
|
+
"""Get or create the tool client instance.
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
ToolClient instance.
|
|
476
|
+
"""
|
|
419
477
|
if self._tool_client is None:
|
|
420
478
|
self._tool_client = ToolClient(parent_client=self)
|
|
421
479
|
return self._tool_client
|
|
422
480
|
|
|
423
481
|
def _get_mcp_client(self) -> MCPClient:
|
|
482
|
+
"""Get or create the MCP client instance.
|
|
483
|
+
|
|
484
|
+
Returns:
|
|
485
|
+
MCPClient instance.
|
|
486
|
+
"""
|
|
424
487
|
if self._mcp_client is None:
|
|
425
488
|
self._mcp_client = MCPClient(parent_client=self)
|
|
426
489
|
return self._mcp_client
|
|
427
490
|
|
|
491
|
+
@property
|
|
492
|
+
def schedules(self) -> "ScheduleClient":
|
|
493
|
+
"""Get or create the schedule client instance.
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
ScheduleClient instance.
|
|
497
|
+
"""
|
|
498
|
+
if self._schedule_client is None:
|
|
499
|
+
# Import here to avoid circular import
|
|
500
|
+
from glaip_sdk.client.schedules import ScheduleClient # noqa: PLC0415
|
|
501
|
+
|
|
502
|
+
self._schedule_client = ScheduleClient(parent_client=self)
|
|
503
|
+
return self._schedule_client
|
|
504
|
+
|
|
428
505
|
def _normalise_reference_entry(
|
|
429
506
|
self,
|
|
430
507
|
entry: Any,
|
|
431
508
|
fallback_iter: Iterator[Any] | None,
|
|
432
509
|
) -> tuple[str | None, str | None]:
|
|
510
|
+
"""Normalize a reference entry to extract ID and name.
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
entry: Reference entry (string, dict, or other).
|
|
514
|
+
fallback_iter: Optional iterator for fallback values.
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
Tuple of (entry_id, entry_name).
|
|
518
|
+
"""
|
|
433
519
|
entry_id: str | None = None
|
|
434
520
|
entry_name: str | None = None
|
|
435
521
|
|
|
@@ -466,6 +552,19 @@ class AgentClient(BaseClient):
|
|
|
466
552
|
label: str,
|
|
467
553
|
plural_label: str | None = None,
|
|
468
554
|
) -> list[str] | None:
|
|
555
|
+
"""Resolve a list of resource references to IDs.
|
|
556
|
+
|
|
557
|
+
Args:
|
|
558
|
+
items: List of resource references to resolve.
|
|
559
|
+
references: Optional list of reference objects for fallback.
|
|
560
|
+
fetch_by_id: Function to fetch resource by ID.
|
|
561
|
+
find_by_name: Function to find resources by name.
|
|
562
|
+
label: Singular label for error messages.
|
|
563
|
+
plural_label: Plural label for error messages.
|
|
564
|
+
|
|
565
|
+
Returns:
|
|
566
|
+
List of resolved resource IDs, or None if items is empty.
|
|
567
|
+
"""
|
|
469
568
|
if not items:
|
|
470
569
|
return None
|
|
471
570
|
|
|
@@ -497,6 +596,22 @@ class AgentClient(BaseClient):
|
|
|
497
596
|
singular: str,
|
|
498
597
|
plural: str,
|
|
499
598
|
) -> str:
|
|
599
|
+
"""Resolve a single resource reference to an ID.
|
|
600
|
+
|
|
601
|
+
Args:
|
|
602
|
+
entry: Resource reference to resolve.
|
|
603
|
+
fallback_iter: Optional iterator for fallback values.
|
|
604
|
+
fetch_by_id: Function to fetch resource by ID.
|
|
605
|
+
find_by_name: Function to find resources by name.
|
|
606
|
+
singular: Singular label for error messages.
|
|
607
|
+
plural: Plural label for error messages.
|
|
608
|
+
|
|
609
|
+
Returns:
|
|
610
|
+
Resolved resource ID string.
|
|
611
|
+
|
|
612
|
+
Raises:
|
|
613
|
+
ValueError: If the resource cannot be resolved.
|
|
614
|
+
"""
|
|
500
615
|
entry_id, entry_name = self._normalise_reference_entry(entry, fallback_iter)
|
|
501
616
|
|
|
502
617
|
validated_id = self._validate_resource_id(fetch_by_id, entry_id)
|
|
@@ -506,15 +621,21 @@ class AgentClient(BaseClient):
|
|
|
506
621
|
return entry_id
|
|
507
622
|
|
|
508
623
|
if entry_name:
|
|
509
|
-
resolved, success = self._resolve_resource_by_name(
|
|
510
|
-
find_by_name, entry_name, singular, plural
|
|
511
|
-
)
|
|
624
|
+
resolved, success = self._resolve_resource_by_name(find_by_name, entry_name, singular, plural)
|
|
512
625
|
return resolved if success else entry_name
|
|
513
626
|
|
|
514
627
|
raise ValueError(f"{singular} references must include a valid ID or name.")
|
|
515
628
|
|
|
516
629
|
@staticmethod
|
|
517
630
|
def _coerce_reference_value(entry: Any) -> str:
|
|
631
|
+
"""Coerce a reference entry to a string value.
|
|
632
|
+
|
|
633
|
+
Args:
|
|
634
|
+
entry: Reference entry (dict, string, or other).
|
|
635
|
+
|
|
636
|
+
Returns:
|
|
637
|
+
String representation of the reference.
|
|
638
|
+
"""
|
|
518
639
|
if isinstance(entry, dict):
|
|
519
640
|
if entry.get("id"):
|
|
520
641
|
return str(entry["id"])
|
|
@@ -523,9 +644,16 @@ class AgentClient(BaseClient):
|
|
|
523
644
|
return str(entry)
|
|
524
645
|
|
|
525
646
|
@staticmethod
|
|
526
|
-
def _validate_resource_id(
|
|
527
|
-
|
|
528
|
-
|
|
647
|
+
def _validate_resource_id(fetch_by_id: Callable[[str], Any], candidate_id: str | None) -> str | None:
|
|
648
|
+
"""Validate a resource ID by attempting to fetch it.
|
|
649
|
+
|
|
650
|
+
Args:
|
|
651
|
+
fetch_by_id: Function to fetch resource by ID.
|
|
652
|
+
candidate_id: Candidate ID to validate.
|
|
653
|
+
|
|
654
|
+
Returns:
|
|
655
|
+
Validated ID if found, None otherwise.
|
|
656
|
+
"""
|
|
529
657
|
if not candidate_id:
|
|
530
658
|
return None
|
|
531
659
|
try:
|
|
@@ -541,27 +669,33 @@ class AgentClient(BaseClient):
|
|
|
541
669
|
singular: str,
|
|
542
670
|
plural: str,
|
|
543
671
|
) -> tuple[str, bool]:
|
|
672
|
+
"""Resolve a resource by name to an ID.
|
|
673
|
+
|
|
674
|
+
Args:
|
|
675
|
+
find_by_name: Function to find resources by name.
|
|
676
|
+
entry_name: Name of the resource to find.
|
|
677
|
+
singular: Singular label for error messages.
|
|
678
|
+
plural: Plural label for error messages.
|
|
679
|
+
|
|
680
|
+
Returns:
|
|
681
|
+
Tuple of (resolved_id, success).
|
|
682
|
+
|
|
683
|
+
Raises:
|
|
684
|
+
ValueError: If resource not found or multiple matches exist.
|
|
685
|
+
"""
|
|
544
686
|
try:
|
|
545
687
|
matches = find_by_name(entry_name)
|
|
546
688
|
except Exception:
|
|
547
689
|
return entry_name, False
|
|
548
690
|
|
|
549
691
|
if not matches:
|
|
550
|
-
raise ValueError(
|
|
551
|
-
f"{singular} '{entry_name}' not found in current workspace."
|
|
552
|
-
)
|
|
692
|
+
raise ValueError(f"{singular} '{entry_name}' not found in current workspace.")
|
|
553
693
|
if len(matches) > 1:
|
|
554
|
-
exact = [
|
|
555
|
-
m
|
|
556
|
-
for m in matches
|
|
557
|
-
if getattr(m, "name", "").lower() == entry_name.lower()
|
|
558
|
-
]
|
|
694
|
+
exact = [m for m in matches if getattr(m, "name", "").lower() == entry_name.lower()]
|
|
559
695
|
if len(exact) == 1:
|
|
560
696
|
matches = exact
|
|
561
697
|
else:
|
|
562
|
-
raise ValueError(
|
|
563
|
-
f"Multiple {plural} named '{entry_name}'. Please disambiguate."
|
|
564
|
-
)
|
|
698
|
+
raise ValueError(f"Multiple {plural} named '{entry_name}'. Please disambiguate.")
|
|
565
699
|
return str(matches[0].id), True
|
|
566
700
|
|
|
567
701
|
def _resolve_tool_ids(
|
|
@@ -569,6 +703,15 @@ class AgentClient(BaseClient):
|
|
|
569
703
|
tools: list[Any] | None,
|
|
570
704
|
references: list[Any] | None = None,
|
|
571
705
|
) -> list[str] | None:
|
|
706
|
+
"""Resolve tool references to IDs.
|
|
707
|
+
|
|
708
|
+
Args:
|
|
709
|
+
tools: List of tool references to resolve.
|
|
710
|
+
references: Optional list of reference objects for fallback.
|
|
711
|
+
|
|
712
|
+
Returns:
|
|
713
|
+
List of resolved tool IDs, or None if tools is empty.
|
|
714
|
+
"""
|
|
572
715
|
tool_client = self._get_tool_client()
|
|
573
716
|
return self._resolve_resource_ids(
|
|
574
717
|
tools,
|
|
@@ -584,6 +727,15 @@ class AgentClient(BaseClient):
|
|
|
584
727
|
agents: list[Any] | None,
|
|
585
728
|
references: list[Any] | None = None,
|
|
586
729
|
) -> list[str] | None:
|
|
730
|
+
"""Resolve agent references to IDs.
|
|
731
|
+
|
|
732
|
+
Args:
|
|
733
|
+
agents: List of agent references to resolve.
|
|
734
|
+
references: Optional list of reference objects for fallback.
|
|
735
|
+
|
|
736
|
+
Returns:
|
|
737
|
+
List of resolved agent IDs, or None if agents is empty.
|
|
738
|
+
"""
|
|
587
739
|
return self._resolve_resource_ids(
|
|
588
740
|
agents,
|
|
589
741
|
references,
|
|
@@ -598,6 +750,15 @@ class AgentClient(BaseClient):
|
|
|
598
750
|
mcps: list[Any] | None,
|
|
599
751
|
references: list[Any] | None = None,
|
|
600
752
|
) -> list[str] | None:
|
|
753
|
+
"""Resolve MCP references to IDs.
|
|
754
|
+
|
|
755
|
+
Args:
|
|
756
|
+
mcps: List of MCP references to resolve.
|
|
757
|
+
references: Optional list of reference objects for fallback.
|
|
758
|
+
|
|
759
|
+
Returns:
|
|
760
|
+
List of resolved MCP IDs, or None if mcps is empty.
|
|
761
|
+
"""
|
|
601
762
|
mcp_client = self._get_mcp_client()
|
|
602
763
|
return self._resolve_resource_ids(
|
|
603
764
|
mcps,
|
|
@@ -610,9 +771,7 @@ class AgentClient(BaseClient):
|
|
|
610
771
|
|
|
611
772
|
def _create_agent_from_payload(self, payload: Mapping[str, Any]) -> "Agent":
|
|
612
773
|
"""Create an agent using a fully prepared payload mapping."""
|
|
613
|
-
known, extras = _split_known_and_extra(
|
|
614
|
-
payload, AgentCreateRequest.__dataclass_fields__
|
|
615
|
-
)
|
|
774
|
+
known, extras = _split_known_and_extra(payload, AgentCreateRequest.__dataclass_fields__)
|
|
616
775
|
|
|
617
776
|
name = known.pop("name", None)
|
|
618
777
|
instruction = known.pop("instruction", None)
|
|
@@ -691,7 +850,8 @@ class AgentClient(BaseClient):
|
|
|
691
850
|
get_endpoint_fmt=f"{AGENTS_ENDPOINT}{{id}}",
|
|
692
851
|
json=payload_dict,
|
|
693
852
|
)
|
|
694
|
-
|
|
853
|
+
response = AgentResponse(**full_agent_data)
|
|
854
|
+
return Agent.from_response(response, client=self)
|
|
695
855
|
|
|
696
856
|
def create_agent(
|
|
697
857
|
self,
|
|
@@ -721,9 +881,7 @@ class AgentClient(BaseClient):
|
|
|
721
881
|
overrides = _merge_override_maps(base_overrides, kwargs)
|
|
722
882
|
|
|
723
883
|
if file is not None:
|
|
724
|
-
payload = _prepare_import_payload(
|
|
725
|
-
Path(file).expanduser(), overrides, drop_model_fields=True
|
|
726
|
-
)
|
|
884
|
+
payload = _prepare_import_payload(Path(file).expanduser(), overrides, drop_model_fields=True)
|
|
727
885
|
if overrides.get("model") is None:
|
|
728
886
|
payload.pop("model", None)
|
|
729
887
|
else:
|
|
@@ -746,9 +904,7 @@ class AgentClient(BaseClient):
|
|
|
746
904
|
payload: Mapping[str, Any],
|
|
747
905
|
) -> "Agent":
|
|
748
906
|
"""Update an agent using a prepared payload mapping."""
|
|
749
|
-
known, extras = _split_known_and_extra(
|
|
750
|
-
payload, AgentUpdateRequest.__dataclass_fields__
|
|
751
|
-
)
|
|
907
|
+
known, extras = _split_known_and_extra(payload, AgentUpdateRequest.__dataclass_fields__)
|
|
752
908
|
_normalise_sequence_fields(known)
|
|
753
909
|
|
|
754
910
|
tool_refs = extras.pop("_tool_refs", None)
|
|
@@ -790,8 +946,9 @@ class AgentClient(BaseClient):
|
|
|
790
946
|
|
|
791
947
|
payload_dict = request.to_payload(current_agent)
|
|
792
948
|
|
|
793
|
-
|
|
794
|
-
|
|
949
|
+
api_response = self._request("PUT", f"/agents/{agent_id}", json=payload_dict)
|
|
950
|
+
response = AgentResponse(**api_response)
|
|
951
|
+
return Agent.from_response(response, client=self)
|
|
795
952
|
|
|
796
953
|
def update_agent(
|
|
797
954
|
self,
|
|
@@ -818,9 +975,7 @@ class AgentClient(BaseClient):
|
|
|
818
975
|
overrides = _merge_override_maps(base_overrides, kwargs)
|
|
819
976
|
|
|
820
977
|
if file is not None:
|
|
821
|
-
payload = _prepare_import_payload(
|
|
822
|
-
Path(file).expanduser(), overrides, drop_model_fields=True
|
|
823
|
-
)
|
|
978
|
+
payload = _prepare_import_payload(Path(file).expanduser(), overrides, drop_model_fields=True)
|
|
824
979
|
else:
|
|
825
980
|
payload = overrides
|
|
826
981
|
|
|
@@ -840,6 +995,62 @@ class AgentClient(BaseClient):
|
|
|
840
995
|
"""Delete an agent."""
|
|
841
996
|
self._request("DELETE", f"/agents/{agent_id}")
|
|
842
997
|
|
|
998
|
+
def upsert_agent(self, identifier: str | Agent, **kwargs) -> Agent:
|
|
999
|
+
"""Create or update an agent by instance, ID, or name.
|
|
1000
|
+
|
|
1001
|
+
Args:
|
|
1002
|
+
identifier: Agent instance, ID (UUID string), or name
|
|
1003
|
+
**kwargs: Agent configuration (instruction, description, tools, etc.)
|
|
1004
|
+
|
|
1005
|
+
Returns:
|
|
1006
|
+
The created or updated agent.
|
|
1007
|
+
|
|
1008
|
+
Example:
|
|
1009
|
+
>>> # By name (creates if not exists)
|
|
1010
|
+
>>> agent = client.agents.upsert_agent(
|
|
1011
|
+
... "hello_agent",
|
|
1012
|
+
... instruction="You are a helpful assistant.",
|
|
1013
|
+
... description="A friendly agent",
|
|
1014
|
+
... )
|
|
1015
|
+
>>> # By instance
|
|
1016
|
+
>>> agent = client.agents.upsert_agent(existing_agent, description="Updated")
|
|
1017
|
+
>>> # By ID
|
|
1018
|
+
>>> agent = client.agents.upsert_agent("uuid-here", description="Updated")
|
|
1019
|
+
"""
|
|
1020
|
+
# Handle Agent instance
|
|
1021
|
+
if isinstance(identifier, Agent):
|
|
1022
|
+
if identifier.id:
|
|
1023
|
+
logger.info("Updating agent by instance: %s", identifier.name)
|
|
1024
|
+
return self.update_agent(identifier.id, name=identifier.name, **kwargs)
|
|
1025
|
+
identifier = identifier.name
|
|
1026
|
+
|
|
1027
|
+
# Handle string (ID or name)
|
|
1028
|
+
if isinstance(identifier, str):
|
|
1029
|
+
# Check if it's a UUID
|
|
1030
|
+
if is_uuid(identifier):
|
|
1031
|
+
logger.info("Updating agent by ID: %s", identifier)
|
|
1032
|
+
return self.update_agent(identifier, **kwargs)
|
|
1033
|
+
|
|
1034
|
+
# It's a name - find or create
|
|
1035
|
+
return self._upsert_agent_by_name(identifier, **kwargs)
|
|
1036
|
+
|
|
1037
|
+
raise ValueError(f"Invalid identifier type: {type(identifier)}")
|
|
1038
|
+
|
|
1039
|
+
def _upsert_agent_by_name(self, name: str, **kwargs) -> Agent:
|
|
1040
|
+
"""Find agent by name and update, or create if not found."""
|
|
1041
|
+
existing = self.find_agents(name)
|
|
1042
|
+
|
|
1043
|
+
if len(existing) == 1:
|
|
1044
|
+
logger.info("Updating existing agent: %s", name)
|
|
1045
|
+
return self.update_agent(existing[0].id, name=name, **kwargs)
|
|
1046
|
+
|
|
1047
|
+
if len(existing) > 1:
|
|
1048
|
+
raise ValueError(f"Multiple agents found with name '{name}'")
|
|
1049
|
+
|
|
1050
|
+
# Create new agent
|
|
1051
|
+
logger.info("Creating new agent: %s", name)
|
|
1052
|
+
return self.create_agent(name=name, **kwargs)
|
|
1053
|
+
|
|
843
1054
|
def _prepare_sync_request_data(
|
|
844
1055
|
self,
|
|
845
1056
|
message: str,
|
|
@@ -882,9 +1093,7 @@ class AgentClient(BaseClient):
|
|
|
882
1093
|
payload["tty"] = True
|
|
883
1094
|
return payload, None, None, headers, None
|
|
884
1095
|
|
|
885
|
-
def _get_timeout_values(
|
|
886
|
-
self, timeout: float | None, **kwargs: Any
|
|
887
|
-
) -> tuple[float, float]:
|
|
1096
|
+
def _get_timeout_values(self, timeout: float | None, **kwargs: Any) -> tuple[float, float]:
|
|
888
1097
|
"""Get request timeout and execution timeout values.
|
|
889
1098
|
|
|
890
1099
|
Args:
|
|
@@ -906,9 +1115,35 @@ class AgentClient(BaseClient):
|
|
|
906
1115
|
tty: bool = False,
|
|
907
1116
|
*,
|
|
908
1117
|
renderer: RichStreamRenderer | str | None = "auto",
|
|
1118
|
+
runtime_config: dict[str, Any] | None = None,
|
|
1119
|
+
hitl_handler: "RemoteHITLHandler | None" = None,
|
|
909
1120
|
**kwargs,
|
|
910
1121
|
) -> str:
|
|
911
|
-
"""Run an agent with a message, streaming via a renderer.
|
|
1122
|
+
"""Run an agent with a message, streaming via a renderer.
|
|
1123
|
+
|
|
1124
|
+
Args:
|
|
1125
|
+
agent_id: The ID of the agent to run.
|
|
1126
|
+
message: The message to send to the agent.
|
|
1127
|
+
files: Optional list of files to include with the request.
|
|
1128
|
+
tty: Whether to enable TTY mode.
|
|
1129
|
+
renderer: Renderer for streaming output.
|
|
1130
|
+
runtime_config: Optional runtime configuration for tools, MCPs, and agents.
|
|
1131
|
+
Keys should be platform IDs. Example:
|
|
1132
|
+
{
|
|
1133
|
+
"tool_configs": {"tool-id": {"param": "value"}},
|
|
1134
|
+
"mcp_configs": {"mcp-id": {"setting": "on"}},
|
|
1135
|
+
"agent_config": {"planning": True},
|
|
1136
|
+
}
|
|
1137
|
+
hitl_handler: Optional RemoteHITLHandler for approval callbacks.
|
|
1138
|
+
Set GLAIP_HITL_AUTO_APPROVE=true for auto-approval without handler.
|
|
1139
|
+
**kwargs: Additional arguments to pass to the run API.
|
|
1140
|
+
|
|
1141
|
+
Returns:
|
|
1142
|
+
The agent's response as a string.
|
|
1143
|
+
"""
|
|
1144
|
+
# Include runtime_config in kwargs only when caller hasn't already provided it
|
|
1145
|
+
if runtime_config is not None and "runtime_config" not in kwargs:
|
|
1146
|
+
kwargs["runtime_config"] = runtime_config
|
|
912
1147
|
(
|
|
913
1148
|
payload,
|
|
914
1149
|
data_payload,
|
|
@@ -957,6 +1192,7 @@ class AgentClient(BaseClient):
|
|
|
957
1192
|
timeout_seconds,
|
|
958
1193
|
agent_name,
|
|
959
1194
|
meta,
|
|
1195
|
+
hitl_handler=hitl_handler,
|
|
960
1196
|
)
|
|
961
1197
|
|
|
962
1198
|
except KeyboardInterrupt:
|
|
@@ -973,6 +1209,13 @@ class AgentClient(BaseClient):
|
|
|
973
1209
|
if multipart_data:
|
|
974
1210
|
multipart_data.close()
|
|
975
1211
|
|
|
1212
|
+
# Wait for pending HITL decisions before returning
|
|
1213
|
+
if hitl_handler and hasattr(hitl_handler, "wait_for_pending_decisions"):
|
|
1214
|
+
try:
|
|
1215
|
+
hitl_handler.wait_for_pending_decisions(timeout=30)
|
|
1216
|
+
except Exception as e:
|
|
1217
|
+
logger.warning(f"Error waiting for HITL decisions: {e}")
|
|
1218
|
+
|
|
976
1219
|
return self._finalize_renderer(
|
|
977
1220
|
r,
|
|
978
1221
|
final_text,
|
|
@@ -1009,9 +1252,7 @@ class AgentClient(BaseClient):
|
|
|
1009
1252
|
headers = {"Accept": SSE_CONTENT_TYPE}
|
|
1010
1253
|
return payload, None, None, headers
|
|
1011
1254
|
|
|
1012
|
-
def _create_async_client_config(
|
|
1013
|
-
self, timeout: float | None, headers: dict | None
|
|
1014
|
-
) -> dict:
|
|
1255
|
+
def _create_async_client_config(self, timeout: float | None, headers: dict | None) -> dict:
|
|
1015
1256
|
"""Create async client configuration with proper headers and timeout."""
|
|
1016
1257
|
config = self._build_async_client(timeout or self.timeout)
|
|
1017
1258
|
if headers:
|
|
@@ -1040,9 +1281,7 @@ class AgentClient(BaseClient):
|
|
|
1040
1281
|
) as stream_response:
|
|
1041
1282
|
stream_response.raise_for_status()
|
|
1042
1283
|
|
|
1043
|
-
async for event in aiter_sse_events(
|
|
1044
|
-
stream_response, timeout_seconds, agent_name
|
|
1045
|
-
):
|
|
1284
|
+
async for event in aiter_sse_events(stream_response, timeout_seconds, agent_name):
|
|
1046
1285
|
try:
|
|
1047
1286
|
chunk = json.loads(event["data"])
|
|
1048
1287
|
yield chunk
|
|
@@ -1056,7 +1295,9 @@ class AgentClient(BaseClient):
|
|
|
1056
1295
|
message: str,
|
|
1057
1296
|
files: list[str | BinaryIO] | None = None,
|
|
1058
1297
|
*,
|
|
1059
|
-
|
|
1298
|
+
request_timeout: float | None = None,
|
|
1299
|
+
runtime_config: dict[str, Any] | None = None,
|
|
1300
|
+
hitl_handler: "RemoteHITLHandler | None" = None,
|
|
1060
1301
|
**kwargs,
|
|
1061
1302
|
) -> AsyncGenerator[dict, None]:
|
|
1062
1303
|
"""Async run an agent with a message, yielding streaming JSON chunks.
|
|
@@ -1065,31 +1306,53 @@ class AgentClient(BaseClient):
|
|
|
1065
1306
|
agent_id: ID of the agent to run
|
|
1066
1307
|
message: Message to send to the agent
|
|
1067
1308
|
files: Optional list of files to include
|
|
1068
|
-
|
|
1309
|
+
request_timeout: Optional request timeout in seconds (defaults to client timeout)
|
|
1310
|
+
runtime_config: Optional runtime configuration for tools, MCPs, and agents.
|
|
1311
|
+
Keys should be platform IDs. Example:
|
|
1312
|
+
{
|
|
1313
|
+
"tool_configs": {"tool-id": {"param": "value"}},
|
|
1314
|
+
"mcp_configs": {"mcp-id": {"setting": "on"}},
|
|
1315
|
+
"agent_config": {"planning": True},
|
|
1316
|
+
}
|
|
1317
|
+
hitl_handler: Optional HITL handler for remote approval requests.
|
|
1318
|
+
Note: Async HITL support is currently deferred. This parameter
|
|
1319
|
+
is accepted for API consistency but will raise NotImplementedError
|
|
1320
|
+
if provided.
|
|
1069
1321
|
**kwargs: Additional arguments (chat_history, pii_mapping, etc.)
|
|
1070
1322
|
|
|
1071
1323
|
Yields:
|
|
1072
1324
|
Dictionary containing parsed JSON chunks from the streaming response
|
|
1073
1325
|
|
|
1074
1326
|
Raises:
|
|
1327
|
+
NotImplementedError: If hitl_handler is provided (async HITL not yet supported)
|
|
1075
1328
|
AgentTimeoutError: When agent execution times out
|
|
1076
1329
|
httpx.TimeoutException: When general timeout occurs
|
|
1077
1330
|
Exception: For other unexpected errors
|
|
1078
1331
|
"""
|
|
1332
|
+
if hitl_handler is not None:
|
|
1333
|
+
raise NotImplementedError(
|
|
1334
|
+
"Async HITL support is currently deferred. "
|
|
1335
|
+
"Please use the synchronous run_agent() method with hitl_handler."
|
|
1336
|
+
)
|
|
1337
|
+
# Include runtime_config in kwargs only when caller hasn't already provided it
|
|
1338
|
+
if runtime_config is not None and "runtime_config" not in kwargs:
|
|
1339
|
+
kwargs["runtime_config"] = runtime_config
|
|
1340
|
+
# Derive timeout values for request/control flow
|
|
1341
|
+
legacy_timeout = kwargs.get("timeout")
|
|
1342
|
+
http_timeout_override = request_timeout if request_timeout is not None else legacy_timeout
|
|
1343
|
+
http_timeout = http_timeout_override or self.timeout
|
|
1344
|
+
|
|
1079
1345
|
# Prepare request data
|
|
1080
|
-
payload, data_payload, files_payload, headers = self._prepare_request_data(
|
|
1081
|
-
message, files, **kwargs
|
|
1082
|
-
)
|
|
1346
|
+
payload, data_payload, files_payload, headers = self._prepare_request_data(message, files, **kwargs)
|
|
1083
1347
|
|
|
1084
1348
|
# Create async client configuration
|
|
1085
|
-
async_client_config = self._create_async_client_config(
|
|
1349
|
+
async_client_config = self._create_async_client_config(http_timeout_override, headers)
|
|
1086
1350
|
|
|
1087
1351
|
# Get execution timeout for streaming control
|
|
1088
1352
|
timeout_seconds = kwargs.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
|
|
1089
1353
|
agent_name = kwargs.get("agent_name")
|
|
1090
1354
|
|
|
1091
|
-
|
|
1092
|
-
# Create async client and stream response
|
|
1355
|
+
async def _chunk_stream() -> AsyncGenerator[dict, None]:
|
|
1093
1356
|
async with httpx.AsyncClient(**async_client_config) as async_client:
|
|
1094
1357
|
async for chunk in self._stream_agent_response(
|
|
1095
1358
|
async_client,
|
|
@@ -1103,7 +1366,14 @@ class AgentClient(BaseClient):
|
|
|
1103
1366
|
):
|
|
1104
1367
|
yield chunk
|
|
1105
1368
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1369
|
+
async with _async_timeout_guard(http_timeout):
|
|
1370
|
+
async for chunk in _chunk_stream():
|
|
1371
|
+
yield chunk
|
|
1372
|
+
|
|
1373
|
+
@property
|
|
1374
|
+
def runs(self) -> "AgentRunsClient":
|
|
1375
|
+
"""Get the agent runs client."""
|
|
1376
|
+
if self._runs_client is None:
|
|
1377
|
+
shared_config = build_shared_config(self)
|
|
1378
|
+
self._runs_client = AgentRunsClient(**shared_config)
|
|
1379
|
+
return self._runs_client
|