glaip-sdk 0.0.7__py3-none-any.whl → 0.6.5b6__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 +6 -3
- glaip_sdk/_version.py +12 -5
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1126 -0
- glaip_sdk/branding.py +79 -15
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +699 -0
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents.py +503 -183
- glaip_sdk/cli/commands/common_config.py +101 -0
- glaip_sdk/cli/commands/configure.py +774 -137
- glaip_sdk/cli/commands/mcps.py +1124 -181
- glaip_sdk/cli/commands/models.py +25 -10
- glaip_sdk/cli/commands/tools.py +144 -92
- glaip_sdk/cli/commands/transcripts.py +755 -0
- glaip_sdk/cli/commands/update.py +61 -0
- glaip_sdk/cli/config.py +95 -0
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +150 -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 +143 -53
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +24 -18
- glaip_sdk/cli/main.py +420 -145
- glaip_sdk/cli/masking.py +136 -0
- glaip_sdk/cli/mcp_validators.py +287 -0
- glaip_sdk/cli/pager.py +266 -0
- glaip_sdk/cli/parsers/__init__.py +7 -0
- glaip_sdk/cli/parsers/json_input.py +177 -0
- glaip_sdk/cli/resolution.py +28 -21
- glaip_sdk/cli/rich_helpers.py +27 -0
- glaip_sdk/cli/slash/__init__.py +15 -0
- glaip_sdk/cli/slash/accounts_controller.py +500 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +282 -0
- glaip_sdk/cli/slash/prompt.py +245 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +1679 -0
- 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 +872 -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 +31 -0
- glaip_sdk/cli/transcript/cache.py +536 -0
- glaip_sdk/cli/transcript/capture.py +329 -0
- glaip_sdk/cli/transcript/export.py +38 -0
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/launcher.py +77 -0
- glaip_sdk/cli/transcript/viewer.py +372 -0
- glaip_sdk/cli/update_notifier.py +290 -0
- glaip_sdk/cli/utils.py +247 -1238
- glaip_sdk/cli/validators.py +16 -18
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_agent_payloads.py +520 -0
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +940 -574
- glaip_sdk/client/base.py +163 -48
- glaip_sdk/client/main.py +35 -12
- glaip_sdk/client/mcps.py +126 -18
- glaip_sdk/client/run_rendering.py +415 -0
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +195 -37
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/config/constants.py +15 -5
- glaip_sdk/exceptions.py +16 -9
- glaip_sdk/icons.py +25 -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 +7 -0
- glaip_sdk/payload_schemas/agent.py +85 -0
- 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 +231 -0
- glaip_sdk/rich_components.py +98 -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 +597 -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 +158 -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 +177 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +435 -0
- glaip_sdk/utils/__init__.py +59 -13
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +53 -40
- glaip_sdk/utils/bundler.py +267 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +58 -26
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +65 -32
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +1 -36
- glaip_sdk/utils/import_export.py +20 -25
- 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 +85 -43
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +51 -19
- glaip_sdk/utils/rendering/layout/progress.py +202 -0
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +39 -7
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -51
- glaip_sdk/utils/rendering/renderer/base.py +672 -759
- glaip_sdk/utils/rendering/renderer/config.py +4 -10
- glaip_sdk/utils/rendering/renderer/debug.py +75 -22
- 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/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 +29 -26
- glaip_sdk/utils/runtime_config.py +422 -0
- glaip_sdk/utils/serialization.py +184 -51
- glaip_sdk/utils/sync.py +142 -0
- glaip_sdk/utils/tool_detection.py +33 -0
- glaip_sdk/utils/validation.py +21 -30
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/METADATA +58 -12
- glaip_sdk-0.6.5b6.dist-info/RECORD +159 -0
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/WHEEL +1 -1
- glaip_sdk/models.py +0 -250
- glaip_sdk/utils/rendering/renderer/progress.py +0 -118
- glaip_sdk/utils/rendering/steps.py +0 -232
- glaip_sdk/utils/rich_utils.py +0 -29
- glaip_sdk-0.0.7.dist-info/RECORD +0 -55
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/entry_points.txt +0 -0
|
@@ -13,21 +13,15 @@ from dataclasses import dataclass
|
|
|
13
13
|
class RendererConfig:
|
|
14
14
|
"""Configuration for the RichStreamRenderer."""
|
|
15
15
|
|
|
16
|
-
# Style and layout
|
|
17
|
-
theme: str = "dark" # dark|light
|
|
18
|
-
style: str = "pretty" # pretty|debug|minimal
|
|
19
|
-
|
|
20
16
|
# Performance
|
|
21
|
-
think_threshold: float = 0.7
|
|
22
17
|
refresh_debounce: float = 0.25
|
|
23
18
|
render_thinking: bool = True
|
|
24
19
|
live: bool = True
|
|
25
20
|
persist_live: bool = True
|
|
26
|
-
|
|
27
|
-
# Debug visibility toggles
|
|
28
|
-
show_delegate_tool_panels: bool = False
|
|
21
|
+
summary_display_window: int = 20
|
|
29
22
|
|
|
30
23
|
# Scrollback/append options
|
|
24
|
+
summary_max_steps: int = 0
|
|
31
25
|
append_finished_snapshots: bool = False
|
|
32
|
-
snapshot_max_chars: int =
|
|
33
|
-
snapshot_max_lines: int =
|
|
26
|
+
snapshot_max_chars: int = 0
|
|
27
|
+
snapshot_max_lines: int = 0
|
|
@@ -5,25 +5,53 @@ Authors:
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
|
-
from datetime import datetime
|
|
9
|
-
from time import monotonic
|
|
8
|
+
from datetime import datetime, timezone
|
|
10
9
|
from typing import Any
|
|
10
|
+
from collections.abc import Callable, Iterable
|
|
11
11
|
|
|
12
12
|
from rich.console import Console
|
|
13
13
|
from rich.markdown import Markdown
|
|
14
14
|
|
|
15
|
+
from glaip_sdk.branding import PRIMARY, SUCCESS, WARNING
|
|
15
16
|
from glaip_sdk.rich_components import AIPPanel
|
|
17
|
+
from glaip_sdk.utils.datetime_helpers import coerce_datetime
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
def
|
|
19
|
-
"""
|
|
20
|
-
|
|
20
|
+
def _parse_event_timestamp(event: dict[str, Any], received_ts: datetime | None = None) -> datetime | None:
|
|
21
|
+
"""Resolve the most accurate timestamp available for the event."""
|
|
22
|
+
if received_ts is not None:
|
|
23
|
+
return received_ts if received_ts.tzinfo else received_ts.replace(tzinfo=timezone.utc)
|
|
24
|
+
|
|
25
|
+
ts_value = event.get("timestamp") or (event.get("metadata") or {}).get("timestamp")
|
|
26
|
+
return coerce_datetime(ts_value)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _format_timestamp_for_display(dt: datetime) -> str:
|
|
30
|
+
"""Format timestamp for panel title, including timezone offset."""
|
|
31
|
+
local_dt = dt.astimezone()
|
|
32
|
+
ts_ms = local_dt.strftime("%H:%M:%S.%f")[:-3]
|
|
33
|
+
offset = local_dt.strftime("%z")
|
|
34
|
+
# offset is always non-empty for timezone-aware datetimes
|
|
35
|
+
offset = f"{offset[:3]}:{offset[3:]}"
|
|
36
|
+
return f"{ts_ms} {offset}"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _calculate_relative_time(
|
|
40
|
+
event_ts: datetime | None,
|
|
41
|
+
baseline_ts: datetime | None,
|
|
42
|
+
) -> tuple[float, str]:
|
|
43
|
+
"""Calculate relative time since start and format event timestamp."""
|
|
21
44
|
rel = 0.0
|
|
22
|
-
if started_ts is not None:
|
|
23
|
-
rel = max(0.0, now_mono - started_ts)
|
|
24
45
|
|
|
25
|
-
|
|
26
|
-
|
|
46
|
+
# Determine display timestamp - use event timestamp when present, otherwise current time
|
|
47
|
+
display_ts: datetime | None = event_ts
|
|
48
|
+
if display_ts is None:
|
|
49
|
+
display_ts = datetime.now(timezone.utc)
|
|
50
|
+
|
|
51
|
+
if event_ts is not None and baseline_ts is not None:
|
|
52
|
+
rel = max(0.0, (event_ts - baseline_ts).total_seconds())
|
|
53
|
+
|
|
54
|
+
ts_ms = _format_timestamp_for_display(display_ts)
|
|
27
55
|
|
|
28
56
|
return rel, ts_ms
|
|
29
57
|
|
|
@@ -35,9 +63,7 @@ def _get_event_metadata(event: dict[str, Any]) -> tuple[str, str | None]:
|
|
|
35
63
|
return sse_kind, status_str
|
|
36
64
|
|
|
37
65
|
|
|
38
|
-
def _build_debug_title(
|
|
39
|
-
sse_kind: str, status_str: str | None, ts_ms: str, rel: float
|
|
40
|
-
) -> str:
|
|
66
|
+
def _build_debug_title(sse_kind: str, status_str: str | None, ts_ms: str, rel: float) -> str:
|
|
41
67
|
"""Build the debug event title."""
|
|
42
68
|
if status_str:
|
|
43
69
|
return f"SSE: {sse_kind} — {status_str} @ {ts_ms} (+{rel:.2f}s)"
|
|
@@ -53,9 +79,7 @@ def _dejson_value(obj: Any) -> Any:
|
|
|
53
79
|
return [_dejson_value(x) for x in obj]
|
|
54
80
|
if isinstance(obj, str):
|
|
55
81
|
s = obj.strip()
|
|
56
|
-
if (s.startswith("{") and s.endswith("}")) or (
|
|
57
|
-
s.startswith("[") and s.endswith("]")
|
|
58
|
-
):
|
|
82
|
+
if (s.startswith("{") and s.endswith("}")) or (s.startswith("[") and s.endswith("]")):
|
|
59
83
|
try:
|
|
60
84
|
return _dejson_value(json.loads(s))
|
|
61
85
|
except Exception:
|
|
@@ -75,10 +99,10 @@ def _format_event_json(event: dict[str, Any]) -> str:
|
|
|
75
99
|
def _get_border_color(sse_kind: str) -> str:
|
|
76
100
|
"""Get border color for event type."""
|
|
77
101
|
border_map = {
|
|
78
|
-
"agent_step":
|
|
79
|
-
"content":
|
|
80
|
-
"final_response":
|
|
81
|
-
"status":
|
|
102
|
+
"agent_step": PRIMARY,
|
|
103
|
+
"content": SUCCESS,
|
|
104
|
+
"final_response": SUCCESS,
|
|
105
|
+
"status": WARNING,
|
|
82
106
|
"artifact": "grey42",
|
|
83
107
|
}
|
|
84
108
|
return border_map.get(sse_kind, "grey42")
|
|
@@ -91,18 +115,24 @@ def _create_debug_panel(title: str, event_json: str, border: str) -> AIPPanel:
|
|
|
91
115
|
|
|
92
116
|
|
|
93
117
|
def render_debug_event(
|
|
94
|
-
event: dict[str, Any],
|
|
118
|
+
event: dict[str, Any],
|
|
119
|
+
console: Console,
|
|
120
|
+
*,
|
|
121
|
+
received_ts: datetime | None = None,
|
|
122
|
+
baseline_ts: datetime | None = None,
|
|
95
123
|
) -> None:
|
|
96
124
|
"""Render a debug panel for an SSE event.
|
|
97
125
|
|
|
98
126
|
Args:
|
|
99
127
|
event: The SSE event data
|
|
100
128
|
console: Rich console to print to
|
|
101
|
-
|
|
129
|
+
received_ts: Client-side receipt timestamp, if available
|
|
130
|
+
baseline_ts: Baseline event timestamp for elapsed timing
|
|
102
131
|
"""
|
|
103
132
|
try:
|
|
104
133
|
# Calculate timing information
|
|
105
|
-
|
|
134
|
+
event_ts = _parse_event_timestamp(event, received_ts)
|
|
135
|
+
rel, ts_ms = _calculate_relative_time(event_ts, baseline_ts)
|
|
106
136
|
|
|
107
137
|
# Extract event metadata
|
|
108
138
|
sse_kind, status_str = _get_event_metadata(event)
|
|
@@ -123,3 +153,26 @@ def render_debug_event(
|
|
|
123
153
|
except Exception as e:
|
|
124
154
|
# Debug helpers must not break streaming
|
|
125
155
|
print(f"Debug error: {e}") # Fallback debug output
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def render_debug_event_stream(
|
|
159
|
+
events: Iterable[dict[str, Any]],
|
|
160
|
+
console: Console,
|
|
161
|
+
*,
|
|
162
|
+
resolve_timestamp: Callable[[dict[str, Any]], datetime | None],
|
|
163
|
+
) -> None:
|
|
164
|
+
"""Render a sequence of SSE events with baseline-aware timestamps."""
|
|
165
|
+
baseline: datetime | None = None
|
|
166
|
+
for event in events:
|
|
167
|
+
try:
|
|
168
|
+
received_ts = resolve_timestamp(event)
|
|
169
|
+
if baseline is None and received_ts is not None:
|
|
170
|
+
baseline = received_ts
|
|
171
|
+
render_debug_event(
|
|
172
|
+
event,
|
|
173
|
+
console,
|
|
174
|
+
received_ts=received_ts,
|
|
175
|
+
baseline_ts=baseline,
|
|
176
|
+
)
|
|
177
|
+
except Exception as exc: # pragma: no cover - debug stream resilience
|
|
178
|
+
console.print(f"[red]Debug stream error: {exc}[/red]")
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Renderer factory helpers for CLI, SDK, and slash sessions.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import io
|
|
10
|
+
from dataclasses import dataclass, is_dataclass, replace
|
|
11
|
+
from inspect import signature
|
|
12
|
+
from typing import Any
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
|
|
17
|
+
from glaip_sdk.utils.rendering.renderer.base import RichStreamRenderer
|
|
18
|
+
from glaip_sdk.utils.rendering.renderer.config import RendererConfig
|
|
19
|
+
from glaip_sdk.utils.rendering.state import TranscriptBuffer
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(slots=True)
|
|
23
|
+
class RendererFactoryOptions:
|
|
24
|
+
"""Shared options for renderer factories."""
|
|
25
|
+
|
|
26
|
+
console: Console | None = None
|
|
27
|
+
cfg_overrides: dict[str, Any] | None = None
|
|
28
|
+
verbose: bool | None = None
|
|
29
|
+
transcript_buffer: TranscriptBuffer | None = None
|
|
30
|
+
callbacks: dict[str, Any] | None = None
|
|
31
|
+
|
|
32
|
+
def build(self, factory: Callable[..., RichStreamRenderer]) -> RichStreamRenderer:
|
|
33
|
+
"""Instantiate a renderer using the provided factory and stored options."""
|
|
34
|
+
params = signature(factory).parameters
|
|
35
|
+
kwargs: dict[str, Any] = {}
|
|
36
|
+
if self.console is not None and "console" in params:
|
|
37
|
+
kwargs["console"] = self.console
|
|
38
|
+
if self.cfg_overrides is not None and "cfg_overrides" in params:
|
|
39
|
+
kwargs["cfg_overrides"] = self.cfg_overrides
|
|
40
|
+
if self.verbose is not None and "verbose" in params:
|
|
41
|
+
kwargs["verbose"] = self.verbose
|
|
42
|
+
if self.transcript_buffer is not None and "transcript_buffer" in params:
|
|
43
|
+
kwargs["transcript_buffer"] = self.transcript_buffer
|
|
44
|
+
if self.callbacks is not None and "callbacks" in params:
|
|
45
|
+
kwargs["callbacks"] = self.callbacks
|
|
46
|
+
return factory(**kwargs)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _build_config(base: RendererConfig, overrides: dict[str, Any] | None = None) -> RendererConfig:
|
|
50
|
+
cfg = replace(base) if is_dataclass(base) else base
|
|
51
|
+
if overrides:
|
|
52
|
+
for key, value in overrides.items():
|
|
53
|
+
if hasattr(cfg, key):
|
|
54
|
+
setattr(cfg, key, value)
|
|
55
|
+
return cfg
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def make_default_renderer(
|
|
59
|
+
*,
|
|
60
|
+
console: Console | None = None,
|
|
61
|
+
cfg_overrides: dict[str, Any] | None = None,
|
|
62
|
+
verbose: bool = False,
|
|
63
|
+
transcript_buffer: TranscriptBuffer | None = None,
|
|
64
|
+
callbacks: dict[str, Any] | None = None,
|
|
65
|
+
) -> RichStreamRenderer:
|
|
66
|
+
"""Create the default renderer used by SDK and CLI flows."""
|
|
67
|
+
cfg = _build_config(RendererConfig(), cfg_overrides)
|
|
68
|
+
return RichStreamRenderer(
|
|
69
|
+
console=console or Console(),
|
|
70
|
+
cfg=cfg,
|
|
71
|
+
verbose=verbose,
|
|
72
|
+
transcript_buffer=transcript_buffer,
|
|
73
|
+
callbacks=callbacks,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def make_verbose_renderer(
|
|
78
|
+
*,
|
|
79
|
+
console: Console | None = None,
|
|
80
|
+
cfg_overrides: dict[str, Any] | None = None,
|
|
81
|
+
transcript_buffer: TranscriptBuffer | None = None,
|
|
82
|
+
callbacks: dict[str, Any] | None = None,
|
|
83
|
+
) -> RichStreamRenderer:
|
|
84
|
+
"""Create a verbose renderer with snapshot appending disabled."""
|
|
85
|
+
verbose_cfg = RendererConfig(live=True, append_finished_snapshots=False)
|
|
86
|
+
cfg = _build_config(verbose_cfg, cfg_overrides)
|
|
87
|
+
return RichStreamRenderer(
|
|
88
|
+
console=console or Console(),
|
|
89
|
+
cfg=cfg,
|
|
90
|
+
verbose=True,
|
|
91
|
+
transcript_buffer=transcript_buffer,
|
|
92
|
+
callbacks=callbacks,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def make_minimal_renderer(
|
|
97
|
+
*,
|
|
98
|
+
console: Console | None = None,
|
|
99
|
+
cfg_overrides: dict[str, Any] | None = None,
|
|
100
|
+
transcript_buffer: TranscriptBuffer | None = None,
|
|
101
|
+
callbacks: dict[str, Any] | None = None,
|
|
102
|
+
) -> RichStreamRenderer:
|
|
103
|
+
"""Create a renderer that prints only essential output."""
|
|
104
|
+
minimal_cfg = RendererConfig(live=False, persist_live=False, render_thinking=False)
|
|
105
|
+
cfg = _build_config(minimal_cfg, cfg_overrides)
|
|
106
|
+
return RichStreamRenderer(
|
|
107
|
+
console=console or Console(),
|
|
108
|
+
cfg=cfg,
|
|
109
|
+
verbose=False,
|
|
110
|
+
transcript_buffer=transcript_buffer,
|
|
111
|
+
callbacks=callbacks,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def make_silent_renderer(
|
|
116
|
+
*,
|
|
117
|
+
console: Console | None = None,
|
|
118
|
+
cfg_overrides: dict[str, Any] | None = None,
|
|
119
|
+
transcript_buffer: TranscriptBuffer | None = None,
|
|
120
|
+
callbacks: dict[str, Any] | None = None,
|
|
121
|
+
) -> RichStreamRenderer:
|
|
122
|
+
"""Create a renderer that suppresses terminal output for background flows."""
|
|
123
|
+
cfg = _build_config(
|
|
124
|
+
RendererConfig(
|
|
125
|
+
live=False,
|
|
126
|
+
persist_live=False,
|
|
127
|
+
render_thinking=False,
|
|
128
|
+
),
|
|
129
|
+
cfg_overrides,
|
|
130
|
+
)
|
|
131
|
+
silent_console = console or Console(file=io.StringIO(), force_terminal=False)
|
|
132
|
+
return RichStreamRenderer(
|
|
133
|
+
console=silent_console,
|
|
134
|
+
cfg=cfg,
|
|
135
|
+
verbose=False,
|
|
136
|
+
transcript_buffer=transcript_buffer,
|
|
137
|
+
callbacks=callbacks,
|
|
138
|
+
)
|
|
@@ -38,27 +38,25 @@ class StreamProcessor:
|
|
|
38
38
|
Returns:
|
|
39
39
|
Dictionary with extracted metadata
|
|
40
40
|
"""
|
|
41
|
-
metadata = event.get("metadata"
|
|
41
|
+
metadata = event.get("metadata") or {}
|
|
42
42
|
# Update server elapsed timing if backend provides it
|
|
43
43
|
try:
|
|
44
44
|
t = metadata.get("time")
|
|
45
|
-
if isinstance(t, int
|
|
45
|
+
if isinstance(t, (int, float)):
|
|
46
46
|
self.server_elapsed_time = float(t)
|
|
47
47
|
except Exception:
|
|
48
48
|
pass
|
|
49
49
|
|
|
50
50
|
return {
|
|
51
51
|
"kind": metadata.get("kind") if metadata else event.get("kind"),
|
|
52
|
-
"task_id": event.get("task_id"),
|
|
53
|
-
"context_id": event.get("context_id"),
|
|
52
|
+
"task_id": metadata.get("task_id") or event.get("task_id"),
|
|
53
|
+
"context_id": metadata.get("context_id") or event.get("context_id"),
|
|
54
54
|
"content": event.get("content", ""),
|
|
55
55
|
"status": metadata.get("status") if metadata else event.get("status"),
|
|
56
56
|
"metadata": metadata,
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
def _extract_metadata_tool_calls(
|
|
60
|
-
self, metadata: dict[str, Any]
|
|
61
|
-
) -> tuple[str | None, dict, Any, list]:
|
|
59
|
+
def _extract_metadata_tool_calls(self, metadata: dict[str, Any]) -> tuple[str | None, dict, Any, list]:
|
|
62
60
|
"""Extract tool calls from metadata."""
|
|
63
61
|
tool_calls = metadata.get("tool_calls", [])
|
|
64
62
|
if not tool_calls:
|
|
@@ -84,9 +82,7 @@ class StreamProcessor:
|
|
|
84
82
|
|
|
85
83
|
return tool_name, tool_args, tool_out, tool_calls_info
|
|
86
84
|
|
|
87
|
-
def _extract_tool_info_calls(
|
|
88
|
-
self, tool_info: dict[str, Any]
|
|
89
|
-
) -> tuple[str | None, dict, Any, list]:
|
|
85
|
+
def _extract_tool_info_calls(self, tool_info: dict[str, Any]) -> tuple[str | None, dict, Any, list]:
|
|
90
86
|
"""Extract tool calls from tool_info structure."""
|
|
91
87
|
tool_calls_info = []
|
|
92
88
|
tool_name = None
|
|
@@ -98,9 +94,7 @@ class StreamProcessor:
|
|
|
98
94
|
if isinstance(ti_calls, list) and ti_calls:
|
|
99
95
|
for call in ti_calls:
|
|
100
96
|
if isinstance(call, dict) and call.get("name"):
|
|
101
|
-
tool_calls_info.append(
|
|
102
|
-
(call.get("name"), call.get("args", {}), call.get("output"))
|
|
103
|
-
)
|
|
97
|
+
tool_calls_info.append((call.get("name"), call.get("args", {}), call.get("output")))
|
|
104
98
|
if tool_calls_info:
|
|
105
99
|
tool_name, tool_args, tool_out = tool_calls_info[0]
|
|
106
100
|
return tool_name, tool_args, tool_out, tool_calls_info
|
|
@@ -114,9 +108,7 @@ class StreamProcessor:
|
|
|
114
108
|
|
|
115
109
|
return tool_name, tool_args, tool_out, tool_calls_info
|
|
116
110
|
|
|
117
|
-
def _extract_tool_calls_from_metadata(
|
|
118
|
-
self, metadata: dict[str, Any]
|
|
119
|
-
) -> tuple[str | None, dict, Any, list]:
|
|
111
|
+
def _extract_tool_calls_from_metadata(self, metadata: dict[str, Any]) -> tuple[str | None, dict, Any, list]:
|
|
120
112
|
"""Extract tool calls from metadata structure."""
|
|
121
113
|
tool_info = metadata.get("tool_info", {}) or {}
|
|
122
114
|
|
|
@@ -125,9 +117,7 @@ class StreamProcessor:
|
|
|
125
117
|
|
|
126
118
|
return None, {}, None, []
|
|
127
119
|
|
|
128
|
-
def parse_tool_calls(
|
|
129
|
-
self, event: dict[str, Any]
|
|
130
|
-
) -> tuple[str | None, Any, Any, list[tuple[str, Any, Any]]]:
|
|
120
|
+
def parse_tool_calls(self, event: dict[str, Any]) -> tuple[str | None, Any, Any, list[tuple[str, Any, Any]]]:
|
|
131
121
|
"""Parse tool call information from an event.
|
|
132
122
|
|
|
133
123
|
Args:
|
|
@@ -139,21 +129,13 @@ class StreamProcessor:
|
|
|
139
129
|
metadata = event.get("metadata", {})
|
|
140
130
|
|
|
141
131
|
# Try primary extraction method
|
|
142
|
-
(
|
|
143
|
-
|
|
144
|
-
tool_args,
|
|
145
|
-
tool_out,
|
|
146
|
-
tool_calls_info,
|
|
147
|
-
) = self._extract_metadata_tool_calls(metadata)
|
|
132
|
+
tool_calls_result = self._extract_metadata_tool_calls(metadata)
|
|
133
|
+
tool_name, tool_args, tool_out, tool_calls_info = tool_calls_result
|
|
148
134
|
|
|
149
135
|
# Fallback to nested metadata.tool_info (newer schema)
|
|
150
136
|
if not tool_calls_info:
|
|
151
|
-
(
|
|
152
|
-
|
|
153
|
-
tool_args,
|
|
154
|
-
tool_out,
|
|
155
|
-
tool_calls_info,
|
|
156
|
-
) = self._extract_tool_calls_from_metadata(metadata)
|
|
137
|
+
fallback_result = self._extract_tool_calls_from_metadata(metadata)
|
|
138
|
+
tool_name, tool_args, tool_out, tool_calls_info = fallback_result
|
|
157
139
|
|
|
158
140
|
return tool_name, tool_args, tool_out, tool_calls_info
|
|
159
141
|
|
|
@@ -166,29 +148,6 @@ class StreamProcessor:
|
|
|
166
148
|
if context_id:
|
|
167
149
|
self.last_event_time_by_ctx[context_id] = monotonic()
|
|
168
150
|
|
|
169
|
-
def should_insert_thinking_gap(
|
|
170
|
-
self, task_id: str | None, context_id: str | None, think_threshold: float
|
|
171
|
-
) -> bool:
|
|
172
|
-
"""Determine if a thinking gap should be inserted.
|
|
173
|
-
|
|
174
|
-
Args:
|
|
175
|
-
task_id: Task identifier
|
|
176
|
-
context_id: Context identifier
|
|
177
|
-
think_threshold: Threshold for thinking gap
|
|
178
|
-
|
|
179
|
-
Returns:
|
|
180
|
-
True if thinking gap should be inserted
|
|
181
|
-
"""
|
|
182
|
-
if not task_id or not context_id:
|
|
183
|
-
return False
|
|
184
|
-
|
|
185
|
-
last_time = self.last_event_time_by_ctx.get(context_id)
|
|
186
|
-
if last_time is None:
|
|
187
|
-
return True
|
|
188
|
-
|
|
189
|
-
elapsed = monotonic() - last_time
|
|
190
|
-
return elapsed >= think_threshold
|
|
191
|
-
|
|
192
151
|
def track_tools_and_agents(
|
|
193
152
|
self,
|
|
194
153
|
tool_name: str | None,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Helpers for clamping the steps summary view to a rolling window.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
|
|
11
|
+
from rich.text import Text
|
|
12
|
+
|
|
13
|
+
Node = tuple[str, tuple[bool, ...]]
|
|
14
|
+
LabelFn = Callable[[str], str]
|
|
15
|
+
ParentFn = Callable[[str], str | None]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def clamp_step_nodes(
|
|
19
|
+
nodes: list[Node],
|
|
20
|
+
*,
|
|
21
|
+
window: int,
|
|
22
|
+
get_label: LabelFn,
|
|
23
|
+
get_parent: ParentFn,
|
|
24
|
+
) -> tuple[list[Node], Text | None, Text | None]:
|
|
25
|
+
"""Return a windowed slice of nodes plus optional header/footer notices."""
|
|
26
|
+
if window <= 0 or len(nodes) <= window:
|
|
27
|
+
return nodes, None, None
|
|
28
|
+
|
|
29
|
+
start_index = len(nodes) - window
|
|
30
|
+
first_visible_step_id = nodes[start_index][0]
|
|
31
|
+
header = _build_header(first_visible_step_id, window, len(nodes), get_label, get_parent)
|
|
32
|
+
footer = _build_footer(len(nodes) - window)
|
|
33
|
+
return nodes[start_index:], header, footer
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _build_header(
|
|
37
|
+
step_id: str,
|
|
38
|
+
window: int,
|
|
39
|
+
total: int,
|
|
40
|
+
get_label: LabelFn,
|
|
41
|
+
get_parent: ParentFn,
|
|
42
|
+
) -> Text:
|
|
43
|
+
"""Construct the leading notice for a truncated window."""
|
|
44
|
+
parts = [f"… (latest {window} of {total} steps shown"]
|
|
45
|
+
path = _collect_path_labels(step_id, get_label, get_parent)
|
|
46
|
+
if path:
|
|
47
|
+
parts.append("; continuing with ")
|
|
48
|
+
parts.append(" / ".join(path))
|
|
49
|
+
parts.append(")")
|
|
50
|
+
return Text("".join(parts), style="dim")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _build_footer(hidden_count: int) -> Text:
|
|
54
|
+
"""Construct the footer notice indicating hidden steps."""
|
|
55
|
+
noun = "step" if hidden_count == 1 else "steps"
|
|
56
|
+
message = f"{hidden_count} earlier {noun} hidden. Press Ctrl+T to inspect the full transcript."
|
|
57
|
+
return Text(message, style="dim")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _collect_path_labels(
|
|
61
|
+
step_id: str,
|
|
62
|
+
get_label: LabelFn,
|
|
63
|
+
get_parent: ParentFn,
|
|
64
|
+
) -> list[str]:
|
|
65
|
+
"""Collect labels for the ancestry of the provided step."""
|
|
66
|
+
labels: list[str] = []
|
|
67
|
+
seen: set[str] = set()
|
|
68
|
+
current = step_id
|
|
69
|
+
while current and current not in seen:
|
|
70
|
+
seen.add(current)
|
|
71
|
+
label = get_label(current)
|
|
72
|
+
if label:
|
|
73
|
+
labels.append(label)
|
|
74
|
+
parent = get_parent(current)
|
|
75
|
+
if not parent:
|
|
76
|
+
break
|
|
77
|
+
current = parent
|
|
78
|
+
labels.reverse()
|
|
79
|
+
return labels
|