glaip-sdk 0.6.12__py3-none-any.whl → 0.6.14__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 (156) hide show
  1. glaip_sdk/__init__.py +42 -5
  2. {glaip_sdk-0.6.12.dist-info → glaip_sdk-0.6.14.dist-info}/METADATA +31 -37
  3. glaip_sdk-0.6.14.dist-info/RECORD +12 -0
  4. {glaip_sdk-0.6.12.dist-info → glaip_sdk-0.6.14.dist-info}/WHEEL +2 -1
  5. glaip_sdk-0.6.14.dist-info/entry_points.txt +2 -0
  6. glaip_sdk-0.6.14.dist-info/top_level.txt +1 -0
  7. glaip_sdk/agents/__init__.py +0 -27
  8. glaip_sdk/agents/base.py +0 -1191
  9. glaip_sdk/cli/__init__.py +0 -9
  10. glaip_sdk/cli/account_store.py +0 -540
  11. glaip_sdk/cli/agent_config.py +0 -78
  12. glaip_sdk/cli/auth.py +0 -699
  13. glaip_sdk/cli/commands/__init__.py +0 -5
  14. glaip_sdk/cli/commands/accounts.py +0 -746
  15. glaip_sdk/cli/commands/agents.py +0 -1509
  16. glaip_sdk/cli/commands/common_config.py +0 -101
  17. glaip_sdk/cli/commands/configure.py +0 -896
  18. glaip_sdk/cli/commands/mcps.py +0 -1356
  19. glaip_sdk/cli/commands/models.py +0 -69
  20. glaip_sdk/cli/commands/tools.py +0 -576
  21. glaip_sdk/cli/commands/transcripts.py +0 -755
  22. glaip_sdk/cli/commands/update.py +0 -61
  23. glaip_sdk/cli/config.py +0 -95
  24. glaip_sdk/cli/constants.py +0 -38
  25. glaip_sdk/cli/context.py +0 -150
  26. glaip_sdk/cli/core/__init__.py +0 -79
  27. glaip_sdk/cli/core/context.py +0 -124
  28. glaip_sdk/cli/core/output.py +0 -846
  29. glaip_sdk/cli/core/prompting.py +0 -649
  30. glaip_sdk/cli/core/rendering.py +0 -187
  31. glaip_sdk/cli/display.py +0 -355
  32. glaip_sdk/cli/hints.py +0 -57
  33. glaip_sdk/cli/io.py +0 -112
  34. glaip_sdk/cli/main.py +0 -604
  35. glaip_sdk/cli/masking.py +0 -136
  36. glaip_sdk/cli/mcp_validators.py +0 -287
  37. glaip_sdk/cli/pager.py +0 -266
  38. glaip_sdk/cli/parsers/__init__.py +0 -7
  39. glaip_sdk/cli/parsers/json_input.py +0 -177
  40. glaip_sdk/cli/resolution.py +0 -67
  41. glaip_sdk/cli/rich_helpers.py +0 -27
  42. glaip_sdk/cli/slash/__init__.py +0 -15
  43. glaip_sdk/cli/slash/accounts_controller.py +0 -578
  44. glaip_sdk/cli/slash/accounts_shared.py +0 -75
  45. glaip_sdk/cli/slash/agent_session.py +0 -285
  46. glaip_sdk/cli/slash/prompt.py +0 -256
  47. glaip_sdk/cli/slash/remote_runs_controller.py +0 -566
  48. glaip_sdk/cli/slash/session.py +0 -1708
  49. glaip_sdk/cli/slash/tui/__init__.py +0 -9
  50. glaip_sdk/cli/slash/tui/accounts_app.py +0 -876
  51. glaip_sdk/cli/slash/tui/background_tasks.py +0 -72
  52. glaip_sdk/cli/slash/tui/loading.py +0 -58
  53. glaip_sdk/cli/slash/tui/remote_runs_app.py +0 -628
  54. glaip_sdk/cli/transcript/__init__.py +0 -31
  55. glaip_sdk/cli/transcript/cache.py +0 -536
  56. glaip_sdk/cli/transcript/capture.py +0 -329
  57. glaip_sdk/cli/transcript/export.py +0 -38
  58. glaip_sdk/cli/transcript/history.py +0 -815
  59. glaip_sdk/cli/transcript/launcher.py +0 -77
  60. glaip_sdk/cli/transcript/viewer.py +0 -374
  61. glaip_sdk/cli/update_notifier.py +0 -290
  62. glaip_sdk/cli/utils.py +0 -263
  63. glaip_sdk/cli/validators.py +0 -238
  64. glaip_sdk/client/__init__.py +0 -11
  65. glaip_sdk/client/_agent_payloads.py +0 -520
  66. glaip_sdk/client/agent_runs.py +0 -147
  67. glaip_sdk/client/agents.py +0 -1335
  68. glaip_sdk/client/base.py +0 -502
  69. glaip_sdk/client/main.py +0 -249
  70. glaip_sdk/client/mcps.py +0 -370
  71. glaip_sdk/client/run_rendering.py +0 -700
  72. glaip_sdk/client/shared.py +0 -21
  73. glaip_sdk/client/tools.py +0 -661
  74. glaip_sdk/client/validators.py +0 -198
  75. glaip_sdk/config/constants.py +0 -52
  76. glaip_sdk/mcps/__init__.py +0 -21
  77. glaip_sdk/mcps/base.py +0 -345
  78. glaip_sdk/models/__init__.py +0 -90
  79. glaip_sdk/models/agent.py +0 -47
  80. glaip_sdk/models/agent_runs.py +0 -116
  81. glaip_sdk/models/common.py +0 -42
  82. glaip_sdk/models/mcp.py +0 -33
  83. glaip_sdk/models/tool.py +0 -33
  84. glaip_sdk/payload_schemas/__init__.py +0 -7
  85. glaip_sdk/payload_schemas/agent.py +0 -85
  86. glaip_sdk/registry/__init__.py +0 -55
  87. glaip_sdk/registry/agent.py +0 -164
  88. glaip_sdk/registry/base.py +0 -139
  89. glaip_sdk/registry/mcp.py +0 -253
  90. glaip_sdk/registry/tool.py +0 -232
  91. glaip_sdk/runner/__init__.py +0 -59
  92. glaip_sdk/runner/base.py +0 -84
  93. glaip_sdk/runner/deps.py +0 -115
  94. glaip_sdk/runner/langgraph.py +0 -782
  95. glaip_sdk/runner/mcp_adapter/__init__.py +0 -13
  96. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +0 -43
  97. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +0 -257
  98. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +0 -95
  99. glaip_sdk/runner/tool_adapter/__init__.py +0 -18
  100. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +0 -44
  101. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +0 -219
  102. glaip_sdk/tools/__init__.py +0 -22
  103. glaip_sdk/tools/base.py +0 -435
  104. glaip_sdk/utils/__init__.py +0 -86
  105. glaip_sdk/utils/a2a/__init__.py +0 -34
  106. glaip_sdk/utils/a2a/event_processor.py +0 -188
  107. glaip_sdk/utils/agent_config.py +0 -194
  108. glaip_sdk/utils/bundler.py +0 -267
  109. glaip_sdk/utils/client.py +0 -111
  110. glaip_sdk/utils/client_utils.py +0 -486
  111. glaip_sdk/utils/datetime_helpers.py +0 -58
  112. glaip_sdk/utils/discovery.py +0 -78
  113. glaip_sdk/utils/display.py +0 -135
  114. glaip_sdk/utils/export.py +0 -143
  115. glaip_sdk/utils/general.py +0 -61
  116. glaip_sdk/utils/import_export.py +0 -168
  117. glaip_sdk/utils/import_resolver.py +0 -492
  118. glaip_sdk/utils/instructions.py +0 -101
  119. glaip_sdk/utils/rendering/__init__.py +0 -115
  120. glaip_sdk/utils/rendering/formatting.py +0 -264
  121. glaip_sdk/utils/rendering/layout/__init__.py +0 -64
  122. glaip_sdk/utils/rendering/layout/panels.py +0 -156
  123. glaip_sdk/utils/rendering/layout/progress.py +0 -202
  124. glaip_sdk/utils/rendering/layout/summary.py +0 -74
  125. glaip_sdk/utils/rendering/layout/transcript.py +0 -606
  126. glaip_sdk/utils/rendering/models.py +0 -85
  127. glaip_sdk/utils/rendering/renderer/__init__.py +0 -55
  128. glaip_sdk/utils/rendering/renderer/base.py +0 -1024
  129. glaip_sdk/utils/rendering/renderer/config.py +0 -27
  130. glaip_sdk/utils/rendering/renderer/console.py +0 -55
  131. glaip_sdk/utils/rendering/renderer/debug.py +0 -178
  132. glaip_sdk/utils/rendering/renderer/factory.py +0 -138
  133. glaip_sdk/utils/rendering/renderer/stream.py +0 -202
  134. glaip_sdk/utils/rendering/renderer/summary_window.py +0 -79
  135. glaip_sdk/utils/rendering/renderer/thinking.py +0 -273
  136. glaip_sdk/utils/rendering/renderer/toggle.py +0 -182
  137. glaip_sdk/utils/rendering/renderer/tool_panels.py +0 -442
  138. glaip_sdk/utils/rendering/renderer/transcript_mode.py +0 -162
  139. glaip_sdk/utils/rendering/state.py +0 -204
  140. glaip_sdk/utils/rendering/step_tree_state.py +0 -100
  141. glaip_sdk/utils/rendering/steps/__init__.py +0 -34
  142. glaip_sdk/utils/rendering/steps/event_processor.py +0 -778
  143. glaip_sdk/utils/rendering/steps/format.py +0 -176
  144. glaip_sdk/utils/rendering/steps/manager.py +0 -387
  145. glaip_sdk/utils/rendering/timing.py +0 -36
  146. glaip_sdk/utils/rendering/viewer/__init__.py +0 -21
  147. glaip_sdk/utils/rendering/viewer/presenter.py +0 -184
  148. glaip_sdk/utils/resource_refs.py +0 -195
  149. glaip_sdk/utils/run_renderer.py +0 -41
  150. glaip_sdk/utils/runtime_config.py +0 -425
  151. glaip_sdk/utils/serialization.py +0 -424
  152. glaip_sdk/utils/sync.py +0 -142
  153. glaip_sdk/utils/tool_detection.py +0 -33
  154. glaip_sdk/utils/validation.py +0 -264
  155. glaip_sdk-0.6.12.dist-info/RECORD +0 -159
  156. glaip_sdk-0.6.12.dist-info/entry_points.txt +0 -3
@@ -1,285 +0,0 @@
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 contextlib import contextmanager
10
- from typing import TYPE_CHECKING, Any
11
-
12
- import click
13
-
14
- from glaip_sdk.branding import ERROR_STYLE, HINT_PREFIX_STYLE
15
- from glaip_sdk.cli.commands.agents import get as agents_get_command
16
- from glaip_sdk.cli.commands.agents import run as agents_run_command
17
- from glaip_sdk.cli.constants import DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
18
- from glaip_sdk.cli.hints import format_command_hint
19
- from glaip_sdk.cli.slash.prompt import _HAS_PROMPT_TOOLKIT, FormattedText
20
- from glaip_sdk.cli.utils import bind_slash_session_context
21
-
22
- if TYPE_CHECKING: # pragma: no cover - type checking only
23
- from glaip_sdk.cli.slash.session import SlashSession
24
-
25
-
26
- class AgentRunSession:
27
- """Per-agent execution context for the command palette."""
28
-
29
- def __init__(self, session: SlashSession, agent: Any) -> None:
30
- """Initialize the agent run session.
31
-
32
- Args:
33
- session: The slash session context
34
- agent: The agent to interact with
35
- """
36
- self.session = session
37
- self.agent = agent
38
- self.console = session.console
39
- self._agent_id = str(getattr(agent, "id", ""))
40
- self._agent_name = getattr(agent, "name", "") or self._agent_id
41
- self._prompt_placeholder: str = (
42
- "Chat with this agent here; use / for shortcuts. "
43
- "Alt+Enter inserts a newline. Ctrl+T opens the last transcript."
44
- )
45
- self._contextual_completion_help: dict[str, str] = {
46
- "details": "Show this agent's configuration (+ expands prompt).",
47
- "help": "Display this context-aware menu.",
48
- "runs": "✨ NEW · Browse remote run history for this agent.",
49
- "exit": "Return to the command palette.",
50
- "q": "Return to the command palette.",
51
- }
52
- self._instruction_preview_limit = DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
53
-
54
- def run(self) -> None:
55
- """Run the interactive agent session loop."""
56
- self.session.set_contextual_commands(self._contextual_completion_help, include_global=False)
57
- previous_agent = getattr(self.session, "_current_agent", None)
58
- self.session._current_agent = self.agent
59
- clear_ready = getattr(self.session, "clear_agent_transcript_ready", None)
60
- if callable(clear_ready):
61
- clear_ready(self._agent_id)
62
- try:
63
- self._display_agent_info()
64
- self._run_agent_loop()
65
- finally:
66
- self.session.set_contextual_commands(None)
67
- self.session._current_agent = previous_agent
68
-
69
- def _display_agent_info(self) -> None:
70
- """Display agent information and summary."""
71
- self.session._render_header(self.agent, focus_agent=True)
72
-
73
- def _run_agent_loop(self) -> None:
74
- """Run the main agent interaction loop."""
75
- while True:
76
- raw = self._get_user_input()
77
- if raw is None:
78
- return
79
-
80
- raw = raw.strip()
81
- if not raw:
82
- continue
83
-
84
- if raw.startswith("/"):
85
- if not self._handle_slash_command(raw, self._agent_id):
86
- return
87
- continue
88
-
89
- self._run_agent(self._agent_id, raw)
90
-
91
- def _get_user_input(self) -> str | None:
92
- """Get user input with proper error handling."""
93
- try:
94
-
95
- def _prompt_message() -> Any:
96
- """Get formatted prompt message for agent session."""
97
- prompt_prefix = f"{self._agent_name} ({self._agent_id}) "
98
-
99
- # Use FormattedText if prompt_toolkit is available, otherwise use simple string
100
- if _HAS_PROMPT_TOOLKIT and FormattedText is not None:
101
- segments = [
102
- ("class:prompt", prompt_prefix),
103
- ("class:prompt", "\n› "),
104
- ]
105
- return FormattedText(segments)
106
-
107
- return f"{prompt_prefix}\n› "
108
-
109
- raw = self.session._prompt(
110
- _prompt_message,
111
- placeholder=self._prompt_placeholder,
112
- )
113
- if self._prompt_placeholder:
114
- # Show the guidance once, then fall back to a clean prompt.
115
- self._prompt_placeholder = ""
116
- return raw
117
- except EOFError:
118
- self.console.print("\nExiting agent context.")
119
- return None
120
- except KeyboardInterrupt:
121
- self.console.print("")
122
- return ""
123
-
124
- def _handle_slash_command(self, raw: str, agent_id: str) -> bool:
125
- """Handle slash commands in agent context. Returns False if should exit."""
126
- # Handle simple commands first
127
- if raw == "/":
128
- return self._handle_help_command()
129
-
130
- if raw in {"/exit", "/back", "/q"}:
131
- return self._handle_exit_command()
132
-
133
- if raw == "/details":
134
- return self._handle_details_command(agent_id)
135
-
136
- if raw in {"/help", "/?"}:
137
- return self._handle_help_command()
138
-
139
- # Handle other commands through the main session
140
- return self._handle_other_command(raw)
141
-
142
- def _handle_help_command(self) -> bool:
143
- """Handle help command."""
144
- self.session._cmd_help([], True)
145
- return True
146
-
147
- def _handle_exit_command(self) -> bool:
148
- """Handle exit command."""
149
- self.console.print("[dim]Returning to the main prompt.[/dim]")
150
- return False
151
-
152
- def _handle_details_command(self, agent_id: str) -> bool:
153
- """Handle details command."""
154
- self._show_details(agent_id)
155
- return True
156
-
157
- def _handle_other_command(self, raw: str) -> bool:
158
- """Handle other commands through the main session."""
159
- self.session.handle_command(raw, invoked_from_agent=True)
160
- return not self.session._should_exit
161
-
162
- def _show_details(self, agent_id: str, *, enable_prompt: bool = True) -> None:
163
- """Render the agent's configuration export inside the command palette."""
164
- try:
165
- self.session.ctx.invoke(
166
- agents_get_command,
167
- agent_ref=agent_id,
168
- instruction_preview=self._instruction_preview_limit,
169
- )
170
- if enable_prompt:
171
- self._prompt_instruction_view_toggle(agent_id)
172
- self.console.print(
173
- f"[{HINT_PREFIX_STYLE}]Tip:[/] Continue the conversation in this prompt, or use "
174
- f"{format_command_hint('/help') or '/help'} for shortcuts."
175
- )
176
- except click.ClickException as exc:
177
- self.console.print(f"[{ERROR_STYLE}]{exc}[/]")
178
-
179
- def _prompt_instruction_view_toggle(self, agent_id: str) -> None:
180
- """Offer a prompt to expand or collapse the instruction preview after details."""
181
- if not getattr(self.console, "is_terminal", False):
182
- return
183
-
184
- while True:
185
- mode = "expanded" if self._instruction_preview_limit == 0 else "trimmed"
186
- self.console.print(f"[dim]Instruction view is {mode}. Press Ctrl+T to toggle, Enter to continue.[/dim]")
187
- try:
188
- ch = click.getchar()
189
- except (EOFError, KeyboardInterrupt): # pragma: no cover - defensive guard
190
- return
191
-
192
- if not self._handle_instruction_toggle_input(agent_id, ch):
193
- break
194
-
195
- if self._instruction_preview_limit == 0:
196
- self._instruction_preview_limit = DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
197
- self.console.print("")
198
-
199
- def _handle_instruction_toggle_input(self, agent_id: str, ch: str) -> bool:
200
- """Process a single toggle keypress; return False when the loop should exit."""
201
- if ch in {"\r", "\n"}:
202
- return False
203
-
204
- lowered = ch.lower()
205
- if lowered == "t" or ch == "\x14": # support literal 't' or Ctrl+T
206
- self._instruction_preview_limit = (
207
- DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT if self._instruction_preview_limit == 0 else 0
208
- )
209
- self._show_details(agent_id, enable_prompt=False)
210
- return True
211
-
212
- # Ignore other keys and continue prompting.
213
- return True
214
-
215
- def _after_agent_run(self) -> None:
216
- """Handle transcript viewer behaviour after a successful run."""
217
- payload, manifest = self.session._get_last_transcript()
218
- if not self._transcript_matches(payload, manifest):
219
- return
220
- run_id = str(manifest.get("run_id") or "")
221
- mark_ready = getattr(self.session, "mark_agent_transcript_ready", None)
222
- if callable(mark_ready):
223
- mark_ready(self._agent_id, run_id)
224
- if self._open_transcript_viewer():
225
- return
226
- self.console.print("[dim]Transcript viewer is unavailable in this environment.[/dim]")
227
-
228
- def _transcript_matches(self, payload: Any, manifest: Any) -> bool:
229
- """Return True when the latest transcript belongs to this agent."""
230
- if not payload or not isinstance(manifest, dict):
231
- return False
232
- if not manifest.get("run_id"):
233
- return False
234
- return manifest.get("agent_id") == self._agent_id
235
-
236
- def _open_transcript_viewer(self) -> bool:
237
- """Launch the transcript viewer when terminal support is available."""
238
- if not getattr(self.console, "is_terminal", False):
239
- return False
240
- try:
241
- current_agent = getattr(self.session, "_current_agent", None)
242
- self.session.open_transcript_viewer(announce=True)
243
- if getattr(self.session.console, "is_terminal", False):
244
- try:
245
- self.session.console.clear()
246
- except Exception: # pragma: no cover - defensive cleanup
247
- pass
248
- if current_agent is not None: # pragma: no cover - UI refresh best effort
249
- try:
250
- self.session._render_header(current_agent, focus_agent=True)
251
- except Exception: # pragma: no cover - defensive cleanup
252
- pass
253
- return True
254
- except Exception: # pragma: no cover - defensive cleanup
255
- return False
256
-
257
- @contextmanager
258
- def _bind_session_context(self) -> Any:
259
- """Temporarily attach this slash session to the Click context."""
260
- with bind_slash_session_context(self.session.ctx, self.session):
261
- yield
262
-
263
- def _run_agent(self, agent_id: str, message: str) -> None:
264
- """Execute the agents run command for the active agent."""
265
- if not message:
266
- return
267
-
268
- try:
269
- self.session.notify_agent_run_started()
270
- with self._bind_session_context():
271
- self.session.ctx.invoke(
272
- agents_run_command,
273
- agent_ref=agent_id,
274
- input_text=message,
275
- verbose=False,
276
- )
277
- self.session.last_run_input = message
278
- self._after_agent_run()
279
- except click.ClickException as exc:
280
- self.console.print(f"[{ERROR_STYLE}]{exc}[/]")
281
- finally:
282
- try:
283
- self.session.notify_agent_run_finished()
284
- except Exception:
285
- pass
@@ -1,256 +0,0 @@
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, to_formatted_text
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
- to_formatted_text = None # type: ignore[assignment]
29
- KeyBindings = None # type: ignore[assignment]
30
- Style = None # type: ignore[assignment]
31
- patch_stdout = None # type: ignore[assignment]
32
-
33
- if TYPE_CHECKING: # pragma: no cover - typing only
34
- from glaip_sdk.cli.slash.session import SlashSession
35
-
36
-
37
- if _HAS_PROMPT_TOOLKIT:
38
-
39
- class SlashCompleter(Completer):
40
- """Provide slash command completions inside the prompt."""
41
-
42
- def __init__(self, session: SlashSession) -> None:
43
- """Initialize the slash completer.
44
-
45
- Args:
46
- session: The slash session context
47
- """
48
- self._session = session
49
-
50
- def get_completions(
51
- self,
52
- document: Any,
53
- _complete_event: Any, # type: ignore[no-any-return]
54
- ) -> Iterable[Completion]:
55
- """Get completions for slash commands.
56
-
57
- Args:
58
- document: The document being edited
59
- _complete_event: The completion event
60
-
61
- Yields:
62
- Completion objects for matching commands
63
- """
64
- if Completion is None:
65
- return
66
-
67
- text = document.text_before_cursor or ""
68
- if not text.startswith("/") or " " in text:
69
- return
70
-
71
- yield from _iter_command_completions(self._session, text)
72
- yield from _iter_contextual_completions(self._session, text)
73
-
74
- else: # pragma: no cover - fallback when prompt_toolkit is missing
75
-
76
- class SlashCompleter: # type: ignore[too-many-ancestors]
77
- """Fallback slash completer when prompt_toolkit is not available."""
78
-
79
- def __init__(self, session: SlashSession) -> None:
80
- """Initialize the fallback slash completer.
81
-
82
- Args:
83
- session: The slash session context
84
- """
85
- self._session = session
86
-
87
-
88
- def setup_prompt_toolkit(
89
- session: SlashSession,
90
- *,
91
- interactive: bool,
92
- ) -> tuple[Any | None, Any | None]:
93
- """Configure prompt_toolkit session and style for interactive mode."""
94
- if not (interactive and _HAS_PROMPT_TOOLKIT):
95
- return None, None
96
-
97
- if PromptSession is None or Style is None:
98
- return None, None
99
-
100
- bindings = _create_key_bindings(session)
101
-
102
- prompt_session = PromptSession(
103
- completer=SlashCompleter(session),
104
- complete_while_typing=True,
105
- key_bindings=bindings,
106
- )
107
- prompt_style = Style.from_dict(
108
- {
109
- "prompt": "bg:#0f172a #facc15 bold",
110
- "": "bg:#0f172a #e2e8f0",
111
- "placeholder": "bg:#0f172a #94a3b8 italic",
112
- }
113
- )
114
-
115
- return prompt_session, prompt_style
116
-
117
-
118
- def _create_key_bindings(_session: SlashSession) -> Any:
119
- """Create prompt_toolkit key bindings for the command palette."""
120
- if KeyBindings is None:
121
- return None
122
-
123
- bindings = KeyBindings()
124
-
125
- def _refresh_completions(buffer: Any) -> None: # type: ignore[no-any-return]
126
- """Refresh completions when slash command is typed.
127
-
128
- Args:
129
- buffer: Prompt buffer instance.
130
- """
131
- text = buffer.document.text_before_cursor or ""
132
- if text.startswith("/") and " " not in text:
133
- buffer.start_completion(select_first=False)
134
- elif buffer.complete_state is not None:
135
- buffer.cancel_completion()
136
-
137
- @bindings.add("/") # type: ignore[misc]
138
- def _handle_slash_key(event: Any) -> None: # vulture: ignore
139
- """Handle '/' key press - insert slash and trigger completion."""
140
- buffer = event.app.current_buffer
141
- buffer.insert_text("/")
142
- _refresh_completions(buffer)
143
-
144
- @bindings.add("backspace") # type: ignore[misc]
145
- def _handle_backspace_key(event: Any) -> None: # vulture: ignore
146
- """Handle backspace key - delete character and refresh completions."""
147
- buffer = event.app.current_buffer
148
- if buffer.document.cursor_position > 0:
149
- buffer.delete_before_cursor()
150
- _refresh_completions(buffer)
151
-
152
- @bindings.add("c-h") # type: ignore[misc]
153
- def _handle_ctrl_h_key(event: Any) -> None: # vulture: ignore
154
- """Handle Ctrl+H key - same as backspace."""
155
- _handle_backspace_key(event) # Reuse backspace handler
156
-
157
- @bindings.add("escape", "enter") # type: ignore[misc]
158
- def _handle_alt_enter_key(event: Any) -> None: # vulture: ignore
159
- """Handle Alt+Enter key - insert line break and cancel completion."""
160
- buffer = event.app.current_buffer
161
- buffer.insert_text("\n")
162
- if buffer.complete_state is not None:
163
- buffer.cancel_completion()
164
-
165
- @bindings.add("c-t") # type: ignore[misc]
166
- def _handle_ctrl_t_key(event: Any) -> None: # vulture: ignore
167
- """Handle Ctrl+T key - open the transcript viewer (when available)."""
168
- buffer = event.app.current_buffer
169
- if buffer.complete_state is not None:
170
- buffer.cancel_completion()
171
-
172
- open_viewer = getattr(_session, "open_transcript_viewer", None)
173
- if callable(open_viewer):
174
- open_viewer(announce=True)
175
-
176
- return bindings
177
-
178
-
179
- def _iter_command_completions(
180
- session: SlashSession, text: str
181
- ) -> Iterable[Completion]: # pragma: no cover - thin wrapper
182
- """Yield completions for global slash commands."""
183
- prefix = text[1:]
184
- seen: set[str] = set()
185
-
186
- # Early return for contextual commands scenario
187
- if not _should_include_commands(session):
188
- return []
189
-
190
- commands = sorted(session._unique_commands.values(), key=lambda c: c.name)
191
- agent_context = bool(getattr(session, "_current_agent", None))
192
-
193
- for cmd in commands:
194
- if getattr(cmd, "agent_only", False) and not agent_context:
195
- continue
196
- yield from _generate_command_completions(cmd, prefix, text, seen)
197
-
198
-
199
- def _should_include_commands(session: SlashSession) -> bool:
200
- """Check if commands should be included in completions."""
201
- return not (session.get_contextual_commands() and not session.should_include_global_commands())
202
-
203
-
204
- def _generate_command_completions(cmd: Any, prefix: str, text: str, seen: set[str]) -> Iterable[Completion]:
205
- """Generate completion items for a single command."""
206
- for alias in (cmd.name, *cmd.aliases):
207
- if alias in seen or alias.startswith("?"):
208
- continue
209
-
210
- if prefix and not alias.startswith(prefix):
211
- continue
212
-
213
- seen.add(alias)
214
- label = f"/{alias}"
215
- yield Completion(
216
- text=label,
217
- start_position=-len(text),
218
- display=label,
219
- display_meta=cmd.help,
220
- )
221
-
222
-
223
- def _iter_contextual_completions(
224
- session: SlashSession, text: str
225
- ) -> Iterable[Completion]: # pragma: no cover - thin wrapper
226
- """Yield completions for context-specific slash commands."""
227
- prefix = text[1:]
228
- seen: set[str] = set()
229
-
230
- contextual_commands = sorted(session.get_contextual_commands().items(), key=lambda item: item[0])
231
-
232
- for alias, help_text in contextual_commands:
233
- if alias in seen:
234
- continue
235
- if prefix and not alias.startswith(prefix):
236
- continue
237
- seen.add(alias)
238
- label = f"/{alias}"
239
- yield Completion(
240
- text=label,
241
- start_position=-len(text),
242
- display=label,
243
- display_meta=help_text,
244
- )
245
-
246
-
247
- __all__ = [
248
- "SlashCompleter",
249
- "setup_prompt_toolkit",
250
- "FormattedText",
251
- "to_formatted_text",
252
- "patch_stdout",
253
- "PromptSession",
254
- "Style",
255
- "_HAS_PROMPT_TOOLKIT",
256
- ]