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,264 +0,0 @@
1
- """Formatting helpers for renderer.
2
-
3
- Authors:
4
- Raymond Christopher (raymond.christopher@gdplabs.id)
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import json
10
- import re
11
- from collections.abc import Callable
12
- from typing import Any
13
-
14
- from glaip_sdk.icons import (
15
- ICON_AGENT_STEP,
16
- ICON_DELEGATE,
17
- ICON_STATUS_FAILED,
18
- ICON_STATUS_SUCCESS,
19
- ICON_STATUS_WARNING,
20
- ICON_TOOL_STEP,
21
- )
22
-
23
- # Constants for argument formatting
24
- DEFAULT_ARGS_MAX_LEN = 100
25
- IMPORTANT_PARAMETER_KEYS = [
26
- "model",
27
- "temperature",
28
- "max_tokens",
29
- "top_p",
30
- "frequency_penalty",
31
- "presence_penalty",
32
- "query",
33
- "url",
34
- ]
35
- SECRET_VALUE_PATTERNS = [
36
- re.compile(r"sk-[a-zA-Z0-9]{20,}"), # OpenAI API keys (at least 20 chars)
37
- re.compile(r"ya29\.[a-zA-Z0-9_-]+"), # Google OAuth tokens
38
- re.compile(r"ghp_[a-zA-Z0-9]{20,}"), # GitHub tokens (at least 20 chars)
39
- re.compile(r"gho_[a-zA-Z0-9]{20,}"), # GitHub tokens (at least 20 chars)
40
- re.compile(r"ghu_[a-zA-Z0-9]{20,}"), # GitHub tokens (at least 20 chars)
41
- re.compile(r"ghs_[a-zA-Z0-9]{20,}"), # GitHub tokens (at least 20 chars)
42
- re.compile(r"ghr_[a-zA-Z0-9]{20,}"), # GitHub tokens (at least 20 chars)
43
- re.compile(r"eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+"), # JWT tokens
44
- ]
45
- SENSITIVE_PATTERNS = re.compile(
46
- r"(?:password|secret|token|key|api_key)(?:\s*[:=]\s*[^\s,}]+)?",
47
- re.IGNORECASE,
48
- )
49
- SECRET_MASK = "••••••"
50
- STATUS_GLYPHS = {
51
- "success": ICON_STATUS_SUCCESS,
52
- "failed": ICON_STATUS_FAILED,
53
- "warning": ICON_STATUS_WARNING,
54
- }
55
-
56
-
57
- def _truncate_string(s: str, max_len: int) -> str:
58
- """Truncate a string to a maximum length."""
59
- if len(s) <= max_len:
60
- return s
61
- return s[: max_len - 3] + "…"
62
-
63
-
64
- def mask_secrets_in_string(text: str) -> str:
65
- """Mask sensitive information in a string."""
66
- result = text
67
- for pattern in SECRET_VALUE_PATTERNS:
68
- result = re.sub(pattern, SECRET_MASK, result)
69
- return result
70
-
71
-
72
- def redact_sensitive(text: str | dict | list) -> str | dict | list:
73
- """Redact sensitive information in a string, dict, or list."""
74
- if isinstance(text, dict):
75
- return _redact_dict_values(text)
76
- elif isinstance(text, list):
77
- return _redact_list_items(text)
78
- elif isinstance(text, str):
79
- return _redact_string_content(text)
80
- else:
81
- return text
82
-
83
-
84
- def _redact_dict_values(text: dict) -> dict:
85
- """Recursively process dictionary values and redact sensitive keys."""
86
- result = {}
87
- for key, value in text.items():
88
- if _is_sensitive_key(key):
89
- result[key] = SECRET_MASK
90
- elif _should_recurse_redaction(value):
91
- result[key] = redact_sensitive(value)
92
- else:
93
- result[key] = value
94
- return result
95
-
96
-
97
- def _redact_list_items(text: list) -> list:
98
- """Recursively process list items."""
99
- return [redact_sensitive(item) for item in text]
100
-
101
-
102
- def _redact_string_content(text: str) -> str:
103
- """Process string - first mask secrets, then redact sensitive patterns."""
104
- result = text
105
- # First mask secrets
106
- for pattern in SECRET_VALUE_PATTERNS:
107
- result = re.sub(pattern, SECRET_MASK, result)
108
- # Then redact sensitive patterns
109
- result = re.sub(
110
- SENSITIVE_PATTERNS,
111
- lambda m: m.group(0).split("=")[0] + "=" + SECRET_MASK,
112
- result,
113
- )
114
- return result
115
-
116
-
117
- def _is_sensitive_key(key: str) -> bool:
118
- """Check if a key contains sensitive information."""
119
- key_lower = key.lower()
120
- return any(sensitive in key_lower for sensitive in ["password", "secret", "token", "key", "api_key"])
121
-
122
-
123
- def _should_recurse_redaction(value: Any) -> bool:
124
- """Check if a value should be recursively processed."""
125
- return isinstance(value, (dict, list)) or isinstance(value, str)
126
-
127
-
128
- def glyph_for_status(icon_key: str | None) -> str | None:
129
- """Return glyph representing a step status icon key."""
130
- if not icon_key:
131
- return None
132
- return STATUS_GLYPHS.get(icon_key)
133
-
134
-
135
- def normalise_display_label(label: str | None) -> str:
136
- """Return a user facing label or the Unknown fallback."""
137
- if not isinstance(label, str):
138
- text = ""
139
- else:
140
- text = label.strip()
141
- return text or "Unknown step detail"
142
-
143
-
144
- def pretty_args(args: dict | None, max_len: int = DEFAULT_ARGS_MAX_LEN) -> str:
145
- """Format arguments in a pretty way."""
146
- if not args:
147
- return "{}"
148
-
149
- # Mask secrets first by recursively processing the structure
150
- try:
151
- masked_args = redact_sensitive(args)
152
- except Exception:
153
- # Fallback to original args if redact_sensitive fails
154
- masked_args = args
155
-
156
- # Convert to JSON string and truncate if needed
157
- try:
158
- args_str = json.dumps(masked_args, ensure_ascii=False, separators=(",", ":"))
159
- return _truncate_string(args_str, max_len)
160
- except Exception:
161
- # Fallback to string representation if JSON serialization fails
162
- args_str = str(masked_args)
163
- return _truncate_string(args_str, max_len)
164
-
165
-
166
- def pretty_out(output: any, max_len: int = DEFAULT_ARGS_MAX_LEN) -> str:
167
- """Format output in a pretty way."""
168
- if output is None:
169
- return "None"
170
-
171
- if isinstance(output, str):
172
- # Mask secrets in string output
173
- masked_output = mask_secrets_in_string(output)
174
-
175
- # Remove LaTeX commands (common math expressions)
176
- masked_output = re.sub(r"\\[a-zA-Z]+\{[^}]*\}", "", masked_output)
177
- masked_output = re.sub(r"\\[a-zA-Z]+", "", masked_output)
178
-
179
- # Strip leading/trailing whitespace but preserve internal spacing
180
- masked_output = masked_output.strip()
181
- # Replace newlines with spaces to preserve formatting
182
- masked_output = masked_output.replace("\n", " ")
183
- return _truncate_string(masked_output, max_len)
184
-
185
- # For other types, convert to string and truncate
186
- output_str = str(output)
187
- return _truncate_string(output_str, max_len)
188
-
189
-
190
- def get_step_icon(step_kind: str) -> str:
191
- """Get the appropriate icon for a step kind."""
192
- if step_kind == "tool":
193
- return ICON_TOOL_STEP
194
- if step_kind == "delegate":
195
- return ICON_DELEGATE
196
- if step_kind == "agent":
197
- return ICON_AGENT_STEP
198
- return ""
199
-
200
-
201
- def is_step_finished(step: Any) -> bool:
202
- """Check if a step is finished.
203
-
204
- Args:
205
- step: The step object to check
206
-
207
- Returns:
208
- True if the step status is "finished", False otherwise
209
- """
210
- return getattr(step, "status", None) == "finished"
211
-
212
-
213
- def format_main_title(
214
- header_text: str,
215
- has_running_steps: bool,
216
- get_spinner_char: Callable[[], str],
217
- ) -> str:
218
- """Generate the main panel title with dynamic status indicators.
219
-
220
- Args:
221
- header_text: The header text from the renderer
222
- has_running_steps: Whether there are running steps
223
- get_spinner_char: Function to get spinner character
224
-
225
- Returns:
226
- A formatted title string showing the agent name and status.
227
- """
228
- # base name
229
- name = (header_text or "").strip() or "Assistant"
230
- # strip leading rule emojis if present
231
- name = name.replace("—", " ").strip()
232
- # spinner if still working
233
- mark = "✓" if not has_running_steps else get_spinner_char()
234
- return f"{name} {mark}"
235
-
236
-
237
- def print_header_once(
238
- console: Any,
239
- text: str,
240
- last_header: str,
241
- rules_enabled: bool,
242
- style: str | None = None,
243
- ) -> str:
244
- """Print header text only when it changes to avoid duplicate output.
245
-
246
- Args:
247
- console: Rich console instance
248
- text: The header text to display
249
- last_header: The last header text that was printed
250
- rules_enabled: Whether header rules are enabled
251
- style: Optional Rich style for the header rule
252
-
253
- Returns:
254
- The updated last_header value
255
- """
256
- if not rules_enabled:
257
- return text
258
- if text and text != last_header:
259
- try:
260
- console.rule(text, style=style)
261
- except Exception:
262
- console.print(text)
263
- return text
264
- return last_header
@@ -1,64 +0,0 @@
1
- """Layout utilities exposed for renderer/viewer consumers.
2
-
3
- Authors:
4
- Raymond Christopher (raymond.christopher@gdplabs.id)
5
- """
6
-
7
- from glaip_sdk.utils.rendering.layout.panels import (
8
- create_context_panel,
9
- create_final_panel,
10
- create_main_panel,
11
- create_tool_panel,
12
- )
13
- from glaip_sdk.utils.rendering.layout.progress import (
14
- TrailingSpinnerLine,
15
- build_progress_footer,
16
- format_elapsed_time,
17
- format_tool_title,
18
- format_working_indicator,
19
- get_spinner,
20
- get_spinner_char,
21
- is_delegation_tool,
22
- )
23
- from glaip_sdk.utils.rendering.layout.transcript import (
24
- DEFAULT_TRANSCRIPT_THEME,
25
- TranscriptGlyphs,
26
- TranscriptRow,
27
- TranscriptSnapshot,
28
- build_final_panel,
29
- build_transcript_snapshot,
30
- build_transcript_view,
31
- extract_query_from_meta,
32
- format_final_panel_title,
33
- render_final_panel,
34
- )
35
- from glaip_sdk.utils.rendering.layout.summary import render_summary_panels
36
-
37
- __all__ = [
38
- # Panels
39
- "create_context_panel",
40
- "create_final_panel",
41
- "create_main_panel",
42
- "create_tool_panel",
43
- "render_summary_panels",
44
- # Progress
45
- "TrailingSpinnerLine",
46
- "build_progress_footer",
47
- "format_elapsed_time",
48
- "format_tool_title",
49
- "format_working_indicator",
50
- "get_spinner",
51
- "get_spinner_char",
52
- "is_delegation_tool",
53
- # Transcript
54
- "DEFAULT_TRANSCRIPT_THEME",
55
- "TranscriptGlyphs",
56
- "TranscriptRow",
57
- "TranscriptSnapshot",
58
- "build_final_panel",
59
- "build_transcript_snapshot",
60
- "build_transcript_view",
61
- "extract_query_from_meta",
62
- "format_final_panel_title",
63
- "render_final_panel",
64
- ]
@@ -1,156 +0,0 @@
1
- """Panel rendering utilities for the renderer package.
2
-
3
- Authors:
4
- Raymond Christopher (raymond.christopher@gdplabs.id)
5
- """
6
-
7
- from __future__ import annotations
8
-
9
-
10
- from rich.align import Align
11
- from rich.markdown import Markdown
12
- from rich.spinner import Spinner
13
- from rich.text import Text
14
-
15
- from glaip_sdk.branding import INFO, PRIMARY, SUCCESS, WARNING
16
- from glaip_sdk.rich_components import AIPPanel
17
-
18
-
19
- def _spinner_renderable(message: str = "Processing...") -> Align:
20
- """Build a Rich spinner renderable for loading placeholders."""
21
- spinner = Spinner(
22
- "dots",
23
- text=Text(f" {message}", style="dim"),
24
- style=INFO,
25
- )
26
- return Align.left(spinner)
27
-
28
-
29
- def create_main_panel(content: str, title: str, theme: str = "dark") -> AIPPanel:
30
- """Create a main content panel.
31
-
32
- Args:
33
- content: The content to display
34
- title: Panel title
35
- theme: Color theme ("dark" or "light")
36
-
37
- Returns:
38
- Rich Panel instance
39
- """
40
- if content.strip():
41
- return AIPPanel(
42
- Markdown(content, code_theme=("monokai" if theme == "dark" else "github")),
43
- title=title,
44
- border_style=SUCCESS,
45
- )
46
- else:
47
- return AIPPanel(
48
- _spinner_renderable(),
49
- title=title,
50
- border_style=SUCCESS,
51
- )
52
-
53
-
54
- def create_tool_panel(
55
- title: str,
56
- content: str,
57
- status: str = "running",
58
- theme: str = "dark",
59
- is_delegation: bool = False,
60
- *,
61
- spinner_message: str | None = None,
62
- ) -> AIPPanel:
63
- """Create a tool execution panel.
64
-
65
- Args:
66
- title: Tool name/title
67
- content: Tool output content
68
- status: Tool execution status
69
- theme: Color theme
70
- is_delegation: Whether this is a delegation tool
71
- spinner_message: Optional custom message to show alongside the spinner
72
-
73
- Returns:
74
- Rich Panel instance
75
- """
76
- mark = "✓" if status == "finished" else ""
77
- border_style = WARNING if is_delegation else PRIMARY
78
-
79
- if content:
80
- body_renderable = Markdown(
81
- content,
82
- code_theme=("monokai" if theme == "dark" else "github"),
83
- )
84
- elif status == "running":
85
- body_renderable = _spinner_renderable(spinner_message or f"{title} running...")
86
- else:
87
- body_renderable = Text("No output yet.", style="dim")
88
-
89
- title_text = f"{title} {mark}".rstrip()
90
-
91
- return AIPPanel(
92
- body_renderable,
93
- title=title_text,
94
- border_style=border_style,
95
- )
96
-
97
-
98
- def create_context_panel(
99
- title: str,
100
- content: str,
101
- status: str = "running",
102
- theme: str = "dark",
103
- is_delegation: bool = False,
104
- ) -> AIPPanel:
105
- """Create a context/sub-agent panel.
106
-
107
- Args:
108
- title: Context title
109
- content: Context content
110
- status: Execution status
111
- theme: Color theme
112
- is_delegation: Whether this is a delegation context
113
-
114
- Returns:
115
- Rich Panel instance
116
- """
117
- mark = "✓" if status == "finished" else ""
118
- border_style = WARNING if is_delegation else INFO
119
-
120
- title_text = f"{title} {mark}".rstrip()
121
-
122
- return AIPPanel(
123
- Markdown(
124
- content,
125
- code_theme=("monokai" if theme == "dark" else "github"),
126
- ),
127
- title=title_text,
128
- border_style=border_style,
129
- )
130
-
131
-
132
- def create_final_panel(content: str, title: str = "Final Result", theme: str = "dark") -> AIPPanel:
133
- """Create a final result panel.
134
-
135
- Args:
136
- content: Final result content
137
- title: Panel title
138
- theme: Color theme
139
-
140
- Returns:
141
- Rich Panel instance
142
- """
143
- return AIPPanel(
144
- Markdown(content, code_theme=("monokai" if theme == "dark" else "github")),
145
- title=title,
146
- border_style=SUCCESS,
147
- padding=(0, 1),
148
- )
149
-
150
-
151
- __all__ = [
152
- "create_main_panel",
153
- "create_tool_panel",
154
- "create_context_panel",
155
- "create_final_panel",
156
- ]
@@ -1,202 +0,0 @@
1
- """Progress and timing utilities for the renderer package.
2
-
3
- Authors:
4
- Raymond Christopher (raymond.christopher@gdplabs.id)
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- from time import monotonic
10
- from typing import Any
11
-
12
- from rich.console import Console as RichConsole
13
- from rich.console import Group
14
- from rich.measure import Measurement
15
- from rich.spinner import Spinner
16
- from rich.text import Text
17
-
18
- from glaip_sdk.utils.rendering.steps.manager import StepManager
19
-
20
- _SPINNER_FRAMES = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
21
-
22
-
23
- def _spinner_time() -> float:
24
- """Return the monotonic time used for spinner animation."""
25
- return monotonic()
26
-
27
-
28
- def get_spinner() -> str:
29
- """Return the current animated spinner character for visual feedback."""
30
- return get_spinner_char()
31
-
32
-
33
- def get_spinner_char() -> str:
34
- """Return the spinner frame based on elapsed time."""
35
- frame_index = int(_spinner_time() * 10) % len(_SPINNER_FRAMES)
36
- return _SPINNER_FRAMES[frame_index]
37
-
38
-
39
- class TrailingSpinnerLine:
40
- """Render a text line with a trailing animated Rich spinner."""
41
-
42
- def __init__(self, base_text: Text, spinner: Spinner) -> None:
43
- """Initialize spinner line with base text and spinner component."""
44
- self._base_text = base_text
45
- self._spinner = spinner
46
-
47
- def __rich_console__(self, console: RichConsole, options: Any) -> Any: # type: ignore[override]
48
- """Render the text with trailing animated spinner."""
49
- spinner_render = self._spinner.render(console.get_time())
50
- combined = Text.assemble(self._base_text.copy(), " ", spinner_render)
51
- yield combined
52
-
53
- def __rich_measure__(self, console: RichConsole, options: Any) -> Measurement: # type: ignore[override]
54
- """Measure the combined text and spinner dimensions."""
55
- snapshot = self._spinner.render(0)
56
- combined = Text.assemble(self._base_text.copy(), " ", snapshot)
57
- return Measurement.get(console, options, combined)
58
-
59
-
60
- def _resolve_elapsed_time(
61
- started_at: float | None,
62
- server_elapsed_time: float | None,
63
- streaming_started_at: float | None,
64
- ) -> float | None:
65
- """Return the elapsed seconds using server data when available."""
66
- if server_elapsed_time is not None and streaming_started_at is not None:
67
- return server_elapsed_time
68
- if started_at is None:
69
- return None
70
- try:
71
- return monotonic() - started_at
72
- except Exception:
73
- return None
74
-
75
-
76
- def _format_elapsed_suffix(elapsed: float) -> str:
77
- """Return formatting suffix for elapsed timing."""
78
- if elapsed >= 1:
79
- return f"{elapsed:.2f}s"
80
- elapsed_ms = int(elapsed * 1000)
81
- return f"{elapsed_ms}ms" if elapsed_ms > 0 else "<1ms"
82
-
83
-
84
- def format_working_indicator(
85
- started_at: float | None,
86
- server_elapsed_time: float | None = None,
87
- streaming_started_at: float | None = None,
88
- ) -> str:
89
- """Format a working indicator with elapsed time."""
90
- base_message = "Working..."
91
-
92
- if started_at is None and (server_elapsed_time is None or streaming_started_at is None):
93
- return base_message
94
-
95
- spinner_chip = f"{get_spinner_char()} {base_message}"
96
- elapsed = _resolve_elapsed_time(started_at, server_elapsed_time, streaming_started_at)
97
- if elapsed is None:
98
- return spinner_chip
99
-
100
- suffix = _format_elapsed_suffix(elapsed)
101
- return f"{spinner_chip} ({suffix})"
102
-
103
-
104
- def format_elapsed_time(elapsed_seconds: float) -> str:
105
- """Format elapsed time in a human-readable format.
106
-
107
- Args:
108
- elapsed_seconds: Time in seconds
109
-
110
- Returns:
111
- Formatted time string
112
- """
113
- if elapsed_seconds >= 60:
114
- minutes = int(elapsed_seconds // 60)
115
- seconds = elapsed_seconds % 60
116
- return f"{minutes}m {seconds:.1f}s"
117
- elif elapsed_seconds >= 1:
118
- return f"{elapsed_seconds:.2f}s"
119
- else:
120
- ms = int(elapsed_seconds * 1000)
121
- return f"{ms}ms" if ms > 0 else "<1ms"
122
-
123
-
124
- def is_delegation_tool(tool_name: str) -> bool:
125
- """Check if a tool name indicates delegation functionality.
126
-
127
- Args:
128
- tool_name: The name of the tool to check
129
-
130
- Returns:
131
- True if this is a delegation tool
132
- """
133
- return tool_name.startswith("delegate_to_") or tool_name.startswith("delegate_") or "sub_agent" in tool_name.lower()
134
-
135
-
136
- def _delegation_tool_title(tool_name: str) -> str | None:
137
- """Return delegation-aware title or ``None`` when not applicable."""
138
- if tool_name.startswith("delegate_to_"):
139
- sub_agent_name = tool_name.replace("delegate_to_", "", 1)
140
- return f"Sub-Agent: {sub_agent_name}"
141
- if tool_name.startswith("delegate_"):
142
- sub_agent_name = tool_name.replace("delegate_", "", 1)
143
- return f"Sub-Agent: {sub_agent_name}"
144
- return None
145
-
146
-
147
- def _strip_path_and_extension(tool_name: str) -> str:
148
- """Return tool name without path segments or extensions."""
149
- filename = tool_name.rsplit("/", 1)[-1]
150
- base_name = filename.split(".", 1)[0]
151
- return base_name
152
-
153
-
154
- def format_tool_title(tool_name: str) -> str:
155
- """Format tool name for panel title display.
156
-
157
- Args:
158
- tool_name: The full tool name (may include file paths)
159
-
160
- Returns:
161
- Formatted title string suitable for panel display
162
- """
163
- # Check if this is a delegation tool
164
- if is_delegation_tool(tool_name):
165
- delegation_title = _delegation_tool_title(tool_name)
166
- if delegation_title:
167
- return delegation_title
168
-
169
- # For regular tools, clean up the name
170
- # Remove file path prefixes if present
171
- clean_name = _strip_path_and_extension(tool_name)
172
-
173
- # Convert snake_case to Title Case
174
- return clean_name.replace("_", " ").title()
175
-
176
-
177
- def _has_running_steps(steps: StepManager) -> bool:
178
- for step in steps.by_id.values():
179
- if getattr(step, "status", None) not in {"finished", "failed", "stopped"}:
180
- return True
181
- return False
182
-
183
-
184
- def build_progress_footer(
185
- *,
186
- state: Any,
187
- steps: StepManager,
188
- started_at: float | None,
189
- server_elapsed_time: float | None,
190
- ) -> Group | None:
191
- """Return a trailing progress indicator when work is ongoing."""
192
- if not _has_running_steps(steps):
193
- return None
194
-
195
- indicator = format_working_indicator(
196
- started_at,
197
- server_elapsed_time,
198
- getattr(state, "streaming_started_at", None),
199
- )
200
- text = Text(indicator, style="dim")
201
- spinner = Spinner("dots", style="dim")
202
- return Group(TrailingSpinnerLine(text, spinner))