glaip-sdk 0.6.12__py3-none-any.whl → 0.6.15__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.
Files changed (156) hide show
  1. glaip_sdk/__init__.py +42 -5
  2. {glaip_sdk-0.6.12.dist-info → glaip_sdk-0.6.15.dist-info}/METADATA +32 -37
  3. glaip_sdk-0.6.15.dist-info/RECORD +12 -0
  4. {glaip_sdk-0.6.12.dist-info → glaip_sdk-0.6.15.dist-info}/WHEEL +2 -1
  5. glaip_sdk-0.6.15.dist-info/entry_points.txt +2 -0
  6. glaip_sdk-0.6.15.dist-info/top_level.txt +1 -0
  7. glaip_sdk/agents/__init__.py +0 -27
  8. glaip_sdk/agents/base.py +0 -1191
  9. glaip_sdk/cli/__init__.py +0 -9
  10. glaip_sdk/cli/account_store.py +0 -540
  11. glaip_sdk/cli/agent_config.py +0 -78
  12. glaip_sdk/cli/auth.py +0 -699
  13. glaip_sdk/cli/commands/__init__.py +0 -5
  14. glaip_sdk/cli/commands/accounts.py +0 -746
  15. glaip_sdk/cli/commands/agents.py +0 -1509
  16. glaip_sdk/cli/commands/common_config.py +0 -101
  17. glaip_sdk/cli/commands/configure.py +0 -896
  18. glaip_sdk/cli/commands/mcps.py +0 -1356
  19. glaip_sdk/cli/commands/models.py +0 -69
  20. glaip_sdk/cli/commands/tools.py +0 -576
  21. glaip_sdk/cli/commands/transcripts.py +0 -755
  22. glaip_sdk/cli/commands/update.py +0 -61
  23. glaip_sdk/cli/config.py +0 -95
  24. glaip_sdk/cli/constants.py +0 -38
  25. glaip_sdk/cli/context.py +0 -150
  26. glaip_sdk/cli/core/__init__.py +0 -79
  27. glaip_sdk/cli/core/context.py +0 -124
  28. glaip_sdk/cli/core/output.py +0 -846
  29. glaip_sdk/cli/core/prompting.py +0 -649
  30. glaip_sdk/cli/core/rendering.py +0 -187
  31. glaip_sdk/cli/display.py +0 -355
  32. glaip_sdk/cli/hints.py +0 -57
  33. glaip_sdk/cli/io.py +0 -112
  34. glaip_sdk/cli/main.py +0 -604
  35. glaip_sdk/cli/masking.py +0 -136
  36. glaip_sdk/cli/mcp_validators.py +0 -287
  37. glaip_sdk/cli/pager.py +0 -266
  38. glaip_sdk/cli/parsers/__init__.py +0 -7
  39. glaip_sdk/cli/parsers/json_input.py +0 -177
  40. glaip_sdk/cli/resolution.py +0 -67
  41. glaip_sdk/cli/rich_helpers.py +0 -27
  42. glaip_sdk/cli/slash/__init__.py +0 -15
  43. glaip_sdk/cli/slash/accounts_controller.py +0 -578
  44. glaip_sdk/cli/slash/accounts_shared.py +0 -75
  45. glaip_sdk/cli/slash/agent_session.py +0 -285
  46. glaip_sdk/cli/slash/prompt.py +0 -256
  47. glaip_sdk/cli/slash/remote_runs_controller.py +0 -566
  48. glaip_sdk/cli/slash/session.py +0 -1708
  49. glaip_sdk/cli/slash/tui/__init__.py +0 -9
  50. glaip_sdk/cli/slash/tui/accounts_app.py +0 -876
  51. glaip_sdk/cli/slash/tui/background_tasks.py +0 -72
  52. glaip_sdk/cli/slash/tui/loading.py +0 -58
  53. glaip_sdk/cli/slash/tui/remote_runs_app.py +0 -628
  54. glaip_sdk/cli/transcript/__init__.py +0 -31
  55. glaip_sdk/cli/transcript/cache.py +0 -536
  56. glaip_sdk/cli/transcript/capture.py +0 -329
  57. glaip_sdk/cli/transcript/export.py +0 -38
  58. glaip_sdk/cli/transcript/history.py +0 -815
  59. glaip_sdk/cli/transcript/launcher.py +0 -77
  60. glaip_sdk/cli/transcript/viewer.py +0 -374
  61. glaip_sdk/cli/update_notifier.py +0 -290
  62. glaip_sdk/cli/utils.py +0 -263
  63. glaip_sdk/cli/validators.py +0 -238
  64. glaip_sdk/client/__init__.py +0 -11
  65. glaip_sdk/client/_agent_payloads.py +0 -520
  66. glaip_sdk/client/agent_runs.py +0 -147
  67. glaip_sdk/client/agents.py +0 -1335
  68. glaip_sdk/client/base.py +0 -502
  69. glaip_sdk/client/main.py +0 -249
  70. glaip_sdk/client/mcps.py +0 -370
  71. glaip_sdk/client/run_rendering.py +0 -700
  72. glaip_sdk/client/shared.py +0 -21
  73. glaip_sdk/client/tools.py +0 -661
  74. glaip_sdk/client/validators.py +0 -198
  75. glaip_sdk/config/constants.py +0 -52
  76. glaip_sdk/mcps/__init__.py +0 -21
  77. glaip_sdk/mcps/base.py +0 -345
  78. glaip_sdk/models/__init__.py +0 -90
  79. glaip_sdk/models/agent.py +0 -47
  80. glaip_sdk/models/agent_runs.py +0 -116
  81. glaip_sdk/models/common.py +0 -42
  82. glaip_sdk/models/mcp.py +0 -33
  83. glaip_sdk/models/tool.py +0 -33
  84. glaip_sdk/payload_schemas/__init__.py +0 -7
  85. glaip_sdk/payload_schemas/agent.py +0 -85
  86. glaip_sdk/registry/__init__.py +0 -55
  87. glaip_sdk/registry/agent.py +0 -164
  88. glaip_sdk/registry/base.py +0 -139
  89. glaip_sdk/registry/mcp.py +0 -253
  90. glaip_sdk/registry/tool.py +0 -232
  91. glaip_sdk/runner/__init__.py +0 -59
  92. glaip_sdk/runner/base.py +0 -84
  93. glaip_sdk/runner/deps.py +0 -115
  94. glaip_sdk/runner/langgraph.py +0 -782
  95. glaip_sdk/runner/mcp_adapter/__init__.py +0 -13
  96. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +0 -43
  97. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +0 -257
  98. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +0 -95
  99. glaip_sdk/runner/tool_adapter/__init__.py +0 -18
  100. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +0 -44
  101. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +0 -219
  102. glaip_sdk/tools/__init__.py +0 -22
  103. glaip_sdk/tools/base.py +0 -435
  104. glaip_sdk/utils/__init__.py +0 -86
  105. glaip_sdk/utils/a2a/__init__.py +0 -34
  106. glaip_sdk/utils/a2a/event_processor.py +0 -188
  107. glaip_sdk/utils/agent_config.py +0 -194
  108. glaip_sdk/utils/bundler.py +0 -267
  109. glaip_sdk/utils/client.py +0 -111
  110. glaip_sdk/utils/client_utils.py +0 -486
  111. glaip_sdk/utils/datetime_helpers.py +0 -58
  112. glaip_sdk/utils/discovery.py +0 -78
  113. glaip_sdk/utils/display.py +0 -135
  114. glaip_sdk/utils/export.py +0 -143
  115. glaip_sdk/utils/general.py +0 -61
  116. glaip_sdk/utils/import_export.py +0 -168
  117. glaip_sdk/utils/import_resolver.py +0 -492
  118. glaip_sdk/utils/instructions.py +0 -101
  119. glaip_sdk/utils/rendering/__init__.py +0 -115
  120. glaip_sdk/utils/rendering/formatting.py +0 -264
  121. glaip_sdk/utils/rendering/layout/__init__.py +0 -64
  122. glaip_sdk/utils/rendering/layout/panels.py +0 -156
  123. glaip_sdk/utils/rendering/layout/progress.py +0 -202
  124. glaip_sdk/utils/rendering/layout/summary.py +0 -74
  125. glaip_sdk/utils/rendering/layout/transcript.py +0 -606
  126. glaip_sdk/utils/rendering/models.py +0 -85
  127. glaip_sdk/utils/rendering/renderer/__init__.py +0 -55
  128. glaip_sdk/utils/rendering/renderer/base.py +0 -1024
  129. glaip_sdk/utils/rendering/renderer/config.py +0 -27
  130. glaip_sdk/utils/rendering/renderer/console.py +0 -55
  131. glaip_sdk/utils/rendering/renderer/debug.py +0 -178
  132. glaip_sdk/utils/rendering/renderer/factory.py +0 -138
  133. glaip_sdk/utils/rendering/renderer/stream.py +0 -202
  134. glaip_sdk/utils/rendering/renderer/summary_window.py +0 -79
  135. glaip_sdk/utils/rendering/renderer/thinking.py +0 -273
  136. glaip_sdk/utils/rendering/renderer/toggle.py +0 -182
  137. glaip_sdk/utils/rendering/renderer/tool_panels.py +0 -442
  138. glaip_sdk/utils/rendering/renderer/transcript_mode.py +0 -162
  139. glaip_sdk/utils/rendering/state.py +0 -204
  140. glaip_sdk/utils/rendering/step_tree_state.py +0 -100
  141. glaip_sdk/utils/rendering/steps/__init__.py +0 -34
  142. glaip_sdk/utils/rendering/steps/event_processor.py +0 -778
  143. glaip_sdk/utils/rendering/steps/format.py +0 -176
  144. glaip_sdk/utils/rendering/steps/manager.py +0 -387
  145. glaip_sdk/utils/rendering/timing.py +0 -36
  146. glaip_sdk/utils/rendering/viewer/__init__.py +0 -21
  147. glaip_sdk/utils/rendering/viewer/presenter.py +0 -184
  148. glaip_sdk/utils/resource_refs.py +0 -195
  149. glaip_sdk/utils/run_renderer.py +0 -41
  150. glaip_sdk/utils/runtime_config.py +0 -425
  151. glaip_sdk/utils/serialization.py +0 -424
  152. glaip_sdk/utils/sync.py +0 -142
  153. glaip_sdk/utils/tool_detection.py +0 -33
  154. glaip_sdk/utils/validation.py +0 -264
  155. glaip_sdk-0.6.12.dist-info/RECORD +0 -159
  156. glaip_sdk-0.6.12.dist-info/entry_points.txt +0 -3
@@ -1,74 +0,0 @@
1
- """Summary panel helpers shared between renderer and viewer.
2
-
3
- Authors:
4
- Raymond Christopher (raymond.christopher@gdplabs.id)
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- from collections.abc import Mapping
10
- from typing import Any
11
-
12
- from glaip_sdk.utils.rendering.layout.transcript import (
13
- DEFAULT_TRANSCRIPT_THEME,
14
- build_transcript_snapshot,
15
- build_transcript_view,
16
- normalise_meta_payload,
17
- )
18
- from glaip_sdk.utils.rendering.state import RendererState
19
- from glaip_sdk.utils.rendering.steps import StepManager
20
-
21
-
22
- def render_summary_panels(
23
- state: RendererState,
24
- steps: StepManager,
25
- *,
26
- theme: str | None = None,
27
- summary_window: int | None = None,
28
- include_query_panel: bool = True,
29
- include_final_panel: bool = True,
30
- step_status_overrides: dict[str, str] | None = None,
31
- ) -> list[Any]:
32
- """Return shared summary panels for renderer and offline viewer."""
33
- resolved_theme = theme or DEFAULT_TRANSCRIPT_THEME
34
- snapshot_source = state.to_snapshot() if hasattr(state, "to_snapshot") else state
35
- if isinstance(snapshot_source, Mapping):
36
- raw_meta = snapshot_source.get("meta")
37
- else:
38
- raw_meta = getattr(state, "meta", None)
39
- snapshot_meta = normalise_meta_payload(raw_meta)
40
- snapshot = build_transcript_snapshot(
41
- snapshot_source,
42
- steps,
43
- meta=snapshot_meta,
44
- summary_window=summary_window,
45
- theme=resolved_theme,
46
- step_status_overrides=step_status_overrides,
47
- )
48
- _header, body = build_transcript_view(snapshot, theme=resolved_theme)
49
-
50
- return [
51
- renderable
52
- for renderable in body
53
- if _should_include_summary_panel(
54
- renderable,
55
- include_query_panel=include_query_panel,
56
- include_final_panel=include_final_panel,
57
- )
58
- ]
59
-
60
-
61
- def _should_include_summary_panel(
62
- renderable: Any,
63
- *,
64
- include_query_panel: bool,
65
- include_final_panel: bool,
66
- ) -> bool:
67
- """Return True when the panel should be included in the summary list."""
68
- title = getattr(renderable, "title", "")
69
- normalised = title.lower() if isinstance(title, str) else ""
70
- if not include_query_panel and normalised == "user request":
71
- return False
72
- if not include_final_panel and normalised.startswith("final result"):
73
- return False
74
- return True
@@ -1,606 +0,0 @@
1
- """Shared transcript presentation helpers.
2
-
3
- Authors:
4
- Raymond Christopher (raymond.christopher@gdplabs.id)
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import json
10
- from collections.abc import Mapping, Sequence
11
- from dataclasses import dataclass
12
- from typing import Any
13
-
14
- from rich.console import Group
15
- from rich.markdown import Markdown
16
- from rich.text import Text
17
-
18
- from glaip_sdk.icons import ICON_AGENT
19
- from glaip_sdk.rich_components import AIPPanel
20
- from glaip_sdk.utils.rendering.layout.panels import create_final_panel
21
- from glaip_sdk.utils.rendering.renderer.summary_window import clamp_step_nodes
22
- from glaip_sdk.utils.rendering.state import RendererState
23
- from glaip_sdk.utils.rendering.steps import (
24
- StepManager,
25
- StepPresentation,
26
- build_connector_prefix,
27
- format_step,
28
- format_step_label,
29
- humanize_tool_name,
30
- )
31
-
32
- DEFAULT_TRANSCRIPT_THEME = "dark"
33
- _NO_STEPS_TEXT = Text("No steps yet", style="dim")
34
-
35
-
36
- @dataclass(slots=True)
37
- class TranscriptGlyphs:
38
- """Glyph overrides for transcript presentation."""
39
-
40
- branch_fill: str = "│ "
41
- branch_empty: str = " "
42
- branch_item: str = "├─ "
43
- branch_last: str = "└─ "
44
- query_prefix: str = " "
45
-
46
-
47
- @dataclass(slots=True)
48
- class TranscriptRow:
49
- """Renderable row metadata for the transcript/summary tree."""
50
-
51
- prefix: str
52
- presentation: StepPresentation
53
-
54
-
55
- @dataclass(slots=True)
56
- class TranscriptSnapshot:
57
- """Snapshot consumed by presenter/viewer components."""
58
-
59
- rows: list[TranscriptRow]
60
- final_panel: Any | None
61
- events: list[dict[str, Any]]
62
- agent_label: str | None = None
63
- model_label: str | None = None
64
- run_id: str | None = None
65
- query_text: str | None = None
66
- duration_text: str | None = None
67
- window_header: Text | None = None
68
- window_footer: Text | None = None
69
-
70
-
71
- def format_final_panel_title(state: RendererState | Mapping[str, Any], base_title: str = "Final Result") -> str:
72
- """Return the final panel title including duration if available."""
73
- if isinstance(state, RendererState):
74
- duration_text = state.final_duration_text
75
- else:
76
- duration_text = state.get("final_duration_text") if isinstance(state, Mapping) else None
77
- if duration_text:
78
- return f"{base_title} · {duration_text}"
79
- return base_title
80
-
81
-
82
- def build_final_panel(
83
- state: RendererState | Mapping[str, Any],
84
- *,
85
- theme: str = DEFAULT_TRANSCRIPT_THEME,
86
- title: str | None = None,
87
- ) -> Any | None:
88
- """Create a Rich panel for the renderer/viewer final output."""
89
- if isinstance(state, RendererState):
90
- body = (state.final_text or state.buffer.render() or "").strip()
91
- else:
92
- final_text = str(state.get("final_text", "")) if isinstance(state, Mapping) else ""
93
- buffer_text = str(state.get("buffer_text", "")) if isinstance(state, Mapping) else ""
94
- body = (final_text or buffer_text).strip()
95
- if not body:
96
- return None
97
- panel_title = title or format_final_panel_title(state)
98
- return create_final_panel(body, title=panel_title, theme=theme)
99
-
100
-
101
- def build_transcript_snapshot(
102
- state: RendererState | Mapping[str, Any],
103
- steps: StepManager,
104
- *,
105
- glyphs: TranscriptGlyphs | None = None,
106
- query_text: str | None = None,
107
- meta: Mapping[str, Any] | Sequence[tuple[str, Any]] | None = None,
108
- summary_window: int | None = None,
109
- theme: str = DEFAULT_TRANSCRIPT_THEME,
110
- step_status_overrides: dict[str, str] | None = None,
111
- ) -> TranscriptSnapshot:
112
- """Compose a snapshot consumable by renderers/viewers alike."""
113
- glyphs = glyphs or TranscriptGlyphs()
114
- final_text, buffer_text, events, meta_payload, duration_text = _resolve_state_payload(state, meta)
115
- query_value = query_text or extract_query_from_meta(meta_payload)
116
- rows, window_header, window_footer = _build_rows(
117
- steps,
118
- glyphs,
119
- meta_payload,
120
- summary_window,
121
- query_text=query_value,
122
- step_status_overrides=step_status_overrides,
123
- )
124
- if not rows:
125
- stored = meta_payload.get("transcript_steps")
126
- if isinstance(stored, list) and stored:
127
- rows, window_header, window_footer = _rows_from_stored_steps(stored)
128
- panel_state: RendererState | Mapping[str, Any]
129
- if isinstance(state, RendererState):
130
- panel_state = state
131
- else:
132
- panel_state = {
133
- "final_text": final_text,
134
- "buffer_text": buffer_text,
135
- "final_duration_text": duration_text,
136
- }
137
- final_panel = build_final_panel(panel_state, theme=theme)
138
-
139
- return TranscriptSnapshot(
140
- rows=rows,
141
- final_panel=final_panel,
142
- events=events,
143
- agent_label=_friendly_agent_label(meta_payload),
144
- model_label=meta_payload.get("model"),
145
- run_id=meta_payload.get("run_id"),
146
- query_text=query_value,
147
- duration_text=duration_text,
148
- window_header=window_header,
149
- window_footer=window_footer,
150
- )
151
-
152
-
153
- def build_transcript_view(
154
- snapshot: TranscriptSnapshot,
155
- *,
156
- theme: str = DEFAULT_TRANSCRIPT_THEME,
157
- ) -> tuple[list[Any], list[Any]]:
158
- """Return header + body renderables for a transcript snapshot."""
159
- header_renderables: list[Any] = []
160
- body_renderables: list[Any] = []
161
-
162
- header_text = _compose_header_text(snapshot)
163
- if header_text is not None:
164
- if theme != DEFAULT_TRANSCRIPT_THEME:
165
- header_text.style = "bold black"
166
- header_renderables.append(header_text)
167
-
168
- if snapshot.query_text:
169
- body_renderables.append(
170
- _build_query_panel(snapshot.query_text),
171
- )
172
-
173
- body_renderables.append(
174
- _build_steps_panel(
175
- snapshot.rows,
176
- window_header=snapshot.window_header,
177
- window_footer=snapshot.window_footer,
178
- theme=theme,
179
- )
180
- )
181
-
182
- if snapshot.final_panel is not None:
183
- body_renderables.append(snapshot.final_panel)
184
-
185
- return header_renderables, body_renderables
186
-
187
-
188
- def render_final_panel(
189
- console: Any,
190
- state: RendererState | Mapping[str, Any],
191
- *,
192
- theme: str = DEFAULT_TRANSCRIPT_THEME,
193
- title: str | None = None,
194
- ) -> bool:
195
- """Print the shared final panel, returning True when rendered."""
196
- panel = build_final_panel(state, theme=theme, title=title)
197
- if panel is None:
198
- return False
199
- console.print(panel)
200
- console.print()
201
- return True
202
-
203
-
204
- __all__ = [
205
- "DEFAULT_TRANSCRIPT_THEME",
206
- "TranscriptGlyphs",
207
- "TranscriptRow",
208
- "TranscriptSnapshot",
209
- "build_transcript_snapshot",
210
- "build_transcript_view",
211
- "render_final_panel",
212
- "build_final_panel",
213
- "format_final_panel_title",
214
- "extract_query_from_meta",
215
- ]
216
-
217
-
218
- def _resolve_state_payload(
219
- state: RendererState | Mapping[str, Any],
220
- meta_override: Mapping[str, Any] | Sequence[tuple[str, Any]] | None,
221
- ) -> tuple[str, str, list[dict[str, Any]], dict[str, Any], str | None]:
222
- if isinstance(state, RendererState):
223
- final_text = state.final_text
224
- buffer_text = state.buffer.render()
225
- events = list(state.events)
226
- meta_payload = normalise_meta_payload(meta_override or getattr(state, "meta", None))
227
- duration_text = state.final_duration_text
228
- else:
229
- mapping_state = dict(state) if isinstance(state, Mapping) else {}
230
- final_text = str(mapping_state.get("final_text") or "")
231
- buffer_text = str(mapping_state.get("buffer_text") or "")
232
- events = list(mapping_state.get("events") or [])
233
- base_meta = mapping_state.get("meta")
234
- meta_payload = normalise_meta_payload(meta_override or base_meta)
235
- duration_text = mapping_state.get("final_duration_text")
236
- return final_text, buffer_text, events, meta_payload, duration_text
237
-
238
-
239
- def _build_rows(
240
- steps: StepManager,
241
- glyphs: TranscriptGlyphs,
242
- meta_payload: dict[str, Any],
243
- summary_window: int | None,
244
- *,
245
- query_text: str | None,
246
- step_status_overrides: dict[str, str] | None = None,
247
- ) -> tuple[list[TranscriptRow], Text | None, Text | None]:
248
- nodes = list(steps.iter_tree())
249
- header_notice: Text | None = None
250
- footer_notice: Text | None = None
251
- if summary_window is not None and summary_window > 0:
252
- nodes, header_notice, footer_notice = _apply_summary_window(steps, nodes, summary_window)
253
-
254
- rows: list[TranscriptRow] = []
255
- for index, (step_id, branch_state) in enumerate(nodes):
256
- row = _create_step_row(
257
- steps,
258
- glyphs,
259
- meta_payload,
260
- step_id,
261
- branch_state,
262
- step_status_overrides=step_status_overrides,
263
- )
264
- if row is not None:
265
- rows.append(row)
266
- if index == 0:
267
- query_row = _create_query_hint_row(glyphs, query_text)
268
- if query_row is not None:
269
- rows.append(query_row)
270
-
271
- return rows, header_notice, footer_notice
272
-
273
-
274
- def _apply_summary_window(
275
- steps: StepManager,
276
- nodes: list[tuple[str, tuple[bool, ...]]],
277
- summary_window: int,
278
- ) -> tuple[list[tuple[str, tuple[bool, ...]]], Text | None, Text | None]:
279
- """Apply summary window clamping to step nodes."""
280
-
281
- def _get_label(step_id: str) -> str:
282
- step = steps.by_id.get(step_id)
283
- return format_step_label(step) if step else ""
284
-
285
- def _get_parent(step_id: str) -> str | None:
286
- step = steps.by_id.get(step_id)
287
- return step.parent_id if step else None
288
-
289
- return clamp_step_nodes(
290
- nodes,
291
- window=summary_window,
292
- get_label=_get_label,
293
- get_parent=_get_parent,
294
- )
295
-
296
-
297
- def _create_step_row(
298
- steps: StepManager,
299
- glyphs: TranscriptGlyphs,
300
- meta_payload: dict[str, Any],
301
- step_id: str,
302
- branch_state: tuple[bool, ...],
303
- *,
304
- step_status_overrides: dict[str, str] | None = None,
305
- ) -> TranscriptRow | None:
306
- """Create a transcript row from a step."""
307
- step = steps.by_id.get(step_id)
308
- if not step:
309
- return None
310
- override = None
311
- if not branch_state and _should_override_root_label(meta_payload):
312
- override = _friendly_root_label(meta_payload, step, getattr(step, "display_label", None))
313
- presentation = format_step(step, glyphs=glyphs, label=override)
314
- if step_status_overrides:
315
- status_text = step_status_overrides.get(step_id)
316
- if status_text:
317
- presentation.status_text = status_text
318
- prefix = build_connector_prefix(branch_state)
319
- return TranscriptRow(prefix=prefix, presentation=presentation)
320
-
321
-
322
- def _create_query_hint_row(glyphs: TranscriptGlyphs, query_text: str | None) -> TranscriptRow | None:
323
- """Create a query hint row mirroring the documented transcript output."""
324
- if not query_text:
325
- return None
326
- return TranscriptRow(
327
- prefix=glyphs.query_prefix,
328
- presentation=StepPresentation(
329
- step_id="query",
330
- title=query_text,
331
- glyph=None,
332
- status_style=None,
333
- args_text=None,
334
- failure_reason=None,
335
- duration_ms=None,
336
- ),
337
- )
338
-
339
-
340
- def _rows_from_stored_steps(entries: list[dict[str, Any]]) -> tuple[list[TranscriptRow], Text | None, Text | None]:
341
- rows: list[TranscriptRow] = []
342
- for index, entry in enumerate(entries):
343
- title = entry.get("display_name") or entry.get("name") or "Step"
344
- finished = entry.get("status") == "finished"
345
- duration_ms = entry.get("duration_ms")
346
- presentation = StepPresentation(
347
- step_id=f"stored-{index}",
348
- title=str(title),
349
- glyph="✓" if finished else None,
350
- status_style="green" if finished else None,
351
- args_text=None,
352
- failure_reason=None,
353
- duration_ms=duration_ms,
354
- )
355
- rows.append(TranscriptRow(prefix=" ", presentation=presentation))
356
- return rows, None, None
357
-
358
-
359
- def _compose_header_text(snapshot: TranscriptSnapshot) -> Text | None:
360
- parts: list[str] = []
361
- if snapshot.agent_label:
362
- parts.append(snapshot.agent_label)
363
- if snapshot.model_label:
364
- parts.append(snapshot.model_label)
365
- if snapshot.duration_text:
366
- parts.append(snapshot.duration_text)
367
- if snapshot.run_id:
368
- parts.append(snapshot.run_id)
369
- if not parts:
370
- return None
371
- return Text(" · ".join(parts), style="bold")
372
-
373
-
374
- def _build_query_panel(query_text: str) -> AIPPanel:
375
- """Build a query panel."""
376
- return AIPPanel(
377
- Markdown(f"**Query:** {query_text.strip()}"),
378
- title="User Request",
379
- border_style="#d97706",
380
- padding=(0, 1),
381
- )
382
-
383
-
384
- def _build_steps_panel(
385
- rows: Sequence[TranscriptRow],
386
- *,
387
- window_header: Text | None = None,
388
- window_footer: Text | None = None,
389
- theme: str = DEFAULT_TRANSCRIPT_THEME,
390
- ) -> AIPPanel:
391
- if not rows:
392
- steps_body: Text | Group = _NO_STEPS_TEXT.copy()
393
- else:
394
- rendered = [_format_row_text(row) for row in rows]
395
- style = "dim" if theme == DEFAULT_TRANSCRIPT_THEME else "default"
396
- steps_body = Text("\n".join(rendered), style=style)
397
-
398
- renderables: list[Any] = []
399
- if window_header is not None:
400
- renderables.append(window_header)
401
- renderables.append(steps_body)
402
- if window_footer is not None:
403
- renderables.append(window_footer)
404
-
405
- if len(renderables) == 1:
406
- body: Any = renderables[0]
407
- else:
408
- body = Group(*renderables)
409
-
410
- return AIPPanel(body, title="Steps", border_style="blue")
411
-
412
-
413
- def _format_row_text(row: TranscriptRow) -> str:
414
- prefix = row.prefix
415
- title, summary = _split_label(row.presentation.title)
416
- line = f"{prefix}{title}".rstrip()
417
-
418
- args_lines = _extract_args_lines(row)
419
- has_args = bool(args_lines)
420
-
421
- if summary:
422
- line += f" — {_truncate_summary(summary)}"
423
- elif has_args:
424
- line += " —"
425
-
426
- badge = _format_duration_badge(row.presentation.duration_ms)
427
- status_text = row.presentation.status_text
428
- if status_text:
429
- line += f" {status_text}"
430
- elif badge:
431
- line += f" {badge}"
432
-
433
- if row.presentation.glyph:
434
- line += f" {row.presentation.glyph}"
435
-
436
- if row.presentation.failure_reason:
437
- line += f" {row.presentation.failure_reason}"
438
-
439
- if has_args:
440
- for args_line in args_lines:
441
- line += f"\n{prefix} {args_line}"
442
-
443
- return line
444
-
445
-
446
- def _format_duration_badge(duration_ms: int | None) -> str | None:
447
- if duration_ms is None:
448
- return None
449
- try:
450
- duration_ms = int(duration_ms)
451
- except Exception:
452
- return None
453
- if duration_ms <= 0:
454
- value = "<1ms"
455
- elif duration_ms < 1000:
456
- value = f"{duration_ms}ms"
457
- else:
458
- seconds = duration_ms / 1000
459
- value = f"{seconds:.2f}s"
460
- return f"[{value}]"
461
-
462
-
463
- def _extract_args_lines(row: TranscriptRow) -> list[str]:
464
- args_text = row.presentation.args_text
465
- if _should_skip_args_summary(row, args_text):
466
- return []
467
-
468
- parsed = _parse_args_payload(args_text or "")
469
- title = row.presentation.title or ""
470
-
471
- if isinstance(parsed, dict) and parsed:
472
- if title.startswith(ICON_AGENT) and set(parsed.keys()) == {"query"}:
473
- return [str(parsed["query"])]
474
- return [f"{key}: {json.dumps(value, ensure_ascii=False)}" for key, value in parsed.items()]
475
-
476
- if isinstance(parsed, list):
477
- return [json.dumps(parsed, ensure_ascii=False)]
478
-
479
- return [args_text or ""]
480
-
481
-
482
- def _should_skip_args_summary(row: TranscriptRow, args_text: str | None) -> bool:
483
- if not args_text or args_text == "{}":
484
- return True
485
- title = (row.presentation.title or "").strip()
486
- if title.startswith("💭 Thinking…"):
487
- return True
488
- if not row.prefix.strip():
489
- return True
490
- if args_text.strip() == '{"reason":"deterministic_timeline"}':
491
- return True
492
- return False
493
-
494
-
495
- def _parse_args_payload(args_text: str) -> Any | None:
496
- stripped = args_text.lstrip()
497
- if stripped.startswith("{") or stripped.startswith("["):
498
- try:
499
- return json.loads(args_text)
500
- except Exception:
501
- return None
502
- return None
503
-
504
-
505
- def _split_label(label: Any) -> tuple[str, str | None]:
506
- if not isinstance(label, str):
507
- try:
508
- label = str(label)
509
- except Exception:
510
- return "Step", None
511
- if "—" not in label:
512
- return label, None
513
- title, summary = label.split("—", 1)
514
- return title.strip(), summary.strip()
515
-
516
-
517
- def _truncate_summary(summary: str, limit: int = 80) -> str:
518
- if len(summary) <= limit:
519
- return summary
520
- return summary[: limit - 1] + "…"
521
-
522
-
523
- def _friendly_agent_label(meta: Mapping[str, Any]) -> str | None:
524
- """Return a user-facing agent label for headers."""
525
- raw_name = _string_or_none(meta.get("agent_name"))
526
- if raw_name:
527
- friendly = humanize_tool_name(raw_name)
528
- if friendly:
529
- return friendly
530
- return _string_or_none(meta.get("agent_id"))
531
-
532
-
533
- def _friendly_root_label(meta: dict[str, Any], step: Any, fallback: str | None) -> str:
534
- fallback_label = _string_or_none(fallback)
535
- raw_agent_name = _string_or_none(meta.get("agent_name"))
536
- agent_name = humanize_tool_name(raw_agent_name) if raw_agent_name else None
537
- if agent_name:
538
- agent_name = agent_name.title()
539
- agent_name = agent_name or fallback_label
540
- agent_id = _string_or_none(meta.get("agent_id") or getattr(step, "name", ""))
541
-
542
- if not agent_name:
543
- return fallback_label or agent_id or ICON_AGENT
544
-
545
- parts = [ICON_AGENT, agent_name]
546
- if agent_id and agent_id != agent_name:
547
- parts.append(f"({agent_id})")
548
- return " ".join(parts)
549
-
550
-
551
- def extract_query_from_meta(meta: Mapping[str, Any] | None) -> str | None:
552
- """Return the canonical query string embedded in renderer metadata."""
553
- if not meta:
554
- return None
555
-
556
- payload = dict(meta)
557
- nested_meta = payload.get("meta") or {}
558
- candidate = (
559
- payload.get("input_message")
560
- or payload.get("query")
561
- or payload.get("message")
562
- or nested_meta.get("input_message")
563
- )
564
- if isinstance(candidate, str):
565
- candidate = candidate.strip()
566
- return candidate or None
567
-
568
-
569
- def _should_override_root_label(meta: Mapping[str, Any] | None) -> bool:
570
- if not meta:
571
- return False
572
- if meta.get("agent_name") or meta.get("agent_id"):
573
- return True
574
- return False
575
-
576
-
577
- def _string_or_none(value: Any) -> str | None:
578
- if value is None:
579
- return None
580
- try:
581
- text = str(value).strip()
582
- except Exception:
583
- return None
584
- return text or None
585
-
586
-
587
- def normalise_meta_payload(meta: Any) -> dict[str, Any]:
588
- """Return a defensive dictionary for arbitrary metadata payloads."""
589
- if not meta:
590
- return {}
591
- if isinstance(meta, dict):
592
- return dict(meta)
593
- if isinstance(meta, Mapping):
594
- try:
595
- return dict(meta)
596
- except Exception:
597
- return {}
598
- if isinstance(meta, Sequence) and not isinstance(meta, (str, bytes)):
599
- try:
600
- return dict(meta)
601
- except Exception:
602
- return {}
603
- try:
604
- return dict(meta)
605
- except Exception:
606
- return {}