glaip-sdk 0.2.1__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- glaip_sdk/_version.py +8 -0
- glaip_sdk/branding.py +13 -0
- glaip_sdk/cli/commands/agents.py +180 -39
- glaip_sdk/cli/commands/mcps.py +44 -18
- glaip_sdk/cli/commands/models.py +11 -5
- glaip_sdk/cli/commands/tools.py +35 -16
- glaip_sdk/cli/commands/transcripts.py +8 -0
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +8 -0
- glaip_sdk/cli/display.py +34 -19
- glaip_sdk/cli/main.py +14 -7
- glaip_sdk/cli/masking.py +8 -33
- glaip_sdk/cli/pager.py +9 -10
- glaip_sdk/cli/slash/agent_session.py +57 -20
- glaip_sdk/cli/slash/prompt.py +8 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +341 -46
- glaip_sdk/cli/slash/tui/__init__.py +9 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +632 -0
- glaip_sdk/cli/transcript/viewer.py +232 -32
- glaip_sdk/cli/update_notifier.py +2 -2
- glaip_sdk/cli/utils.py +266 -35
- glaip_sdk/cli/validators.py +5 -6
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_agent_payloads.py +30 -0
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +186 -22
- glaip_sdk/client/main.py +23 -6
- glaip_sdk/client/mcps.py +2 -4
- glaip_sdk/client/run_rendering.py +66 -0
- glaip_sdk/client/tools.py +2 -3
- glaip_sdk/config/constants.py +11 -0
- glaip_sdk/models/__init__.py +56 -0
- glaip_sdk/models/agent_runs.py +117 -0
- glaip_sdk/rich_components.py +58 -2
- glaip_sdk/utils/client_utils.py +13 -0
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/import_export.py +6 -9
- glaip_sdk/utils/rendering/__init__.py +122 -1
- glaip_sdk/utils/rendering/renderer/base.py +3 -7
- glaip_sdk/utils/rendering/renderer/debug.py +0 -1
- glaip_sdk/utils/rendering/renderer/stream.py +4 -12
- glaip_sdk/utils/rendering/steps.py +1 -0
- glaip_sdk/utils/resource_refs.py +26 -15
- glaip_sdk/utils/serialization.py +16 -0
- {glaip_sdk-0.2.1.dist-info → glaip_sdk-0.3.0.dist-info}/METADATA +24 -2
- glaip_sdk-0.3.0.dist-info/RECORD +94 -0
- glaip_sdk-0.2.1.dist-info/RECORD +0 -86
- {glaip_sdk-0.2.1.dist-info → glaip_sdk-0.3.0.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.2.1.dist-info → glaip_sdk-0.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -25,6 +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.cli.utils import prompt_export_choice_questionary, questionary_safe_ask
|
|
28
29
|
from glaip_sdk.icons import ICON_AGENT, ICON_DELEGATE, ICON_TOOL_STEP
|
|
29
30
|
from glaip_sdk.rich_components import AIPPanel
|
|
30
31
|
from glaip_sdk.utils.rendering.formatting import (
|
|
@@ -85,6 +86,7 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
85
86
|
# Rendering helpers
|
|
86
87
|
# ------------------------------------------------------------------
|
|
87
88
|
def _render(self) -> None:
|
|
89
|
+
"""Render the transcript viewer interface."""
|
|
88
90
|
try:
|
|
89
91
|
if self.console.is_terminal:
|
|
90
92
|
self.console.clear()
|
|
@@ -115,12 +117,22 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
115
117
|
self._render_transcript_view(query)
|
|
116
118
|
|
|
117
119
|
def _render_default_view(self, query: str | None) -> None:
|
|
120
|
+
"""Render the default summary view.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
query: Optional user query to display.
|
|
124
|
+
"""
|
|
118
125
|
if query:
|
|
119
126
|
self._render_user_query(query)
|
|
120
127
|
self._render_steps_summary()
|
|
121
128
|
self._render_final_panel()
|
|
122
129
|
|
|
123
130
|
def _render_transcript_view(self, query: str | None) -> None:
|
|
131
|
+
"""Render the full transcript view with events.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
query: Optional user query to display.
|
|
135
|
+
"""
|
|
124
136
|
if not self.ctx.events:
|
|
125
137
|
self.console.print("[dim]No SSE events were captured for this run.[/dim]")
|
|
126
138
|
return
|
|
@@ -148,6 +160,7 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
148
160
|
self.console.print()
|
|
149
161
|
|
|
150
162
|
def _render_final_panel(self) -> None:
|
|
163
|
+
"""Render the final result panel."""
|
|
151
164
|
content = self.ctx.final_output or self.ctx.default_output or "No response content captured."
|
|
152
165
|
title = "Final Result"
|
|
153
166
|
duration_text = self._extract_final_duration()
|
|
@@ -161,6 +174,7 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
161
174
|
# Interaction loops
|
|
162
175
|
# ------------------------------------------------------------------
|
|
163
176
|
def _fallback_loop(self) -> None:
|
|
177
|
+
"""Fallback interaction loop for non-interactive terminals."""
|
|
164
178
|
while True:
|
|
165
179
|
try:
|
|
166
180
|
ch = click.getchar()
|
|
@@ -181,6 +195,14 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
181
195
|
continue
|
|
182
196
|
|
|
183
197
|
def _handle_command(self, raw: str) -> bool:
|
|
198
|
+
"""Handle a command input.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
raw: Raw command string.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
True to continue, False to exit.
|
|
205
|
+
"""
|
|
184
206
|
lowered = raw.lower()
|
|
185
207
|
if lowered in {"exit", "quit", "q"}:
|
|
186
208
|
return True
|
|
@@ -238,38 +260,10 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
238
260
|
|
|
239
261
|
def _prompt_export_choice(self, default_path: Path, default_display: str) -> tuple[str, Any] | None:
|
|
240
262
|
"""Render interactive export menu with numeric shortcuts."""
|
|
241
|
-
if not self.console.is_terminal
|
|
242
|
-
return None
|
|
243
|
-
|
|
244
|
-
try:
|
|
245
|
-
answer = questionary.select(
|
|
246
|
-
"Export transcript",
|
|
247
|
-
choices=[
|
|
248
|
-
Choice(
|
|
249
|
-
title=f"Save to default ({default_display})",
|
|
250
|
-
value=("default", default_path),
|
|
251
|
-
shortcut_key="1",
|
|
252
|
-
),
|
|
253
|
-
Choice(
|
|
254
|
-
title="Choose a different path",
|
|
255
|
-
value=("custom", None),
|
|
256
|
-
shortcut_key="2",
|
|
257
|
-
),
|
|
258
|
-
Choice(
|
|
259
|
-
title="Cancel",
|
|
260
|
-
value=("cancel", None),
|
|
261
|
-
shortcut_key="3",
|
|
262
|
-
),
|
|
263
|
-
],
|
|
264
|
-
use_shortcuts=True,
|
|
265
|
-
instruction="Press 1-3 (or arrows) then Enter.",
|
|
266
|
-
).ask()
|
|
267
|
-
except Exception:
|
|
263
|
+
if not self.console.is_terminal:
|
|
268
264
|
return None
|
|
269
265
|
|
|
270
|
-
|
|
271
|
-
return ("cancel", None)
|
|
272
|
-
return answer
|
|
266
|
+
return prompt_export_choice_questionary(default_path, default_display)
|
|
273
267
|
|
|
274
268
|
def _prompt_custom_destination(self) -> Path | None:
|
|
275
269
|
"""Prompt for custom export path with filesystem completion."""
|
|
@@ -277,11 +271,12 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
277
271
|
return None
|
|
278
272
|
|
|
279
273
|
try:
|
|
280
|
-
|
|
274
|
+
question = questionary.path(
|
|
281
275
|
"Destination path (Tab to autocomplete):",
|
|
282
276
|
default="",
|
|
283
277
|
only_directories=False,
|
|
284
|
-
)
|
|
278
|
+
)
|
|
279
|
+
response = questionary_safe_ask(question)
|
|
285
280
|
except Exception:
|
|
286
281
|
return None
|
|
287
282
|
|
|
@@ -339,15 +334,26 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
339
334
|
self.console.print(f"[red]Failed to export transcript: {exc}[/red]")
|
|
340
335
|
|
|
341
336
|
def _print_command_hint(self) -> None:
|
|
337
|
+
"""Print command hint for user interaction."""
|
|
342
338
|
self.console.print("[dim]Ctrl+T to toggle transcript · type `e` to export · press Enter to exit[/dim]")
|
|
343
339
|
self.console.print()
|
|
344
340
|
|
|
345
341
|
def _get_user_query(self) -> str | None:
|
|
342
|
+
"""Extract user query from metadata or manifest.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
User query string or None.
|
|
346
|
+
"""
|
|
346
347
|
meta = self.ctx.meta or {}
|
|
347
348
|
manifest = self.ctx.manifest_entry or {}
|
|
348
349
|
return meta.get("input_message") or meta.get("query") or meta.get("message") or manifest.get("input_message")
|
|
349
350
|
|
|
350
351
|
def _render_user_query(self, query: str) -> None:
|
|
352
|
+
"""Render user query in a panel.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
query: User query string to render.
|
|
356
|
+
"""
|
|
351
357
|
panel = AIPPanel(
|
|
352
358
|
Markdown(f"Query: {query}"),
|
|
353
359
|
title="User Request",
|
|
@@ -357,6 +363,7 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
357
363
|
self.console.print()
|
|
358
364
|
|
|
359
365
|
def _render_steps_summary(self) -> None:
|
|
366
|
+
"""Render steps summary panel."""
|
|
360
367
|
stored_lines = self.ctx.meta.get("transcript_step_lines")
|
|
361
368
|
if stored_lines:
|
|
362
369
|
body = Text("\n".join(stored_lines), style="dim")
|
|
@@ -373,6 +380,14 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
373
380
|
|
|
374
381
|
@staticmethod
|
|
375
382
|
def _format_steps_summary(steps: list[dict[str, Any]]) -> str:
|
|
383
|
+
"""Format steps summary as text.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
steps: List of step dictionaries.
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
Formatted text string.
|
|
390
|
+
"""
|
|
376
391
|
if not steps:
|
|
377
392
|
return " No steps yet"
|
|
378
393
|
|
|
@@ -388,6 +403,14 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
388
403
|
|
|
389
404
|
@staticmethod
|
|
390
405
|
def _extract_event_time(event: dict[str, Any]) -> float | None:
|
|
406
|
+
"""Extract timestamp from event metadata.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
event: Event dictionary.
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
Time value as float or None.
|
|
413
|
+
"""
|
|
391
414
|
metadata = event.get("metadata") or {}
|
|
392
415
|
time_value = metadata.get("time")
|
|
393
416
|
try:
|
|
@@ -399,6 +422,14 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
399
422
|
|
|
400
423
|
@staticmethod
|
|
401
424
|
def _parse_received_timestamp(event: dict[str, Any]) -> datetime | None:
|
|
425
|
+
"""Parse received timestamp from event.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
event: Event dictionary.
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
Parsed datetime or None.
|
|
432
|
+
"""
|
|
402
433
|
value = event.get("received_at")
|
|
403
434
|
if not value:
|
|
404
435
|
return None
|
|
@@ -412,6 +443,11 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
412
443
|
return None
|
|
413
444
|
|
|
414
445
|
def _extract_final_duration(self) -> str | None:
|
|
446
|
+
"""Extract final duration from events.
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
Duration string or None.
|
|
450
|
+
"""
|
|
415
451
|
for event in self.ctx.events:
|
|
416
452
|
metadata = event.get("metadata") or {}
|
|
417
453
|
if metadata.get("kind") == "final_response":
|
|
@@ -424,6 +460,11 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
424
460
|
return None
|
|
425
461
|
|
|
426
462
|
def _build_step_summary(self) -> list[dict[str, Any]]:
|
|
463
|
+
"""Build step summary from stored steps or events.
|
|
464
|
+
|
|
465
|
+
Returns:
|
|
466
|
+
List of step dictionaries.
|
|
467
|
+
"""
|
|
427
468
|
stored = self.ctx.meta.get("transcript_steps")
|
|
428
469
|
if isinstance(stored, list) and stored:
|
|
429
470
|
return [
|
|
@@ -496,6 +537,15 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
496
537
|
is_last: bool,
|
|
497
538
|
lines: list[str],
|
|
498
539
|
) -> None:
|
|
540
|
+
"""Recursively render a tree branch of steps.
|
|
541
|
+
|
|
542
|
+
Args:
|
|
543
|
+
manager: StepManager instance.
|
|
544
|
+
step_id: ID of step to render.
|
|
545
|
+
ancestor_state: Tuple of ancestor branch states.
|
|
546
|
+
is_last: Whether this is the last child.
|
|
547
|
+
lines: List to append rendered lines to.
|
|
548
|
+
"""
|
|
499
549
|
step = manager.by_id.get(step_id)
|
|
500
550
|
if not step:
|
|
501
551
|
return
|
|
@@ -525,6 +575,14 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
525
575
|
)
|
|
526
576
|
|
|
527
577
|
def _should_hide_step(self, step: Any) -> bool:
|
|
578
|
+
"""Check if a step should be hidden.
|
|
579
|
+
|
|
580
|
+
Args:
|
|
581
|
+
step: Step object.
|
|
582
|
+
|
|
583
|
+
Returns:
|
|
584
|
+
True if step should be hidden.
|
|
585
|
+
"""
|
|
528
586
|
if getattr(step, "parent_id", None) is None:
|
|
529
587
|
return False
|
|
530
588
|
name = getattr(step, "name", "") or ""
|
|
@@ -536,6 +594,13 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
536
594
|
root_id: str,
|
|
537
595
|
lines: list[str],
|
|
538
596
|
) -> None:
|
|
597
|
+
"""Decorate root step presentation with friendly label.
|
|
598
|
+
|
|
599
|
+
Args:
|
|
600
|
+
manager: StepManager instance.
|
|
601
|
+
root_id: Root step ID.
|
|
602
|
+
lines: Lines list to modify.
|
|
603
|
+
"""
|
|
539
604
|
if not lines:
|
|
540
605
|
return
|
|
541
606
|
|
|
@@ -554,6 +619,14 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
554
619
|
lines.insert(1, f" {query}")
|
|
555
620
|
|
|
556
621
|
def _coerce_step_event(self, event: dict[str, Any]) -> dict[str, Any] | None:
|
|
622
|
+
"""Coerce event to step event format.
|
|
623
|
+
|
|
624
|
+
Args:
|
|
625
|
+
event: Event dictionary.
|
|
626
|
+
|
|
627
|
+
Returns:
|
|
628
|
+
Step event dictionary or None.
|
|
629
|
+
"""
|
|
557
630
|
metadata = event.get("metadata")
|
|
558
631
|
if not isinstance(metadata, dict):
|
|
559
632
|
return None
|
|
@@ -569,6 +642,15 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
569
642
|
}
|
|
570
643
|
|
|
571
644
|
def _format_tree_line(self, step: Any, branch_state: tuple[bool, ...]) -> str:
|
|
645
|
+
"""Format a tree line for a step.
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
step: Step object.
|
|
649
|
+
branch_state: Branch state tuple.
|
|
650
|
+
|
|
651
|
+
Returns:
|
|
652
|
+
Formatted line string.
|
|
653
|
+
"""
|
|
572
654
|
prefix = build_connector_prefix(branch_state)
|
|
573
655
|
raw_label = normalise_display_label(getattr(step, "display_label", None))
|
|
574
656
|
title, summary = self._split_label(raw_label)
|
|
@@ -594,6 +676,15 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
594
676
|
return line
|
|
595
677
|
|
|
596
678
|
def _friendly_root_label(self, step: Any, fallback: str | None) -> str:
|
|
679
|
+
"""Generate friendly label for root step.
|
|
680
|
+
|
|
681
|
+
Args:
|
|
682
|
+
step: Step object.
|
|
683
|
+
fallback: Fallback label string.
|
|
684
|
+
|
|
685
|
+
Returns:
|
|
686
|
+
Friendly label string.
|
|
687
|
+
"""
|
|
597
688
|
agent_name = self.ctx.manifest_entry.get("agent_name") or (self.ctx.meta or {}).get("agent_name")
|
|
598
689
|
agent_id = self.ctx.manifest_entry.get("agent_id") or getattr(step, "name", "")
|
|
599
690
|
|
|
@@ -607,6 +698,14 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
607
698
|
|
|
608
699
|
@staticmethod
|
|
609
700
|
def _format_duration_badge(step: Any) -> str | None:
|
|
701
|
+
"""Format duration badge for a step.
|
|
702
|
+
|
|
703
|
+
Args:
|
|
704
|
+
step: Step object.
|
|
705
|
+
|
|
706
|
+
Returns:
|
|
707
|
+
Duration badge string or None.
|
|
708
|
+
"""
|
|
610
709
|
duration_ms = getattr(step, "duration_ms", None)
|
|
611
710
|
if duration_ms is None:
|
|
612
711
|
return None
|
|
@@ -626,6 +725,14 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
626
725
|
|
|
627
726
|
@staticmethod
|
|
628
727
|
def _split_label(label: str) -> tuple[str, str | None]:
|
|
728
|
+
"""Split label into title and summary.
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
label: Label string.
|
|
732
|
+
|
|
733
|
+
Returns:
|
|
734
|
+
Tuple of (title, summary).
|
|
735
|
+
"""
|
|
629
736
|
if " — " in label:
|
|
630
737
|
title, summary = label.split(" — ", 1)
|
|
631
738
|
return title.strip(), summary.strip()
|
|
@@ -633,6 +740,15 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
633
740
|
|
|
634
741
|
@staticmethod
|
|
635
742
|
def _truncate_summary(summary: str, limit: int = 48) -> str:
|
|
743
|
+
"""Truncate summary to specified length.
|
|
744
|
+
|
|
745
|
+
Args:
|
|
746
|
+
summary: Summary string.
|
|
747
|
+
limit: Maximum length.
|
|
748
|
+
|
|
749
|
+
Returns:
|
|
750
|
+
Truncated summary string.
|
|
751
|
+
"""
|
|
636
752
|
summary = summary.strip()
|
|
637
753
|
if len(summary) <= limit:
|
|
638
754
|
return summary
|
|
@@ -640,6 +756,14 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
640
756
|
|
|
641
757
|
@staticmethod
|
|
642
758
|
def _looks_like_uuid(value: str) -> bool:
|
|
759
|
+
"""Check if string looks like a UUID.
|
|
760
|
+
|
|
761
|
+
Args:
|
|
762
|
+
value: String to check.
|
|
763
|
+
|
|
764
|
+
Returns:
|
|
765
|
+
True if value looks like UUID.
|
|
766
|
+
"""
|
|
643
767
|
stripped = value.replace("-", "").replace(" ", "")
|
|
644
768
|
if len(stripped) not in {32, 36}:
|
|
645
769
|
return False
|
|
@@ -647,6 +771,14 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
647
771
|
|
|
648
772
|
@staticmethod
|
|
649
773
|
def _format_duration_from_ms(value: Any) -> str | None:
|
|
774
|
+
"""Format duration from milliseconds.
|
|
775
|
+
|
|
776
|
+
Args:
|
|
777
|
+
value: Duration value in milliseconds.
|
|
778
|
+
|
|
779
|
+
Returns:
|
|
780
|
+
Formatted duration string or None.
|
|
781
|
+
"""
|
|
650
782
|
try:
|
|
651
783
|
if value is None:
|
|
652
784
|
return None
|
|
@@ -662,12 +794,29 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
662
794
|
|
|
663
795
|
@staticmethod
|
|
664
796
|
def _is_step_event(metadata: dict[str, Any]) -> bool:
|
|
797
|
+
"""Check if metadata represents a step event.
|
|
798
|
+
|
|
799
|
+
Args:
|
|
800
|
+
metadata: Event metadata dictionary.
|
|
801
|
+
|
|
802
|
+
Returns:
|
|
803
|
+
True if metadata represents a step event.
|
|
804
|
+
"""
|
|
665
805
|
kind = metadata.get("kind")
|
|
666
806
|
return kind in {"agent_step", "agent_thinking_step"}
|
|
667
807
|
|
|
668
808
|
def _iter_step_candidates(
|
|
669
809
|
self, event: dict[str, Any], metadata: dict[str, Any]
|
|
670
810
|
) -> Iterable[tuple[str, dict[str, Any]]]:
|
|
811
|
+
"""Iterate step candidates from event.
|
|
812
|
+
|
|
813
|
+
Args:
|
|
814
|
+
event: Event dictionary.
|
|
815
|
+
metadata: Event metadata dictionary.
|
|
816
|
+
|
|
817
|
+
Yields:
|
|
818
|
+
Tuples of (step_name, step_info).
|
|
819
|
+
"""
|
|
671
820
|
tool_info = metadata.get("tool_info") or {}
|
|
672
821
|
|
|
673
822
|
yielded = False
|
|
@@ -691,6 +840,14 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
691
840
|
def _iter_tool_call_candidates(
|
|
692
841
|
tool_info: dict[str, Any],
|
|
693
842
|
) -> Iterable[tuple[str, dict[str, Any]]]:
|
|
843
|
+
"""Iterate tool call candidates from tool_info.
|
|
844
|
+
|
|
845
|
+
Args:
|
|
846
|
+
tool_info: Tool info dictionary.
|
|
847
|
+
|
|
848
|
+
Yields:
|
|
849
|
+
Tuples of (tool_name, tool_call_info).
|
|
850
|
+
"""
|
|
694
851
|
tool_calls = tool_info.get("tool_calls")
|
|
695
852
|
if isinstance(tool_calls, list):
|
|
696
853
|
for call in tool_calls:
|
|
@@ -702,6 +859,14 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
702
859
|
def _extract_direct_tool(
|
|
703
860
|
tool_info: dict[str, Any],
|
|
704
861
|
) -> tuple[str, dict[str, Any]] | None:
|
|
862
|
+
"""Extract direct tool from tool_info.
|
|
863
|
+
|
|
864
|
+
Args:
|
|
865
|
+
tool_info: Tool info dictionary.
|
|
866
|
+
|
|
867
|
+
Returns:
|
|
868
|
+
Tuple of (tool_name, tool_info) or None.
|
|
869
|
+
"""
|
|
705
870
|
if isinstance(tool_info, dict):
|
|
706
871
|
name = tool_info.get("name")
|
|
707
872
|
if name:
|
|
@@ -710,6 +875,14 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
710
875
|
|
|
711
876
|
@staticmethod
|
|
712
877
|
def _extract_completed_name(event: dict[str, Any]) -> str | None:
|
|
878
|
+
"""Extract completed tool name from event content.
|
|
879
|
+
|
|
880
|
+
Args:
|
|
881
|
+
event: Event dictionary.
|
|
882
|
+
|
|
883
|
+
Returns:
|
|
884
|
+
Tool name or None.
|
|
885
|
+
"""
|
|
713
886
|
content = event.get("content") or ""
|
|
714
887
|
if isinstance(content, str) and content.startswith("Completed "):
|
|
715
888
|
name = content.replace("Completed ", "").strip()
|
|
@@ -723,6 +896,16 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
723
896
|
order: list[str],
|
|
724
897
|
name: str,
|
|
725
898
|
) -> dict[str, Any]:
|
|
899
|
+
"""Ensure step entry exists, creating if needed.
|
|
900
|
+
|
|
901
|
+
Args:
|
|
902
|
+
steps: Steps dictionary.
|
|
903
|
+
order: Order list.
|
|
904
|
+
name: Step name.
|
|
905
|
+
|
|
906
|
+
Returns:
|
|
907
|
+
Step dictionary.
|
|
908
|
+
"""
|
|
726
909
|
if name not in steps:
|
|
727
910
|
steps[name] = {
|
|
728
911
|
"name": name,
|
|
@@ -742,6 +925,14 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
742
925
|
info: dict[str, Any],
|
|
743
926
|
event: dict[str, Any],
|
|
744
927
|
) -> None:
|
|
928
|
+
"""Apply update to step from event metadata.
|
|
929
|
+
|
|
930
|
+
Args:
|
|
931
|
+
step: Step dictionary to update.
|
|
932
|
+
metadata: Event metadata.
|
|
933
|
+
info: Step info dictionary.
|
|
934
|
+
event: Event dictionary.
|
|
935
|
+
"""
|
|
745
936
|
status = metadata.get("status")
|
|
746
937
|
event_time = metadata.get("time")
|
|
747
938
|
|
|
@@ -760,6 +951,15 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
760
951
|
|
|
761
952
|
@staticmethod
|
|
762
953
|
def _is_step_finished(metadata: dict[str, Any], event: dict[str, Any]) -> bool:
|
|
954
|
+
"""Check if step is finished.
|
|
955
|
+
|
|
956
|
+
Args:
|
|
957
|
+
metadata: Event metadata.
|
|
958
|
+
event: Event dictionary.
|
|
959
|
+
|
|
960
|
+
Returns:
|
|
961
|
+
True if step is finished.
|
|
962
|
+
"""
|
|
763
963
|
status = metadata.get("status")
|
|
764
964
|
return status == "finished" or bool(event.get("final"))
|
|
765
965
|
|
glaip_sdk/cli/update_notifier.py
CHANGED
|
@@ -8,7 +8,6 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import importlib
|
|
10
10
|
import logging
|
|
11
|
-
import os
|
|
12
11
|
from collections.abc import Callable, Iterable, Iterator
|
|
13
12
|
from contextlib import contextmanager
|
|
14
13
|
from typing import Any, Literal
|
|
@@ -26,6 +25,7 @@ from glaip_sdk.branding import (
|
|
|
26
25
|
WARNING_STYLE,
|
|
27
26
|
)
|
|
28
27
|
from glaip_sdk.cli.commands.update import update_command
|
|
28
|
+
from glaip_sdk.cli.constants import UPDATE_CHECK_ENABLED
|
|
29
29
|
from glaip_sdk.cli.utils import command_hint, format_command_hint
|
|
30
30
|
from glaip_sdk.rich_components import AIPPanel
|
|
31
31
|
|
|
@@ -72,7 +72,7 @@ def _fetch_latest_version(package_name: str) -> str | None:
|
|
|
72
72
|
|
|
73
73
|
def _should_check_for_updates() -> bool:
|
|
74
74
|
"""Return False when update checks are explicitly disabled."""
|
|
75
|
-
return
|
|
75
|
+
return UPDATE_CHECK_ENABLED
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
def _build_update_panel(
|