glaip-sdk 0.0.7__py3-none-any.whl → 0.6.5b6__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 (161) hide show
  1. glaip_sdk/__init__.py +6 -3
  2. glaip_sdk/_version.py +12 -5
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1126 -0
  5. glaip_sdk/branding.py +79 -15
  6. glaip_sdk/cli/account_store.py +540 -0
  7. glaip_sdk/cli/agent_config.py +2 -6
  8. glaip_sdk/cli/auth.py +699 -0
  9. glaip_sdk/cli/commands/__init__.py +2 -2
  10. glaip_sdk/cli/commands/accounts.py +746 -0
  11. glaip_sdk/cli/commands/agents.py +503 -183
  12. glaip_sdk/cli/commands/common_config.py +101 -0
  13. glaip_sdk/cli/commands/configure.py +774 -137
  14. glaip_sdk/cli/commands/mcps.py +1124 -181
  15. glaip_sdk/cli/commands/models.py +25 -10
  16. glaip_sdk/cli/commands/tools.py +144 -92
  17. glaip_sdk/cli/commands/transcripts.py +755 -0
  18. glaip_sdk/cli/commands/update.py +61 -0
  19. glaip_sdk/cli/config.py +95 -0
  20. glaip_sdk/cli/constants.py +38 -0
  21. glaip_sdk/cli/context.py +150 -0
  22. glaip_sdk/cli/core/__init__.py +79 -0
  23. glaip_sdk/cli/core/context.py +124 -0
  24. glaip_sdk/cli/core/output.py +846 -0
  25. glaip_sdk/cli/core/prompting.py +649 -0
  26. glaip_sdk/cli/core/rendering.py +187 -0
  27. glaip_sdk/cli/display.py +143 -53
  28. glaip_sdk/cli/hints.py +57 -0
  29. glaip_sdk/cli/io.py +24 -18
  30. glaip_sdk/cli/main.py +420 -145
  31. glaip_sdk/cli/masking.py +136 -0
  32. glaip_sdk/cli/mcp_validators.py +287 -0
  33. glaip_sdk/cli/pager.py +266 -0
  34. glaip_sdk/cli/parsers/__init__.py +7 -0
  35. glaip_sdk/cli/parsers/json_input.py +177 -0
  36. glaip_sdk/cli/resolution.py +28 -21
  37. glaip_sdk/cli/rich_helpers.py +27 -0
  38. glaip_sdk/cli/slash/__init__.py +15 -0
  39. glaip_sdk/cli/slash/accounts_controller.py +500 -0
  40. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  41. glaip_sdk/cli/slash/agent_session.py +282 -0
  42. glaip_sdk/cli/slash/prompt.py +245 -0
  43. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  44. glaip_sdk/cli/slash/session.py +1679 -0
  45. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  46. glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
  47. glaip_sdk/cli/slash/tui/accounts_app.py +872 -0
  48. glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
  49. glaip_sdk/cli/slash/tui/loading.py +58 -0
  50. glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
  51. glaip_sdk/cli/transcript/__init__.py +31 -0
  52. glaip_sdk/cli/transcript/cache.py +536 -0
  53. glaip_sdk/cli/transcript/capture.py +329 -0
  54. glaip_sdk/cli/transcript/export.py +38 -0
  55. glaip_sdk/cli/transcript/history.py +815 -0
  56. glaip_sdk/cli/transcript/launcher.py +77 -0
  57. glaip_sdk/cli/transcript/viewer.py +372 -0
  58. glaip_sdk/cli/update_notifier.py +290 -0
  59. glaip_sdk/cli/utils.py +247 -1238
  60. glaip_sdk/cli/validators.py +16 -18
  61. glaip_sdk/client/__init__.py +2 -1
  62. glaip_sdk/client/_agent_payloads.py +520 -0
  63. glaip_sdk/client/agent_runs.py +147 -0
  64. glaip_sdk/client/agents.py +940 -574
  65. glaip_sdk/client/base.py +163 -48
  66. glaip_sdk/client/main.py +35 -12
  67. glaip_sdk/client/mcps.py +126 -18
  68. glaip_sdk/client/run_rendering.py +415 -0
  69. glaip_sdk/client/shared.py +21 -0
  70. glaip_sdk/client/tools.py +195 -37
  71. glaip_sdk/client/validators.py +20 -48
  72. glaip_sdk/config/constants.py +15 -5
  73. glaip_sdk/exceptions.py +16 -9
  74. glaip_sdk/icons.py +25 -0
  75. glaip_sdk/mcps/__init__.py +21 -0
  76. glaip_sdk/mcps/base.py +345 -0
  77. glaip_sdk/models/__init__.py +90 -0
  78. glaip_sdk/models/agent.py +47 -0
  79. glaip_sdk/models/agent_runs.py +116 -0
  80. glaip_sdk/models/common.py +42 -0
  81. glaip_sdk/models/mcp.py +33 -0
  82. glaip_sdk/models/tool.py +33 -0
  83. glaip_sdk/payload_schemas/__init__.py +7 -0
  84. glaip_sdk/payload_schemas/agent.py +85 -0
  85. glaip_sdk/registry/__init__.py +55 -0
  86. glaip_sdk/registry/agent.py +164 -0
  87. glaip_sdk/registry/base.py +139 -0
  88. glaip_sdk/registry/mcp.py +253 -0
  89. glaip_sdk/registry/tool.py +231 -0
  90. glaip_sdk/rich_components.py +98 -2
  91. glaip_sdk/runner/__init__.py +59 -0
  92. glaip_sdk/runner/base.py +84 -0
  93. glaip_sdk/runner/deps.py +115 -0
  94. glaip_sdk/runner/langgraph.py +597 -0
  95. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  96. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  97. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +158 -0
  98. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  99. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  100. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  101. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +177 -0
  102. glaip_sdk/tools/__init__.py +22 -0
  103. glaip_sdk/tools/base.py +435 -0
  104. glaip_sdk/utils/__init__.py +59 -13
  105. glaip_sdk/utils/a2a/__init__.py +34 -0
  106. glaip_sdk/utils/a2a/event_processor.py +188 -0
  107. glaip_sdk/utils/agent_config.py +53 -40
  108. glaip_sdk/utils/bundler.py +267 -0
  109. glaip_sdk/utils/client.py +111 -0
  110. glaip_sdk/utils/client_utils.py +58 -26
  111. glaip_sdk/utils/datetime_helpers.py +58 -0
  112. glaip_sdk/utils/discovery.py +78 -0
  113. glaip_sdk/utils/display.py +65 -32
  114. glaip_sdk/utils/export.py +143 -0
  115. glaip_sdk/utils/general.py +1 -36
  116. glaip_sdk/utils/import_export.py +20 -25
  117. glaip_sdk/utils/import_resolver.py +492 -0
  118. glaip_sdk/utils/instructions.py +101 -0
  119. glaip_sdk/utils/rendering/__init__.py +115 -1
  120. glaip_sdk/utils/rendering/formatting.py +85 -43
  121. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  122. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +51 -19
  123. glaip_sdk/utils/rendering/layout/progress.py +202 -0
  124. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  125. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  126. glaip_sdk/utils/rendering/models.py +39 -7
  127. glaip_sdk/utils/rendering/renderer/__init__.py +9 -51
  128. glaip_sdk/utils/rendering/renderer/base.py +672 -759
  129. glaip_sdk/utils/rendering/renderer/config.py +4 -10
  130. glaip_sdk/utils/rendering/renderer/debug.py +75 -22
  131. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  132. glaip_sdk/utils/rendering/renderer/stream.py +13 -54
  133. glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
  134. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  135. glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
  136. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  137. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  138. glaip_sdk/utils/rendering/state.py +204 -0
  139. glaip_sdk/utils/rendering/step_tree_state.py +100 -0
  140. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  141. glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
  142. glaip_sdk/utils/rendering/steps/format.py +176 -0
  143. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  144. glaip_sdk/utils/rendering/timing.py +36 -0
  145. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  146. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  147. glaip_sdk/utils/resource_refs.py +29 -26
  148. glaip_sdk/utils/runtime_config.py +422 -0
  149. glaip_sdk/utils/serialization.py +184 -51
  150. glaip_sdk/utils/sync.py +142 -0
  151. glaip_sdk/utils/tool_detection.py +33 -0
  152. glaip_sdk/utils/validation.py +21 -30
  153. {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/METADATA +58 -12
  154. glaip_sdk-0.6.5b6.dist-info/RECORD +159 -0
  155. {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/WHEEL +1 -1
  156. glaip_sdk/models.py +0 -250
  157. glaip_sdk/utils/rendering/renderer/progress.py +0 -118
  158. glaip_sdk/utils/rendering/steps.py +0 -232
  159. glaip_sdk/utils/rich_utils.py +0 -29
  160. glaip_sdk-0.0.7.dist-info/RECORD +0 -55
  161. {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/entry_points.txt +0 -0
@@ -1 +1,115 @@
1
- """Rendering utilities package (formatting, models, steps, debug)."""
1
+ """Rendering utilities package (formatting, models, steps, debug).
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from datetime import datetime
8
+ from typing import Any
9
+
10
+ from rich.console import Console
11
+
12
+ from glaip_sdk.models.agent_runs import RunWithOutput
13
+ from glaip_sdk.utils.rendering.renderer.debug import render_debug_event, render_debug_event_stream
14
+
15
+
16
+ def _parse_event_received_timestamp(event: dict[str, Any]) -> datetime | None:
17
+ """Parse received_at timestamp from SSE event.
18
+
19
+ Args:
20
+ event: SSE event dictionary
21
+
22
+ Returns:
23
+ Parsed datetime or None if not available
24
+ """
25
+ received_at = event.get("received_at")
26
+ if not received_at:
27
+ return None
28
+
29
+ if isinstance(received_at, datetime):
30
+ return received_at
31
+
32
+ if isinstance(received_at, str):
33
+ try:
34
+ # Try ISO format first
35
+ return datetime.fromisoformat(received_at.replace("Z", "+00:00"))
36
+ except ValueError:
37
+ try:
38
+ # Try common formats
39
+ return datetime.strptime(received_at, "%Y-%m-%dT%H:%M:%S.%fZ")
40
+ except ValueError:
41
+ return None
42
+
43
+ return None
44
+
45
+
46
+ def render_remote_sse_transcript(
47
+ run: RunWithOutput,
48
+ console: Console,
49
+ *,
50
+ show_metadata: bool = True,
51
+ ) -> None:
52
+ """Render remote SSE transcript events for a RunWithOutput.
53
+
54
+ Args:
55
+ run: RunWithOutput instance containing events
56
+ console: Rich console to render to
57
+ show_metadata: Whether to show run metadata summary
58
+ """
59
+ if show_metadata:
60
+ # Render metadata summary
61
+ console.print(f"[bold]Run: {run.id}[/bold]")
62
+ console.print(f"[dim]Agent: {run.agent_id}[/dim]")
63
+ console.print(f"[dim]Status: {run.status}[/dim]")
64
+ console.print(f"[dim]Type: {run.run_type}[/dim]")
65
+ if run.schedule_id:
66
+ console.print(f"[dim]Schedule ID: {run.schedule_id}[/dim]")
67
+ else:
68
+ console.print("[dim]Schedule: —[/dim]")
69
+ console.print(f"[dim]Started: {run.started_at.isoformat()}[/dim]")
70
+ if run.completed_at:
71
+ console.print(f"[dim]Completed: {run.completed_at.isoformat()}[/dim]")
72
+ console.print(f"[dim]Duration: {run.duration_formatted()}[/dim]")
73
+ console.print()
74
+
75
+ # Render events
76
+ if not run.output:
77
+ console.print("[dim]No SSE events available for this run.[/dim]")
78
+ return
79
+
80
+ console.print("[bold]SSE Events[/bold]")
81
+ console.print("[dim]────────────────────────────────────────────────────────[/dim]")
82
+
83
+ render_debug_event_stream(
84
+ run.output,
85
+ console,
86
+ resolve_timestamp=_parse_event_received_timestamp,
87
+ )
88
+ console.print()
89
+
90
+
91
+ class RemoteSSETranscriptRenderer:
92
+ """Renderer for remote SSE transcripts from RunWithOutput."""
93
+
94
+ def __init__(self, console: Console | None = None):
95
+ """Initialize the renderer.
96
+
97
+ Args:
98
+ console: Rich console instance (creates default if None)
99
+ """
100
+ self.console = console or Console()
101
+
102
+ def render(self, run: RunWithOutput, *, show_metadata: bool = True) -> None:
103
+ """Render a remote run transcript.
104
+
105
+ Args:
106
+ run: RunWithOutput instance to render
107
+ show_metadata: Whether to show run metadata summary
108
+ """
109
+ render_remote_sse_transcript(run, self.console, show_metadata=show_metadata)
110
+
111
+
112
+ __all__ = [
113
+ "render_remote_sse_transcript",
114
+ "RemoteSSETranscriptRenderer",
115
+ ]
@@ -6,11 +6,20 @@ Authors:
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import json
9
10
  import re
10
- import time
11
11
  from collections.abc import Callable
12
12
  from typing import Any
13
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
+
14
23
  # Constants for argument formatting
15
24
  DEFAULT_ARGS_MAX_LEN = 100
16
25
  IMPORTANT_PARAMETER_KEYS = [
@@ -34,9 +43,15 @@ SECRET_VALUE_PATTERNS = [
34
43
  re.compile(r"eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+"), # JWT tokens
35
44
  ]
36
45
  SENSITIVE_PATTERNS = re.compile(
37
- r"password\s*[:=]\s*[^\s,}]+|secret\s*[:=]\s*[^\s,}]+|token\s*[:=]\s*[^\s,}]+|key\s*[:=]\s*[^\s,}]+|api_key\s*[:=]\s*[^\s,}]+|^password$|^secret$|^token$|^key$|^api_key$",
46
+ r"(?:password|secret|token|key|api_key)(?:\s*[:=]\s*[^\s,}]+)?",
38
47
  re.IGNORECASE,
39
48
  )
49
+ SECRET_MASK = "••••••"
50
+ STATUS_GLYPHS = {
51
+ "success": ICON_STATUS_SUCCESS,
52
+ "failed": ICON_STATUS_FAILED,
53
+ "warning": ICON_STATUS_WARNING,
54
+ }
40
55
 
41
56
 
42
57
  def _truncate_string(s: str, max_len: int) -> str:
@@ -50,48 +65,82 @@ def mask_secrets_in_string(text: str) -> str:
50
65
  """Mask sensitive information in a string."""
51
66
  result = text
52
67
  for pattern in SECRET_VALUE_PATTERNS:
53
- result = re.sub(pattern, "••••••", result)
68
+ result = re.sub(pattern, SECRET_MASK, result)
54
69
  return result
55
70
 
56
71
 
57
72
  def redact_sensitive(text: str | dict | list) -> str | dict | list:
58
73
  """Redact sensitive information in a string, dict, or list."""
59
74
  if isinstance(text, dict):
60
- # Recursively process dictionary values
61
- result = {}
62
- for key, value in text.items():
63
- # Check if the key itself is sensitive
64
- key_lower = key.lower()
65
- if any(
66
- sensitive in key_lower
67
- for sensitive in ["password", "secret", "token", "key", "api_key"]
68
- ):
69
- result[key] = "••••••"
70
- elif isinstance(value, dict | list) or isinstance(value, str):
71
- result[key] = redact_sensitive(value)
72
- else:
73
- result[key] = value
74
- return result
75
+ return _redact_dict_values(text)
75
76
  elif isinstance(text, list):
76
- # Recursively process list items
77
- return [redact_sensitive(item) for item in text]
77
+ return _redact_list_items(text)
78
78
  elif isinstance(text, str):
79
- # Process string - first mask secrets, then redact sensitive patterns
80
- result = text
81
- # First mask secrets
82
- for pattern in SECRET_VALUE_PATTERNS:
83
- result = re.sub(pattern, "••••••", result)
84
- # Then redact sensitive patterns
85
- result = re.sub(
86
- SENSITIVE_PATTERNS,
87
- lambda m: m.group(0).split("=")[0] + "=••••••",
88
- result,
89
- )
90
- return result
79
+ return _redact_string_content(text)
91
80
  else:
92
81
  return text
93
82
 
94
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
+
95
144
  def pretty_args(args: dict | None, max_len: int = DEFAULT_ARGS_MAX_LEN) -> str:
96
145
  """Format arguments in a pretty way."""
97
146
  if not args:
@@ -106,11 +155,9 @@ def pretty_args(args: dict | None, max_len: int = DEFAULT_ARGS_MAX_LEN) -> str:
106
155
 
107
156
  # Convert to JSON string and truncate if needed
108
157
  try:
109
- import json
110
-
111
158
  args_str = json.dumps(masked_args, ensure_ascii=False, separators=(",", ":"))
112
159
  return _truncate_string(args_str, max_len)
113
- except (TypeError, ValueError, Exception):
160
+ except Exception:
114
161
  # Fallback to string representation if JSON serialization fails
115
162
  args_str = str(masked_args)
116
163
  return _truncate_string(args_str, max_len)
@@ -140,19 +187,14 @@ def pretty_out(output: any, max_len: int = DEFAULT_ARGS_MAX_LEN) -> str:
140
187
  return _truncate_string(output_str, max_len)
141
188
 
142
189
 
143
- def get_spinner_char() -> str:
144
- frames = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
145
- return frames[int(time.time() * 10) % len(frames)]
146
-
147
-
148
190
  def get_step_icon(step_kind: str) -> str:
149
191
  """Get the appropriate icon for a step kind."""
150
192
  if step_kind == "tool":
151
- return "⚙️" # Gear emoji for tool
193
+ return ICON_TOOL_STEP
152
194
  if step_kind == "delegate":
153
- return "🤝" # Handshake for delegate
195
+ return ICON_DELEGATE
154
196
  if step_kind == "agent":
155
- return "🧠" # Brain emoji for agent
197
+ return ICON_AGENT_STEP
156
198
  return ""
157
199
 
158
200
 
@@ -0,0 +1,64 @@
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
+ ]
@@ -6,12 +6,26 @@ Authors:
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+
10
+ from rich.align import Align
9
11
  from rich.markdown import Markdown
12
+ from rich.spinner import Spinner
10
13
  from rich.text import Text
11
14
 
15
+ from glaip_sdk.branding import INFO, PRIMARY, SUCCESS, WARNING
12
16
  from glaip_sdk.rich_components import AIPPanel
13
17
 
14
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
+
15
29
  def create_main_panel(content: str, title: str, theme: str = "dark") -> AIPPanel:
16
30
  """Create a main content panel.
17
31
 
@@ -27,15 +41,13 @@ def create_main_panel(content: str, title: str, theme: str = "dark") -> AIPPanel
27
41
  return AIPPanel(
28
42
  Markdown(content, code_theme=("monokai" if theme == "dark" else "github")),
29
43
  title=title,
30
- border_style="green",
44
+ border_style=SUCCESS,
31
45
  )
32
46
  else:
33
- # Placeholder panel
34
- placeholder = Text("Processing...", style="dim")
35
47
  return AIPPanel(
36
- placeholder,
48
+ _spinner_renderable(),
37
49
  title=title,
38
- border_style="green",
50
+ border_style=SUCCESS,
39
51
  )
40
52
 
41
53
 
@@ -45,6 +57,8 @@ def create_tool_panel(
45
57
  status: str = "running",
46
58
  theme: str = "dark",
47
59
  is_delegation: bool = False,
60
+ *,
61
+ spinner_message: str | None = None,
48
62
  ) -> AIPPanel:
49
63
  """Create a tool execution panel.
50
64
 
@@ -54,19 +68,29 @@ def create_tool_panel(
54
68
  status: Tool execution status
55
69
  theme: Color theme
56
70
  is_delegation: Whether this is a delegation tool
71
+ spinner_message: Optional custom message to show alongside the spinner
57
72
 
58
73
  Returns:
59
74
  Rich Panel instance
60
75
  """
61
- mark = "✓" if status == "finished" else ""
62
- border_style = "magenta" if is_delegation else "blue"
76
+ mark = "✓" if status == "finished" else ""
77
+ border_style = WARNING if is_delegation else PRIMARY
63
78
 
64
- return AIPPanel(
65
- Markdown(
66
- content or "Processing...",
79
+ if content:
80
+ body_renderable = Markdown(
81
+ content,
67
82
  code_theme=("monokai" if theme == "dark" else "github"),
68
- ),
69
- title=f"{title} {mark}",
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,
70
94
  border_style=border_style,
71
95
  )
72
96
 
@@ -90,22 +114,22 @@ def create_context_panel(
90
114
  Returns:
91
115
  Rich Panel instance
92
116
  """
93
- mark = "✓" if status == "finished" else ""
94
- border_style = "magenta" if is_delegation else "cyan"
117
+ mark = "✓" if status == "finished" else ""
118
+ border_style = WARNING if is_delegation else INFO
119
+
120
+ title_text = f"{title} {mark}".rstrip()
95
121
 
96
122
  return AIPPanel(
97
123
  Markdown(
98
124
  content,
99
125
  code_theme=("monokai" if theme == "dark" else "github"),
100
126
  ),
101
- title=f"{title} {mark}",
127
+ title=title_text,
102
128
  border_style=border_style,
103
129
  )
104
130
 
105
131
 
106
- def create_final_panel(
107
- content: str, title: str = "Final Result", theme: str = "dark"
108
- ) -> AIPPanel:
132
+ def create_final_panel(content: str, title: str = "Final Result", theme: str = "dark") -> AIPPanel:
109
133
  """Create a final result panel.
110
134
 
111
135
  Args:
@@ -119,6 +143,14 @@ def create_final_panel(
119
143
  return AIPPanel(
120
144
  Markdown(content, code_theme=("monokai" if theme == "dark" else "github")),
121
145
  title=title,
122
- border_style="green",
146
+ border_style=SUCCESS,
123
147
  padding=(0, 1),
124
148
  )
149
+
150
+
151
+ __all__ = [
152
+ "create_main_panel",
153
+ "create_tool_panel",
154
+ "create_context_panel",
155
+ "create_final_panel",
156
+ ]