glaip-sdk 0.6.19__py3-none-any.whl → 0.7.27__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/agents/base.py +283 -30
- glaip_sdk/agents/component.py +233 -0
- 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 +1 -1
- glaip_sdk/cli/commands/configure.py +1 -2
- 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/entrypoint.py +20 -0
- glaip_sdk/cli/main.py +112 -35
- glaip_sdk/cli/pager.py +3 -3
- glaip_sdk/cli/resolution.py +2 -1
- glaip_sdk/cli/slash/accounts_controller.py +3 -1
- glaip_sdk/cli/slash/agent_session.py +1 -1
- glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
- glaip_sdk/cli/slash/session.py +343 -20
- glaip_sdk/cli/slash/tui/__init__.py +29 -1
- glaip_sdk/cli/slash/tui/accounts.tcss +97 -6
- glaip_sdk/cli/slash/tui/accounts_app.py +1117 -126
- glaip_sdk/cli/slash/tui/clipboard.py +316 -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 +178 -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 +1 -1
- 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 +293 -17
- 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 +109 -30
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/tools.py +52 -23
- glaip_sdk/config/constants.py +22 -2
- glaip_sdk/guardrails/__init__.py +80 -0
- glaip_sdk/guardrails/serializer.py +91 -0
- glaip_sdk/hitl/__init__.py +35 -2
- glaip_sdk/hitl/base.py +64 -0
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +1 -31
- 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/ptc.py +145 -0
- glaip_sdk/registry/tool.py +270 -57
- glaip_sdk/runner/__init__.py +20 -3
- glaip_sdk/runner/deps.py +4 -1
- glaip_sdk/runner/langgraph.py +251 -27
- glaip_sdk/runner/logging_config.py +77 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +30 -9
- glaip_sdk/runner/ptc_adapter.py +98 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +25 -2
- 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/agent_config.py +8 -2
- glaip_sdk/utils/bundler.py +138 -2
- glaip_sdk/utils/import_resolver.py +427 -49
- glaip_sdk/utils/runtime_config.py +3 -2
- glaip_sdk/utils/sync.py +31 -11
- glaip_sdk/utils/tool_detection.py +274 -6
- {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/METADATA +22 -8
- glaip_sdk-0.7.27.dist-info/RECORD +227 -0
- {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/WHEEL +1 -1
- glaip_sdk-0.7.27.dist-info/entry_points.txt +2 -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.19.dist-info/RECORD +0 -163
- glaip_sdk-0.6.19.dist-info/entry_points.txt +0 -2
- {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/top_level.txt +0 -0
glaip_sdk/client/main.py
CHANGED
|
@@ -12,13 +12,15 @@ from typing import TYPE_CHECKING, Any
|
|
|
12
12
|
|
|
13
13
|
from glaip_sdk.client.agents import AgentClient
|
|
14
14
|
from glaip_sdk.client.base import BaseClient
|
|
15
|
+
from glaip_sdk.client.hitl import HITLClient
|
|
15
16
|
from glaip_sdk.client.mcps import MCPClient
|
|
17
|
+
from glaip_sdk.client.schedules import ScheduleClient
|
|
16
18
|
from glaip_sdk.client.shared import build_shared_config
|
|
17
19
|
from glaip_sdk.client.tools import ToolClient
|
|
18
20
|
|
|
19
21
|
if TYPE_CHECKING: # pragma: no cover
|
|
20
22
|
from glaip_sdk.agents import Agent
|
|
21
|
-
from glaip_sdk.client.
|
|
23
|
+
from glaip_sdk.client.payloads.agent import AgentListResult
|
|
22
24
|
from glaip_sdk.mcps import MCP
|
|
23
25
|
from glaip_sdk.tools import Tool
|
|
24
26
|
|
|
@@ -38,6 +40,8 @@ class Client(BaseClient):
|
|
|
38
40
|
self.agents = AgentClient(**shared_config)
|
|
39
41
|
self.tools = ToolClient(**shared_config)
|
|
40
42
|
self.mcps = MCPClient(**shared_config)
|
|
43
|
+
self.schedules = ScheduleClient(**shared_config)
|
|
44
|
+
self.hitl = HITLClient(**shared_config)
|
|
41
45
|
|
|
42
46
|
# ---- Core API Methods (Public Interface) ----
|
|
43
47
|
|
|
@@ -208,10 +212,6 @@ class Client(BaseClient):
|
|
|
208
212
|
return self.mcps.get_mcp_tools_from_config(config)
|
|
209
213
|
|
|
210
214
|
# Language Models
|
|
211
|
-
def list_language_models(self) -> list[dict]:
|
|
212
|
-
"""List available language models."""
|
|
213
|
-
data = self._request("GET", "/language-models")
|
|
214
|
-
return data or []
|
|
215
215
|
|
|
216
216
|
# ---- Timeout propagation ----
|
|
217
217
|
@property
|
|
@@ -236,6 +236,8 @@ class Client(BaseClient):
|
|
|
236
236
|
self.tools.http_client = self.http_client
|
|
237
237
|
if hasattr(self, "mcps"):
|
|
238
238
|
self.mcps.http_client = self.http_client
|
|
239
|
+
if hasattr(self, "schedules"):
|
|
240
|
+
self.schedules.http_client = self.http_client
|
|
239
241
|
except Exception:
|
|
240
242
|
pass
|
|
241
243
|
|
glaip_sdk/client/mcps.py
CHANGED
|
@@ -85,26 +85,56 @@ class MCPClient(BaseClient):
|
|
|
85
85
|
response = MCPResponse(**full_mcp_data)
|
|
86
86
|
return MCP.from_response(response, client=self)
|
|
87
87
|
|
|
88
|
-
def update_mcp(self, mcp_id: str, **kwargs) -> MCP:
|
|
88
|
+
def update_mcp(self, mcp_id: str | MCP, **kwargs) -> MCP:
|
|
89
89
|
"""Update an existing MCP.
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
Notes:
|
|
92
|
+
- Payload construction is centralized via ``_build_update_payload`` so required
|
|
93
|
+
defaults (e.g., ``type``) and value normalization stay consistent across SDK and CLI.
|
|
94
|
+
- For backward compatibility, still chooses PATCH vs PUT based on which fields the
|
|
95
|
+
caller provided, but uses the SDK payload builder for the final payload.
|
|
95
96
|
"""
|
|
96
|
-
#
|
|
97
|
+
# Backward-compatible: allow passing an MCP instance to avoid an extra fetch.
|
|
98
|
+
if isinstance(mcp_id, MCP):
|
|
99
|
+
current_mcp = mcp_id
|
|
100
|
+
if not current_mcp.id:
|
|
101
|
+
raise ValueError("MCP instance has no id; cannot update.")
|
|
102
|
+
mcp_id_value = str(current_mcp.id)
|
|
103
|
+
else:
|
|
104
|
+
current_mcp = None
|
|
105
|
+
mcp_id_value = mcp_id
|
|
106
|
+
|
|
97
107
|
required_fields = {"name", "config", "transport"}
|
|
98
108
|
provided_fields = set(kwargs.keys())
|
|
109
|
+
method = "PUT" if required_fields.issubset(provided_fields) else "PATCH"
|
|
110
|
+
|
|
111
|
+
if not kwargs:
|
|
112
|
+
data = self._request(method, f"{MCPS_ENDPOINT}{mcp_id_value}", json={})
|
|
113
|
+
response = MCPResponse(**data)
|
|
114
|
+
return MCP.from_response(response, client=self)
|
|
115
|
+
|
|
116
|
+
if current_mcp is None:
|
|
117
|
+
current_mcp = self.get_mcp_by_id(mcp_id_value)
|
|
118
|
+
|
|
119
|
+
payload_kwargs = kwargs.copy()
|
|
120
|
+
name = payload_kwargs.pop("name", None)
|
|
121
|
+
description = payload_kwargs.pop("description", None)
|
|
122
|
+
full_payload = self._build_update_payload(
|
|
123
|
+
current_mcp=current_mcp,
|
|
124
|
+
name=name,
|
|
125
|
+
description=description,
|
|
126
|
+
**payload_kwargs,
|
|
127
|
+
)
|
|
99
128
|
|
|
100
|
-
if
|
|
101
|
-
|
|
102
|
-
method = "PUT"
|
|
129
|
+
if method == "PUT":
|
|
130
|
+
json_payload = full_payload
|
|
103
131
|
else:
|
|
104
|
-
|
|
105
|
-
|
|
132
|
+
json_payload = {key: full_payload[key] for key in provided_fields if key in full_payload}
|
|
133
|
+
json_payload["type"] = full_payload["type"]
|
|
134
|
+
if "config" in provided_fields and "transport" not in provided_fields and "transport" in full_payload:
|
|
135
|
+
json_payload["transport"] = full_payload["transport"]
|
|
106
136
|
|
|
107
|
-
data = self._request(method, f"{MCPS_ENDPOINT}{
|
|
137
|
+
data = self._request(method, f"{MCPS_ENDPOINT}{mcp_id_value}", json=json_payload)
|
|
108
138
|
response = MCPResponse(**data)
|
|
109
139
|
return MCP.from_response(response, client=self)
|
|
110
140
|
|
|
@@ -188,7 +218,8 @@ class MCPClient(BaseClient):
|
|
|
188
218
|
**kwargs,
|
|
189
219
|
) -> MCP:
|
|
190
220
|
"""Find by name and update, or create if not found."""
|
|
191
|
-
|
|
221
|
+
all_mcps = self.list_mcps()
|
|
222
|
+
existing = [mcp for mcp in all_mcps if mcp.name.lower() == name.lower()]
|
|
192
223
|
|
|
193
224
|
if len(existing) == 1:
|
|
194
225
|
logger.info("Updating existing MCP: %s", name)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Agent payload types for requests and responses.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from glaip_sdk.client.payloads.agent.requests import (
|
|
8
|
+
AgentCreateRequest,
|
|
9
|
+
AgentListParams,
|
|
10
|
+
AgentUpdateRequest,
|
|
11
|
+
merge_payload_fields,
|
|
12
|
+
resolve_language_model_fields,
|
|
13
|
+
)
|
|
14
|
+
from glaip_sdk.client.payloads.agent.responses import AgentListResult
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AgentCreateRequest",
|
|
18
|
+
"AgentListParams",
|
|
19
|
+
"AgentListResult",
|
|
20
|
+
"AgentUpdateRequest",
|
|
21
|
+
"merge_payload_fields",
|
|
22
|
+
"resolve_language_model_fields",
|
|
23
|
+
]
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
"""Shared helpers for Agent client payload construction and query handling."""
|
|
1
|
+
"""Agent request payload types and helpers.
|
|
3
2
|
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# pylint: disable=duplicate-code
|
|
4
8
|
from __future__ import annotations
|
|
5
9
|
|
|
6
10
|
from collections.abc import Callable, Mapping, MutableMapping, Sequence
|
|
7
11
|
from copy import deepcopy
|
|
8
|
-
from dataclasses import dataclass
|
|
12
|
+
from dataclasses import dataclass
|
|
9
13
|
from typing import Any
|
|
10
14
|
|
|
11
15
|
from glaip_sdk.config.constants import (
|
|
@@ -13,8 +17,8 @@ from glaip_sdk.config.constants import (
|
|
|
13
17
|
DEFAULT_AGENT_PROVIDER,
|
|
14
18
|
DEFAULT_AGENT_TYPE,
|
|
15
19
|
DEFAULT_AGENT_VERSION,
|
|
16
|
-
DEFAULT_MODEL,
|
|
17
20
|
)
|
|
21
|
+
from glaip_sdk.models.constants import DEFAULT_MODEL
|
|
18
22
|
from glaip_sdk.payload_schemas.agent import AgentImportOperation, get_import_field_plan
|
|
19
23
|
from glaip_sdk.utils.client_utils import extract_ids
|
|
20
24
|
|
|
@@ -96,6 +100,11 @@ def resolve_language_model_fields(
|
|
|
96
100
|
resolved_model = model_name or model or default_model
|
|
97
101
|
resolved_provider = provider if provider is not None else default_provider
|
|
98
102
|
|
|
103
|
+
if resolved_model and isinstance(resolved_model, str) and "/" in resolved_model:
|
|
104
|
+
parts = resolved_model.split("/", 1)
|
|
105
|
+
resolved_provider = parts[0]
|
|
106
|
+
resolved_model = parts[1]
|
|
107
|
+
|
|
99
108
|
result: dict[str, Any] = {}
|
|
100
109
|
if resolved_model is not None:
|
|
101
110
|
result["model_name"] = resolved_model
|
|
@@ -273,38 +282,6 @@ class AgentListParams:
|
|
|
273
282
|
params[f"metadata.{key}"] = value
|
|
274
283
|
|
|
275
284
|
|
|
276
|
-
@dataclass(slots=True)
|
|
277
|
-
class AgentListResult:
|
|
278
|
-
"""Structured response for list_agents that retains pagination metadata."""
|
|
279
|
-
|
|
280
|
-
items: list[Any] = field(default_factory=list)
|
|
281
|
-
total: int | None = None
|
|
282
|
-
page: int | None = None
|
|
283
|
-
limit: int | None = None
|
|
284
|
-
has_next: bool | None = None
|
|
285
|
-
has_prev: bool | None = None
|
|
286
|
-
message: str | None = None
|
|
287
|
-
|
|
288
|
-
def __len__(self) -> int: # pragma: no cover - simple delegation
|
|
289
|
-
"""Return the number of items in the result list."""
|
|
290
|
-
return len(self.items)
|
|
291
|
-
|
|
292
|
-
def __iter__(self): # pragma: no cover - simple delegation
|
|
293
|
-
"""Return an iterator over the items in the result list."""
|
|
294
|
-
return iter(self.items)
|
|
295
|
-
|
|
296
|
-
def __getitem__(self, index: int) -> Any: # pragma: no cover - simple delegation
|
|
297
|
-
"""Get an item from the result list by index.
|
|
298
|
-
|
|
299
|
-
Args:
|
|
300
|
-
index: Index of the item to retrieve.
|
|
301
|
-
|
|
302
|
-
Returns:
|
|
303
|
-
The item at the specified index.
|
|
304
|
-
"""
|
|
305
|
-
return self.items[index]
|
|
306
|
-
|
|
307
|
-
|
|
308
285
|
@dataclass(slots=True)
|
|
309
286
|
class AgentCreateRequest:
|
|
310
287
|
"""Declarative representation of an agent creation payload."""
|
|
@@ -422,16 +399,6 @@ class AgentUpdateRequest:
|
|
|
422
399
|
return payload
|
|
423
400
|
|
|
424
401
|
|
|
425
|
-
__all__ = [
|
|
426
|
-
"AgentCreateRequest",
|
|
427
|
-
"AgentListParams",
|
|
428
|
-
"AgentListResult",
|
|
429
|
-
"AgentUpdateRequest",
|
|
430
|
-
"merge_payload_fields",
|
|
431
|
-
"resolve_language_model_fields",
|
|
432
|
-
]
|
|
433
|
-
|
|
434
|
-
|
|
435
402
|
def _build_base_update_payload(request: AgentUpdateRequest, current_agent: Any) -> dict[str, Any]:
|
|
436
403
|
"""Populate immutable agent update fields using request data or existing agent defaults."""
|
|
437
404
|
# Support both "agent_type" (runtime class) and "type" (API response) attributes
|
|
@@ -451,14 +418,27 @@ def _build_base_update_payload(request: AgentUpdateRequest, current_agent: Any)
|
|
|
451
418
|
|
|
452
419
|
def _resolve_update_language_model_fields(request: AgentUpdateRequest, current_agent: Any) -> dict[str, Any]:
|
|
453
420
|
"""Resolve the language-model portion of an update request with sensible fallbacks."""
|
|
421
|
+
# Check if any LM inputs were provided
|
|
422
|
+
has_lm_inputs = any(
|
|
423
|
+
[
|
|
424
|
+
request.model is not None,
|
|
425
|
+
request.language_model_id is not None,
|
|
426
|
+
request.provider is not None,
|
|
427
|
+
request.model_name is not None,
|
|
428
|
+
]
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
if not has_lm_inputs:
|
|
432
|
+
# No LM inputs provided - preserve existing fields
|
|
433
|
+
return _existing_language_model_fields(current_agent)
|
|
434
|
+
|
|
435
|
+
# LM inputs provided - resolve them (may return defaults if only partial info)
|
|
454
436
|
fields = resolve_language_model_fields(
|
|
455
437
|
model=request.model,
|
|
456
438
|
language_model_id=request.language_model_id,
|
|
457
439
|
provider=request.provider,
|
|
458
440
|
model_name=request.model_name,
|
|
459
441
|
)
|
|
460
|
-
if not fields:
|
|
461
|
-
fields = _existing_language_model_fields(current_agent)
|
|
462
442
|
return fields
|
|
463
443
|
|
|
464
444
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Agent response payload types.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# pylint: disable=duplicate-code
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(slots=True)
|
|
15
|
+
class AgentListResult:
|
|
16
|
+
"""Structured response for list_agents that retains pagination metadata."""
|
|
17
|
+
|
|
18
|
+
items: list[Any] = field(default_factory=list)
|
|
19
|
+
total: int | None = None
|
|
20
|
+
page: int | None = None
|
|
21
|
+
limit: int | None = None
|
|
22
|
+
has_next: bool | None = None
|
|
23
|
+
has_prev: bool | None = None
|
|
24
|
+
message: str | None = None
|
|
25
|
+
|
|
26
|
+
def __len__(self) -> int: # pragma: no cover - simple delegation
|
|
27
|
+
"""Return the number of items in the result list."""
|
|
28
|
+
return len(self.items)
|
|
29
|
+
|
|
30
|
+
def __iter__(self): # pragma: no cover - simple delegation
|
|
31
|
+
"""Return an iterator over the items in the result list."""
|
|
32
|
+
return iter(self.items)
|
|
33
|
+
|
|
34
|
+
def __getitem__(self, index: int) -> Any: # pragma: no cover - simple delegation
|
|
35
|
+
"""Get an item from the result list by index.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
index: Index of the item to retrieve.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
The item at the specified index.
|
|
42
|
+
"""
|
|
43
|
+
return self.items[index]
|
|
@@ -11,7 +11,10 @@ import json
|
|
|
11
11
|
import logging
|
|
12
12
|
from collections.abc import AsyncIterable, Callable
|
|
13
13
|
from time import monotonic
|
|
14
|
-
from typing import Any
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from glaip_sdk.hitl.remote import RemoteHITLHandler
|
|
15
18
|
|
|
16
19
|
import httpx
|
|
17
20
|
from rich.console import Console as _Console
|
|
@@ -130,6 +133,7 @@ class AgentRunRenderingManager:
|
|
|
130
133
|
timeout_seconds: float,
|
|
131
134
|
agent_name: str | None,
|
|
132
135
|
meta: dict[str, Any],
|
|
136
|
+
hitl_handler: RemoteHITLHandler | None = None,
|
|
133
137
|
) -> tuple[str, dict[str, Any], float | None, float | None]:
|
|
134
138
|
"""Process streaming events and accumulate response."""
|
|
135
139
|
final_text = ""
|
|
@@ -153,6 +157,7 @@ class AgentRunRenderingManager:
|
|
|
153
157
|
final_text,
|
|
154
158
|
stats_usage,
|
|
155
159
|
meta,
|
|
160
|
+
hitl_handler=hitl_handler,
|
|
156
161
|
)
|
|
157
162
|
|
|
158
163
|
if controller and getattr(controller, "enabled", False):
|
|
@@ -167,6 +172,63 @@ class AgentRunRenderingManager:
|
|
|
167
172
|
finished_monotonic = monotonic()
|
|
168
173
|
return final_text, stats_usage, started_monotonic, finished_monotonic
|
|
169
174
|
|
|
175
|
+
async def _consume_event_stream(
|
|
176
|
+
self,
|
|
177
|
+
event_stream: AsyncIterable[dict[str, Any]],
|
|
178
|
+
renderer: RichStreamRenderer,
|
|
179
|
+
final_text: str,
|
|
180
|
+
stats_usage: dict[str, Any],
|
|
181
|
+
meta: dict[str, Any],
|
|
182
|
+
skip_final_render: bool,
|
|
183
|
+
last_rendered_content: str | None,
|
|
184
|
+
controller: Any | None,
|
|
185
|
+
) -> tuple[str, dict[str, Any], float | None]:
|
|
186
|
+
"""Consume event stream and update state.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
event_stream: Async iterable yielding SSE-like event dicts.
|
|
190
|
+
renderer: Renderer to use for displaying events.
|
|
191
|
+
final_text: Current accumulated final text.
|
|
192
|
+
stats_usage: Usage statistics dictionary.
|
|
193
|
+
meta: Metadata dictionary.
|
|
194
|
+
skip_final_render: If True, skip rendering final_response events.
|
|
195
|
+
last_rendered_content: Last rendered content to avoid duplicates.
|
|
196
|
+
controller: Controller instance.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Tuple of (final_text, stats_usage, started_monotonic).
|
|
200
|
+
"""
|
|
201
|
+
started_monotonic: float | None = None
|
|
202
|
+
|
|
203
|
+
async for event in event_stream:
|
|
204
|
+
if started_monotonic is None:
|
|
205
|
+
started_monotonic = monotonic()
|
|
206
|
+
|
|
207
|
+
parsed_event = self._parse_event(event)
|
|
208
|
+
if parsed_event is None:
|
|
209
|
+
continue
|
|
210
|
+
|
|
211
|
+
final_text, stats_usage = self._handle_parsed_event(
|
|
212
|
+
parsed_event,
|
|
213
|
+
renderer,
|
|
214
|
+
final_text,
|
|
215
|
+
stats_usage,
|
|
216
|
+
meta,
|
|
217
|
+
skip_final_render=skip_final_render,
|
|
218
|
+
last_rendered_content=last_rendered_content,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
content_str = self._extract_content_string(parsed_event)
|
|
222
|
+
if content_str:
|
|
223
|
+
last_rendered_content = content_str
|
|
224
|
+
|
|
225
|
+
if controller and getattr(controller, "enabled", False):
|
|
226
|
+
controller.poll(renderer)
|
|
227
|
+
if parsed_event and self._is_final_event(parsed_event):
|
|
228
|
+
break
|
|
229
|
+
|
|
230
|
+
return final_text, stats_usage, started_monotonic
|
|
231
|
+
|
|
170
232
|
async def async_process_stream_events(
|
|
171
233
|
self,
|
|
172
234
|
event_stream: AsyncIterable[dict[str, Any]],
|
|
@@ -202,35 +264,25 @@ class AgentRunRenderingManager:
|
|
|
202
264
|
controller.on_stream_start(renderer)
|
|
203
265
|
|
|
204
266
|
try:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
# Track last rendered content to avoid duplicates
|
|
226
|
-
content_str = self._extract_content_string(parsed_event)
|
|
227
|
-
if content_str:
|
|
228
|
-
last_rendered_content = content_str
|
|
229
|
-
|
|
230
|
-
if controller and getattr(controller, "enabled", False):
|
|
231
|
-
controller.poll(renderer)
|
|
232
|
-
if parsed_event and self._is_final_event(parsed_event):
|
|
233
|
-
break
|
|
267
|
+
final_text, stats_usage, started_monotonic = await self._consume_event_stream(
|
|
268
|
+
event_stream,
|
|
269
|
+
renderer,
|
|
270
|
+
final_text,
|
|
271
|
+
stats_usage,
|
|
272
|
+
meta,
|
|
273
|
+
skip_final_render,
|
|
274
|
+
last_rendered_content,
|
|
275
|
+
controller,
|
|
276
|
+
)
|
|
277
|
+
except Exception as e:
|
|
278
|
+
err_msg = str(e)
|
|
279
|
+
reason = getattr(getattr(e, "result", None), "reason", None)
|
|
280
|
+
if reason:
|
|
281
|
+
final_text = f"⚠️ Guardrail violation: {reason}"
|
|
282
|
+
elif "⚠️ Guardrail violation" in err_msg or "Content blocked by guardrails" in err_msg:
|
|
283
|
+
final_text = err_msg
|
|
284
|
+
else:
|
|
285
|
+
raise e
|
|
234
286
|
finally:
|
|
235
287
|
if controller and getattr(controller, "enabled", False):
|
|
236
288
|
controller.on_stream_complete()
|
|
@@ -504,6 +556,7 @@ class AgentRunRenderingManager:
|
|
|
504
556
|
final_text: str,
|
|
505
557
|
stats_usage: dict[str, Any],
|
|
506
558
|
meta: dict[str, Any],
|
|
559
|
+
hitl_handler: RemoteHITLHandler | None = None,
|
|
507
560
|
) -> tuple[str, dict[str, Any]]:
|
|
508
561
|
"""Process a single streaming event.
|
|
509
562
|
|
|
@@ -513,6 +566,7 @@ class AgentRunRenderingManager:
|
|
|
513
566
|
final_text: Accumulated text so far.
|
|
514
567
|
stats_usage: Usage statistics dictionary.
|
|
515
568
|
meta: Metadata dictionary.
|
|
569
|
+
hitl_handler: Optional HITL handler for approval callbacks.
|
|
516
570
|
|
|
517
571
|
Returns:
|
|
518
572
|
Tuple of (updated_final_text, updated_stats_usage).
|
|
@@ -523,6 +577,17 @@ class AgentRunRenderingManager:
|
|
|
523
577
|
self._logger.debug("Non-JSON SSE fragment skipped")
|
|
524
578
|
return final_text, stats_usage
|
|
525
579
|
|
|
580
|
+
# Handle HITL event (non-blocking via thread)
|
|
581
|
+
if hitl_handler and self._is_hitl_pending_event(ev):
|
|
582
|
+
try:
|
|
583
|
+
hitl_handler.handle_hitl_event(ev)
|
|
584
|
+
except Exception as e:
|
|
585
|
+
# Log but don't crash stream
|
|
586
|
+
self._logger.error(
|
|
587
|
+
f"HITL handler error: {e}",
|
|
588
|
+
exc_info=True,
|
|
589
|
+
)
|
|
590
|
+
|
|
526
591
|
kind = (ev.get("metadata") or {}).get("kind")
|
|
527
592
|
renderer.on_event(ev)
|
|
528
593
|
|
|
@@ -590,6 +655,20 @@ class AgentRunRenderingManager:
|
|
|
590
655
|
return content
|
|
591
656
|
return final_text
|
|
592
657
|
|
|
658
|
+
@staticmethod
|
|
659
|
+
def _is_hitl_pending_event(event: dict[str, Any]) -> bool:
|
|
660
|
+
"""Check if event is a pending HITL approval request.
|
|
661
|
+
|
|
662
|
+
Args:
|
|
663
|
+
event: Parsed event dictionary.
|
|
664
|
+
|
|
665
|
+
Returns:
|
|
666
|
+
True if event is a pending HITL request.
|
|
667
|
+
"""
|
|
668
|
+
metadata = event.get("metadata", {})
|
|
669
|
+
hitl_meta = metadata.get("hitl", {})
|
|
670
|
+
return hitl_meta.get("required") is True and hitl_meta.get("decision") == "pending"
|
|
671
|
+
|
|
593
672
|
def _handle_run_info_event(
|
|
594
673
|
self,
|
|
595
674
|
ev: dict[str, Any],
|