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.
@@ -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
- return resolve_resource(
51
- ctx,
52
- reference,
53
- get_by_id=get_by_id_func,
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
+ ]