glaip-sdk 0.0.19__py3-none-any.whl → 0.0.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- glaip_sdk/_version.py +2 -2
- glaip_sdk/branding.py +27 -2
- glaip_sdk/cli/auth.py +93 -28
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/agents.py +108 -21
- glaip_sdk/cli/commands/configure.py +141 -90
- glaip_sdk/cli/commands/mcps.py +81 -29
- glaip_sdk/cli/commands/models.py +4 -3
- glaip_sdk/cli/commands/tools.py +27 -14
- glaip_sdk/cli/commands/update.py +66 -0
- glaip_sdk/cli/config.py +13 -2
- glaip_sdk/cli/display.py +35 -26
- glaip_sdk/cli/io.py +14 -5
- glaip_sdk/cli/main.py +185 -73
- glaip_sdk/cli/pager.py +2 -1
- glaip_sdk/cli/resolution.py +4 -1
- glaip_sdk/cli/slash/__init__.py +3 -4
- glaip_sdk/cli/slash/agent_session.py +88 -36
- glaip_sdk/cli/slash/prompt.py +20 -48
- glaip_sdk/cli/slash/session.py +440 -189
- glaip_sdk/cli/transcript/__init__.py +71 -0
- glaip_sdk/cli/transcript/cache.py +338 -0
- glaip_sdk/cli/transcript/capture.py +278 -0
- glaip_sdk/cli/transcript/export.py +38 -0
- glaip_sdk/cli/transcript/launcher.py +79 -0
- glaip_sdk/cli/transcript/viewer.py +624 -0
- glaip_sdk/cli/update_notifier.py +29 -5
- glaip_sdk/cli/utils.py +256 -74
- glaip_sdk/client/agents.py +3 -1
- glaip_sdk/client/run_rendering.py +2 -2
- glaip_sdk/icons.py +19 -0
- glaip_sdk/models.py +6 -0
- glaip_sdk/rich_components.py +29 -1
- glaip_sdk/utils/__init__.py +1 -1
- glaip_sdk/utils/client_utils.py +6 -4
- glaip_sdk/utils/display.py +61 -32
- glaip_sdk/utils/rendering/formatting.py +6 -5
- glaip_sdk/utils/rendering/renderer/base.py +213 -66
- glaip_sdk/utils/rendering/renderer/debug.py +73 -16
- glaip_sdk/utils/rendering/renderer/panels.py +27 -15
- glaip_sdk/utils/rendering/renderer/progress.py +61 -38
- glaip_sdk/utils/serialization.py +5 -2
- glaip_sdk/utils/validation.py +1 -2
- {glaip_sdk-0.0.19.dist-info → glaip_sdk-0.0.20.dist-info}/METADATA +1 -1
- glaip_sdk-0.0.20.dist-info/RECORD +80 -0
- glaip_sdk/utils/rich_utils.py +0 -29
- glaip_sdk-0.0.19.dist-info/RECORD +0 -73
- {glaip_sdk-0.0.19.dist-info → glaip_sdk-0.0.20.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.19.dist-info → glaip_sdk-0.0.20.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}
|
|
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
|
-
"[
|
|
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"[
|
|
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.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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"[
|
|
244
|
+
self.console.print(f"[{ERROR_STYLE}]{exc}[/]")
|
|
189
245
|
finally:
|
|
190
246
|
try:
|
|
191
247
|
self.session.notify_agent_run_finished()
|
|
192
|
-
|
|
193
|
-
|
|
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
|
glaip_sdk/cli/slash/prompt.py
CHANGED
|
@@ -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]:
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
164
|
-
|
|
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("
|
|
167
|
-
def
|
|
168
|
-
|
|
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
|