glaip-sdk 0.1.2__py3-none-any.whl → 0.1.4__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.
@@ -994,7 +994,7 @@ def _handle_command_exception(ctx: Any, e: Exception) -> None:
994
994
  handle_json_output(ctx, error=e)
995
995
  if get_ctx_value(ctx, "view") != "json":
996
996
  print_api_error(e)
997
- raise click.ClickException(str(e)) from e
997
+ raise click.exceptions.Exit(1) from e
998
998
 
999
999
 
1000
1000
  def _handle_creation_exception(ctx: Any, e: Exception) -> None:
@@ -11,7 +11,6 @@ from rich.console import Console
11
11
  from rich.text import Text
12
12
 
13
13
  from glaip_sdk import Client
14
- from glaip_sdk._version import __version__ as _SDK_VERSION
15
14
  from glaip_sdk.branding import (
16
15
  ACCENT_STYLE,
17
16
  ERROR_STYLE,
@@ -24,7 +23,7 @@ from glaip_sdk.branding import (
24
23
  )
25
24
  from glaip_sdk.cli.config import CONFIG_FILE, load_config, save_config
26
25
  from glaip_sdk.cli.rich_helpers import markup_text
27
- from glaip_sdk.cli.utils import command_hint, format_command_hint
26
+ from glaip_sdk.cli.utils import command_hint, format_command_hint, sdk_version
28
27
  from glaip_sdk.icons import ICON_TOOL
29
28
  from glaip_sdk.rich_components import AIPTable
30
29
 
@@ -196,7 +195,7 @@ def _render_config_table(config: dict[str, str]) -> None:
196
195
 
197
196
 
198
197
  def _render_configuration_header() -> None:
199
- branding = AIPBranding.create_from_sdk(sdk_version=_SDK_VERSION, package_name="glaip-sdk")
198
+ branding = AIPBranding.create_from_sdk(sdk_version=sdk_version(), package_name="glaip-sdk")
200
199
  heading = "[bold]>_ GDP Labs AI Agents Package (AIP CLI)[/bold]"
201
200
  console.print(heading)
202
201
  console.print()
glaip_sdk/cli/display.py CHANGED
@@ -117,7 +117,12 @@ def print_api_error(e: Exception) -> None:
117
117
  console.print(f"[{ERROR_STYLE}]Error: {e}[/]")
118
118
  return
119
119
 
120
- console.print(f"[{ERROR_STYLE}]API Error: {e}[/]")
120
+ error_text = str(e).strip()
121
+ if not error_text:
122
+ error_text = "Unknown error"
123
+ if "\n" in error_text:
124
+ error_text = error_text.splitlines()[0]
125
+ console.print(f"[{ERROR_STYLE}]API Error: {error_text}[/]")
121
126
  status_code = getattr(e, "status_code", None)
122
127
  if status_code is not None:
123
128
  console.print(f"[{WARNING_STYLE}]Status: {status_code}[/]")
glaip_sdk/cli/main.py CHANGED
@@ -13,7 +13,6 @@ import click
13
13
  from rich.console import Console
14
14
 
15
15
  from glaip_sdk import Client
16
- from glaip_sdk._version import __version__ as _SDK_VERSION
17
16
  from glaip_sdk.branding import (
18
17
  ERROR,
19
18
  ERROR_STYLE,
@@ -38,7 +37,7 @@ from glaip_sdk.cli.commands.update import update_command
38
37
  from glaip_sdk.cli.config import load_config
39
38
  from glaip_sdk.cli.transcript import get_transcript_cache_stats
40
39
  from glaip_sdk.cli.update_notifier import maybe_notify_update
41
- from glaip_sdk.cli.utils import in_slash_mode, spinner_context, update_spinner
40
+ from glaip_sdk.cli.utils import in_slash_mode, sdk_version, spinner_context, update_spinner
42
41
  from glaip_sdk.config.constants import (
43
42
  DEFAULT_AGENT_RUN_TIMEOUT,
44
43
  )
@@ -75,7 +74,7 @@ def _format_size(num: int) -> str:
75
74
 
76
75
 
77
76
  @click.group(invoke_without_command=True)
78
- @click.version_option(version=_SDK_VERSION, prog_name="aip")
77
+ @click.version_option(package_name="glaip-sdk", prog_name="aip")
79
78
  @click.option(
80
79
  "--api-url",
81
80
  envvar="AIP_API_URL",
@@ -136,7 +135,7 @@ def main(
136
135
  if not ctx.resilient_parsing and ctx.obj["tty"] and not launching_slash:
137
136
  console = Console()
138
137
  maybe_notify_update(
139
- _SDK_VERSION,
138
+ sdk_version(),
140
139
  console=console,
141
140
  ctx=ctx,
142
141
  slash_command="update",
@@ -215,7 +214,7 @@ def _validate_config_and_show_error(config: dict, console: Console) -> None:
215
214
  border_style=ERROR,
216
215
  )
217
216
  )
218
- console.print(f"\n[{SUCCESS_STYLE}]✅ AIP - Ready[/] (SDK v{_SDK_VERSION}) - Configure to connect")
217
+ console.print(f"\n[{SUCCESS_STYLE}]✅ AIP - Ready[/] (SDK v{sdk_version()}) - Configure to connect")
219
218
  sys.exit(1)
220
219
 
221
220
 
@@ -233,7 +232,7 @@ def _render_status_heading(console: Console, slash_mode: bool) -> None:
233
232
  del slash_mode # heading now consistent across invocation contexts
234
233
  console.print(f"[{INFO_STYLE}]GL AIP status[/]")
235
234
  console.print()
236
- console.print(f"[{SUCCESS_STYLE}]✅ GL AIP ready[/] (SDK v{_SDK_VERSION})")
235
+ console.print(f"[{SUCCESS_STYLE}]✅ GL AIP ready[/] (SDK v{sdk_version()})")
237
236
 
238
237
 
239
238
  def _collect_cache_summary() -> tuple[str | None, str | None]:
@@ -391,7 +390,7 @@ def status(ctx: Any) -> None:
391
390
  @main.command()
392
391
  def version() -> None:
393
392
  """Show version information."""
394
- branding = AIPBranding.create_from_sdk(sdk_version=_SDK_VERSION, package_name="glaip-sdk")
393
+ branding = AIPBranding.create_from_sdk(sdk_version=sdk_version(), package_name="glaip-sdk")
395
394
  branding.display_version_panel()
396
395
 
397
396
 
@@ -8,8 +8,11 @@ from __future__ import annotations
8
8
 
9
9
  import json
10
10
  from dataclasses import dataclass
11
+ from io import StringIO
11
12
  from typing import Any
12
13
 
14
+ from rich.console import Console
15
+
13
16
  from glaip_sdk.cli.transcript.cache import (
14
17
  TranscriptPayload,
15
18
  TranscriptStoreResult,
@@ -164,6 +167,38 @@ def _format_step_display_name(name: str) -> str:
164
167
  return name
165
168
 
166
169
 
170
+ def _extract_step_summary_lines(renderer: Any) -> list[str]:
171
+ """Render the live steps summary to plain text lines."""
172
+ if not hasattr(renderer, "_render_steps_text"):
173
+ return []
174
+
175
+ try:
176
+ renderable = renderer._render_steps_text()
177
+ except Exception:
178
+ return []
179
+
180
+ buffer = StringIO()
181
+ console = Console(file=buffer, record=True, force_terminal=False, width=120)
182
+ try:
183
+ console.print(renderable)
184
+ except Exception:
185
+ return []
186
+
187
+ text = console.export_text() or buffer.getvalue()
188
+ lines = [line.rstrip() for line in text.splitlines()]
189
+ half = len(lines) // 2
190
+ if half and lines[:half] == lines[half : half * 2]:
191
+ return lines[:half]
192
+ start = 0
193
+ prefixes = ("🤖", "🔧", "💭", "├", "└", "│", "•")
194
+ for idx, line in enumerate(lines):
195
+ if line.lstrip().startswith(prefixes):
196
+ start = idx
197
+ break
198
+ trimmed = lines[start:]
199
+ return [line for line in trimmed if line]
200
+
201
+
167
202
  def _collect_renderer_outputs(
168
203
  renderer: Any, final_result: Any
169
204
  ) -> tuple[
@@ -203,6 +238,10 @@ def _derive_transcript_meta(
203
238
  if step_summaries:
204
239
  meta["transcript_steps"] = step_summaries
205
240
 
241
+ step_lines = _extract_step_summary_lines(renderer)
242
+ if step_lines:
243
+ meta["transcript_step_lines"] = step_lines
244
+
206
245
  stream_processor = getattr(renderer, "stream_processor", None)
207
246
  stream_started_at = (
208
247
  getattr(stream_processor, "streaming_started_at", None) if stream_processor is not None else None
@@ -25,7 +25,7 @@ except Exception: # pragma: no cover - optional dependency
25
25
  Choice = None # type: ignore[assignment]
26
26
 
27
27
  from glaip_sdk.cli.transcript.cache import suggest_filename
28
- from glaip_sdk.icons import ICON_DELEGATE, ICON_TOOL_STEP
28
+ from glaip_sdk.icons import ICON_AGENT, ICON_DELEGATE, ICON_TOOL_STEP
29
29
  from glaip_sdk.rich_components import AIPPanel
30
30
  from glaip_sdk.utils.rendering.formatting import (
31
31
  build_connector_prefix,
@@ -355,12 +355,16 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
355
355
  self.console.print()
356
356
 
357
357
  def _render_steps_summary(self) -> None:
358
- tree_text = self._build_tree_summary_text()
359
- if tree_text is not None:
360
- body = tree_text
358
+ stored_lines = self.ctx.meta.get("transcript_step_lines")
359
+ if stored_lines:
360
+ body = Text("\n".join(stored_lines), style="dim")
361
361
  else:
362
- panel_content = self._format_steps_summary(self._build_step_summary())
363
- body = Text(panel_content, style="dim")
362
+ tree_text = self._build_tree_summary_text()
363
+ if tree_text is not None:
364
+ body = tree_text
365
+ else:
366
+ panel_content = self._format_steps_summary(self._build_step_summary())
367
+ body = Text(panel_content, style="dim")
364
368
  panel = AIPPanel(body, title="Steps", border_style="blue")
365
369
  self.console.print(panel)
366
370
  self.console.print()
@@ -477,6 +481,8 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
477
481
  if not lines:
478
482
  return None
479
483
 
484
+ self._decorate_root_presentation(manager, roots[0], lines)
485
+
480
486
  return Text("\n".join(lines), style="dim")
481
487
 
482
488
  def _render_tree_branch(
@@ -491,7 +497,6 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
491
497
  step = manager.by_id.get(step_id)
492
498
  if not step:
493
499
  return
494
-
495
500
  suppress = self._should_hide_step(step)
496
501
  children = manager.children.get(step_id, [])
497
502
 
@@ -518,15 +523,34 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
518
523
  )
519
524
 
520
525
  def _should_hide_step(self, step: Any) -> bool:
521
- if getattr(step, "parent_id", None) is not None:
526
+ if getattr(step, "parent_id", None) is None:
522
527
  return False
523
- if getattr(step, "kind", None) == "thinking":
524
- return True
525
- if getattr(step, "kind", None) == "agent":
526
- return True
527
528
  name = getattr(step, "name", "") or ""
528
529
  return self._looks_like_uuid(name)
529
530
 
531
+ def _decorate_root_presentation(
532
+ self,
533
+ manager: StepManager,
534
+ root_id: str,
535
+ lines: list[str],
536
+ ) -> None:
537
+ if not lines:
538
+ return
539
+
540
+ root_step = manager.by_id.get(root_id)
541
+ if not root_step:
542
+ return
543
+
544
+ original_label = getattr(root_step, "display_label", None)
545
+ root_step.display_label = self._friendly_root_label(root_step, original_label)
546
+ lines[0] = self._format_tree_line(root_step, ())
547
+ if original_label is not None:
548
+ root_step.display_label = original_label
549
+
550
+ query = self._get_user_query()
551
+ if query:
552
+ lines.insert(1, f" {query}")
553
+
530
554
  def _coerce_step_event(self, event: dict[str, Any]) -> dict[str, Any] | None:
531
555
  metadata = event.get("metadata")
532
556
  if not isinstance(metadata, dict):
@@ -567,6 +591,18 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
567
591
 
568
592
  return line
569
593
 
594
+ def _friendly_root_label(self, step: Any, fallback: str | None) -> str:
595
+ agent_name = self.ctx.manifest_entry.get("agent_name") or (self.ctx.meta or {}).get("agent_name")
596
+ agent_id = self.ctx.manifest_entry.get("agent_id") or getattr(step, "name", "")
597
+
598
+ if not agent_name:
599
+ return fallback or agent_id or ICON_AGENT
600
+
601
+ parts = [ICON_AGENT, agent_name]
602
+ if agent_id and agent_id != agent_name:
603
+ parts.append(f"({agent_id})")
604
+ return " ".join(parts)
605
+
570
606
  @staticmethod
571
607
  def _format_duration_badge(step: Any) -> str | None:
572
608
  duration_ms = getattr(step, "duration_ms", None)
@@ -602,7 +638,7 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
602
638
 
603
639
  @staticmethod
604
640
  def _looks_like_uuid(value: str) -> bool:
605
- stripped = value.replace("-", "")
641
+ stripped = value.replace("-", "").replace(" ", "")
606
642
  if len(stripped) not in {32, 36}:
607
643
  return False
608
644
  return all(ch in "0123456789abcdefABCDEF" for ch in stripped)
glaip_sdk/cli/utils.py CHANGED
@@ -21,6 +21,7 @@ from rich.console import Console, Group
21
21
  from rich.markdown import Markdown
22
22
  from rich.pretty import Pretty
23
23
 
24
+ from glaip_sdk import _version as _version_module
24
25
  from glaip_sdk.branding import (
25
26
  ACCENT_STYLE,
26
27
  HINT_COMMAND_STYLE,
@@ -74,6 +75,8 @@ from glaip_sdk.utils.rendering.renderer import (
74
75
  console = Console()
75
76
  pager.console = console
76
77
  logger = logging.getLogger("glaip_sdk.cli.utils")
78
+ _version_logger = logging.getLogger("glaip_sdk.cli.version")
79
+ _WARNED_SDK_VERSION_FALLBACK = False
77
80
 
78
81
 
79
82
  # ----------------------------- Context helpers ---------------------------- #
@@ -151,6 +154,20 @@ def format_command_hint(
151
154
  return highlighted
152
155
 
153
156
 
157
+ def sdk_version() -> str:
158
+ """Return the current SDK version, warning if metadata is unavailable."""
159
+ version = getattr(_version_module, "__version__", None)
160
+ if isinstance(version, str) and version:
161
+ return version
162
+
163
+ global _WARNED_SDK_VERSION_FALLBACK
164
+ if not _WARNED_SDK_VERSION_FALLBACK:
165
+ _version_logger.warning("Unable to resolve glaip-sdk version metadata; using fallback '0.0.0'.")
166
+ _WARNED_SDK_VERSION_FALLBACK = True
167
+
168
+ return "0.0.0"
169
+
170
+
154
171
  def spinner_context(
155
172
  ctx: Any | None,
156
173
  message: str,
@@ -917,7 +934,6 @@ def build_renderer(
917
934
  _ctx: Any,
918
935
  *,
919
936
  save_path: str | os.PathLike[str] | None,
920
- theme: str = "dark",
921
937
  verbose: bool = False,
922
938
  _tty_enabled: bool = True,
923
939
  live: bool | None = None,
@@ -928,7 +944,6 @@ def build_renderer(
928
944
  Args:
929
945
  _ctx: Click context object for CLI operations.
930
946
  save_path: Path to save output to (enables capturing console).
931
- theme: Color theme ("dark" or "light").
932
947
  verbose: Whether to enable verbose mode.
933
948
  _tty_enabled: Whether TTY is available for interactive features.
934
949
  live: Whether to enable live rendering mode (overrides verbose default).
@@ -942,11 +957,7 @@ def build_renderer(
942
957
 
943
958
  # Configure renderer based on verbose mode and explicit overrides
944
959
  live_enabled = bool(live) if live is not None else not verbose
945
- style = "debug" if verbose else "pretty"
946
-
947
960
  renderer_cfg = RendererConfig(
948
- theme=theme,
949
- style=style,
950
961
  live=live_enabled,
951
962
  append_finished_snapshots=bool(snapshots)
952
963
  if snapshots is not None
@@ -149,8 +149,6 @@ class AgentRunRenderingManager:
149
149
 
150
150
  def _create_verbose_renderer(self) -> RichStreamRenderer:
151
151
  verbose_config = RendererConfig(
152
- theme="dark",
153
- style="debug",
154
152
  live=False,
155
153
  append_finished_snapshots=False,
156
154
  )
@@ -50,7 +50,11 @@ from glaip_sdk.utils.rendering.renderer.progress import (
50
50
  is_delegation_tool,
51
51
  )
52
52
  from glaip_sdk.utils.rendering.renderer.stream import StreamProcessor
53
- from glaip_sdk.utils.rendering.steps import StepManager
53
+ from glaip_sdk.utils.rendering.renderer.summary_window import clamp_step_nodes
54
+ from glaip_sdk.utils.rendering.steps import UNKNOWN_STEP_DETAIL, StepManager
55
+
56
+ DEFAULT_RENDERER_THEME = "dark"
57
+ _NO_STEPS_TEXT = Text("No steps yet", style="dim")
54
58
 
55
59
  # Configure logger
56
60
  logger = logging.getLogger("glaip_sdk.run_renderer")
@@ -344,6 +348,32 @@ class RichStreamRenderer:
344
348
  if query:
345
349
  self.console.print(self._build_user_query_panel(query))
346
350
 
351
+ def _render_summary_after_transcript_toggle(self) -> None:
352
+ """Render the summary panel after leaving transcript mode."""
353
+ if self.state.finalizing_ui:
354
+ self._render_final_summary_panels()
355
+ elif self.live:
356
+ self._refresh_live_panels()
357
+ else:
358
+ self._render_static_summary_panels()
359
+
360
+ def _render_final_summary_panels(self) -> None:
361
+ """Render a static summary and disable live mode for final output."""
362
+ self.cfg.live = False
363
+ self.live = None
364
+ self._render_static_summary_panels()
365
+
366
+ def _render_static_summary_panels(self) -> None:
367
+ """Render the steps and main panels in a static (non-live) layout."""
368
+ steps_renderable = self._render_steps_text()
369
+ steps_panel = AIPPanel(
370
+ steps_renderable,
371
+ title="Steps",
372
+ border_style="blue",
373
+ )
374
+ self.console.print(steps_panel)
375
+ self.console.print(self._render_main_panel())
376
+
347
377
  def _ensure_streaming_started_baseline(self, timestamp: float) -> None:
348
378
  """Synchronize streaming start state across renderer components."""
349
379
  self.state.streaming_started_at = timestamp
@@ -872,7 +902,7 @@ class RichStreamRenderer:
872
902
  final_panel = create_final_panel(
873
903
  body,
874
904
  title=self._final_panel_title(),
875
- theme=self.cfg.theme,
905
+ theme=DEFAULT_RENDERER_THEME,
876
906
  )
877
907
  self.console.print(final_panel)
878
908
  self.state.printed_final_output = True
@@ -1023,11 +1053,11 @@ class RichStreamRenderer:
1023
1053
  return create_final_panel(
1024
1054
  final_content,
1025
1055
  title=title,
1026
- theme=self.cfg.theme,
1056
+ theme=DEFAULT_RENDERER_THEME,
1027
1057
  )
1028
1058
  # Dynamic title with spinner + elapsed/hints
1029
1059
  title = self._format_enhanced_main_title()
1030
- return create_main_panel(body, title, self.cfg.theme)
1060
+ return create_main_panel(body, title, DEFAULT_RENDERER_THEME)
1031
1061
 
1032
1062
  def _final_panel_title(self) -> str:
1033
1063
  """Compose title for the final result panel including duration."""
@@ -1042,8 +1072,6 @@ class RichStreamRenderer:
1042
1072
  return
1043
1073
 
1044
1074
  self.verbose = verbose
1045
- self.cfg.style = "debug" if verbose else "pretty"
1046
-
1047
1075
  desired_live = not verbose
1048
1076
  if desired_live != self.cfg.live:
1049
1077
  self.cfg.live = desired_live
@@ -1089,26 +1117,16 @@ class RichStreamRenderer:
1089
1117
  self._transcript_header_printed = False
1090
1118
  self._transcript_enabled_message_printed = False
1091
1119
  self._clear_console_safe()
1092
- self._render_summary_static_sections()
1093
- summary_notice = (
1094
- "[dim]Returning to the summary view. Streaming will continue here.[/dim]"
1095
- if not self.state.finalizing_ui
1096
- else "[dim]Returning to the summary view.[/dim]"
1097
- )
1098
- self.console.print(summary_notice)
1099
- if self.live:
1100
- self._refresh_live_panels()
1101
- else:
1102
- steps_renderable = self._render_steps_text()
1103
- steps_panel = AIPPanel(
1104
- steps_renderable,
1105
- title="Steps",
1106
- border_style="blue",
1107
- )
1108
- self.console.print(steps_panel)
1109
- self.console.print(self._render_main_panel())
1110
- if not self.state.finalizing_ui:
1111
- self._print_summary_hint(force=True)
1120
+ self._render_summary_static_sections()
1121
+ summary_notice = (
1122
+ "[dim]Returning to the summary view. Streaming will continue here.[/dim]"
1123
+ if not self.state.finalizing_ui
1124
+ else "[dim]Returning to the summary view.[/dim]"
1125
+ )
1126
+ self.console.print(summary_notice)
1127
+ self._render_summary_after_transcript_toggle()
1128
+ if not self.state.finalizing_ui:
1129
+ self._print_summary_hint(force=True)
1112
1130
 
1113
1131
  def _clear_console_safe(self) -> None:
1114
1132
  """Best-effort console clear that ignores platform quirks."""
@@ -1529,7 +1547,7 @@ class RichStreamRenderer:
1529
1547
  title=adjusted_title,
1530
1548
  content=body_text or "(no output)",
1531
1549
  status="finished",
1532
- theme=self.cfg.theme,
1550
+ theme=DEFAULT_RENDERER_THEME,
1533
1551
  is_delegation=is_delegation_tool(finished_tool_name),
1534
1552
  )
1535
1553
 
@@ -1774,7 +1792,7 @@ class RichStreamRenderer:
1774
1792
  return normalise_display_label(label)
1775
1793
 
1776
1794
  if not (step.name or "").strip():
1777
- return "Unknown step detail"
1795
+ return UNKNOWN_STEP_DETAIL
1778
1796
 
1779
1797
  icon = self._get_step_icon(step.kind)
1780
1798
  base_name = self._get_step_display_name(step)
@@ -2111,21 +2129,69 @@ class RichStreamRenderer:
2111
2129
  def _render_steps_text(self) -> Any:
2112
2130
  """Render the steps panel content."""
2113
2131
  if not (self.steps.order or self.steps.children):
2114
- return Text("No steps yet", style="dim")
2132
+ return _NO_STEPS_TEXT.copy()
2133
+
2134
+ nodes = list(self.steps.iter_tree())
2135
+ if not nodes:
2136
+ return _NO_STEPS_TEXT.copy()
2137
+
2138
+ window = self._summary_window_size()
2139
+ display_nodes, header_notice, footer_notice = clamp_step_nodes(
2140
+ nodes,
2141
+ window=window,
2142
+ get_label=self._get_step_label,
2143
+ get_parent=self._get_step_parent,
2144
+ )
2145
+ step_renderables = self._build_step_renderables(display_nodes)
2146
+
2147
+ if not step_renderables and not header_notice and not footer_notice:
2148
+ return _NO_STEPS_TEXT.copy()
2149
+
2150
+ return self._assemble_step_renderables(step_renderables, header_notice, footer_notice)
2151
+
2152
+ def _get_step_label(self, step_id: str) -> str:
2153
+ """Get label for a step by ID."""
2154
+ step = self.steps.by_id.get(step_id)
2155
+ if step:
2156
+ return self._resolve_step_label(step)
2157
+ return UNKNOWN_STEP_DETAIL
2158
+
2159
+ def _get_step_parent(self, step_id: str) -> str | None:
2160
+ """Get parent ID for a step by ID."""
2161
+ step = self.steps.by_id.get(step_id)
2162
+ return step.parent_id if step else None
2115
2163
 
2164
+ def _summary_window_size(self) -> int:
2165
+ """Return the active window size for step display."""
2166
+ if self.state.finalizing_ui:
2167
+ return 0
2168
+ return int(self.cfg.summary_display_window or 0)
2169
+
2170
+ def _assemble_step_renderables(self, step_renderables: list[Any], header_notice: Any, footer_notice: Any) -> Any:
2171
+ """Assemble step renderables with header and footer into final output."""
2116
2172
  renderables: list[Any] = []
2117
- for step_id, branch_state in self.steps.iter_tree():
2173
+ if header_notice is not None:
2174
+ renderables.append(header_notice)
2175
+ renderables.extend(step_renderables)
2176
+ if footer_notice is not None:
2177
+ renderables.append(footer_notice)
2178
+
2179
+ if len(renderables) == 1:
2180
+ return renderables[0]
2181
+
2182
+ return Group(*renderables)
2183
+
2184
+ def _build_step_renderables(self, display_nodes: list[tuple[str, tuple[bool, ...]]]) -> list[Any]:
2185
+ """Convert step nodes to renderables for the steps panel."""
2186
+ renderables: list[Any] = []
2187
+ for step_id, branch_state in display_nodes:
2118
2188
  step = self.steps.by_id.get(step_id)
2119
2189
  if not step:
2120
2190
  continue
2121
2191
  renderable = self._compose_step_renderable(step, branch_state)
2122
2192
  if renderable is not None:
2123
2193
  renderables.append(renderable)
2124
-
2125
- if not renderables:
2126
- return Text("No steps yet", style="dim")
2127
-
2128
- return Group(*renderables)
2194
+ return renderables
2129
2195
 
2130
2196
  def _update_final_duration(self, duration: float | None, *, overwrite: bool = False) -> None:
2131
2197
  """Store formatted duration for eventual final panels."""
@@ -13,16 +13,12 @@ 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
21
+ summary_display_window: int = 20
26
22
 
27
23
  # Scrollback/append options
28
24
  summary_max_steps: int = 0
@@ -156,27 +156,6 @@ class StreamProcessor:
156
156
  if context_id:
157
157
  self.last_event_time_by_ctx[context_id] = monotonic()
158
158
 
159
- def should_insert_thinking_gap(self, task_id: str | None, context_id: str | None, think_threshold: float) -> bool:
160
- """Determine if a thinking gap should be inserted.
161
-
162
- Args:
163
- task_id: Task identifier
164
- context_id: Context identifier
165
- think_threshold: Threshold for thinking gap
166
-
167
- Returns:
168
- True if thinking gap should be inserted
169
- """
170
- if not task_id or not context_id:
171
- return False
172
-
173
- last_time = self.last_event_time_by_ctx.get(context_id)
174
- if last_time is None:
175
- return True
176
-
177
- elapsed = monotonic() - last_time
178
- return elapsed >= think_threshold
179
-
180
159
  def track_tools_and_agents(
181
160
  self,
182
161
  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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: glaip-sdk
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Python SDK for GL AIP (GDP Labs AI Agent Package) - Simplified CLI Design
5
5
  License: MIT
6
6
  Author: Raymond Christopher
@@ -5,17 +5,17 @@ glaip_sdk/cli/__init__.py,sha256=xCCfuF1Yc7mpCDcfhHZTX0vizvtrDSLeT8MJ3V7m5A0,156
5
5
  glaip_sdk/cli/agent_config.py,sha256=YAbFKrTNTRqNA6b0i0Q3pH-01rhHDRi5v8dxSFwGSwM,2401
6
6
  glaip_sdk/cli/auth.py,sha256=oZLgZTqVgx_o2ppcp1ueFwuu88acOUPUr9ed1WDe_HY,15860
7
7
  glaip_sdk/cli/commands/__init__.py,sha256=N2go38u3C0MPxfDXk-K2zz93OnqSTpQyOE6dIC82lHg,191
8
- glaip_sdk/cli/commands/agents.py,sha256=l2Uy6tN8SCZ4CEY7P__Wx3CrcIcaG51exbqUh63-cVs,43901
9
- glaip_sdk/cli/commands/configure.py,sha256=7dmMSs-rMRETtpWgUDEGeNedSJl04yrp6VCRiLEpBUI,9074
8
+ glaip_sdk/cli/commands/agents.py,sha256=t80p_olOL4htn8VZJSBizj5ajeKomVaeN72iP1fk72I,43897
9
+ glaip_sdk/cli/commands/configure.py,sha256=xBOvT5FkfAxPMoaT8we9gltY5jjcIs1j0w33OH8Gk-A,9029
10
10
  glaip_sdk/cli/commands/mcps.py,sha256=oYzL52gUImi5x5mx8grbWkTwUTpAp7VoYGalOzEyLqU,37465
11
11
  glaip_sdk/cli/commands/models.py,sha256=CQrjIwVeZED6AQXJ321UVB8IvmGrEjOIWrtlgDBu6HI,1775
12
12
  glaip_sdk/cli/commands/tools.py,sha256=5y0ec1K1sNvLv0MwvEY0ggb5xiU2k2O2FsWL6k9NDxI,19102
13
13
  glaip_sdk/cli/commands/update.py,sha256=rIZo_x-tvpvcwpQLpwYwso1ix6qTHuNNTL4egmn5fEM,1812
14
14
  glaip_sdk/cli/config.py,sha256=sZLG0wPlLegl71g_J4PZxWb46wCZJgtEQQ5ppsr45CU,1297
15
15
  glaip_sdk/cli/context.py,sha256=M4weRf8dmp5bMtPLRF3w1StnRB7Lo8FPFq2GQMv3Rv8,3617
16
- glaip_sdk/cli/display.py,sha256=22L3dt8exPd4_dtjpTmg1vwYqoNLLkTB_8GLukMfkyg,11209
16
+ glaip_sdk/cli/display.py,sha256=AIB81s85ejMg-nM-CIAQ5s3pmVDo6oNykUW680rluJw,11385
17
17
  glaip_sdk/cli/io.py,sha256=_7qHA3K4VfzNXP7NYHShby_Bw9xigJ26oIaESXYDAQ8,3678
18
- glaip_sdk/cli/main.py,sha256=ESxVe_m5NDXPUc9XMHhgneH4MtBVOZpxdwPyYDtNz64,17110
18
+ glaip_sdk/cli/main.py,sha256=JFhFFxTx2gkC2Pjm5oJpDviG1dYCKaUsTYrOM_fTG00,17072
19
19
  glaip_sdk/cli/masking.py,sha256=BOZjwUqxQf3LQlYgUMwq7UYgve8x4_1Qk04ixiJJPZ8,4399
20
20
  glaip_sdk/cli/mcp_validators.py,sha256=cwbz7p_p7_9xVuuF96OBQOdmEgo5UObU6iWWQ2X03PI,10047
21
21
  glaip_sdk/cli/pager.py,sha256=Cp1t47ViCUW3T3IkHdRBF2yNOrVLrnIBIemTxBso17Q,8049
@@ -29,12 +29,12 @@ glaip_sdk/cli/slash/prompt.py,sha256=4rCGSu56EEj24J42vHWAuJNwiUE5Cb6AK3gOhpms38k
29
29
  glaip_sdk/cli/slash/session.py,sha256=A8xO4gKZENmEwVGp6BN8791kuo2cjEJlaLlhsITc5vI,41191
30
30
  glaip_sdk/cli/transcript/__init__.py,sha256=zQNgAETJsj2tO3OmuINgXiCQCmh_ODzI6HQPPmxMXVs,1816
31
31
  glaip_sdk/cli/transcript/cache.py,sha256=1jZdrLo9CebXuKnD1tIiMJy2-6D-zjf0WmJcBJ5gOvE,9760
32
- glaip_sdk/cli/transcript/capture.py,sha256=d3WZW13NQ3Xq_8qt-WScveFXzV06iV89IPqZBAGIGIc,8221
32
+ glaip_sdk/cli/transcript/capture.py,sha256=1Oguv9syhp7n4eb42-gw90X19RqEe4phnrq9T1oj8Pw,9397
33
33
  glaip_sdk/cli/transcript/export.py,sha256=reCvrZVzli8_LzYe5ZNdaa-MwZ1ov2RjnDzKZWr_6-E,1117
34
34
  glaip_sdk/cli/transcript/launcher.py,sha256=z5ivkPXDQJpATIqtRLUK8jH3p3WIZ72PvOPqYRDMJvw,2327
35
- glaip_sdk/cli/transcript/viewer.py,sha256=goQiIAAR2nL0jWnRggFTcpSQE0rts7O8nfHDtSGV78c,26698
35
+ glaip_sdk/cli/transcript/viewer.py,sha256=dsEPki92XuVICneXZSfVq73rED_ryjxpu4_mY40qgwM,28030
36
36
  glaip_sdk/cli/update_notifier.py,sha256=4h3F7q2DoMGyJSp8tu5NJHt8yeCVBOL-3A0ZyHyvRpw,9532
37
- glaip_sdk/cli/utils.py,sha256=JTcfMIJr-aN_g3iI2VD33h9W9Ai06TEzaVqzWhd6MGQ,40750
37
+ glaip_sdk/cli/utils.py,sha256=P-gpIfwiMe5h5nkXaQXawo8gqscajVm5GVqPWTtAj0U,41222
38
38
  glaip_sdk/cli/validators.py,sha256=Squ2W-fMz9kfvhtTt7pCcAYnzFU28ZxxTEqH1vF9r00,5620
39
39
  glaip_sdk/client/__init__.py,sha256=nYLXfBVTTWwKjP0e63iumPYO4k5FifwWaELQPaPIKIg,188
40
40
  glaip_sdk/client/_agent_payloads.py,sha256=QjaSqXZvDNX0bQVKC7Eb5nHj7KxhU0zLSfNUQiKf2J8,16269
@@ -42,7 +42,7 @@ glaip_sdk/client/agents.py,sha256=hRkt-D2ZRkeWamPqKsqKwl74ZjScKL4UHLbbVezGCWc,37
42
42
  glaip_sdk/client/base.py,sha256=ikW33raz2M6rXzo3JmhttfXXuVdMv5zBRKEZkU1F-4I,18176
43
43
  glaip_sdk/client/main.py,sha256=tELAA36rzthnNKTgwZ6lLPb3Au8Wh1mF8Kz-9N-YtCg,8652
44
44
  glaip_sdk/client/mcps.py,sha256=GQ1EBTSVc-WrFigyw8iocK34DT3TMW85XtnDeOawP9E,8945
45
- glaip_sdk/client/run_rendering.py,sha256=T6NF3SjTWSV6IpU2TexBq8AFKU1yq98NLsTp1N4B2yk,12209
45
+ glaip_sdk/client/run_rendering.py,sha256=gtXdOow1U-eol17l3kOf_EjaBwcuqHoKlgiQfuyI10w,12156
46
46
  glaip_sdk/client/tools.py,sha256=xCPqDqtVNePCBeJvbKDqzZI-jbaduIeWd-6XbCSg_2Y,17324
47
47
  glaip_sdk/client/validators.py,sha256=ioF9VCs-LG2yLkaRDd7Hff74lojDZZ0_Q3CiLbdm1RY,8381
48
48
  glaip_sdk/config/constants.py,sha256=B9CSlYG8LYjQuo_vNpqy-eSks3ej37FMcvJMy6d_F4U,888
@@ -62,13 +62,14 @@ glaip_sdk/utils/rendering/__init__.py,sha256=vXjwk5rPhhfPyD8S0DnV4GFFEtPJp4HCCg1
62
62
  glaip_sdk/utils/rendering/formatting.py,sha256=PBT1jD6pxpkiUSs5RQOVWImmKgAwbWuQCUpgu0PNs3Q,8828
63
63
  glaip_sdk/utils/rendering/models.py,sha256=wB9QtEiwN-AaY8k_YKBBT2qMQSBNMv27VcaZH88DdCM,2823
64
64
  glaip_sdk/utils/rendering/renderer/__init__.py,sha256=gQEbZhZl9qXEVKDDTy0ijd-RnHWdqcazcJyOmORDwEk,2843
65
- glaip_sdk/utils/rendering/renderer/base.py,sha256=yQbZv5iihsL-Nov9N0Ko80NrIXV60T9kEakL2RoDPNg,82992
66
- glaip_sdk/utils/rendering/renderer/config.py,sha256=ePomLA3iBHkjk5bJftvuMx_qVtT83nbj4YrFSZqGRAs,734
65
+ glaip_sdk/utils/rendering/renderer/base.py,sha256=Obm5J9UopxgPfYZSfXwU6AqhGDGNyFqsmiMVKGzgUZg,85660
66
+ glaip_sdk/utils/rendering/renderer/config.py,sha256=FgSAZpG1g7Atm2MXg0tY0lOEciY90MR-RO6YuGFhp0E,626
67
67
  glaip_sdk/utils/rendering/renderer/console.py,sha256=4cLOw4Q1fkHkApuj6dWW8eYpeYdcT0t2SO5MbVt5UTc,1844
68
68
  glaip_sdk/utils/rendering/renderer/debug.py,sha256=_qPP03RicGhCCCAEEJ2zoSEJaUT2ZvgL4xOF5ikg9Uw,5569
69
69
  glaip_sdk/utils/rendering/renderer/panels.py,sha256=tbExgFXzK6NHlvuJlVwsejznGJY1ALwTi9KfIej9aWM,3784
70
70
  glaip_sdk/utils/rendering/renderer/progress.py,sha256=iwAx76q0hdnjDrHMF_MB2AYQ2kAA4pwfIn0FiSAEkyg,4068
71
- glaip_sdk/utils/rendering/renderer/stream.py,sha256=41JF-0rj-Tbp_ze7WVbHxDINf7bQRTu5sFzRjluQIBk,8545
71
+ glaip_sdk/utils/rendering/renderer/stream.py,sha256=Y0egKCdWSdM5kGiiDG5Bwc1VQuimI-NUlal2kDojb08,7862
72
+ glaip_sdk/utils/rendering/renderer/summary_window.py,sha256=ffBsVHaUyy2RfIuXLjhfiO31HeeprVcPP_pe4cjDLsU,2286
72
73
  glaip_sdk/utils/rendering/renderer/toggle.py,sha256=N3LB4g1r8EdDkQyItQdrP5gig6Sszz9uZ6WJuD0KUmk,5396
73
74
  glaip_sdk/utils/rendering/step_tree_state.py,sha256=EItKFTV2FYvm5pSyHbXk7lkzJ-0DW_s-VENIBZe8sp4,4062
74
75
  glaip_sdk/utils/rendering/steps.py,sha256=J9qEZLnGqxeEWrlsDczvAJS1pbCN19sjD5_OZbMoinA,42275
@@ -76,7 +77,7 @@ glaip_sdk/utils/resource_refs.py,sha256=OpwJxTCKq3RIqLAmKE0xspJplD4IhKqLCo0OqPaf
76
77
  glaip_sdk/utils/run_renderer.py,sha256=d_VMI6LbvHPUUeRmGqh5wK_lHqDEIAcym2iqpbtDad0,1365
77
78
  glaip_sdk/utils/serialization.py,sha256=cvlPQ73y4jhsbJeQ6Bv7vaq0NV8uZ27p2vD_R-nyiRQ,12646
78
79
  glaip_sdk/utils/validation.py,sha256=NPDexNgGUIoLkEIz6hl3K6EG7ZKSEkcNLDElqm8-Ng4,7019
79
- glaip_sdk-0.1.2.dist-info/METADATA,sha256=VQWPGOU1y8-yuf8bUrODnK24DVrobD9GJ_wJxAfhybk,6023
80
- glaip_sdk-0.1.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
81
- glaip_sdk-0.1.2.dist-info/entry_points.txt,sha256=EGs8NO8J1fdFMWA3CsF7sKBEvtHb_fujdCoNPhfMouE,47
82
- glaip_sdk-0.1.2.dist-info/RECORD,,
80
+ glaip_sdk-0.1.4.dist-info/METADATA,sha256=lNUe3-_HnPLjOtB1hlourCsQG4bnl2FpDfThEcKRJnQ,6023
81
+ glaip_sdk-0.1.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
82
+ glaip_sdk-0.1.4.dist-info/entry_points.txt,sha256=EGs8NO8J1fdFMWA3CsF7sKBEvtHb_fujdCoNPhfMouE,47
83
+ glaip_sdk-0.1.4.dist-info/RECORD,,