glaip-sdk 0.1.0__py3-none-any.whl → 0.1.1__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 +1 -3
- glaip_sdk/branding.py +2 -6
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +11 -30
- glaip_sdk/cli/commands/agents.py +45 -107
- glaip_sdk/cli/commands/configure.py +12 -36
- glaip_sdk/cli/commands/mcps.py +26 -63
- glaip_sdk/cli/commands/models.py +2 -4
- glaip_sdk/cli/commands/tools.py +22 -35
- glaip_sdk/cli/commands/update.py +3 -8
- glaip_sdk/cli/config.py +1 -3
- glaip_sdk/cli/display.py +4 -12
- glaip_sdk/cli/io.py +8 -14
- glaip_sdk/cli/main.py +10 -30
- glaip_sdk/cli/mcp_validators.py +5 -15
- glaip_sdk/cli/pager.py +3 -9
- glaip_sdk/cli/parsers/json_input.py +11 -22
- glaip_sdk/cli/resolution.py +3 -9
- glaip_sdk/cli/rich_helpers.py +1 -3
- glaip_sdk/cli/slash/agent_session.py +5 -10
- glaip_sdk/cli/slash/prompt.py +3 -10
- glaip_sdk/cli/slash/session.py +46 -95
- glaip_sdk/cli/transcript/cache.py +6 -19
- glaip_sdk/cli/transcript/capture.py +6 -20
- glaip_sdk/cli/transcript/launcher.py +1 -3
- glaip_sdk/cli/transcript/viewer.py +11 -40
- glaip_sdk/cli/update_notifier.py +165 -21
- glaip_sdk/cli/utils.py +33 -84
- glaip_sdk/cli/validators.py +11 -12
- glaip_sdk/client/_agent_payloads.py +10 -30
- glaip_sdk/client/agents.py +33 -63
- glaip_sdk/client/base.py +6 -22
- glaip_sdk/client/mcps.py +1 -3
- glaip_sdk/client/run_rendering.py +6 -14
- glaip_sdk/client/tools.py +8 -24
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/exceptions.py +1 -3
- glaip_sdk/models.py +14 -33
- glaip_sdk/payload_schemas/agent.py +1 -3
- glaip_sdk/utils/agent_config.py +4 -14
- glaip_sdk/utils/client_utils.py +7 -21
- glaip_sdk/utils/display.py +2 -6
- glaip_sdk/utils/general.py +1 -3
- glaip_sdk/utils/import_export.py +3 -9
- glaip_sdk/utils/rendering/formatting.py +2 -5
- glaip_sdk/utils/rendering/models.py +2 -6
- glaip_sdk/utils/rendering/renderer/__init__.py +1 -3
- glaip_sdk/utils/rendering/renderer/base.py +63 -189
- glaip_sdk/utils/rendering/renderer/debug.py +4 -14
- glaip_sdk/utils/rendering/renderer/panels.py +1 -3
- glaip_sdk/utils/rendering/renderer/progress.py +3 -11
- glaip_sdk/utils/rendering/renderer/stream.py +7 -19
- glaip_sdk/utils/rendering/renderer/toggle.py +1 -3
- glaip_sdk/utils/rendering/step_tree_state.py +1 -3
- glaip_sdk/utils/rendering/steps.py +29 -83
- glaip_sdk/utils/resource_refs.py +4 -13
- glaip_sdk/utils/serialization.py +14 -46
- glaip_sdk/utils/validation.py +4 -4
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.1.dist-info}/METADATA +1 -1
- glaip_sdk-0.1.1.dist-info/RECORD +82 -0
- glaip_sdk-0.1.0.dist-info/RECORD +0 -82
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.1.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.1.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/mcp_validators.py
CHANGED
|
@@ -79,9 +79,7 @@ def validate_mcp_config_structure(
|
|
|
79
79
|
requirement = "Missing required 'url' field with a non-empty string value."
|
|
80
80
|
if transport:
|
|
81
81
|
requirement += f" Required for transport '{transport}'."
|
|
82
|
-
raise click.ClickException(
|
|
83
|
-
format_validation_error(f"Invalid {source} value", requirement)
|
|
84
|
-
)
|
|
82
|
+
raise click.ClickException(format_validation_error(f"Invalid {source} value", requirement))
|
|
85
83
|
|
|
86
84
|
parsed_url = urlparse(url_value)
|
|
87
85
|
if parsed_url.scheme not in {"http", "https"} or not parsed_url.netloc:
|
|
@@ -95,9 +93,7 @@ def validate_mcp_config_structure(
|
|
|
95
93
|
return config
|
|
96
94
|
|
|
97
95
|
|
|
98
|
-
def _validate_headers_mapping(
|
|
99
|
-
headers: Any, *, source: str, context: str
|
|
100
|
-
) -> dict[str, str]:
|
|
96
|
+
def _validate_headers_mapping(headers: Any, *, source: str, context: str) -> dict[str, str]:
|
|
101
97
|
"""Validate headers mapping for authentication.
|
|
102
98
|
|
|
103
99
|
Args:
|
|
@@ -156,9 +152,7 @@ def _validate_bearer_token_auth(auth: dict[str, Any], source: str) -> dict[str,
|
|
|
156
152
|
if isinstance(token, str) and token.strip():
|
|
157
153
|
return {"type": "bearer-token", "token": token}
|
|
158
154
|
headers = auth.get("headers")
|
|
159
|
-
normalized_headers = _validate_headers_mapping(
|
|
160
|
-
headers, source=source, context="bearer-token authentication"
|
|
161
|
-
)
|
|
155
|
+
normalized_headers = _validate_headers_mapping(headers, source=source, context="bearer-token authentication")
|
|
162
156
|
return {"type": "bearer-token", "headers": normalized_headers}
|
|
163
157
|
|
|
164
158
|
|
|
@@ -177,9 +171,7 @@ def _validate_api_key_auth(auth: dict[str, Any], source: str) -> dict[str, Any]:
|
|
|
177
171
|
"""
|
|
178
172
|
headers = auth.get("headers")
|
|
179
173
|
if headers is not None:
|
|
180
|
-
normalized_headers = _validate_headers_mapping(
|
|
181
|
-
headers, source=source, context="api-key authentication"
|
|
182
|
-
)
|
|
174
|
+
normalized_headers = _validate_headers_mapping(headers, source=source, context="api-key authentication")
|
|
183
175
|
return {"type": "api-key", "headers": normalized_headers}
|
|
184
176
|
|
|
185
177
|
key = auth.get("key")
|
|
@@ -215,9 +207,7 @@ def _validate_custom_header_auth(auth: dict[str, Any], source: str) -> dict[str,
|
|
|
215
207
|
click.ClickException: If custom-header structure is invalid
|
|
216
208
|
"""
|
|
217
209
|
headers = auth.get("headers")
|
|
218
|
-
normalized_headers = _validate_headers_mapping(
|
|
219
|
-
headers, source=source, context="custom-header authentication"
|
|
220
|
-
)
|
|
210
|
+
normalized_headers = _validate_headers_mapping(headers, source=source, context="custom-header authentication")
|
|
221
211
|
return {"type": "custom-header", "headers": normalized_headers}
|
|
222
212
|
|
|
223
213
|
|
glaip_sdk/cli/pager.py
CHANGED
|
@@ -156,9 +156,7 @@ def _resolve_pager_command() -> tuple[list[str] | None, str | None]:
|
|
|
156
156
|
return pager_cmd, less_path
|
|
157
157
|
|
|
158
158
|
|
|
159
|
-
def _run_less_pager(
|
|
160
|
-
pager_cmd: list[str] | None, less_path: str | None, tmp_path: str
|
|
161
|
-
) -> None:
|
|
159
|
+
def _run_less_pager(pager_cmd: list[str] | None, less_path: str | None, tmp_path: str) -> None:
|
|
162
160
|
"""Run less pager with appropriate command and flags.
|
|
163
161
|
|
|
164
162
|
Args:
|
|
@@ -195,9 +193,7 @@ def _run_more_pager(tmp_path: str) -> None:
|
|
|
195
193
|
raise FileNotFoundError("more command not found")
|
|
196
194
|
|
|
197
195
|
|
|
198
|
-
def _run_pager_with_temp_file(
|
|
199
|
-
pager_runner: Callable[[str], None], ansi_text: str
|
|
200
|
-
) -> bool:
|
|
196
|
+
def _run_pager_with_temp_file(pager_runner: Callable[[str], None], ansi_text: str) -> bool:
|
|
201
197
|
"""Run a pager using a temporary file containing the content.
|
|
202
198
|
|
|
203
199
|
Args:
|
|
@@ -239,9 +235,7 @@ def _page_with_system_pager(ansi_text: str) -> bool:
|
|
|
239
235
|
pager_cmd, less_path = _resolve_pager_command()
|
|
240
236
|
|
|
241
237
|
if pager_cmd or less_path:
|
|
242
|
-
return _run_pager_with_temp_file(
|
|
243
|
-
lambda tmp_path: _run_less_pager(pager_cmd, less_path, tmp_path), ansi_text
|
|
244
|
-
)
|
|
238
|
+
return _run_pager_with_temp_file(lambda tmp_path: _run_less_pager(pager_cmd, less_path, tmp_path), ansi_text)
|
|
245
239
|
|
|
246
240
|
if platform.system().lower().startswith("win"):
|
|
247
241
|
return False
|
|
@@ -33,9 +33,7 @@ def _looks_like_file_path(value: str) -> bool:
|
|
|
33
33
|
)
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def _format_file_error(
|
|
37
|
-
prefix: str, file_path_str: str, resolved_path: Path, *, detail: str | None = None
|
|
38
|
-
) -> str:
|
|
36
|
+
def _format_file_error(prefix: str, file_path_str: str, resolved_path: Path, *, detail: str | None = None) -> str:
|
|
39
37
|
r"""Format a file-related error message with path context.
|
|
40
38
|
|
|
41
39
|
Args:
|
|
@@ -77,16 +75,12 @@ def _parse_json_from_file(file_path_str: str) -> Any:
|
|
|
77
75
|
|
|
78
76
|
# Check if file exists and is a regular file
|
|
79
77
|
if not file_path.is_file():
|
|
80
|
-
raise click.ClickException(
|
|
81
|
-
_format_file_error("File not found or not a file", file_path_str, file_path)
|
|
82
|
-
)
|
|
78
|
+
raise click.ClickException(_format_file_error("File not found or not a file", file_path_str, file_path))
|
|
83
79
|
|
|
84
80
|
# Check if file is readable
|
|
85
81
|
if not os.access(file_path, os.R_OK):
|
|
86
82
|
raise click.ClickException(
|
|
87
|
-
_format_file_error(
|
|
88
|
-
"File not readable (permission denied)", file_path_str, file_path
|
|
89
|
-
)
|
|
83
|
+
_format_file_error("File not readable (permission denied)", file_path_str, file_path)
|
|
90
84
|
)
|
|
91
85
|
|
|
92
86
|
# Read file content
|
|
@@ -94,16 +88,12 @@ def _parse_json_from_file(file_path_str: str) -> Any:
|
|
|
94
88
|
content = file_path.read_text(encoding="utf-8")
|
|
95
89
|
except Exception as e:
|
|
96
90
|
raise click.ClickException(
|
|
97
|
-
_format_file_error(
|
|
98
|
-
|
|
99
|
-
)
|
|
100
|
-
)
|
|
91
|
+
_format_file_error("Error reading file", file_path_str, file_path, detail=f"Error: {e}")
|
|
92
|
+
) from e
|
|
101
93
|
|
|
102
94
|
# Check for empty content
|
|
103
95
|
if not content.strip():
|
|
104
|
-
raise click.ClickException(
|
|
105
|
-
_format_file_error("File is empty", file_path_str, file_path)
|
|
106
|
-
)
|
|
96
|
+
raise click.ClickException(_format_file_error("File is empty", file_path_str, file_path))
|
|
107
97
|
|
|
108
98
|
# Determine file format and parse accordingly
|
|
109
99
|
file_ext = file_path.suffix.lower()
|
|
@@ -120,7 +110,7 @@ def _parse_json_from_file(file_path_str: str) -> Any:
|
|
|
120
110
|
file_path,
|
|
121
111
|
detail=f"Error: {e}",
|
|
122
112
|
)
|
|
123
|
-
)
|
|
113
|
+
) from e
|
|
124
114
|
else:
|
|
125
115
|
# Default to JSON parsing
|
|
126
116
|
try:
|
|
@@ -133,7 +123,7 @@ def _parse_json_from_file(file_path_str: str) -> Any:
|
|
|
133
123
|
file_path,
|
|
134
124
|
detail=f"Error: {e.msg} at line {e.lineno}, column {e.colno}",
|
|
135
125
|
)
|
|
136
|
-
)
|
|
126
|
+
) from e
|
|
137
127
|
|
|
138
128
|
|
|
139
129
|
def parse_json_input(value: str | None) -> Any:
|
|
@@ -180,9 +170,8 @@ def parse_json_input(value: str | None) -> Any:
|
|
|
180
170
|
f"Error: {e.msg} at line {e.lineno}, column {e.colno}\n"
|
|
181
171
|
f"\n💡 Did you mean to load this from a file? "
|
|
182
172
|
f"File-based config values should start with @ (e.g., @{trimmed})"
|
|
183
|
-
)
|
|
173
|
+
) from e
|
|
184
174
|
|
|
185
175
|
raise click.ClickException(
|
|
186
|
-
f"Invalid JSON in inline value\
|
|
187
|
-
|
|
188
|
-
)
|
|
176
|
+
f"Invalid JSON in inline value\nError: {e.msg} at line {e.lineno}, column {e.colno}"
|
|
177
|
+
) from e
|
glaip_sdk/cli/resolution.py
CHANGED
|
@@ -51,14 +51,8 @@ def resolve_resource_reference(
|
|
|
51
51
|
click.ClickException: If resolution fails.
|
|
52
52
|
"""
|
|
53
53
|
try:
|
|
54
|
-
message =
|
|
55
|
-
|
|
56
|
-
if spinner_message is not None
|
|
57
|
-
else f"[bold blue]Fetching {label}…[/bold blue]"
|
|
58
|
-
)
|
|
59
|
-
with spinner_context(
|
|
60
|
-
ctx, message, spinner_style=ACCENT_STYLE
|
|
61
|
-
) as status_indicator:
|
|
54
|
+
message = spinner_message if spinner_message is not None else f"[bold blue]Fetching {label}…[/bold blue]"
|
|
55
|
+
with spinner_context(ctx, message, spinner_style=ACCENT_STYLE) as status_indicator:
|
|
62
56
|
return resolve_resource(
|
|
63
57
|
ctx,
|
|
64
58
|
reference,
|
|
@@ -70,4 +64,4 @@ def resolve_resource_reference(
|
|
|
70
64
|
status_indicator=status_indicator,
|
|
71
65
|
)
|
|
72
66
|
except Exception as e:
|
|
73
|
-
raise click.ClickException(f"Failed to resolve {resource_type.lower()}: {e}")
|
|
67
|
+
raise click.ClickException(f"Failed to resolve {resource_type.lower()}: {e}") from e
|
glaip_sdk/cli/rich_helpers.py
CHANGED
|
@@ -21,9 +21,7 @@ def markup_text(message: str, **kwargs: Any) -> Text:
|
|
|
21
21
|
return Text(message, **kwargs)
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def print_markup(
|
|
25
|
-
message: str, *, console: Console | None = None, **kwargs: Any
|
|
26
|
-
) -> None:
|
|
24
|
+
def print_markup(message: str, *, console: Console | None = None, **kwargs: Any) -> None:
|
|
27
25
|
"""Print markup-aware text to the provided console (default: new Console)."""
|
|
28
26
|
target_console = console or Console()
|
|
29
27
|
target_console.print(markup_text(message, **kwargs))
|
|
@@ -46,9 +46,7 @@ class AgentRunSession:
|
|
|
46
46
|
|
|
47
47
|
def run(self) -> None:
|
|
48
48
|
"""Run the interactive agent session loop."""
|
|
49
|
-
self.session.set_contextual_commands(
|
|
50
|
-
self._contextual_completion_help, include_global=False
|
|
51
|
-
)
|
|
49
|
+
self.session.set_contextual_commands(self._contextual_completion_help, include_global=False)
|
|
52
50
|
previous_agent = getattr(self.session, "_current_agent", None)
|
|
53
51
|
self.session._current_agent = self.agent
|
|
54
52
|
clear_ready = getattr(self.session, "clear_agent_transcript_ready", None)
|
|
@@ -157,7 +155,8 @@ class AgentRunSession:
|
|
|
157
155
|
try:
|
|
158
156
|
self.session.ctx.invoke(agents_get_command, agent_ref=agent_id)
|
|
159
157
|
self.console.print(
|
|
160
|
-
f"[{HINT_PREFIX_STYLE}]Tip:[/] Continue the conversation in this prompt, or use
|
|
158
|
+
f"[{HINT_PREFIX_STYLE}]Tip:[/] Continue the conversation in this prompt, or use "
|
|
159
|
+
f"{format_command_hint('/help') or '/help'} for shortcuts."
|
|
161
160
|
)
|
|
162
161
|
except click.ClickException as exc:
|
|
163
162
|
self.console.print(f"[{ERROR_STYLE}]{exc}[/]")
|
|
@@ -173,9 +172,7 @@ class AgentRunSession:
|
|
|
173
172
|
mark_ready(self._agent_id, run_id)
|
|
174
173
|
if self._open_transcript_viewer():
|
|
175
174
|
return
|
|
176
|
-
self.console.print(
|
|
177
|
-
"[dim]Transcript viewer is unavailable in this environment.[/dim]"
|
|
178
|
-
)
|
|
175
|
+
self.console.print("[dim]Transcript viewer is unavailable in this environment.[/dim]")
|
|
179
176
|
|
|
180
177
|
def _transcript_matches(self, payload: Any, manifest: Any) -> bool:
|
|
181
178
|
"""Return True when the latest transcript belongs to this agent."""
|
|
@@ -197,9 +194,7 @@ class AgentRunSession:
|
|
|
197
194
|
self.session.console.clear()
|
|
198
195
|
except Exception: # pragma: no cover - defensive cleanup
|
|
199
196
|
pass
|
|
200
|
-
if
|
|
201
|
-
current_agent is not None
|
|
202
|
-
): # pragma: no cover - UI refresh best effort
|
|
197
|
+
if current_agent is not None: # pragma: no cover - UI refresh best effort
|
|
203
198
|
try:
|
|
204
199
|
self.session._render_header(current_agent, focus_agent=True)
|
|
205
200
|
except Exception: # pragma: no cover - defensive cleanup
|
glaip_sdk/cli/slash/prompt.py
CHANGED
|
@@ -178,15 +178,10 @@ def _iter_command_completions(
|
|
|
178
178
|
|
|
179
179
|
def _should_include_commands(session: SlashSession) -> bool:
|
|
180
180
|
"""Check if commands should be included in completions."""
|
|
181
|
-
return not (
|
|
182
|
-
session.get_contextual_commands()
|
|
183
|
-
and not session.should_include_global_commands()
|
|
184
|
-
)
|
|
181
|
+
return not (session.get_contextual_commands() and not session.should_include_global_commands())
|
|
185
182
|
|
|
186
183
|
|
|
187
|
-
def _generate_command_completions(
|
|
188
|
-
cmd: Any, prefix: str, text: str, seen: set[str]
|
|
189
|
-
) -> Iterable[Completion]:
|
|
184
|
+
def _generate_command_completions(cmd: Any, prefix: str, text: str, seen: set[str]) -> Iterable[Completion]:
|
|
190
185
|
"""Generate completion items for a single command."""
|
|
191
186
|
for alias in (cmd.name, *cmd.aliases):
|
|
192
187
|
if alias in seen or alias.startswith("?"):
|
|
@@ -211,9 +206,7 @@ def _iter_contextual_completions(
|
|
|
211
206
|
prefix = text[1:]
|
|
212
207
|
seen: set[str] = set()
|
|
213
208
|
|
|
214
|
-
contextual_commands = sorted(
|
|
215
|
-
session.get_contextual_commands().items(), key=lambda item: item[0]
|
|
216
|
-
)
|
|
209
|
+
contextual_commands = sorted(session.get_contextual_commands().items(), key=lambda item: item[0])
|
|
217
210
|
|
|
218
211
|
for alias, help_text in contextual_commands:
|
|
219
212
|
if alias in seen:
|
glaip_sdk/cli/slash/session.py
CHANGED
|
@@ -120,6 +120,16 @@ class SlashSession:
|
|
|
120
120
|
# ------------------------------------------------------------------
|
|
121
121
|
# Session orchestration
|
|
122
122
|
# ------------------------------------------------------------------
|
|
123
|
+
def refresh_branding(self, sdk_version: str | None = None) -> None:
|
|
124
|
+
"""Refresh branding assets after an in-session SDK upgrade."""
|
|
125
|
+
self._branding = AIPBranding.create_from_sdk(
|
|
126
|
+
sdk_version=sdk_version,
|
|
127
|
+
package_name="glaip-sdk",
|
|
128
|
+
)
|
|
129
|
+
self._welcome_rendered = False
|
|
130
|
+
self.console.print()
|
|
131
|
+
self.console.print(f"[{SUCCESS_STYLE}]CLI updated to {self._branding.version}. Refreshing banner...[/]")
|
|
132
|
+
self._render_header(initial=True)
|
|
123
133
|
|
|
124
134
|
def _setup_prompt_toolkit(self) -> None:
|
|
125
135
|
session, style = setup_prompt_toolkit(self, interactive=self._interactive)
|
|
@@ -181,16 +191,12 @@ class SlashSession:
|
|
|
181
191
|
return True
|
|
182
192
|
|
|
183
193
|
if not raw.startswith("/"):
|
|
184
|
-
self.console.print(
|
|
185
|
-
f"[{INFO_STYLE}]Hint:[/] start commands with `/`. Try `/agents` to select an agent."
|
|
186
|
-
)
|
|
194
|
+
self.console.print(f"[{INFO_STYLE}]Hint:[/] start commands with `/`. Try `/agents` to select an agent.")
|
|
187
195
|
return True
|
|
188
196
|
|
|
189
197
|
return self.handle_command(raw)
|
|
190
198
|
|
|
191
|
-
def _run_non_interactive(
|
|
192
|
-
self, initial_commands: Iterable[str] | None = None
|
|
193
|
-
) -> None:
|
|
199
|
+
def _run_non_interactive(self, initial_commands: Iterable[str] | None = None) -> None:
|
|
194
200
|
"""Run slash commands in non-interactive mode."""
|
|
195
201
|
commands = list(initial_commands or [])
|
|
196
202
|
if not commands:
|
|
@@ -205,16 +211,12 @@ class SlashSession:
|
|
|
205
211
|
def _ensure_configuration(self) -> bool:
|
|
206
212
|
"""Ensure the CLI has both API URL and credentials before continuing."""
|
|
207
213
|
while not self._configuration_ready():
|
|
208
|
-
self.console.print(
|
|
209
|
-
f"[{WARNING_STYLE}]Configuration required.[/] Launching `/login` wizard..."
|
|
210
|
-
)
|
|
214
|
+
self.console.print(f"[{WARNING_STYLE}]Configuration required.[/] Launching `/login` wizard...")
|
|
211
215
|
self._suppress_login_layout = True
|
|
212
216
|
try:
|
|
213
217
|
self._cmd_login([], False)
|
|
214
218
|
except KeyboardInterrupt:
|
|
215
|
-
self.console.print(
|
|
216
|
-
f"[{ERROR_STYLE}]Configuration aborted. Closing the command palette.[/]"
|
|
217
|
-
)
|
|
219
|
+
self.console.print(f"[{ERROR_STYLE}]Configuration aborted. Closing the command palette.[/]")
|
|
218
220
|
return False
|
|
219
221
|
finally:
|
|
220
222
|
self._suppress_login_layout = False
|
|
@@ -246,9 +248,7 @@ class SlashSession:
|
|
|
246
248
|
if command is None:
|
|
247
249
|
suggestion = self._suggest(verb)
|
|
248
250
|
if suggestion:
|
|
249
|
-
self.console.print(
|
|
250
|
-
f"[{WARNING_STYLE}]Unknown command '{verb}'. Did you mean '/{suggestion}'?[/]"
|
|
251
|
-
)
|
|
251
|
+
self.console.print(f"[{WARNING_STYLE}]Unknown command '{verb}'. Did you mean '/{suggestion}'?[/]")
|
|
252
252
|
else:
|
|
253
253
|
help_command = "/help"
|
|
254
254
|
help_hint = format_command_hint(help_command) or help_command
|
|
@@ -291,9 +291,7 @@ class SlashSession:
|
|
|
291
291
|
|
|
292
292
|
panel_items = [table]
|
|
293
293
|
if self.last_run_input:
|
|
294
|
-
panel_items.append(
|
|
295
|
-
Text.from_markup(f"[dim]Last run input:[/] {self.last_run_input}")
|
|
296
|
-
)
|
|
294
|
+
panel_items.append(Text.from_markup(f"[dim]Last run input:[/] {self.last_run_input}"))
|
|
297
295
|
panel_items.append(
|
|
298
296
|
Text.from_markup(
|
|
299
297
|
"[dim]Global commands (e.g. `/login`, `/status`) remain available inside the agent prompt.[/dim]"
|
|
@@ -321,7 +319,9 @@ class SlashSession:
|
|
|
321
319
|
table.add_row(verb, cmd.help)
|
|
322
320
|
|
|
323
321
|
tip = Text.from_markup(
|
|
324
|
-
f"[{HINT_PREFIX_STYLE}]Tip:[/]
|
|
322
|
+
f"[{HINT_PREFIX_STYLE}]Tip:[/] "
|
|
323
|
+
f"{format_command_hint(self.AGENTS_COMMAND) or self.AGENTS_COMMAND} "
|
|
324
|
+
"lets you jump into an agent run prompt quickly."
|
|
325
325
|
)
|
|
326
326
|
|
|
327
327
|
self.console.print(
|
|
@@ -352,7 +352,7 @@ class SlashSession:
|
|
|
352
352
|
previous_console = None
|
|
353
353
|
try:
|
|
354
354
|
status_module = importlib.import_module("glaip_sdk.cli.main")
|
|
355
|
-
status_command =
|
|
355
|
+
status_command = status_module.status
|
|
356
356
|
|
|
357
357
|
if ctx_obj is not None:
|
|
358
358
|
previous_console = ctx_obj.get("_slash_console")
|
|
@@ -360,9 +360,7 @@ class SlashSession:
|
|
|
360
360
|
|
|
361
361
|
self.ctx.invoke(status_command)
|
|
362
362
|
|
|
363
|
-
hints: list[tuple[str, str]] = [
|
|
364
|
-
(self.AGENTS_COMMAND, "Browse agents and run them")
|
|
365
|
-
]
|
|
363
|
+
hints: list[tuple[str, str]] = [(self.AGENTS_COMMAND, "Browse agents and run them")]
|
|
366
364
|
if self.recent_agents:
|
|
367
365
|
top = self.recent_agents[0]
|
|
368
366
|
label = top.get("name") or top.get("id")
|
|
@@ -417,9 +415,7 @@ class SlashSession:
|
|
|
417
415
|
"""Handle case when no agents are available."""
|
|
418
416
|
hint = command_hint("agents create", slash_command=None, ctx=self.ctx)
|
|
419
417
|
if hint:
|
|
420
|
-
self.console.print(
|
|
421
|
-
f"[{WARNING_STYLE}]No agents available. Use `{hint}` to add one.[/]"
|
|
422
|
-
)
|
|
418
|
+
self.console.print(f"[{WARNING_STYLE}]No agents available. Use `{hint}` to add one.[/]")
|
|
423
419
|
else:
|
|
424
420
|
self.console.print(f"[{WARNING_STYLE}]No agents available.[/]")
|
|
425
421
|
|
|
@@ -542,17 +538,13 @@ class SlashSession:
|
|
|
542
538
|
payload, manifest = self._get_last_transcript()
|
|
543
539
|
if payload is None or manifest is None:
|
|
544
540
|
if announce:
|
|
545
|
-
self.console.print(
|
|
546
|
-
f"[{WARNING_STYLE}]No transcript is available yet. Run an agent first.[/]"
|
|
547
|
-
)
|
|
541
|
+
self.console.print(f"[{WARNING_STYLE}]No transcript is available yet. Run an agent first.[/]")
|
|
548
542
|
return
|
|
549
543
|
|
|
550
544
|
run_id = manifest.get("run_id")
|
|
551
545
|
if not run_id:
|
|
552
546
|
if announce:
|
|
553
|
-
self.console.print(
|
|
554
|
-
f"[{WARNING_STYLE}]Latest transcript is missing run metadata.[/]"
|
|
555
|
-
)
|
|
547
|
+
self.console.print(f"[{WARNING_STYLE}]Latest transcript is missing run metadata.[/]")
|
|
556
548
|
return
|
|
557
549
|
|
|
558
550
|
viewer_ctx = ViewerContext(
|
|
@@ -571,9 +563,7 @@ class SlashSession:
|
|
|
571
563
|
run_viewer_session(self.console, viewer_ctx, _export)
|
|
572
564
|
except Exception as exc: # pragma: no cover - interactive failures
|
|
573
565
|
if announce:
|
|
574
|
-
self.console.print(
|
|
575
|
-
f"[{ERROR_STYLE}]Failed to launch transcript viewer: {exc}[/]"
|
|
576
|
-
)
|
|
566
|
+
self.console.print(f"[{ERROR_STYLE}]Failed to launch transcript viewer: {exc}[/]")
|
|
577
567
|
|
|
578
568
|
def _get_last_transcript(self) -> tuple[Any | None, dict[str, Any] | None]:
|
|
579
569
|
"""Fetch the most recently stored transcript payload and manifest."""
|
|
@@ -593,12 +583,11 @@ class SlashSession:
|
|
|
593
583
|
if manifest_entry is None:
|
|
594
584
|
if run_id:
|
|
595
585
|
self.console.print(
|
|
596
|
-
f"[{WARNING_STYLE}]No cached transcript found with run id {run_id!r}.
|
|
586
|
+
f"[{WARNING_STYLE}]No cached transcript found with run id {run_id!r}. "
|
|
587
|
+
"Omit the run id to export the most recent run.[/]"
|
|
597
588
|
)
|
|
598
589
|
else:
|
|
599
|
-
self.console.print(
|
|
600
|
-
f"[{WARNING_STYLE}]No cached transcripts available yet. Run an agent first.[/]"
|
|
601
|
-
)
|
|
590
|
+
self.console.print(f"[{WARNING_STYLE}]No cached transcripts available yet. Run an agent first.[/]")
|
|
602
591
|
return False
|
|
603
592
|
|
|
604
593
|
destination = self._resolve_export_destination(path_arg, manifest_entry)
|
|
@@ -620,9 +609,7 @@ class SlashSession:
|
|
|
620
609
|
self.console.print(f"[{SUCCESS_STYLE}]Transcript exported to[/] {exported}")
|
|
621
610
|
return True
|
|
622
611
|
|
|
623
|
-
def _resolve_export_destination(
|
|
624
|
-
self, path_arg: str | None, manifest_entry: dict[str, Any]
|
|
625
|
-
) -> Path | None:
|
|
612
|
+
def _resolve_export_destination(self, path_arg: str | None, manifest_entry: dict[str, Any]) -> Path | None:
|
|
626
613
|
if path_arg:
|
|
627
614
|
return normalise_export_destination(Path(path_arg))
|
|
628
615
|
|
|
@@ -640,9 +627,7 @@ class SlashSession:
|
|
|
640
627
|
def _cmd_update(self, args: list[str], _invoked_from_agent: bool) -> bool:
|
|
641
628
|
"""Slash handler for `/update` command."""
|
|
642
629
|
if args:
|
|
643
|
-
self.console.print(
|
|
644
|
-
"Usage: `/update` upgrades glaip-sdk to the latest published version."
|
|
645
|
-
)
|
|
630
|
+
self.console.print("Usage: `/update` upgrades glaip-sdk to the latest published version.")
|
|
646
631
|
return True
|
|
647
632
|
|
|
648
633
|
try:
|
|
@@ -742,16 +727,12 @@ class SlashSession:
|
|
|
742
727
|
prompt_kwargs: dict[str, Any] = {"style": self._ptk_style}
|
|
743
728
|
if placeholder:
|
|
744
729
|
placeholder_text = (
|
|
745
|
-
FormattedText([("class:placeholder", placeholder)])
|
|
746
|
-
if FormattedText is not None
|
|
747
|
-
else placeholder
|
|
730
|
+
FormattedText([("class:placeholder", placeholder)]) if FormattedText is not None else placeholder
|
|
748
731
|
)
|
|
749
732
|
prompt_kwargs["placeholder"] = placeholder_text
|
|
750
733
|
return prompt_kwargs
|
|
751
734
|
|
|
752
|
-
def _prompt_with_prompt_toolkit(
|
|
753
|
-
self, message: str | Callable[[], Any], placeholder: str | None
|
|
754
|
-
) -> str:
|
|
735
|
+
def _prompt_with_prompt_toolkit(self, message: str | Callable[[], Any], placeholder: str | None) -> str:
|
|
755
736
|
"""Handle prompting with prompt_toolkit."""
|
|
756
737
|
with patch_stdout(): # pragma: no cover - UI specific
|
|
757
738
|
if callable(message):
|
|
@@ -765,9 +746,7 @@ class SlashSession:
|
|
|
765
746
|
|
|
766
747
|
try:
|
|
767
748
|
return self._ptk_session.prompt(prompt_text, **prompt_kwargs)
|
|
768
|
-
except
|
|
769
|
-
TypeError
|
|
770
|
-
): # pragma: no cover - compatibility with older prompt_toolkit
|
|
749
|
+
except TypeError: # pragma: no cover - compatibility with older prompt_toolkit
|
|
771
750
|
prompt_kwargs.pop("placeholder", None)
|
|
772
751
|
return self._ptk_session.prompt(prompt_text, **prompt_kwargs)
|
|
773
752
|
|
|
@@ -786,9 +765,7 @@ class SlashSession:
|
|
|
786
765
|
except Exception:
|
|
787
766
|
return str(raw_value)
|
|
788
767
|
|
|
789
|
-
def _prompt_with_basic_input(
|
|
790
|
-
self, message: str | Callable[[], Any], placeholder: str | None
|
|
791
|
-
) -> str:
|
|
768
|
+
def _prompt_with_basic_input(self, message: str | Callable[[], Any], placeholder: str | None) -> str:
|
|
792
769
|
"""Handle prompting with basic input."""
|
|
793
770
|
if placeholder:
|
|
794
771
|
self.console.print(f"[dim]{placeholder}[/dim]")
|
|
@@ -798,9 +775,7 @@ class SlashSession:
|
|
|
798
775
|
|
|
799
776
|
return input(actual_message)
|
|
800
777
|
|
|
801
|
-
def _prompt(
|
|
802
|
-
self, message: str | Callable[[], Any], *, placeholder: str | None = None
|
|
803
|
-
) -> str:
|
|
778
|
+
def _prompt(self, message: str | Callable[[], Any], *, placeholder: str | None = None) -> str:
|
|
804
779
|
"""Main prompt function with reduced complexity."""
|
|
805
780
|
if self._ptk_session and self._ptk_style and patch_stdout:
|
|
806
781
|
return self._prompt_with_prompt_toolkit(message, placeholder)
|
|
@@ -812,9 +787,7 @@ class SlashSession:
|
|
|
812
787
|
self._client = get_client(self.ctx)
|
|
813
788
|
return self._client
|
|
814
789
|
|
|
815
|
-
def set_contextual_commands(
|
|
816
|
-
self, commands: dict[str, str] | None, *, include_global: bool = True
|
|
817
|
-
) -> None:
|
|
790
|
+
def set_contextual_commands(self, commands: dict[str, str] | None, *, include_global: bool = True) -> None:
|
|
818
791
|
"""Set context-specific commands that should appear in completions."""
|
|
819
792
|
self._contextual_commands = dict(commands or {})
|
|
820
793
|
self._contextual_include_global = include_global if commands else True
|
|
@@ -834,9 +807,7 @@ class SlashSession:
|
|
|
834
807
|
"type": getattr(agent, "type", "") or "",
|
|
835
808
|
}
|
|
836
809
|
|
|
837
|
-
self.recent_agents = [
|
|
838
|
-
a for a in self.recent_agents if a.get("id") != agent_data["id"]
|
|
839
|
-
]
|
|
810
|
+
self.recent_agents = [a for a in self.recent_agents if a.get("id") != agent_data["id"]]
|
|
840
811
|
self.recent_agents.insert(0, agent_data)
|
|
841
812
|
self.recent_agents = self.recent_agents[:5]
|
|
842
813
|
|
|
@@ -891,9 +862,7 @@ class SlashSession:
|
|
|
891
862
|
keybar = self._build_keybar()
|
|
892
863
|
|
|
893
864
|
header_grid.add_row(keybar, "")
|
|
894
|
-
self.console.print(
|
|
895
|
-
AIPPanel(header_grid, title="Agent Session", border_style=PRIMARY)
|
|
896
|
-
)
|
|
865
|
+
self.console.print(AIPPanel(header_grid, title="Agent Session", border_style=PRIMARY))
|
|
897
866
|
|
|
898
867
|
def _get_agent_info(self, active_agent: Any) -> dict[str, str]:
|
|
899
868
|
"""Extract agent information for display."""
|
|
@@ -914,9 +883,7 @@ class SlashSession:
|
|
|
914
883
|
has_transcript = bool(payload and manifest and manifest.get("run_id"))
|
|
915
884
|
run_id = (manifest or {}).get("run_id")
|
|
916
885
|
transcript_ready = (
|
|
917
|
-
has_transcript
|
|
918
|
-
and latest_agent_id == agent_id
|
|
919
|
-
and self._agent_transcript_ready.get(agent_id) == run_id
|
|
886
|
+
has_transcript and latest_agent_id == agent_id and self._agent_transcript_ready.get(agent_id) == run_id
|
|
920
887
|
)
|
|
921
888
|
|
|
922
889
|
return {
|
|
@@ -925,9 +892,7 @@ class SlashSession:
|
|
|
925
892
|
"run_id": run_id,
|
|
926
893
|
}
|
|
927
894
|
|
|
928
|
-
def _build_header_grid(
|
|
929
|
-
self, agent_info: dict[str, str], transcript_status: dict[str, Any]
|
|
930
|
-
) -> AIPGrid:
|
|
895
|
+
def _build_header_grid(self, agent_info: dict[str, str], transcript_status: dict[str, Any]) -> AIPGrid:
|
|
931
896
|
"""Build the main header grid with agent information."""
|
|
932
897
|
header_grid = AIPGrid(expand=True)
|
|
933
898
|
header_grid.add_column(ratio=3)
|
|
@@ -938,11 +903,7 @@ class SlashSession:
|
|
|
938
903
|
f"[{ACCENT_STYLE}]{agent_info['id']}[/]"
|
|
939
904
|
)
|
|
940
905
|
status_line = f"[{SUCCESS_STYLE}]ready[/]"
|
|
941
|
-
status_line +=
|
|
942
|
-
" · transcript ready"
|
|
943
|
-
if transcript_status["transcript_ready"]
|
|
944
|
-
else " · transcript pending"
|
|
945
|
-
)
|
|
906
|
+
status_line += " · transcript ready" if transcript_status["transcript_ready"] else " · transcript pending"
|
|
946
907
|
header_grid.add_row(primary_line, status_line)
|
|
947
908
|
|
|
948
909
|
if agent_info["description"]:
|
|
@@ -967,9 +928,7 @@ class SlashSession:
|
|
|
967
928
|
|
|
968
929
|
return keybar
|
|
969
930
|
|
|
970
|
-
def _render_main_header(
|
|
971
|
-
self, active_agent: Any | None = None, *, full: bool = False
|
|
972
|
-
) -> None:
|
|
931
|
+
def _render_main_header(self, active_agent: Any | None = None, *, full: bool = False) -> None:
|
|
973
932
|
"""Render the main AIP environment header."""
|
|
974
933
|
config = self._load_config()
|
|
975
934
|
|
|
@@ -987,7 +946,7 @@ class SlashSession:
|
|
|
987
946
|
rendered_line = " ".join(segments)
|
|
988
947
|
|
|
989
948
|
if full:
|
|
990
|
-
self.console.print(rendered_line)
|
|
949
|
+
self.console.print(rendered_line, soft_wrap=False)
|
|
991
950
|
return
|
|
992
951
|
|
|
993
952
|
status_bar = AIPGrid(expand=True)
|
|
@@ -1060,9 +1019,7 @@ class SlashSession:
|
|
|
1060
1019
|
title: str = "Quick actions",
|
|
1061
1020
|
inline: bool = False,
|
|
1062
1021
|
) -> None:
|
|
1063
|
-
hint_list = [
|
|
1064
|
-
(command, description) for command, description in hints if command
|
|
1065
|
-
]
|
|
1022
|
+
hint_list = [(command, description) for command, description in hints if command]
|
|
1066
1023
|
if not hint_list:
|
|
1067
1024
|
return
|
|
1068
1025
|
|
|
@@ -1083,11 +1040,7 @@ class SlashSession:
|
|
|
1083
1040
|
body_lines.append(Text.from_markup(formatted))
|
|
1084
1041
|
|
|
1085
1042
|
panel_content = Group(*body_lines)
|
|
1086
|
-
self.console.print(
|
|
1087
|
-
AIPPanel(
|
|
1088
|
-
panel_content, title=title, border_style=SECONDARY_LIGHT, expand=False
|
|
1089
|
-
)
|
|
1090
|
-
)
|
|
1043
|
+
self.console.print(AIPPanel(panel_content, title=title, border_style=SECONDARY_LIGHT, expand=False))
|
|
1091
1044
|
|
|
1092
1045
|
def _load_config(self) -> dict[str, Any]:
|
|
1093
1046
|
if self._config_cache is None:
|
|
@@ -1097,9 +1050,7 @@ class SlashSession:
|
|
|
1097
1050
|
self._config_cache = {}
|
|
1098
1051
|
return self._config_cache
|
|
1099
1052
|
|
|
1100
|
-
def _resolve_agent_from_ref(
|
|
1101
|
-
self, client: Any, available_agents: list[Any], ref: str
|
|
1102
|
-
) -> Any | None:
|
|
1053
|
+
def _resolve_agent_from_ref(self, client: Any, available_agents: list[Any], ref: str) -> Any | None:
|
|
1103
1054
|
ref = ref.strip()
|
|
1104
1055
|
if not ref:
|
|
1105
1056
|
return None
|