glaip-sdk 0.1.3__py3-none-any.whl → 0.6.10__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 +5 -2
- glaip_sdk/_version.py +9 -0
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1191 -0
- glaip_sdk/branding.py +13 -0
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/auth.py +254 -15
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents.py +213 -73
- glaip_sdk/cli/commands/common_config.py +101 -0
- glaip_sdk/cli/commands/configure.py +729 -113
- glaip_sdk/cli/commands/mcps.py +241 -72
- glaip_sdk/cli/commands/models.py +11 -5
- glaip_sdk/cli/commands/tools.py +49 -57
- glaip_sdk/cli/commands/transcripts.py +755 -0
- glaip_sdk/cli/config.py +48 -4
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +8 -0
- glaip_sdk/cli/core/__init__.py +79 -0
- glaip_sdk/cli/core/context.py +124 -0
- glaip_sdk/cli/core/output.py +846 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +35 -19
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +6 -3
- glaip_sdk/cli/main.py +228 -119
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/pager.py +9 -10
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/slash/__init__.py +0 -9
- glaip_sdk/cli/slash/accounts_controller.py +578 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +62 -21
- glaip_sdk/cli/slash/prompt.py +21 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +771 -140
- glaip_sdk/cli/slash/tui/__init__.py +9 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +876 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +255 -44
- glaip_sdk/cli/transcript/capture.py +27 -1
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/viewer.py +72 -499
- glaip_sdk/cli/update_notifier.py +14 -5
- glaip_sdk/cli/utils.py +243 -1252
- glaip_sdk/cli/validators.py +5 -6
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_agent_payloads.py +45 -9
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +287 -29
- glaip_sdk/client/base.py +1 -0
- glaip_sdk/client/main.py +19 -10
- glaip_sdk/client/mcps.py +122 -12
- glaip_sdk/client/run_rendering.py +133 -88
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +155 -10
- glaip_sdk/config/constants.py +11 -0
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +90 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +116 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +1 -13
- glaip_sdk/registry/__init__.py +55 -0
- glaip_sdk/registry/agent.py +164 -0
- glaip_sdk/registry/base.py +139 -0
- glaip_sdk/registry/mcp.py +253 -0
- glaip_sdk/registry/tool.py +232 -0
- glaip_sdk/rich_components.py +58 -2
- glaip_sdk/runner/__init__.py +59 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +115 -0
- glaip_sdk/runner/langgraph.py +706 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +435 -0
- glaip_sdk/utils/__init__.py +58 -12
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/bundler.py +267 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +39 -7
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +23 -15
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +0 -33
- glaip_sdk/utils/import_export.py +12 -7
- glaip_sdk/utils/import_resolver.py +492 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -1
- glaip_sdk/utils/rendering/formatting.py +5 -30
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +1 -0
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -47
- glaip_sdk/utils/rendering/renderer/base.py +217 -1476
- glaip_sdk/utils/rendering/renderer/debug.py +26 -20
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +4 -12
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
- glaip_sdk/utils/rendering/state.py +204 -0
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -440
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/steps/manager.py +387 -0
- glaip_sdk/utils/rendering/timing.py +36 -0
- glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
- glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
- glaip_sdk/utils/resource_refs.py +25 -13
- glaip_sdk/utils/runtime_config.py +425 -0
- glaip_sdk/utils/serialization.py +18 -0
- glaip_sdk/utils/sync.py +142 -0
- glaip_sdk/utils/tool_detection.py +33 -0
- glaip_sdk/utils/validation.py +16 -24
- {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.10.dist-info}/METADATA +42 -4
- glaip_sdk-0.6.10.dist-info/RECORD +159 -0
- {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.10.dist-info}/WHEEL +1 -1
- glaip_sdk/models.py +0 -240
- glaip_sdk-0.1.3.dist-info/RECORD +0 -83
- {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.10.dist-info}/entry_points.txt +0 -0
glaip_sdk/client/mcps.py
CHANGED
|
@@ -3,18 +3,22 @@
|
|
|
3
3
|
|
|
4
4
|
Authors:
|
|
5
5
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
import logging
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
11
12
|
from glaip_sdk.client.base import BaseClient
|
|
12
|
-
from glaip_sdk.config.constants import
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
from glaip_sdk.config.constants import DEFAULT_MCP_TRANSPORT, DEFAULT_MCP_TYPE
|
|
14
|
+
from glaip_sdk.mcps import MCP
|
|
15
|
+
from glaip_sdk.models import MCPResponse
|
|
16
|
+
from glaip_sdk.utils.client_utils import (
|
|
17
|
+
add_kwargs_to_payload,
|
|
18
|
+
create_model_instances,
|
|
19
|
+
find_by_name,
|
|
15
20
|
)
|
|
16
|
-
from glaip_sdk.
|
|
17
|
-
from glaip_sdk.utils.client_utils import create_model_instances, find_by_name
|
|
21
|
+
from glaip_sdk.utils.resource_refs import is_uuid
|
|
18
22
|
|
|
19
23
|
# API endpoints
|
|
20
24
|
MCPS_ENDPOINT = "/mcps/"
|
|
@@ -45,7 +49,8 @@ class MCPClient(BaseClient):
|
|
|
45
49
|
def get_mcp_by_id(self, mcp_id: str) -> MCP:
|
|
46
50
|
"""Get MCP by ID."""
|
|
47
51
|
data = self._request("GET", f"{MCPS_ENDPOINT}{mcp_id}")
|
|
48
|
-
|
|
52
|
+
response = MCPResponse(**data)
|
|
53
|
+
return MCP.from_response(response, client=self)
|
|
49
54
|
|
|
50
55
|
def find_mcps(self, name: str | None = None) -> list[MCP]:
|
|
51
56
|
"""Find MCPs by name."""
|
|
@@ -77,7 +82,8 @@ class MCPClient(BaseClient):
|
|
|
77
82
|
get_endpoint_fmt=f"{MCPS_ENDPOINT}{{id}}",
|
|
78
83
|
json=payload,
|
|
79
84
|
)
|
|
80
|
-
|
|
85
|
+
response = MCPResponse(**full_mcp_data)
|
|
86
|
+
return MCP.from_response(response, client=self)
|
|
81
87
|
|
|
82
88
|
def update_mcp(self, mcp_id: str, **kwargs) -> MCP:
|
|
83
89
|
"""Update an existing MCP.
|
|
@@ -99,12 +105,102 @@ class MCPClient(BaseClient):
|
|
|
99
105
|
method = "PATCH"
|
|
100
106
|
|
|
101
107
|
data = self._request(method, f"{MCPS_ENDPOINT}{mcp_id}", json=kwargs)
|
|
102
|
-
|
|
108
|
+
response = MCPResponse(**data)
|
|
109
|
+
return MCP.from_response(response, client=self)
|
|
103
110
|
|
|
104
111
|
def delete_mcp(self, mcp_id: str) -> None:
|
|
105
112
|
"""Delete an MCP."""
|
|
106
113
|
self._request("DELETE", f"{MCPS_ENDPOINT}{mcp_id}")
|
|
107
114
|
|
|
115
|
+
def upsert_mcp(
|
|
116
|
+
self,
|
|
117
|
+
identifier: str | MCP,
|
|
118
|
+
description: str | None = None,
|
|
119
|
+
config: dict[str, Any] | None = None,
|
|
120
|
+
**kwargs,
|
|
121
|
+
) -> MCP:
|
|
122
|
+
"""Create or update an MCP by instance, ID, or name.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
identifier: MCP instance, ID (UUID string), or name
|
|
126
|
+
description: MCP description
|
|
127
|
+
config: MCP configuration dictionary
|
|
128
|
+
**kwargs: Additional parameters (transport, metadata, etc.)
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
The created or updated MCP.
|
|
132
|
+
|
|
133
|
+
Example:
|
|
134
|
+
>>> # By name (creates if not exists)
|
|
135
|
+
>>> mcp = client.mcps.upsert_mcp(
|
|
136
|
+
... "deepwiki",
|
|
137
|
+
... transport="sse",
|
|
138
|
+
... config={"url": "https://mcp.deepwiki.com/sse"},
|
|
139
|
+
... )
|
|
140
|
+
>>> # By instance
|
|
141
|
+
>>> mcp = client.mcps.upsert_mcp(existing_mcp, description="Updated")
|
|
142
|
+
>>> # By ID
|
|
143
|
+
>>> mcp = client.mcps.upsert_mcp("uuid-here", description="Updated")
|
|
144
|
+
"""
|
|
145
|
+
# Handle MCP instance
|
|
146
|
+
if isinstance(identifier, MCP):
|
|
147
|
+
if identifier.id:
|
|
148
|
+
logger.info("Updating MCP by instance: %s", identifier.name)
|
|
149
|
+
return self._do_upsert_update(identifier.id, identifier.name, description, config, **kwargs)
|
|
150
|
+
# MCP without ID - treat name as identifier
|
|
151
|
+
identifier = identifier.name
|
|
152
|
+
|
|
153
|
+
# Handle string (ID or name)
|
|
154
|
+
if isinstance(identifier, str):
|
|
155
|
+
if is_uuid(identifier):
|
|
156
|
+
logger.info("Updating MCP by ID: %s", identifier)
|
|
157
|
+
existing = self.get_mcp_by_id(identifier)
|
|
158
|
+
return self._do_upsert_update(identifier, existing.name, description, config, **kwargs)
|
|
159
|
+
|
|
160
|
+
# It's a name - find or create
|
|
161
|
+
return self._upsert_by_name(identifier, description, config, **kwargs)
|
|
162
|
+
|
|
163
|
+
raise ValueError(f"Invalid identifier type: {type(identifier)}")
|
|
164
|
+
|
|
165
|
+
def _do_upsert_update(
|
|
166
|
+
self,
|
|
167
|
+
mcp_id: str,
|
|
168
|
+
name: str | None,
|
|
169
|
+
description: str | None,
|
|
170
|
+
config: dict[str, Any] | None,
|
|
171
|
+
**kwargs,
|
|
172
|
+
) -> MCP:
|
|
173
|
+
"""Perform the update part of upsert."""
|
|
174
|
+
update_kwargs = {**kwargs}
|
|
175
|
+
if name is not None:
|
|
176
|
+
update_kwargs["name"] = name
|
|
177
|
+
if description is not None:
|
|
178
|
+
update_kwargs["description"] = description
|
|
179
|
+
if config is not None:
|
|
180
|
+
update_kwargs["config"] = config
|
|
181
|
+
return self.update_mcp(mcp_id, **update_kwargs)
|
|
182
|
+
|
|
183
|
+
def _upsert_by_name(
|
|
184
|
+
self,
|
|
185
|
+
name: str,
|
|
186
|
+
description: str | None,
|
|
187
|
+
config: dict[str, Any] | None,
|
|
188
|
+
**kwargs,
|
|
189
|
+
) -> MCP:
|
|
190
|
+
"""Find by name and update, or create if not found."""
|
|
191
|
+
existing = self.find_mcps(name)
|
|
192
|
+
|
|
193
|
+
if len(existing) == 1:
|
|
194
|
+
logger.info("Updating existing MCP: %s", name)
|
|
195
|
+
return self._do_upsert_update(existing[0].id, name, description, config, **kwargs)
|
|
196
|
+
|
|
197
|
+
if len(existing) > 1:
|
|
198
|
+
raise ValueError(f"Multiple MCPs found with name '{name}'")
|
|
199
|
+
|
|
200
|
+
# Create new MCP
|
|
201
|
+
logger.info("Creating new MCP: %s", name)
|
|
202
|
+
return self.create_mcp(name=name, description=description, config=config, **kwargs)
|
|
203
|
+
|
|
108
204
|
def _build_create_payload(
|
|
109
205
|
self,
|
|
110
206
|
name: str,
|
|
@@ -147,9 +243,7 @@ class MCPClient(BaseClient):
|
|
|
147
243
|
|
|
148
244
|
# Add any other kwargs (excluding already handled ones)
|
|
149
245
|
excluded_keys = {"type"} # type is handled above
|
|
150
|
-
|
|
151
|
-
if key not in excluded_keys:
|
|
152
|
-
payload[key] = value
|
|
246
|
+
add_kwargs_to_payload(payload, kwargs, excluded_keys)
|
|
153
247
|
|
|
154
248
|
return payload
|
|
155
249
|
|
|
@@ -206,7 +300,23 @@ class MCPClient(BaseClient):
|
|
|
206
300
|
def get_mcp_tools(self, mcp_id: str) -> list[dict[str, Any]]:
|
|
207
301
|
"""Get tools available from an MCP."""
|
|
208
302
|
data = self._request("GET", f"{MCPS_ENDPOINT}{mcp_id}/tools")
|
|
209
|
-
|
|
303
|
+
if data is None:
|
|
304
|
+
return []
|
|
305
|
+
if isinstance(data, list):
|
|
306
|
+
return data
|
|
307
|
+
if isinstance(data, dict):
|
|
308
|
+
if "tools" in data:
|
|
309
|
+
return data.get("tools", []) or []
|
|
310
|
+
logger.warning(
|
|
311
|
+
"Unexpected MCP tools response keys %s; returning empty list",
|
|
312
|
+
list(data.keys()),
|
|
313
|
+
)
|
|
314
|
+
return []
|
|
315
|
+
logger.warning(
|
|
316
|
+
"Unexpected MCP tools response type %s; returning empty list",
|
|
317
|
+
type(data).__name__,
|
|
318
|
+
)
|
|
319
|
+
return []
|
|
210
320
|
|
|
211
321
|
def test_mcp_connection(self, config: dict[str, Any]) -> dict[str, Any]:
|
|
212
322
|
"""Test MCP connection using configuration.
|
|
@@ -7,9 +7,9 @@ Authors:
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
-
import io
|
|
11
10
|
import json
|
|
12
11
|
import logging
|
|
12
|
+
from collections.abc import Callable
|
|
13
13
|
from time import monotonic
|
|
14
14
|
from typing import Any
|
|
15
15
|
|
|
@@ -19,8 +19,17 @@ from rich.console import Console as _Console
|
|
|
19
19
|
from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT
|
|
20
20
|
from glaip_sdk.utils.client_utils import iter_sse_events
|
|
21
21
|
from glaip_sdk.utils.rendering.models import RunStats
|
|
22
|
-
from glaip_sdk.utils.rendering.renderer import
|
|
23
|
-
|
|
22
|
+
from glaip_sdk.utils.rendering.renderer import (
|
|
23
|
+
RendererFactoryOptions,
|
|
24
|
+
RichStreamRenderer,
|
|
25
|
+
make_default_renderer,
|
|
26
|
+
make_minimal_renderer,
|
|
27
|
+
make_silent_renderer,
|
|
28
|
+
make_verbose_renderer,
|
|
29
|
+
)
|
|
30
|
+
from glaip_sdk.utils.rendering.state import TranscriptBuffer
|
|
31
|
+
|
|
32
|
+
NO_AGENT_RESPONSE_FALLBACK = "No agent response received."
|
|
24
33
|
|
|
25
34
|
|
|
26
35
|
def _coerce_to_string(value: Any) -> str:
|
|
@@ -36,41 +45,6 @@ def _has_visible_text(value: Any) -> bool:
|
|
|
36
45
|
return isinstance(value, str) and bool(value.strip())
|
|
37
46
|
|
|
38
47
|
|
|
39
|
-
def _update_state_transcript(state: Any, text_value: str) -> bool:
|
|
40
|
-
"""Inject transcript text into renderer state if possible."""
|
|
41
|
-
if state is None:
|
|
42
|
-
return False
|
|
43
|
-
|
|
44
|
-
updated = False
|
|
45
|
-
|
|
46
|
-
if hasattr(state, "final_text") and not _has_visible_text(getattr(state, "final_text", "")):
|
|
47
|
-
try:
|
|
48
|
-
state.final_text = text_value
|
|
49
|
-
updated = True
|
|
50
|
-
except Exception:
|
|
51
|
-
pass
|
|
52
|
-
|
|
53
|
-
buffer = getattr(state, "buffer", None)
|
|
54
|
-
if isinstance(buffer, list) and not any(_has_visible_text(item) for item in buffer):
|
|
55
|
-
buffer.append(text_value)
|
|
56
|
-
updated = True
|
|
57
|
-
|
|
58
|
-
return updated
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _update_renderer_transcript(renderer: Any, text_value: str) -> None:
|
|
62
|
-
"""Populate the renderer (or its state) with the supplied text."""
|
|
63
|
-
state = getattr(renderer, "state", None)
|
|
64
|
-
if _update_state_transcript(state, text_value):
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
if hasattr(renderer, "final_text") and not _has_visible_text(getattr(renderer, "final_text", "")):
|
|
68
|
-
try:
|
|
69
|
-
renderer.final_text = text_value
|
|
70
|
-
except Exception:
|
|
71
|
-
pass
|
|
72
|
-
|
|
73
|
-
|
|
74
48
|
class AgentRunRenderingManager:
|
|
75
49
|
"""Coordinate renderer creation and streaming event handling."""
|
|
76
50
|
|
|
@@ -81,6 +55,7 @@ class AgentRunRenderingManager:
|
|
|
81
55
|
logger: Optional logger instance, creates default if None
|
|
82
56
|
"""
|
|
83
57
|
self._logger = logger or logging.getLogger(__name__)
|
|
58
|
+
self._buffer_factory = TranscriptBuffer
|
|
84
59
|
|
|
85
60
|
# --------------------------------------------------------------------- #
|
|
86
61
|
# Renderer setup helpers
|
|
@@ -92,17 +67,38 @@ class AgentRunRenderingManager:
|
|
|
92
67
|
verbose: bool = False,
|
|
93
68
|
) -> RichStreamRenderer:
|
|
94
69
|
"""Create an appropriate renderer based on the supplied spec."""
|
|
70
|
+
transcript_buffer = self._buffer_factory()
|
|
71
|
+
base_options = RendererFactoryOptions(console=_Console(), transcript_buffer=transcript_buffer)
|
|
95
72
|
if isinstance(renderer_spec, RichStreamRenderer):
|
|
96
73
|
return renderer_spec
|
|
97
74
|
|
|
98
75
|
if isinstance(renderer_spec, str):
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
76
|
+
lowered = renderer_spec.lower()
|
|
77
|
+
if lowered == "silent":
|
|
78
|
+
return self._attach_buffer(base_options.build(make_silent_renderer), transcript_buffer)
|
|
79
|
+
if lowered == "minimal":
|
|
80
|
+
return self._attach_buffer(base_options.build(make_minimal_renderer), transcript_buffer)
|
|
81
|
+
if lowered == "verbose":
|
|
82
|
+
return self._attach_buffer(base_options.build(make_verbose_renderer), transcript_buffer)
|
|
104
83
|
|
|
105
|
-
|
|
84
|
+
if verbose:
|
|
85
|
+
return self._attach_buffer(base_options.build(make_verbose_renderer), transcript_buffer)
|
|
86
|
+
|
|
87
|
+
default_options = RendererFactoryOptions(
|
|
88
|
+
console=_Console(),
|
|
89
|
+
transcript_buffer=transcript_buffer,
|
|
90
|
+
verbose=verbose,
|
|
91
|
+
)
|
|
92
|
+
return self._attach_buffer(default_options.build(make_default_renderer), transcript_buffer)
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def _attach_buffer(renderer: RichStreamRenderer, buffer: TranscriptBuffer) -> RichStreamRenderer:
|
|
96
|
+
"""Attach a captured transcript buffer to a renderer for later inspection."""
|
|
97
|
+
try:
|
|
98
|
+
renderer._captured_transcript_buffer = buffer # type: ignore[attr-defined]
|
|
99
|
+
except Exception:
|
|
100
|
+
pass
|
|
101
|
+
return renderer
|
|
106
102
|
|
|
107
103
|
def build_initial_metadata(
|
|
108
104
|
self,
|
|
@@ -123,47 +119,6 @@ class AgentRunRenderingManager:
|
|
|
123
119
|
"""Notify renderer that streaming is starting."""
|
|
124
120
|
renderer.on_start(meta)
|
|
125
121
|
|
|
126
|
-
def _create_silent_renderer(self) -> RichStreamRenderer:
|
|
127
|
-
silent_config = RendererConfig(
|
|
128
|
-
live=False,
|
|
129
|
-
persist_live=False,
|
|
130
|
-
render_thinking=False,
|
|
131
|
-
)
|
|
132
|
-
return RichStreamRenderer(
|
|
133
|
-
console=_Console(file=io.StringIO(), force_terminal=False),
|
|
134
|
-
cfg=silent_config,
|
|
135
|
-
verbose=False,
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
def _create_minimal_renderer(self) -> RichStreamRenderer:
|
|
139
|
-
minimal_config = RendererConfig(
|
|
140
|
-
live=False,
|
|
141
|
-
persist_live=False,
|
|
142
|
-
render_thinking=False,
|
|
143
|
-
)
|
|
144
|
-
return RichStreamRenderer(
|
|
145
|
-
console=_Console(),
|
|
146
|
-
cfg=minimal_config,
|
|
147
|
-
verbose=False,
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
def _create_verbose_renderer(self) -> RichStreamRenderer:
|
|
151
|
-
verbose_config = RendererConfig(
|
|
152
|
-
live=False,
|
|
153
|
-
append_finished_snapshots=False,
|
|
154
|
-
)
|
|
155
|
-
return RichStreamRenderer(
|
|
156
|
-
console=_Console(),
|
|
157
|
-
cfg=verbose_config,
|
|
158
|
-
verbose=True,
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
def _create_default_renderer(self, verbose: bool) -> RichStreamRenderer:
|
|
162
|
-
if verbose:
|
|
163
|
-
return self._create_verbose_renderer()
|
|
164
|
-
default_config = RendererConfig()
|
|
165
|
-
return RichStreamRenderer(console=_Console(), cfg=default_config)
|
|
166
|
-
|
|
167
122
|
# --------------------------------------------------------------------- #
|
|
168
123
|
# Streaming event handling
|
|
169
124
|
# --------------------------------------------------------------------- #
|
|
@@ -214,12 +169,27 @@ class AgentRunRenderingManager:
|
|
|
214
169
|
meta: dict[str, Any],
|
|
215
170
|
renderer: RichStreamRenderer,
|
|
216
171
|
) -> None:
|
|
172
|
+
"""Capture request ID from response headers and update metadata.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
stream_response: HTTP response stream.
|
|
176
|
+
meta: Metadata dictionary to update.
|
|
177
|
+
renderer: Renderer instance.
|
|
178
|
+
"""
|
|
217
179
|
req_id = stream_response.headers.get("x-request-id") or stream_response.headers.get("x-run-id")
|
|
218
180
|
if req_id:
|
|
219
181
|
meta["run_id"] = req_id
|
|
220
182
|
renderer.on_start(meta)
|
|
221
183
|
|
|
222
184
|
def _maybe_start_timer(self, event: dict[str, Any]) -> float | None:
|
|
185
|
+
"""Start timing if this is a content-bearing event.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
event: Event dictionary.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Monotonic time if timer should start, None otherwise.
|
|
192
|
+
"""
|
|
223
193
|
try:
|
|
224
194
|
ev = json.loads(event["data"])
|
|
225
195
|
except json.JSONDecodeError:
|
|
@@ -237,6 +207,18 @@ class AgentRunRenderingManager:
|
|
|
237
207
|
stats_usage: dict[str, Any],
|
|
238
208
|
meta: dict[str, Any],
|
|
239
209
|
) -> tuple[str, dict[str, Any]]:
|
|
210
|
+
"""Process a single streaming event.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
event: Event dictionary.
|
|
214
|
+
renderer: Renderer instance.
|
|
215
|
+
final_text: Accumulated text so far.
|
|
216
|
+
stats_usage: Usage statistics dictionary.
|
|
217
|
+
meta: Metadata dictionary.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Tuple of (updated_final_text, updated_stats_usage).
|
|
221
|
+
"""
|
|
240
222
|
try:
|
|
241
223
|
ev = json.loads(event["data"])
|
|
242
224
|
except json.JSONDecodeError:
|
|
@@ -292,6 +274,15 @@ class AgentRunRenderingManager:
|
|
|
292
274
|
return None
|
|
293
275
|
|
|
294
276
|
def _handle_content_event(self, ev: dict[str, Any], final_text: str) -> str:
|
|
277
|
+
"""Handle a content event and update final text.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
ev: Event dictionary.
|
|
281
|
+
final_text: Current accumulated text.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Updated final text.
|
|
285
|
+
"""
|
|
295
286
|
content = ev.get("content", "")
|
|
296
287
|
if not content.startswith("Artifact received:"):
|
|
297
288
|
return content
|
|
@@ -303,6 +294,13 @@ class AgentRunRenderingManager:
|
|
|
303
294
|
meta: dict[str, Any],
|
|
304
295
|
renderer: RichStreamRenderer,
|
|
305
296
|
) -> None:
|
|
297
|
+
"""Handle a run_info event and update metadata.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
ev: Event dictionary.
|
|
301
|
+
meta: Metadata dictionary to update.
|
|
302
|
+
renderer: Renderer instance.
|
|
303
|
+
"""
|
|
306
304
|
if ev.get("model"):
|
|
307
305
|
meta["model"] = ev["model"]
|
|
308
306
|
renderer.on_start(meta)
|
|
@@ -316,7 +314,52 @@ class AgentRunRenderingManager:
|
|
|
316
314
|
return
|
|
317
315
|
|
|
318
316
|
text_value = _coerce_to_string(text)
|
|
319
|
-
|
|
317
|
+
state = getattr(renderer, "state", None)
|
|
318
|
+
if state is None:
|
|
319
|
+
self._ensure_renderer_text(renderer, text_value)
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
self._ensure_state_final_text(state, text_value)
|
|
323
|
+
self._ensure_state_buffer(state, text_value)
|
|
324
|
+
|
|
325
|
+
def _ensure_renderer_text(self, renderer: RichStreamRenderer, text_value: str) -> None:
|
|
326
|
+
"""Best-effort assignment for renderer.final_text."""
|
|
327
|
+
if not hasattr(renderer, "final_text"):
|
|
328
|
+
return
|
|
329
|
+
current_text = getattr(renderer, "final_text", "")
|
|
330
|
+
if _has_visible_text(current_text):
|
|
331
|
+
return
|
|
332
|
+
self._safe_set_attr(renderer, "final_text", text_value)
|
|
333
|
+
|
|
334
|
+
def _ensure_state_final_text(self, state: Any, text_value: str) -> None:
|
|
335
|
+
"""Best-effort assignment for renderer.state.final_text."""
|
|
336
|
+
current_text = getattr(state, "final_text", "")
|
|
337
|
+
if _has_visible_text(current_text):
|
|
338
|
+
return
|
|
339
|
+
self._safe_set_attr(state, "final_text", text_value)
|
|
340
|
+
|
|
341
|
+
def _ensure_state_buffer(self, state: Any, text_value: str) -> None:
|
|
342
|
+
"""Append fallback text to the state buffer when available."""
|
|
343
|
+
buffer = getattr(state, "buffer", None)
|
|
344
|
+
if not hasattr(buffer, "append"):
|
|
345
|
+
return
|
|
346
|
+
self._safe_append(buffer.append, text_value)
|
|
347
|
+
|
|
348
|
+
@staticmethod
|
|
349
|
+
def _safe_set_attr(target: Any, attr: str, value: str) -> None:
|
|
350
|
+
"""Assign attribute while masking renderer-specific failures."""
|
|
351
|
+
try:
|
|
352
|
+
setattr(target, attr, value)
|
|
353
|
+
except Exception:
|
|
354
|
+
pass
|
|
355
|
+
|
|
356
|
+
@staticmethod
|
|
357
|
+
def _safe_append(appender: Callable[[str], Any], value: str) -> None:
|
|
358
|
+
"""Invoke append-like functions without leaking renderer errors."""
|
|
359
|
+
try:
|
|
360
|
+
appender(value)
|
|
361
|
+
except Exception:
|
|
362
|
+
pass
|
|
320
363
|
|
|
321
364
|
# --------------------------------------------------------------------- #
|
|
322
365
|
# Finalisation helpers
|
|
@@ -343,7 +386,9 @@ class AgentRunRenderingManager:
|
|
|
343
386
|
elif hasattr(renderer, "buffer"):
|
|
344
387
|
buffer_values = renderer.buffer
|
|
345
388
|
|
|
346
|
-
if buffer_values
|
|
389
|
+
if isinstance(buffer_values, TranscriptBuffer):
|
|
390
|
+
rendered_text = buffer_values.render()
|
|
391
|
+
elif buffer_values is not None:
|
|
347
392
|
try:
|
|
348
393
|
rendered_text = "".join(buffer_values)
|
|
349
394
|
except TypeError:
|
|
@@ -354,7 +399,7 @@ class AgentRunRenderingManager:
|
|
|
354
399
|
self._ensure_renderer_final_content(renderer, fallback_text)
|
|
355
400
|
|
|
356
401
|
renderer.on_complete(st)
|
|
357
|
-
return final_text or rendered_text or
|
|
402
|
+
return final_text or rendered_text or NO_AGENT_RESPONSE_FALLBACK
|
|
358
403
|
|
|
359
404
|
|
|
360
405
|
def compute_timeout_seconds(kwargs: dict[str, Any]) -> float:
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Shared helpers for client configuration wiring.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from glaip_sdk.client.base import BaseClient
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def build_shared_config(client: BaseClient) -> dict[str, Any]:
|
|
15
|
+
"""Return the keyword arguments used to initialize sub-clients."""
|
|
16
|
+
return {
|
|
17
|
+
"parent_client": client,
|
|
18
|
+
"api_url": client.api_url,
|
|
19
|
+
"api_key": client.api_key,
|
|
20
|
+
"timeout": client._timeout,
|
|
21
|
+
}
|