glaip-sdk 0.3.0__py3-none-any.whl → 0.5.0__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/cli/account_store.py +522 -0
- glaip_sdk/cli/auth.py +224 -8
- glaip_sdk/cli/commands/accounts.py +414 -0
- glaip_sdk/cli/commands/agents.py +2 -2
- glaip_sdk/cli/commands/common_config.py +65 -0
- glaip_sdk/cli/commands/configure.py +153 -87
- glaip_sdk/cli/commands/mcps.py +191 -44
- glaip_sdk/cli/commands/transcripts.py +1 -1
- glaip_sdk/cli/config.py +31 -3
- glaip_sdk/cli/display.py +1 -1
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +6 -3
- glaip_sdk/cli/main.py +181 -79
- glaip_sdk/cli/masking.py +14 -1
- glaip_sdk/cli/slash/agent_session.py +2 -1
- glaip_sdk/cli/slash/remote_runs_controller.py +1 -1
- glaip_sdk/cli/slash/session.py +11 -9
- glaip_sdk/cli/slash/tui/remote_runs_app.py +2 -3
- glaip_sdk/cli/transcript/capture.py +12 -18
- glaip_sdk/cli/transcript/viewer.py +13 -646
- glaip_sdk/cli/update_notifier.py +2 -1
- glaip_sdk/cli/utils.py +95 -139
- glaip_sdk/client/agents.py +2 -4
- glaip_sdk/client/main.py +2 -18
- glaip_sdk/client/mcps.py +11 -1
- glaip_sdk/client/run_rendering.py +90 -111
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/models.py +8 -7
- glaip_sdk/utils/display.py +23 -15
- glaip_sdk/utils/rendering/__init__.py +6 -13
- 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 +10 -28
- glaip_sdk/utils/rendering/renderer/base.py +214 -1469
- glaip_sdk/utils/rendering/renderer/debug.py +24 -0
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
- glaip_sdk/utils/rendering/state.py +204 -0
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -440
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/steps/manager.py +387 -0
- glaip_sdk/utils/rendering/timing.py +36 -0
- glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
- glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
- glaip_sdk/utils/validation.py +13 -21
- {glaip_sdk-0.3.0.dist-info → glaip_sdk-0.5.0.dist-info}/METADATA +1 -1
- glaip_sdk-0.5.0.dist-info/RECORD +113 -0
- glaip_sdk-0.3.0.dist-info/RECORD +0 -94
- {glaip_sdk-0.3.0.dist-info → glaip_sdk-0.5.0.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.3.0.dist-info → glaip_sdk-0.5.0.dist-info}/entry_points.txt +0 -0
|
@@ -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)
|
|
83
|
+
|
|
84
|
+
if verbose:
|
|
85
|
+
return self._attach_buffer(base_options.build(make_verbose_renderer), transcript_buffer)
|
|
104
86
|
|
|
105
|
-
|
|
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,70 +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
|
-
"""Create a silent renderer that outputs to a string buffer.
|
|
128
|
-
|
|
129
|
-
Returns:
|
|
130
|
-
RichStreamRenderer configured for silent output.
|
|
131
|
-
"""
|
|
132
|
-
silent_config = RendererConfig(
|
|
133
|
-
live=False,
|
|
134
|
-
persist_live=False,
|
|
135
|
-
render_thinking=False,
|
|
136
|
-
)
|
|
137
|
-
return RichStreamRenderer(
|
|
138
|
-
console=_Console(file=io.StringIO(), force_terminal=False),
|
|
139
|
-
cfg=silent_config,
|
|
140
|
-
verbose=False,
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
def _create_minimal_renderer(self) -> RichStreamRenderer:
|
|
144
|
-
"""Create a minimal renderer with reduced output.
|
|
145
|
-
|
|
146
|
-
Returns:
|
|
147
|
-
RichStreamRenderer configured for minimal output.
|
|
148
|
-
"""
|
|
149
|
-
minimal_config = RendererConfig(
|
|
150
|
-
live=False,
|
|
151
|
-
persist_live=False,
|
|
152
|
-
render_thinking=False,
|
|
153
|
-
)
|
|
154
|
-
return RichStreamRenderer(
|
|
155
|
-
console=_Console(),
|
|
156
|
-
cfg=minimal_config,
|
|
157
|
-
verbose=False,
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
def _create_verbose_renderer(self) -> RichStreamRenderer:
|
|
161
|
-
"""Create a verbose renderer with detailed output.
|
|
162
|
-
|
|
163
|
-
Returns:
|
|
164
|
-
RichStreamRenderer configured for verbose output.
|
|
165
|
-
"""
|
|
166
|
-
verbose_config = RendererConfig(
|
|
167
|
-
live=False,
|
|
168
|
-
append_finished_snapshots=False,
|
|
169
|
-
)
|
|
170
|
-
return RichStreamRenderer(
|
|
171
|
-
console=_Console(),
|
|
172
|
-
cfg=verbose_config,
|
|
173
|
-
verbose=True,
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
def _create_default_renderer(self, verbose: bool) -> RichStreamRenderer:
|
|
177
|
-
"""Create the default renderer based on verbosity.
|
|
178
|
-
|
|
179
|
-
Args:
|
|
180
|
-
verbose: Whether to create a verbose renderer.
|
|
181
|
-
|
|
182
|
-
Returns:
|
|
183
|
-
RichStreamRenderer instance.
|
|
184
|
-
"""
|
|
185
|
-
if verbose:
|
|
186
|
-
return self._create_verbose_renderer()
|
|
187
|
-
default_config = RendererConfig()
|
|
188
|
-
return RichStreamRenderer(console=_Console(), cfg=default_config)
|
|
189
|
-
|
|
190
122
|
# --------------------------------------------------------------------- #
|
|
191
123
|
# Streaming event handling
|
|
192
124
|
# --------------------------------------------------------------------- #
|
|
@@ -382,7 +314,52 @@ class AgentRunRenderingManager:
|
|
|
382
314
|
return
|
|
383
315
|
|
|
384
316
|
text_value = _coerce_to_string(text)
|
|
385
|
-
|
|
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
|
|
386
363
|
|
|
387
364
|
# --------------------------------------------------------------------- #
|
|
388
365
|
# Finalisation helpers
|
|
@@ -409,7 +386,9 @@ class AgentRunRenderingManager:
|
|
|
409
386
|
elif hasattr(renderer, "buffer"):
|
|
410
387
|
buffer_values = renderer.buffer
|
|
411
388
|
|
|
412
|
-
if buffer_values
|
|
389
|
+
if isinstance(buffer_values, TranscriptBuffer):
|
|
390
|
+
rendered_text = buffer_values.render()
|
|
391
|
+
elif buffer_values is not None:
|
|
413
392
|
try:
|
|
414
393
|
rendered_text = "".join(buffer_values)
|
|
415
394
|
except TypeError:
|
|
@@ -420,7 +399,7 @@ class AgentRunRenderingManager:
|
|
|
420
399
|
self._ensure_renderer_final_content(renderer, fallback_text)
|
|
421
400
|
|
|
422
401
|
renderer.on_complete(st)
|
|
423
|
-
return final_text or rendered_text or
|
|
402
|
+
return final_text or rendered_text or NO_AGENT_RESPONSE_FALLBACK
|
|
424
403
|
|
|
425
404
|
|
|
426
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
|
+
}
|
glaip_sdk/models.py
CHANGED
|
@@ -23,21 +23,22 @@ class Agent(BaseModel):
|
|
|
23
23
|
id: str
|
|
24
24
|
name: str
|
|
25
25
|
instruction: str | None = None
|
|
26
|
-
description: str | None = None
|
|
26
|
+
description: str | None = None
|
|
27
27
|
type: str | None = None
|
|
28
28
|
framework: str | None = None
|
|
29
29
|
version: str | None = None
|
|
30
|
-
tools: list[dict[str, Any]] | None = None
|
|
31
|
-
agents: list[dict[str, Any]] | None = None
|
|
32
|
-
mcps: list[dict[str, Any]] | None = None
|
|
33
|
-
tool_configs: dict[str, Any] | None = None
|
|
30
|
+
tools: list[dict[str, Any]] | None = None
|
|
31
|
+
agents: list[dict[str, Any]] | None = None
|
|
32
|
+
mcps: list[dict[str, Any]] | None = None
|
|
33
|
+
tool_configs: dict[str, Any] | None = None
|
|
34
|
+
mcp_configs: dict[str, Any] | None = None
|
|
34
35
|
agent_config: dict[str, Any] | None = None
|
|
35
36
|
timeout: int = DEFAULT_AGENT_RUN_TIMEOUT
|
|
36
37
|
metadata: dict[str, Any] | None = None
|
|
37
38
|
language_model_id: str | None = None
|
|
38
39
|
a2a_profile: dict[str, Any] | None = None
|
|
39
|
-
created_at: datetime | None = None
|
|
40
|
-
updated_at: datetime | None = None
|
|
40
|
+
created_at: datetime | None = None
|
|
41
|
+
updated_at: datetime | None = None
|
|
41
42
|
_client: Any = None
|
|
42
43
|
|
|
43
44
|
def _set_client(self, client: Any) -> "Agent":
|
glaip_sdk/utils/display.py
CHANGED
|
@@ -4,6 +4,9 @@ Authors:
|
|
|
4
4
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from importlib import import_module
|
|
7
10
|
from typing import TYPE_CHECKING, Any
|
|
8
11
|
|
|
9
12
|
from glaip_sdk.branding import SUCCESS, SUCCESS_STYLE
|
|
@@ -13,42 +16,47 @@ if TYPE_CHECKING: # pragma: no cover - import-time typing helpers
|
|
|
13
16
|
from rich.console import Console
|
|
14
17
|
from rich.text import Text
|
|
15
18
|
|
|
16
|
-
from glaip_sdk.rich_components import
|
|
19
|
+
from glaip_sdk.rich_components import AIPanel
|
|
20
|
+
else: # pragma: no cover - runtime fallback for type checking
|
|
21
|
+
AIPanel = Any # type: ignore[assignment]
|
|
17
22
|
|
|
18
23
|
|
|
19
24
|
def _check_rich_available() -> bool:
|
|
20
|
-
"""
|
|
25
|
+
"""Return True when core Rich display dependencies are importable."""
|
|
21
26
|
try:
|
|
22
27
|
__import__("rich.console")
|
|
23
28
|
__import__("rich.text")
|
|
24
29
|
__import__("glaip_sdk.rich_components")
|
|
25
|
-
return True
|
|
26
30
|
except Exception:
|
|
27
31
|
return False
|
|
32
|
+
return True
|
|
28
33
|
|
|
29
34
|
|
|
30
35
|
RICH_AVAILABLE = _check_rich_available()
|
|
31
36
|
|
|
32
37
|
|
|
33
|
-
def _create_console() ->
|
|
38
|
+
def _create_console() -> Console:
|
|
34
39
|
"""Return a Console instance with lazy import to ease mocking."""
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
if not RICH_AVAILABLE: # pragma: no cover - defensive guard
|
|
41
|
+
raise RuntimeError("Rich Console is not available")
|
|
42
|
+
console_module = import_module("rich.console")
|
|
43
|
+
return console_module.Console()
|
|
38
44
|
|
|
39
45
|
|
|
40
|
-
def _create_text(*args: Any, **kwargs: Any) ->
|
|
46
|
+
def _create_text(*args: Any, **kwargs: Any) -> Text:
|
|
41
47
|
"""Return a Text instance with lazy import to ease mocking."""
|
|
42
|
-
|
|
48
|
+
if not RICH_AVAILABLE: # pragma: no cover - defensive guard
|
|
49
|
+
raise RuntimeError("Rich Text is not available")
|
|
50
|
+
text_module = import_module("rich.text")
|
|
51
|
+
return text_module.Text(*args, **kwargs)
|
|
43
52
|
|
|
44
|
-
return Text(*args, **kwargs)
|
|
45
53
|
|
|
46
|
-
|
|
47
|
-
def _create_panel(*args: Any, **kwargs: Any) -> "AIPPanel":
|
|
54
|
+
def _create_panel(*args: Any, **kwargs: Any) -> AIPanel:
|
|
48
55
|
"""Return an AIPPanel instance with lazy import to ease mocking."""
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
if not RICH_AVAILABLE: # pragma: no cover - defensive guard
|
|
57
|
+
raise RuntimeError("AIPPanel is not available")
|
|
58
|
+
components = import_module("glaip_sdk.rich_components")
|
|
59
|
+
return components.AIPPanel(*args, **kwargs)
|
|
52
60
|
|
|
53
61
|
|
|
54
62
|
def print_agent_output(output: str, title: str = "Agent Output") -> None:
|
|
@@ -10,7 +10,7 @@ from typing import Any
|
|
|
10
10
|
from rich.console import Console
|
|
11
11
|
|
|
12
12
|
from glaip_sdk.models.agent_runs import RunWithOutput
|
|
13
|
-
from glaip_sdk.utils.rendering.renderer.debug import render_debug_event
|
|
13
|
+
from glaip_sdk.utils.rendering.renderer.debug import render_debug_event, render_debug_event_stream
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def _parse_event_received_timestamp(event: dict[str, Any]) -> datetime | None:
|
|
@@ -80,18 +80,11 @@ def render_remote_sse_transcript(
|
|
|
80
80
|
console.print("[bold]SSE Events[/bold]")
|
|
81
81
|
console.print("[dim]────────────────────────────────────────────────────────[/dim]")
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
render_debug_event(
|
|
89
|
-
event,
|
|
90
|
-
console,
|
|
91
|
-
received_ts=received_ts,
|
|
92
|
-
baseline_ts=baseline,
|
|
93
|
-
)
|
|
94
|
-
|
|
83
|
+
render_debug_event_stream(
|
|
84
|
+
run.output,
|
|
85
|
+
console,
|
|
86
|
+
resolve_timestamp=_parse_event_received_timestamp,
|
|
87
|
+
)
|
|
95
88
|
console.print()
|
|
96
89
|
|
|
97
90
|
|
|
@@ -8,7 +8,6 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import json
|
|
10
10
|
import re
|
|
11
|
-
import time
|
|
12
11
|
from collections.abc import Callable
|
|
13
12
|
from typing import Any
|
|
14
13
|
|
|
@@ -47,11 +46,6 @@ SENSITIVE_PATTERNS = re.compile(
|
|
|
47
46
|
r"(?:password|secret|token|key|api_key)(?:\s*[:=]\s*[^\s,}]+)?",
|
|
48
47
|
re.IGNORECASE,
|
|
49
48
|
)
|
|
50
|
-
CONNECTOR_VERTICAL = "│ "
|
|
51
|
-
CONNECTOR_EMPTY = " "
|
|
52
|
-
CONNECTOR_BRANCH = "├─ "
|
|
53
|
-
CONNECTOR_LAST = "└─ "
|
|
54
|
-
ROOT_MARKER = ""
|
|
55
49
|
SECRET_MASK = "••••••"
|
|
56
50
|
STATUS_GLYPHS = {
|
|
57
51
|
"success": ICON_STATUS_SUCCESS,
|
|
@@ -140,20 +134,11 @@ def glyph_for_status(icon_key: str | None) -> str | None:
|
|
|
140
134
|
|
|
141
135
|
def normalise_display_label(label: str | None) -> str:
|
|
142
136
|
"""Return a user facing label or the Unknown fallback."""
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
"""Build connector prefix for a tree line based on ancestry state."""
|
|
149
|
-
if not branch_state:
|
|
150
|
-
return ROOT_MARKER
|
|
151
|
-
|
|
152
|
-
parts: list[str] = []
|
|
153
|
-
for ancestor_is_last in branch_state[:-1]:
|
|
154
|
-
parts.append(CONNECTOR_EMPTY if ancestor_is_last else CONNECTOR_VERTICAL)
|
|
155
|
-
parts.append(CONNECTOR_LAST if branch_state[-1] else CONNECTOR_BRANCH)
|
|
156
|
-
return "".join(parts)
|
|
137
|
+
if not isinstance(label, str):
|
|
138
|
+
text = ""
|
|
139
|
+
else:
|
|
140
|
+
text = label.strip()
|
|
141
|
+
return text or "Unknown step detail"
|
|
157
142
|
|
|
158
143
|
|
|
159
144
|
def pretty_args(args: dict | None, max_len: int = DEFAULT_ARGS_MAX_LEN) -> str:
|
|
@@ -202,16 +187,6 @@ def pretty_out(output: any, max_len: int = DEFAULT_ARGS_MAX_LEN) -> str:
|
|
|
202
187
|
return _truncate_string(output_str, max_len)
|
|
203
188
|
|
|
204
189
|
|
|
205
|
-
def get_spinner_char() -> str:
|
|
206
|
-
"""Get the next character for a spinner animation.
|
|
207
|
-
|
|
208
|
-
Returns:
|
|
209
|
-
A single character from the spinner frames based on current time
|
|
210
|
-
"""
|
|
211
|
-
frames = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
|
212
|
-
return frames[int(time.time() * 10) % len(frames)]
|
|
213
|
-
|
|
214
|
-
|
|
215
190
|
def get_step_icon(step_kind: str) -> str:
|
|
216
191
|
"""Get the appropriate icon for a step kind."""
|
|
217
192
|
if step_kind == "tool":
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Layout utilities exposed for renderer/viewer consumers.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from glaip_sdk.utils.rendering.layout.panels import (
|
|
8
|
+
create_context_panel,
|
|
9
|
+
create_final_panel,
|
|
10
|
+
create_main_panel,
|
|
11
|
+
create_tool_panel,
|
|
12
|
+
)
|
|
13
|
+
from glaip_sdk.utils.rendering.layout.progress import (
|
|
14
|
+
TrailingSpinnerLine,
|
|
15
|
+
build_progress_footer,
|
|
16
|
+
format_elapsed_time,
|
|
17
|
+
format_tool_title,
|
|
18
|
+
format_working_indicator,
|
|
19
|
+
get_spinner,
|
|
20
|
+
get_spinner_char,
|
|
21
|
+
is_delegation_tool,
|
|
22
|
+
)
|
|
23
|
+
from glaip_sdk.utils.rendering.layout.transcript import (
|
|
24
|
+
DEFAULT_TRANSCRIPT_THEME,
|
|
25
|
+
TranscriptGlyphs,
|
|
26
|
+
TranscriptRow,
|
|
27
|
+
TranscriptSnapshot,
|
|
28
|
+
build_final_panel,
|
|
29
|
+
build_transcript_snapshot,
|
|
30
|
+
build_transcript_view,
|
|
31
|
+
extract_query_from_meta,
|
|
32
|
+
format_final_panel_title,
|
|
33
|
+
render_final_panel,
|
|
34
|
+
)
|
|
35
|
+
from glaip_sdk.utils.rendering.layout.summary import render_summary_panels
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
# Panels
|
|
39
|
+
"create_context_panel",
|
|
40
|
+
"create_final_panel",
|
|
41
|
+
"create_main_panel",
|
|
42
|
+
"create_tool_panel",
|
|
43
|
+
"render_summary_panels",
|
|
44
|
+
# Progress
|
|
45
|
+
"TrailingSpinnerLine",
|
|
46
|
+
"build_progress_footer",
|
|
47
|
+
"format_elapsed_time",
|
|
48
|
+
"format_tool_title",
|
|
49
|
+
"format_working_indicator",
|
|
50
|
+
"get_spinner",
|
|
51
|
+
"get_spinner_char",
|
|
52
|
+
"is_delegation_tool",
|
|
53
|
+
# Transcript
|
|
54
|
+
"DEFAULT_TRANSCRIPT_THEME",
|
|
55
|
+
"TranscriptGlyphs",
|
|
56
|
+
"TranscriptRow",
|
|
57
|
+
"TranscriptSnapshot",
|
|
58
|
+
"build_final_panel",
|
|
59
|
+
"build_transcript_snapshot",
|
|
60
|
+
"build_transcript_view",
|
|
61
|
+
"extract_query_from_meta",
|
|
62
|
+
"format_final_panel_title",
|
|
63
|
+
"render_final_panel",
|
|
64
|
+
]
|
|
@@ -6,6 +6,7 @@ Authors:
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
from rich.align import Align
|
|
10
11
|
from rich.markdown import Markdown
|
|
11
12
|
from rich.spinner import Spinner
|
|
@@ -145,3 +146,11 @@ def create_final_panel(content: str, title: str = "Final Result", theme: str = "
|
|
|
145
146
|
border_style=SUCCESS,
|
|
146
147
|
padding=(0, 1),
|
|
147
148
|
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
__all__ = [
|
|
152
|
+
"create_main_panel",
|
|
153
|
+
"create_tool_panel",
|
|
154
|
+
"create_context_panel",
|
|
155
|
+
"create_final_panel",
|
|
156
|
+
]
|
|
@@ -7,8 +7,22 @@ Authors:
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
from time import monotonic
|
|
10
|
+
from typing import Any
|
|
10
11
|
|
|
11
|
-
from
|
|
12
|
+
from rich.console import Console as RichConsole
|
|
13
|
+
from rich.console import Group
|
|
14
|
+
from rich.measure import Measurement
|
|
15
|
+
from rich.spinner import Spinner
|
|
16
|
+
from rich.text import Text
|
|
17
|
+
|
|
18
|
+
from glaip_sdk.utils.rendering.steps.manager import StepManager
|
|
19
|
+
|
|
20
|
+
_SPINNER_FRAMES = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _spinner_time() -> float:
|
|
24
|
+
"""Return the monotonic time used for spinner animation."""
|
|
25
|
+
return monotonic()
|
|
12
26
|
|
|
13
27
|
|
|
14
28
|
def get_spinner() -> str:
|
|
@@ -16,6 +30,33 @@ def get_spinner() -> str:
|
|
|
16
30
|
return get_spinner_char()
|
|
17
31
|
|
|
18
32
|
|
|
33
|
+
def get_spinner_char() -> str:
|
|
34
|
+
"""Return the spinner frame based on elapsed time."""
|
|
35
|
+
frame_index = int(_spinner_time() * 10) % len(_SPINNER_FRAMES)
|
|
36
|
+
return _SPINNER_FRAMES[frame_index]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TrailingSpinnerLine:
|
|
40
|
+
"""Render a text line with a trailing animated Rich spinner."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, base_text: Text, spinner: Spinner) -> None:
|
|
43
|
+
"""Initialize spinner line with base text and spinner component."""
|
|
44
|
+
self._base_text = base_text
|
|
45
|
+
self._spinner = spinner
|
|
46
|
+
|
|
47
|
+
def __rich_console__(self, console: RichConsole, options: Any) -> Any: # type: ignore[override]
|
|
48
|
+
"""Render the text with trailing animated spinner."""
|
|
49
|
+
spinner_render = self._spinner.render(console.get_time())
|
|
50
|
+
combined = Text.assemble(self._base_text.copy(), " ", spinner_render)
|
|
51
|
+
yield combined
|
|
52
|
+
|
|
53
|
+
def __rich_measure__(self, console: RichConsole, options: Any) -> Measurement: # type: ignore[override]
|
|
54
|
+
"""Measure the combined text and spinner dimensions."""
|
|
55
|
+
snapshot = self._spinner.render(0)
|
|
56
|
+
combined = Text.assemble(self._base_text.copy(), " ", snapshot)
|
|
57
|
+
return Measurement.get(console, options, combined)
|
|
58
|
+
|
|
59
|
+
|
|
19
60
|
def _resolve_elapsed_time(
|
|
20
61
|
started_at: float | None,
|
|
21
62
|
server_elapsed_time: float | None,
|
|
@@ -131,3 +172,31 @@ def format_tool_title(tool_name: str) -> str:
|
|
|
131
172
|
|
|
132
173
|
# Convert snake_case to Title Case
|
|
133
174
|
return clean_name.replace("_", " ").title()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _has_running_steps(steps: StepManager) -> bool:
|
|
178
|
+
for step in steps.by_id.values():
|
|
179
|
+
if getattr(step, "status", None) not in {"finished", "failed", "stopped"}:
|
|
180
|
+
return True
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def build_progress_footer(
|
|
185
|
+
*,
|
|
186
|
+
state: Any,
|
|
187
|
+
steps: StepManager,
|
|
188
|
+
started_at: float | None,
|
|
189
|
+
server_elapsed_time: float | None,
|
|
190
|
+
) -> Group | None:
|
|
191
|
+
"""Return a trailing progress indicator when work is ongoing."""
|
|
192
|
+
if not _has_running_steps(steps):
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
indicator = format_working_indicator(
|
|
196
|
+
started_at,
|
|
197
|
+
server_elapsed_time,
|
|
198
|
+
getattr(state, "streaming_started_at", None),
|
|
199
|
+
)
|
|
200
|
+
text = Text(indicator, style="dim")
|
|
201
|
+
spinner = Spinner("dots", style="dim")
|
|
202
|
+
return Group(TrailingSpinnerLine(text, spinner))
|