glaip-sdk 0.0.20__py3-none-any.whl → 0.7.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- glaip_sdk/__init__.py +44 -4
- glaip_sdk/_version.py +10 -3
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1250 -0
- glaip_sdk/branding.py +15 -6
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +271 -45
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents/__init__.py +119 -0
- glaip_sdk/cli/commands/agents/_common.py +561 -0
- glaip_sdk/cli/commands/agents/create.py +151 -0
- glaip_sdk/cli/commands/agents/delete.py +64 -0
- glaip_sdk/cli/commands/agents/get.py +89 -0
- glaip_sdk/cli/commands/agents/list.py +129 -0
- glaip_sdk/cli/commands/agents/run.py +264 -0
- glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
- glaip_sdk/cli/commands/agents/update.py +112 -0
- glaip_sdk/cli/commands/common_config.py +104 -0
- glaip_sdk/cli/commands/configure.py +734 -143
- glaip_sdk/cli/commands/mcps/__init__.py +94 -0
- glaip_sdk/cli/commands/mcps/_common.py +459 -0
- glaip_sdk/cli/commands/mcps/connect.py +82 -0
- glaip_sdk/cli/commands/mcps/create.py +152 -0
- glaip_sdk/cli/commands/mcps/delete.py +73 -0
- glaip_sdk/cli/commands/mcps/get.py +212 -0
- glaip_sdk/cli/commands/mcps/list.py +69 -0
- glaip_sdk/cli/commands/mcps/tools.py +235 -0
- glaip_sdk/cli/commands/mcps/update.py +190 -0
- glaip_sdk/cli/commands/models.py +14 -12
- glaip_sdk/cli/commands/shared/__init__.py +21 -0
- glaip_sdk/cli/commands/shared/formatters.py +91 -0
- glaip_sdk/cli/commands/tools/__init__.py +69 -0
- glaip_sdk/cli/commands/tools/_common.py +80 -0
- glaip_sdk/cli/commands/tools/create.py +228 -0
- glaip_sdk/cli/commands/tools/delete.py +61 -0
- glaip_sdk/cli/commands/tools/get.py +103 -0
- glaip_sdk/cli/commands/tools/list.py +69 -0
- glaip_sdk/cli/commands/tools/script.py +49 -0
- glaip_sdk/cli/commands/tools/update.py +102 -0
- glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
- glaip_sdk/cli/commands/transcripts/_common.py +9 -0
- glaip_sdk/cli/commands/transcripts/clear.py +5 -0
- glaip_sdk/cli/commands/transcripts/detail.py +5 -0
- glaip_sdk/cli/commands/transcripts_original.py +756 -0
- glaip_sdk/cli/commands/update.py +164 -23
- glaip_sdk/cli/config.py +49 -7
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +8 -0
- glaip_sdk/cli/core/__init__.py +79 -0
- glaip_sdk/cli/core/context.py +124 -0
- glaip_sdk/cli/core/output.py +851 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +45 -32
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +14 -17
- glaip_sdk/cli/main.py +344 -167
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/mcp_validators.py +5 -15
- glaip_sdk/cli/pager.py +15 -22
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/parsers/json_input.py +11 -22
- glaip_sdk/cli/resolution.py +5 -10
- glaip_sdk/cli/rich_helpers.py +1 -3
- glaip_sdk/cli/slash/__init__.py +0 -9
- glaip_sdk/cli/slash/accounts_controller.py +580 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +65 -29
- glaip_sdk/cli/slash/prompt.py +24 -10
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +827 -232
- glaip_sdk/cli/slash/tui/__init__.py +34 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +88 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +933 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/clipboard.py +147 -0
- glaip_sdk/cli/slash/tui/context.py +59 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/slash/tui/terminal.py +402 -0
- glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
- glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
- glaip_sdk/cli/slash/tui/theme/manager.py +86 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +123 -0
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +258 -60
- glaip_sdk/cli/transcript/capture.py +72 -21
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/launcher.py +1 -3
- glaip_sdk/cli/transcript/viewer.py +79 -329
- glaip_sdk/cli/update_notifier.py +385 -24
- glaip_sdk/cli/validators.py +16 -18
- glaip_sdk/client/__init__.py +3 -1
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +370 -100
- glaip_sdk/client/base.py +78 -35
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +25 -10
- glaip_sdk/client/mcps.py +166 -27
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +65 -74
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +583 -79
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +214 -56
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/config/constants.py +11 -0
- glaip_sdk/exceptions.py +1 -3
- glaip_sdk/hitl/__init__.py +48 -0
- glaip_sdk/hitl/base.py +64 -0
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +121 -0
- glaip_sdk/hitl/remote.py +523 -0
- glaip_sdk/icons.py +9 -3
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +107 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +117 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/schedule.py +224 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +1 -13
- glaip_sdk/payload_schemas/agent.py +1 -3
- glaip_sdk/registry/__init__.py +55 -0
- glaip_sdk/registry/agent.py +164 -0
- glaip_sdk/registry/base.py +139 -0
- glaip_sdk/registry/mcp.py +253 -0
- glaip_sdk/registry/tool.py +445 -0
- glaip_sdk/rich_components.py +58 -2
- glaip_sdk/runner/__init__.py +76 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +112 -0
- glaip_sdk/runner/langgraph.py +872 -0
- glaip_sdk/runner/logging_config.py +77 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +242 -0
- glaip_sdk/schedules/__init__.py +22 -0
- glaip_sdk/schedules/base.py +291 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +468 -0
- glaip_sdk/utils/__init__.py +59 -12
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +4 -14
- glaip_sdk/utils/bundler.py +403 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +46 -28
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +25 -21
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +1 -36
- glaip_sdk/utils/import_export.py +15 -16
- glaip_sdk/utils/import_resolver.py +524 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -1
- glaip_sdk/utils/rendering/formatting.py +38 -23
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +10 -3
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +73 -12
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +18 -8
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -51
- glaip_sdk/utils/rendering/renderer/base.py +534 -882
- glaip_sdk/utils/rendering/renderer/config.py +4 -10
- glaip_sdk/utils/rendering/renderer/debug.py +30 -34
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +13 -54
- glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
- glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
- glaip_sdk/utils/rendering/state.py +204 -0
- glaip_sdk/utils/rendering/step_tree_state.py +100 -0
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/{steps.py → steps/manager.py} +122 -26
- glaip_sdk/utils/rendering/timing.py +36 -0
- glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
- glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
- glaip_sdk/utils/resource_refs.py +29 -26
- glaip_sdk/utils/runtime_config.py +425 -0
- glaip_sdk/utils/serialization.py +32 -46
- glaip_sdk/utils/sync.py +162 -0
- glaip_sdk/utils/tool_detection.py +301 -0
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- glaip_sdk/utils/validation.py +20 -28
- {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.7.7.dist-info}/METADATA +78 -23
- glaip_sdk-0.7.7.dist-info/RECORD +213 -0
- {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.7.7.dist-info}/WHEEL +2 -1
- glaip_sdk-0.7.7.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.7.7.dist-info/top_level.txt +1 -0
- glaip_sdk/cli/commands/agents.py +0 -1412
- glaip_sdk/cli/commands/mcps.py +0 -1225
- glaip_sdk/cli/commands/tools.py +0 -597
- glaip_sdk/cli/utils.py +0 -1330
- glaip_sdk/models.py +0 -259
- glaip_sdk-0.0.20.dist-info/RECORD +0 -80
- glaip_sdk-0.0.20.dist-info/entry_points.txt +0 -3
|
@@ -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
|
|
@@ -7,46 +7,23 @@ Authors:
|
|
|
7
7
|
import json
|
|
8
8
|
from datetime import datetime, timezone
|
|
9
9
|
from typing import Any
|
|
10
|
+
from collections.abc import Callable, Iterable
|
|
10
11
|
|
|
11
12
|
from rich.console import Console
|
|
12
13
|
from rich.markdown import Markdown
|
|
13
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
|
-
"""Attempt to coerce an arbitrary value to an aware datetime."""
|
|
20
|
-
if value is None:
|
|
21
|
-
return None
|
|
22
|
-
|
|
23
|
-
if isinstance(value, datetime):
|
|
24
|
-
return value if value.tzinfo else value.replace(tzinfo=timezone.utc)
|
|
25
|
-
|
|
26
|
-
if isinstance(value, str):
|
|
27
|
-
try:
|
|
28
|
-
normalised = value.replace("Z", "+00:00")
|
|
29
|
-
dt = datetime.fromisoformat(normalised)
|
|
30
|
-
except ValueError:
|
|
31
|
-
return None
|
|
32
|
-
return dt if dt.tzinfo else dt.replace(tzinfo=timezone.utc)
|
|
33
|
-
|
|
34
|
-
return None
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _parse_event_timestamp(
|
|
38
|
-
event: dict[str, Any], received_ts: datetime | None = None
|
|
39
|
-
) -> datetime | None:
|
|
20
|
+
def _parse_event_timestamp(event: dict[str, Any], received_ts: datetime | None = None) -> datetime | None:
|
|
40
21
|
"""Resolve the most accurate timestamp available for the event."""
|
|
41
22
|
if received_ts is not None:
|
|
42
|
-
return (
|
|
43
|
-
received_ts
|
|
44
|
-
if received_ts.tzinfo
|
|
45
|
-
else received_ts.replace(tzinfo=timezone.utc)
|
|
46
|
-
)
|
|
23
|
+
return received_ts if received_ts.tzinfo else received_ts.replace(tzinfo=timezone.utc)
|
|
47
24
|
|
|
48
25
|
ts_value = event.get("timestamp") or (event.get("metadata") or {}).get("timestamp")
|
|
49
|
-
return
|
|
26
|
+
return coerce_datetime(ts_value)
|
|
50
27
|
|
|
51
28
|
|
|
52
29
|
def _format_timestamp_for_display(dt: datetime) -> str:
|
|
@@ -86,9 +63,7 @@ def _get_event_metadata(event: dict[str, Any]) -> tuple[str, str | None]:
|
|
|
86
63
|
return sse_kind, status_str
|
|
87
64
|
|
|
88
65
|
|
|
89
|
-
def _build_debug_title(
|
|
90
|
-
sse_kind: str, status_str: str | None, ts_ms: str, rel: float
|
|
91
|
-
) -> str:
|
|
66
|
+
def _build_debug_title(sse_kind: str, status_str: str | None, ts_ms: str, rel: float) -> str:
|
|
92
67
|
"""Build the debug event title."""
|
|
93
68
|
if status_str:
|
|
94
69
|
return f"SSE: {sse_kind} — {status_str} @ {ts_ms} (+{rel:.2f}s)"
|
|
@@ -104,9 +79,7 @@ def _dejson_value(obj: Any) -> Any:
|
|
|
104
79
|
return [_dejson_value(x) for x in obj]
|
|
105
80
|
if isinstance(obj, str):
|
|
106
81
|
s = obj.strip()
|
|
107
|
-
if (s.startswith("{") and s.endswith("}")) or (
|
|
108
|
-
s.startswith("[") and s.endswith("]")
|
|
109
|
-
):
|
|
82
|
+
if (s.startswith("{") and s.endswith("}")) or (s.startswith("[") and s.endswith("]")):
|
|
110
83
|
try:
|
|
111
84
|
return _dejson_value(json.loads(s))
|
|
112
85
|
except Exception:
|
|
@@ -180,3 +153,26 @@ def render_debug_event(
|
|
|
180
153
|
except Exception as e:
|
|
181
154
|
# Debug helpers must not break streaming
|
|
182
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
|