glaip-sdk 0.0.19__py3-none-any.whl → 0.1.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.
Files changed (56) hide show
  1. glaip_sdk/_version.py +2 -2
  2. glaip_sdk/branding.py +27 -2
  3. glaip_sdk/cli/auth.py +93 -28
  4. glaip_sdk/cli/commands/__init__.py +2 -2
  5. glaip_sdk/cli/commands/agents.py +127 -21
  6. glaip_sdk/cli/commands/configure.py +141 -90
  7. glaip_sdk/cli/commands/mcps.py +82 -31
  8. glaip_sdk/cli/commands/models.py +4 -3
  9. glaip_sdk/cli/commands/tools.py +27 -14
  10. glaip_sdk/cli/commands/update.py +66 -0
  11. glaip_sdk/cli/config.py +13 -2
  12. glaip_sdk/cli/display.py +35 -26
  13. glaip_sdk/cli/io.py +14 -5
  14. glaip_sdk/cli/main.py +185 -73
  15. glaip_sdk/cli/pager.py +2 -1
  16. glaip_sdk/cli/resolution.py +4 -1
  17. glaip_sdk/cli/slash/__init__.py +3 -4
  18. glaip_sdk/cli/slash/agent_session.py +88 -36
  19. glaip_sdk/cli/slash/prompt.py +20 -48
  20. glaip_sdk/cli/slash/session.py +437 -189
  21. glaip_sdk/cli/transcript/__init__.py +71 -0
  22. glaip_sdk/cli/transcript/cache.py +338 -0
  23. glaip_sdk/cli/transcript/capture.py +278 -0
  24. glaip_sdk/cli/transcript/export.py +38 -0
  25. glaip_sdk/cli/transcript/launcher.py +79 -0
  26. glaip_sdk/cli/transcript/viewer.py +794 -0
  27. glaip_sdk/cli/update_notifier.py +29 -5
  28. glaip_sdk/cli/utils.py +255 -74
  29. glaip_sdk/client/agents.py +3 -1
  30. glaip_sdk/client/run_rendering.py +126 -21
  31. glaip_sdk/icons.py +25 -0
  32. glaip_sdk/models.py +6 -0
  33. glaip_sdk/rich_components.py +29 -1
  34. glaip_sdk/utils/__init__.py +1 -1
  35. glaip_sdk/utils/client_utils.py +6 -4
  36. glaip_sdk/utils/display.py +61 -32
  37. glaip_sdk/utils/rendering/formatting.py +55 -11
  38. glaip_sdk/utils/rendering/models.py +15 -2
  39. glaip_sdk/utils/rendering/renderer/__init__.py +0 -2
  40. glaip_sdk/utils/rendering/renderer/base.py +1287 -227
  41. glaip_sdk/utils/rendering/renderer/config.py +3 -5
  42. glaip_sdk/utils/rendering/renderer/debug.py +73 -16
  43. glaip_sdk/utils/rendering/renderer/panels.py +27 -15
  44. glaip_sdk/utils/rendering/renderer/progress.py +61 -38
  45. glaip_sdk/utils/rendering/renderer/stream.py +3 -3
  46. glaip_sdk/utils/rendering/renderer/toggle.py +184 -0
  47. glaip_sdk/utils/rendering/step_tree_state.py +102 -0
  48. glaip_sdk/utils/rendering/steps.py +944 -16
  49. glaip_sdk/utils/serialization.py +5 -2
  50. glaip_sdk/utils/validation.py +1 -2
  51. {glaip_sdk-0.0.19.dist-info → glaip_sdk-0.1.0.dist-info}/METADATA +12 -1
  52. glaip_sdk-0.1.0.dist-info/RECORD +82 -0
  53. glaip_sdk/utils/rich_utils.py +0 -29
  54. glaip_sdk-0.0.19.dist-info/RECORD +0 -73
  55. {glaip_sdk-0.0.19.dist-info → glaip_sdk-0.1.0.dist-info}/WHEEL +0 -0
  56. {glaip_sdk-0.0.19.dist-info → glaip_sdk-0.1.0.dist-info}/entry_points.txt +0 -0
@@ -6,16 +6,19 @@ Authors:
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ from contextlib import contextmanager
9
10
  from typing import TYPE_CHECKING, Any
10
11
 
11
12
  import click
12
13
 
14
+ from glaip_sdk.branding import ERROR_STYLE, HINT_PREFIX_STYLE
13
15
  from glaip_sdk.cli.commands.agents import get as agents_get_command
14
16
  from glaip_sdk.cli.commands.agents import run as agents_run_command
15
17
  from glaip_sdk.cli.slash.prompt import _HAS_PROMPT_TOOLKIT, FormattedText
18
+ from glaip_sdk.cli.utils import format_command_hint
16
19
 
17
20
  if TYPE_CHECKING: # pragma: no cover - type checking only
18
- from .session import SlashSession
21
+ from glaip_sdk.cli.slash.session import SlashSession
19
22
 
20
23
 
21
24
  class AgentRunSession:
@@ -33,15 +36,12 @@ class AgentRunSession:
33
36
  self.console = session.console
34
37
  self._agent_id = str(getattr(agent, "id", ""))
35
38
  self._agent_name = getattr(agent, "name", "") or self._agent_id
36
- self._prompt_placeholder: str = (
37
- "Chat with this agent here; use / for shortcuts."
38
- )
39
+ self._prompt_placeholder: str = "Chat with this agent here; use / for shortcuts. Alt+Enter inserts a newline."
39
40
  self._contextual_completion_help: dict[str, str] = {
40
41
  "details": "Show this agent's full configuration.",
41
42
  "help": "Display this context-aware menu.",
42
43
  "exit": "Return to the command palette.",
43
44
  "q": "Return to the command palette.",
44
- "verbose": "Toggle verbose streaming output.",
45
45
  }
46
46
 
47
47
  def run(self) -> None:
@@ -49,11 +49,17 @@ class AgentRunSession:
49
49
  self.session.set_contextual_commands(
50
50
  self._contextual_completion_help, include_global=False
51
51
  )
52
+ previous_agent = getattr(self.session, "_current_agent", None)
53
+ self.session._current_agent = self.agent
54
+ clear_ready = getattr(self.session, "clear_agent_transcript_ready", None)
55
+ if callable(clear_ready):
56
+ clear_ready(self._agent_id)
52
57
  try:
53
58
  self._display_agent_info()
54
59
  self._run_agent_loop()
55
60
  finally:
56
61
  self.session.set_contextual_commands(None)
62
+ self.session._current_agent = previous_agent
57
63
 
58
64
  def _display_agent_info(self) -> None:
59
65
  """Display agent information and summary."""
@@ -82,25 +88,17 @@ class AgentRunSession:
82
88
  try:
83
89
 
84
90
  def _prompt_message() -> Any:
85
- verbose_enabled = self.session.verbose_enabled
86
- verbose_tag = "[verbose:on]" if verbose_enabled else "[verbose:off]"
87
91
  prompt_prefix = f"{self._agent_name} ({self._agent_id}) "
88
92
 
89
93
  # Use FormattedText if prompt_toolkit is available, otherwise use simple string
90
94
  if _HAS_PROMPT_TOOLKIT and FormattedText is not None:
91
95
  segments = [
92
96
  ("class:prompt", prompt_prefix),
93
- (
94
- "class:prompt-verbose-on"
95
- if verbose_enabled
96
- else "class:prompt-verbose-off",
97
- verbose_tag,
98
- ),
99
97
  ("class:prompt", "\n› "),
100
98
  ]
101
99
  return FormattedText(segments)
102
100
 
103
- return f"{prompt_prefix}{verbose_tag}\n› "
101
+ return f"{prompt_prefix}\n› "
104
102
 
105
103
  raw = self.session._prompt(
106
104
  _prompt_message,
@@ -159,39 +157,93 @@ class AgentRunSession:
159
157
  try:
160
158
  self.session.ctx.invoke(agents_get_command, agent_ref=agent_id)
161
159
  self.console.print(
162
- "[dim]Tip: Continue the conversation in this prompt, or use /help for shortcuts."
160
+ f"[{HINT_PREFIX_STYLE}]Tip:[/] Continue the conversation in this prompt, or use {format_command_hint('/help') or '/help'} for shortcuts."
163
161
  )
164
162
  except click.ClickException as exc:
165
- self.console.print(f"[red]{exc}[/red]")
163
+ self.console.print(f"[{ERROR_STYLE}]{exc}[/]")
164
+
165
+ def _after_agent_run(self) -> None:
166
+ """Handle transcript viewer behaviour after a successful run."""
167
+ payload, manifest = self.session._get_last_transcript()
168
+ if not self._transcript_matches(payload, manifest):
169
+ return
170
+ run_id = str(manifest.get("run_id") or "")
171
+ mark_ready = getattr(self.session, "mark_agent_transcript_ready", None)
172
+ if callable(mark_ready):
173
+ mark_ready(self._agent_id, run_id)
174
+ if self._open_transcript_viewer():
175
+ return
176
+ self.console.print(
177
+ "[dim]Transcript viewer is unavailable in this environment.[/dim]"
178
+ )
179
+
180
+ def _transcript_matches(self, payload: Any, manifest: Any) -> bool:
181
+ """Return True when the latest transcript belongs to this agent."""
182
+ if not payload or not isinstance(manifest, dict):
183
+ return False
184
+ if not manifest.get("run_id"):
185
+ return False
186
+ return manifest.get("agent_id") == self._agent_id
187
+
188
+ def _open_transcript_viewer(self) -> bool:
189
+ """Launch the transcript viewer when terminal support is available."""
190
+ if not getattr(self.console, "is_terminal", False):
191
+ return False
192
+ try:
193
+ current_agent = getattr(self.session, "_current_agent", None)
194
+ self.session.open_transcript_viewer(announce=True)
195
+ if getattr(self.session.console, "is_terminal", False):
196
+ try:
197
+ self.session.console.clear()
198
+ except Exception: # pragma: no cover - defensive cleanup
199
+ pass
200
+ if (
201
+ current_agent is not None
202
+ ): # pragma: no cover - UI refresh best effort
203
+ try:
204
+ self.session._render_header(current_agent, focus_agent=True)
205
+ except Exception: # pragma: no cover - defensive cleanup
206
+ pass
207
+ return True
208
+ except Exception: # pragma: no cover - defensive cleanup
209
+ return False
210
+
211
+ @contextmanager
212
+ def _bind_session_context(self) -> Any:
213
+ """Temporarily attach this slash session to the Click context."""
214
+ ctx_obj = getattr(self.session.ctx, "obj", None)
215
+ has_context = isinstance(ctx_obj, dict)
216
+ previous_session = ctx_obj.get("_slash_session") if has_context else None
217
+ if has_context:
218
+ ctx_obj["_slash_session"] = self.session
219
+ try:
220
+ yield
221
+ finally:
222
+ if has_context:
223
+ if previous_session is None:
224
+ ctx_obj.pop("_slash_session", None)
225
+ else:
226
+ ctx_obj["_slash_session"] = previous_session
166
227
 
167
228
  def _run_agent(self, agent_id: str, message: str) -> None:
168
229
  if not message:
169
230
  return
170
231
 
171
232
  try:
172
- ctx = self.session.ctx
173
- ctx_obj = getattr(ctx, "obj", None)
174
- previous_session = None
175
- if isinstance(ctx_obj, dict):
176
- previous_session = ctx_obj.get("_slash_session")
177
- ctx_obj["_slash_session"] = self.session
178
-
179
233
  self.session.notify_agent_run_started()
180
- self.session.ctx.invoke(
181
- agents_run_command,
182
- agent_ref=agent_id,
183
- input_text=message,
184
- verbose=self.session.verbose_enabled,
185
- )
234
+ with self._bind_session_context():
235
+ self.session.ctx.invoke(
236
+ agents_run_command,
237
+ agent_ref=agent_id,
238
+ input_text=message,
239
+ verbose=False,
240
+ )
186
241
  self.session.last_run_input = message
242
+ self._after_agent_run()
187
243
  except click.ClickException as exc:
188
- self.console.print(f"[red]{exc}[/red]")
244
+ self.console.print(f"[{ERROR_STYLE}]{exc}[/]")
189
245
  finally:
190
246
  try:
191
247
  self.session.notify_agent_run_finished()
192
- finally:
193
- if isinstance(ctx_obj, dict):
194
- if previous_session is None:
195
- ctx_obj.pop("_slash_session", None)
196
- else:
197
- ctx_obj["_slash_session"] = previous_session
248
+ except Exception:
249
+ pass
@@ -19,11 +19,6 @@ try: # pragma: no cover - optional dependency
19
19
  from prompt_toolkit.patch_stdout import patch_stdout
20
20
  from prompt_toolkit.styles import Style
21
21
 
22
- try:
23
- from prompt_toolkit.application import run_in_terminal as ptk_run_in_terminal
24
- except Exception: # pragma: no cover - compatibility fallback
25
- ptk_run_in_terminal = None
26
-
27
22
  _HAS_PROMPT_TOOLKIT = True
28
23
  except Exception: # pragma: no cover - optional dependency
29
24
  PromptSession = None # type: ignore[assignment]
@@ -34,10 +29,9 @@ except Exception: # pragma: no cover - optional dependency
34
29
  KeyBindings = None # type: ignore[assignment]
35
30
  Style = None # type: ignore[assignment]
36
31
  patch_stdout = None # type: ignore[assignment]
37
- ptk_run_in_terminal = None
38
32
 
39
33
  if TYPE_CHECKING: # pragma: no cover - typing only
40
- from .session import SlashSession
34
+ from glaip_sdk.cli.slash.session import SlashSession
41
35
 
42
36
 
43
37
  if _HAS_PROMPT_TOOLKIT:
@@ -57,7 +51,7 @@ if _HAS_PROMPT_TOOLKIT:
57
51
  self,
58
52
  document: Any,
59
53
  _complete_event: Any, # type: ignore[no-any-return]
60
- ) -> Iterable[Completion]: # pragma: no cover - UI
54
+ ) -> Iterable[Completion]:
61
55
  """Get completions for slash commands.
62
56
 
63
57
  Args:
@@ -82,7 +76,7 @@ else: # pragma: no cover - fallback when prompt_toolkit is missing
82
76
  class SlashCompleter: # type: ignore[too-many-ancestors]
83
77
  """Fallback slash completer when prompt_toolkit is not available."""
84
78
 
85
- def __init__(self, session: SlashSession) -> None: # noqa: D401 - stub
79
+ def __init__(self, session: SlashSession) -> None:
86
80
  """Initialize the fallback slash completer.
87
81
 
88
82
  Args:
@@ -113,8 +107,6 @@ def setup_prompt_toolkit(
113
107
  prompt_style = Style.from_dict(
114
108
  {
115
109
  "prompt": "bg:#0f172a #facc15 bold",
116
- "prompt-verbose-on": "bg:#0f172a #34d399 bold",
117
- "prompt-verbose-off": "bg:#0f172a #f87171 bold",
118
110
  "": "bg:#0f172a #e2e8f0",
119
111
  "placeholder": "bg:#0f172a #94a3b8 italic",
120
112
  }
@@ -123,7 +115,7 @@ def setup_prompt_toolkit(
123
115
  return prompt_session, prompt_style
124
116
 
125
117
 
126
- def _create_key_bindings(session: SlashSession) -> Any:
118
+ def _create_key_bindings(_session: SlashSession) -> Any:
127
119
  """Create prompt_toolkit key bindings for the command palette."""
128
120
  if KeyBindings is None:
129
121
  return None
@@ -137,57 +129,37 @@ def _create_key_bindings(session: SlashSession) -> Any:
137
129
  elif buffer.complete_state is not None:
138
130
  buffer.cancel_completion()
139
131
 
140
- def _trigger_slash_completion(event: Any) -> None: # pragma: no cover - UI
132
+ @bindings.add("/") # type: ignore[misc]
133
+ def _handle_slash_key(event: Any) -> None: # vulture: ignore
134
+ """Handle '/' key press - insert slash and trigger completion."""
141
135
  buffer = event.app.current_buffer
142
136
  buffer.insert_text("/")
143
137
  _refresh_completions(buffer)
144
138
 
145
- def _handle_backspace(event: Any) -> None: # pragma: no cover - UI
139
+ @bindings.add("backspace") # type: ignore[misc]
140
+ def _handle_backspace_key(event: Any) -> None: # vulture: ignore
141
+ """Handle backspace key - delete character and refresh completions."""
146
142
  buffer = event.app.current_buffer
147
143
  if buffer.document.cursor_position > 0:
148
144
  buffer.delete_before_cursor()
149
145
  _refresh_completions(buffer)
150
146
 
151
- def _toggle_verbose(event: Any) -> None: # pragma: no cover - UI
152
- _execute_toggle_verbose(session, event.app)
153
-
154
- @bindings.add("/") # type: ignore[misc]
155
- def _add_trigger_slash_completion(event: Any) -> None:
156
- _trigger_slash_completion(event)
157
-
158
- @bindings.add("backspace") # type: ignore[misc]
159
- def _add_handle_backspace(event: Any) -> None:
160
- _handle_backspace(event)
161
-
162
147
  @bindings.add("c-h") # type: ignore[misc]
163
- def _add_handle_ctrl_h(event: Any) -> None:
164
- _handle_backspace(event) # Reuse backspace handler
148
+ def _handle_ctrl_h_key(event: Any) -> None: # vulture: ignore
149
+ """Handle Ctrl+H key - same as backspace."""
150
+ _handle_backspace_key(event) # Reuse backspace handler
165
151
 
166
- @bindings.add("c-t") # type: ignore[misc]
167
- def _add_toggle_verbose(event: Any) -> None:
168
- _toggle_verbose(event)
152
+ @bindings.add("escape", "enter") # type: ignore[misc]
153
+ def _handle_alt_enter_key(event: Any) -> None: # vulture: ignore
154
+ """Handle Alt+Enter key - insert line break and cancel completion."""
155
+ buffer = event.app.current_buffer
156
+ buffer.insert_text("\n")
157
+ if buffer.complete_state is not None:
158
+ buffer.cancel_completion()
169
159
 
170
160
  return bindings
171
161
 
172
162
 
173
- def _execute_toggle_verbose(
174
- session: SlashSession, app: Any
175
- ) -> None: # pragma: no cover - UI
176
- """Execute verbose toggle with proper terminal handling."""
177
-
178
- def _announce() -> None:
179
- session.toggle_verbose(announce=False)
180
-
181
- run_in_terminal = getattr(app, "run_in_terminal", None)
182
- if callable(run_in_terminal):
183
- run_in_terminal(_announce)
184
- elif ptk_run_in_terminal is not None:
185
- ptk_run_in_terminal(_announce)
186
- else:
187
- _announce()
188
- app.invalidate()
189
-
190
-
191
163
  def _iter_command_completions(
192
164
  session: SlashSession, text: str
193
165
  ) -> Iterable[Completion]: # pragma: no cover - thin wrapper