glaip-sdk 0.0.7__py3-none-any.whl → 0.0.9__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/branding.py +3 -3
- glaip_sdk/cli/commands/agents.py +119 -12
- glaip_sdk/cli/commands/mcps.py +61 -15
- glaip_sdk/cli/commands/models.py +12 -2
- glaip_sdk/cli/commands/tools.py +69 -13
- glaip_sdk/cli/display.py +4 -3
- glaip_sdk/cli/main.py +54 -5
- glaip_sdk/cli/resolution.py +17 -9
- glaip_sdk/cli/slash/__init__.py +25 -0
- glaip_sdk/cli/slash/agent_session.py +146 -0
- glaip_sdk/cli/slash/prompt.py +198 -0
- glaip_sdk/cli/slash/session.py +665 -0
- glaip_sdk/cli/utils.py +150 -12
- glaip_sdk/utils/rendering/renderer/base.py +20 -2
- glaip_sdk/utils/rendering/renderer/panels.py +21 -7
- glaip_sdk/utils/serialization.py +49 -17
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.0.9.dist-info}/METADATA +2 -2
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.0.9.dist-info}/RECORD +20 -16
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.0.9.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.0.9.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/resolution.py
CHANGED
|
@@ -12,7 +12,7 @@ from typing import Any
|
|
|
12
12
|
|
|
13
13
|
import click
|
|
14
14
|
|
|
15
|
-
from glaip_sdk.cli.utils import resolve_resource
|
|
15
|
+
from glaip_sdk.cli.utils import resolve_resource, spinner_context
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def resolve_resource_reference(
|
|
@@ -25,6 +25,7 @@ def resolve_resource_reference(
|
|
|
25
25
|
label: str,
|
|
26
26
|
select: int | None = None,
|
|
27
27
|
interface_preference: str | None = None,
|
|
28
|
+
spinner_message: str | None = None,
|
|
28
29
|
) -> Any | None:
|
|
29
30
|
"""Resolve resource reference (ID or name) with ambiguity handling.
|
|
30
31
|
|
|
@@ -47,14 +48,21 @@ def resolve_resource_reference(
|
|
|
47
48
|
click.ClickException: If resolution fails
|
|
48
49
|
"""
|
|
49
50
|
try:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
find_by_name=find_by_name_func,
|
|
55
|
-
label=label,
|
|
56
|
-
select=select,
|
|
57
|
-
interface_preference=interface_preference,
|
|
51
|
+
message = (
|
|
52
|
+
spinner_message
|
|
53
|
+
if spinner_message is not None
|
|
54
|
+
else f"[bold blue]Fetching {label}…[/bold blue]"
|
|
58
55
|
)
|
|
56
|
+
with spinner_context(ctx, message, spinner_style="cyan") as status_indicator:
|
|
57
|
+
return resolve_resource(
|
|
58
|
+
ctx,
|
|
59
|
+
reference,
|
|
60
|
+
get_by_id=get_by_id_func,
|
|
61
|
+
find_by_name=find_by_name_func,
|
|
62
|
+
label=label,
|
|
63
|
+
select=select,
|
|
64
|
+
interface_preference=interface_preference,
|
|
65
|
+
status_indicator=status_indicator,
|
|
66
|
+
)
|
|
59
67
|
except Exception as e:
|
|
60
68
|
raise click.ClickException(f"Failed to resolve {resource_type.lower()}: {e}")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Slash command palette entrypoints.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from glaip_sdk.cli.commands.agents import get as agents_get_command
|
|
8
|
+
from glaip_sdk.cli.commands.agents import run as agents_run_command
|
|
9
|
+
from glaip_sdk.cli.commands.configure import configure_command, load_config
|
|
10
|
+
from glaip_sdk.cli.utils import get_client
|
|
11
|
+
|
|
12
|
+
from .agent_session import AgentRunSession
|
|
13
|
+
from .prompt import _HAS_PROMPT_TOOLKIT
|
|
14
|
+
from .session import SlashSession
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AgentRunSession",
|
|
18
|
+
"SlashSession",
|
|
19
|
+
"_HAS_PROMPT_TOOLKIT",
|
|
20
|
+
"agents_get_command",
|
|
21
|
+
"agents_run_command",
|
|
22
|
+
"configure_command",
|
|
23
|
+
"get_client",
|
|
24
|
+
"load_config",
|
|
25
|
+
]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Agent-specific interaction loop for the command palette.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
from glaip_sdk.cli.commands.agents import get as agents_get_command
|
|
14
|
+
from glaip_sdk.cli.commands.agents import run as agents_run_command
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING: # pragma: no cover - type checking only
|
|
17
|
+
from .session import SlashSession
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AgentRunSession:
|
|
21
|
+
"""Per-agent execution context for the command palette."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, session: SlashSession, agent: Any) -> None:
|
|
24
|
+
self.session = session
|
|
25
|
+
self.agent = agent
|
|
26
|
+
self.console = session.console
|
|
27
|
+
self._agent_id = str(getattr(agent, "id", ""))
|
|
28
|
+
self._agent_name = getattr(agent, "name", "") or self._agent_id
|
|
29
|
+
self._prompt_placeholder: str = (
|
|
30
|
+
"Chat with this agent here; use / for shortcuts."
|
|
31
|
+
)
|
|
32
|
+
self._contextual_completion_help: dict[str, str] = {
|
|
33
|
+
"details": "Show this agent's full configuration.",
|
|
34
|
+
"help": "Display this context-aware menu.",
|
|
35
|
+
"exit": "Return to the command palette.",
|
|
36
|
+
"q": "Return to the command palette.",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def run(self) -> None:
|
|
40
|
+
self.session.set_contextual_commands(
|
|
41
|
+
self._contextual_completion_help, include_global=False
|
|
42
|
+
)
|
|
43
|
+
try:
|
|
44
|
+
self._display_agent_info()
|
|
45
|
+
self._run_agent_loop()
|
|
46
|
+
finally:
|
|
47
|
+
self.session.set_contextual_commands(None)
|
|
48
|
+
|
|
49
|
+
def _display_agent_info(self) -> None:
|
|
50
|
+
"""Display agent information and summary."""
|
|
51
|
+
self.session._render_header(self.agent, focus_agent=True)
|
|
52
|
+
|
|
53
|
+
def _run_agent_loop(self) -> None:
|
|
54
|
+
"""Run the main agent interaction loop."""
|
|
55
|
+
while True:
|
|
56
|
+
raw = self._get_user_input()
|
|
57
|
+
if raw is None:
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
raw = raw.strip()
|
|
61
|
+
if not raw:
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
if raw.startswith("/"):
|
|
65
|
+
if not self._handle_slash_command(raw, self._agent_id):
|
|
66
|
+
return
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
self._run_agent(self._agent_id, raw)
|
|
70
|
+
|
|
71
|
+
def _get_user_input(self) -> str | None:
|
|
72
|
+
"""Get user input with proper error handling."""
|
|
73
|
+
try:
|
|
74
|
+
raw = self.session._prompt(
|
|
75
|
+
f"{self._agent_name} ({self._agent_id})\n› ",
|
|
76
|
+
placeholder=self._prompt_placeholder,
|
|
77
|
+
)
|
|
78
|
+
if self._prompt_placeholder:
|
|
79
|
+
# Show the guidance once, then fall back to a clean prompt.
|
|
80
|
+
self._prompt_placeholder = ""
|
|
81
|
+
return raw
|
|
82
|
+
except EOFError:
|
|
83
|
+
self.console.print("\nExiting agent context.")
|
|
84
|
+
return None
|
|
85
|
+
except KeyboardInterrupt:
|
|
86
|
+
self.console.print("")
|
|
87
|
+
return ""
|
|
88
|
+
|
|
89
|
+
def _handle_slash_command(self, raw: str, agent_id: str) -> bool:
|
|
90
|
+
"""Handle slash commands in agent context. Returns False if should exit."""
|
|
91
|
+
# Handle simple commands first
|
|
92
|
+
if raw == "/":
|
|
93
|
+
return self._handle_help_command()
|
|
94
|
+
|
|
95
|
+
if raw in {"/exit", "/back", "/q"}:
|
|
96
|
+
return self._handle_exit_command()
|
|
97
|
+
|
|
98
|
+
if raw in {"/details", "/detail"}:
|
|
99
|
+
return self._handle_details_command(agent_id)
|
|
100
|
+
|
|
101
|
+
if raw in {"/help", "/?"}:
|
|
102
|
+
return self._handle_help_command()
|
|
103
|
+
|
|
104
|
+
# Handle other commands through the main session
|
|
105
|
+
return self._handle_other_command(raw)
|
|
106
|
+
|
|
107
|
+
def _handle_help_command(self) -> bool:
|
|
108
|
+
"""Handle help command."""
|
|
109
|
+
self.session._cmd_help([], True)
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
def _handle_exit_command(self) -> bool:
|
|
113
|
+
"""Handle exit command."""
|
|
114
|
+
self.console.print("[dim]Returning to the main prompt.[/dim]")
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
def _handle_details_command(self, agent_id: str) -> bool:
|
|
118
|
+
"""Handle details command."""
|
|
119
|
+
self._show_details(agent_id)
|
|
120
|
+
return True
|
|
121
|
+
|
|
122
|
+
def _handle_other_command(self, raw: str) -> bool:
|
|
123
|
+
"""Handle other commands through the main session."""
|
|
124
|
+
self.session.handle_command(raw, invoked_from_agent=True)
|
|
125
|
+
return not self.session._should_exit
|
|
126
|
+
|
|
127
|
+
def _show_details(self, agent_id: str) -> None:
|
|
128
|
+
try:
|
|
129
|
+
self.session.ctx.invoke(agents_get_command, agent_ref=agent_id)
|
|
130
|
+
self.console.print(
|
|
131
|
+
"[dim]Tip: Continue the conversation in this prompt, or use /help for shortcuts."
|
|
132
|
+
)
|
|
133
|
+
except click.ClickException as exc:
|
|
134
|
+
self.console.print(f"[red]{exc}[/red]")
|
|
135
|
+
|
|
136
|
+
def _run_agent(self, agent_id: str, message: str) -> None:
|
|
137
|
+
if not message:
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
self.session.ctx.invoke(
|
|
142
|
+
agents_run_command, agent_ref=agent_id, input_text=message
|
|
143
|
+
)
|
|
144
|
+
self.session.last_run_input = message
|
|
145
|
+
except click.ClickException as exc:
|
|
146
|
+
self.console.print(f"[red]{exc}[/red]")
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""prompt_toolkit integration helpers for the slash session.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Iterable
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
_HAS_PROMPT_TOOLKIT = False
|
|
13
|
+
|
|
14
|
+
try: # pragma: no cover - optional dependency
|
|
15
|
+
from prompt_toolkit import PromptSession
|
|
16
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
17
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
18
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
19
|
+
from prompt_toolkit.patch_stdout import patch_stdout
|
|
20
|
+
from prompt_toolkit.styles import Style
|
|
21
|
+
|
|
22
|
+
_HAS_PROMPT_TOOLKIT = True
|
|
23
|
+
except Exception: # pragma: no cover - optional dependency
|
|
24
|
+
PromptSession = None # type: ignore[assignment]
|
|
25
|
+
Completer = None # type: ignore[assignment]
|
|
26
|
+
Completion = None # type: ignore[assignment]
|
|
27
|
+
FormattedText = None # type: ignore[assignment]
|
|
28
|
+
KeyBindings = None # type: ignore[assignment]
|
|
29
|
+
Style = None # type: ignore[assignment]
|
|
30
|
+
patch_stdout = None # type: ignore[assignment]
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING: # pragma: no cover - typing only
|
|
33
|
+
from .session import SlashSession
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if _HAS_PROMPT_TOOLKIT:
|
|
37
|
+
|
|
38
|
+
class SlashCompleter(Completer):
|
|
39
|
+
"""Provide slash command completions inside the prompt."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, session: SlashSession) -> None:
|
|
42
|
+
self._session = session
|
|
43
|
+
|
|
44
|
+
def get_completions(
|
|
45
|
+
self,
|
|
46
|
+
document: Any,
|
|
47
|
+
_complete_event: Any, # type: ignore[no-any-return]
|
|
48
|
+
) -> Iterable[Completion]: # pragma: no cover - UI
|
|
49
|
+
if Completion is None:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
text = document.text_before_cursor or ""
|
|
53
|
+
if not text.startswith("/") or " " in text:
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
yield from _iter_command_completions(self._session, text)
|
|
57
|
+
yield from _iter_contextual_completions(self._session, text)
|
|
58
|
+
|
|
59
|
+
else: # pragma: no cover - fallback when prompt_toolkit is missing
|
|
60
|
+
|
|
61
|
+
class SlashCompleter: # type: ignore[too-many-ancestors]
|
|
62
|
+
def __init__(self, session: SlashSession) -> None: # noqa: D401 - stub
|
|
63
|
+
self._session = session
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def setup_prompt_toolkit(
|
|
67
|
+
session: SlashSession,
|
|
68
|
+
*,
|
|
69
|
+
interactive: bool,
|
|
70
|
+
) -> tuple[Any | None, Any | None]:
|
|
71
|
+
"""Configure prompt_toolkit session and style for interactive mode."""
|
|
72
|
+
|
|
73
|
+
if not (interactive and _HAS_PROMPT_TOOLKIT):
|
|
74
|
+
return None, None
|
|
75
|
+
|
|
76
|
+
if PromptSession is None or Style is None:
|
|
77
|
+
return None, None
|
|
78
|
+
|
|
79
|
+
bindings = _create_key_bindings()
|
|
80
|
+
|
|
81
|
+
prompt_session = PromptSession(
|
|
82
|
+
completer=SlashCompleter(session),
|
|
83
|
+
complete_while_typing=True,
|
|
84
|
+
key_bindings=bindings,
|
|
85
|
+
)
|
|
86
|
+
prompt_style = Style.from_dict(
|
|
87
|
+
{
|
|
88
|
+
"prompt": "bg:#0f172a #facc15 bold",
|
|
89
|
+
"": "bg:#0f172a #e2e8f0",
|
|
90
|
+
"placeholder": "bg:#0f172a #94a3b8 italic",
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return prompt_session, prompt_style
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _create_key_bindings() -> Any:
|
|
98
|
+
"""Create prompt_toolkit key bindings for the command palette."""
|
|
99
|
+
|
|
100
|
+
if KeyBindings is None:
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
bindings = KeyBindings()
|
|
104
|
+
|
|
105
|
+
def _refresh_completions(buffer: Any) -> None: # type: ignore[no-any-return]
|
|
106
|
+
text = buffer.document.text_before_cursor or ""
|
|
107
|
+
if text.startswith("/") and " " not in text:
|
|
108
|
+
buffer.start_completion(select_first=False)
|
|
109
|
+
elif buffer.complete_state is not None:
|
|
110
|
+
buffer.cancel_completion()
|
|
111
|
+
|
|
112
|
+
@bindings.add("/") # type: ignore[misc]
|
|
113
|
+
def _trigger_slash_completion(event: Any) -> None: # pragma: no cover - UI
|
|
114
|
+
buffer = event.app.current_buffer
|
|
115
|
+
buffer.insert_text("/")
|
|
116
|
+
_refresh_completions(buffer)
|
|
117
|
+
|
|
118
|
+
@bindings.add("backspace") # type: ignore[misc]
|
|
119
|
+
def _handle_backspace(event: Any) -> None: # pragma: no cover - UI
|
|
120
|
+
buffer = event.app.current_buffer
|
|
121
|
+
if buffer.document.cursor_position > 0:
|
|
122
|
+
buffer.delete_before_cursor()
|
|
123
|
+
_refresh_completions(buffer)
|
|
124
|
+
|
|
125
|
+
@bindings.add("c-h") # type: ignore[misc]
|
|
126
|
+
def _handle_ctrl_h(event: Any) -> None: # pragma: no cover - UI
|
|
127
|
+
buffer = event.app.current_buffer
|
|
128
|
+
if buffer.document.cursor_position > 0:
|
|
129
|
+
buffer.delete_before_cursor()
|
|
130
|
+
_refresh_completions(buffer)
|
|
131
|
+
|
|
132
|
+
return bindings
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _iter_command_completions(
|
|
136
|
+
session: SlashSession, text: str
|
|
137
|
+
) -> Iterable[Completion]: # pragma: no cover - thin wrapper
|
|
138
|
+
prefix = text[1:]
|
|
139
|
+
seen: set[str] = set()
|
|
140
|
+
|
|
141
|
+
if (
|
|
142
|
+
session.get_contextual_commands()
|
|
143
|
+
and not session.should_include_global_commands()
|
|
144
|
+
):
|
|
145
|
+
return []
|
|
146
|
+
|
|
147
|
+
commands = sorted(session._unique_commands.values(), key=lambda c: c.name)
|
|
148
|
+
|
|
149
|
+
for cmd in commands:
|
|
150
|
+
for alias in (cmd.name, *cmd.aliases):
|
|
151
|
+
if alias in seen or alias.startswith("?"):
|
|
152
|
+
continue
|
|
153
|
+
if prefix and not alias.startswith(prefix):
|
|
154
|
+
continue
|
|
155
|
+
seen.add(alias)
|
|
156
|
+
label = f"/{alias}"
|
|
157
|
+
yield Completion(
|
|
158
|
+
text=label,
|
|
159
|
+
start_position=-len(text),
|
|
160
|
+
display=label,
|
|
161
|
+
display_meta=cmd.help,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _iter_contextual_completions(
|
|
166
|
+
session: SlashSession, text: str
|
|
167
|
+
) -> Iterable[Completion]: # pragma: no cover - thin wrapper
|
|
168
|
+
prefix = text[1:]
|
|
169
|
+
seen: set[str] = set()
|
|
170
|
+
|
|
171
|
+
contextual_commands = sorted(
|
|
172
|
+
session.get_contextual_commands().items(), key=lambda item: item[0]
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
for alias, help_text in contextual_commands:
|
|
176
|
+
if alias in seen:
|
|
177
|
+
continue
|
|
178
|
+
if prefix and not alias.startswith(prefix):
|
|
179
|
+
continue
|
|
180
|
+
seen.add(alias)
|
|
181
|
+
label = f"/{alias}"
|
|
182
|
+
yield Completion(
|
|
183
|
+
text=label,
|
|
184
|
+
start_position=-len(text),
|
|
185
|
+
display=label,
|
|
186
|
+
display_meta=help_text,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
__all__ = [
|
|
191
|
+
"SlashCompleter",
|
|
192
|
+
"setup_prompt_toolkit",
|
|
193
|
+
"FormattedText",
|
|
194
|
+
"patch_stdout",
|
|
195
|
+
"PromptSession",
|
|
196
|
+
"Style",
|
|
197
|
+
"_HAS_PROMPT_TOOLKIT",
|
|
198
|
+
]
|