glaip-sdk 0.1.0__py3-none-any.whl → 0.6.10__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 +5 -2
  2. glaip_sdk/_version.py +10 -3
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1191 -0
  5. glaip_sdk/branding.py +15 -6
  6. glaip_sdk/cli/account_store.py +540 -0
  7. glaip_sdk/cli/agent_config.py +2 -6
  8. glaip_sdk/cli/auth.py +265 -45
  9. glaip_sdk/cli/commands/__init__.py +2 -2
  10. glaip_sdk/cli/commands/accounts.py +746 -0
  11. glaip_sdk/cli/commands/agents.py +251 -173
  12. glaip_sdk/cli/commands/common_config.py +101 -0
  13. glaip_sdk/cli/commands/configure.py +735 -143
  14. glaip_sdk/cli/commands/mcps.py +266 -134
  15. glaip_sdk/cli/commands/models.py +13 -9
  16. glaip_sdk/cli/commands/tools.py +67 -88
  17. glaip_sdk/cli/commands/transcripts.py +755 -0
  18. glaip_sdk/cli/commands/update.py +3 -8
  19. glaip_sdk/cli/config.py +49 -7
  20. glaip_sdk/cli/constants.py +38 -0
  21. glaip_sdk/cli/context.py +8 -0
  22. glaip_sdk/cli/core/__init__.py +79 -0
  23. glaip_sdk/cli/core/context.py +124 -0
  24. glaip_sdk/cli/core/output.py +846 -0
  25. glaip_sdk/cli/core/prompting.py +649 -0
  26. glaip_sdk/cli/core/rendering.py +187 -0
  27. glaip_sdk/cli/display.py +45 -32
  28. glaip_sdk/cli/hints.py +57 -0
  29. glaip_sdk/cli/io.py +14 -17
  30. glaip_sdk/cli/main.py +232 -143
  31. glaip_sdk/cli/masking.py +21 -33
  32. glaip_sdk/cli/mcp_validators.py +5 -15
  33. glaip_sdk/cli/pager.py +12 -19
  34. glaip_sdk/cli/parsers/__init__.py +1 -3
  35. glaip_sdk/cli/parsers/json_input.py +11 -22
  36. glaip_sdk/cli/resolution.py +3 -9
  37. glaip_sdk/cli/rich_helpers.py +1 -3
  38. glaip_sdk/cli/slash/__init__.py +0 -9
  39. glaip_sdk/cli/slash/accounts_controller.py +578 -0
  40. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  41. glaip_sdk/cli/slash/agent_session.py +65 -29
  42. glaip_sdk/cli/slash/prompt.py +24 -10
  43. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  44. glaip_sdk/cli/slash/session.py +807 -225
  45. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  46. glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
  47. glaip_sdk/cli/slash/tui/accounts_app.py +876 -0
  48. glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
  49. glaip_sdk/cli/slash/tui/loading.py +58 -0
  50. glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
  51. glaip_sdk/cli/transcript/__init__.py +12 -52
  52. glaip_sdk/cli/transcript/cache.py +258 -60
  53. glaip_sdk/cli/transcript/capture.py +72 -21
  54. glaip_sdk/cli/transcript/history.py +815 -0
  55. glaip_sdk/cli/transcript/launcher.py +1 -3
  56. glaip_sdk/cli/transcript/viewer.py +79 -499
  57. glaip_sdk/cli/update_notifier.py +177 -24
  58. glaip_sdk/cli/utils.py +242 -1308
  59. glaip_sdk/cli/validators.py +16 -18
  60. glaip_sdk/client/__init__.py +2 -1
  61. glaip_sdk/client/_agent_payloads.py +53 -37
  62. glaip_sdk/client/agent_runs.py +147 -0
  63. glaip_sdk/client/agents.py +320 -92
  64. glaip_sdk/client/base.py +78 -35
  65. glaip_sdk/client/main.py +19 -10
  66. glaip_sdk/client/mcps.py +123 -15
  67. glaip_sdk/client/run_rendering.py +136 -101
  68. glaip_sdk/client/shared.py +21 -0
  69. glaip_sdk/client/tools.py +163 -34
  70. glaip_sdk/client/validators.py +20 -48
  71. glaip_sdk/config/constants.py +11 -0
  72. glaip_sdk/exceptions.py +1 -3
  73. glaip_sdk/mcps/__init__.py +21 -0
  74. glaip_sdk/mcps/base.py +345 -0
  75. glaip_sdk/models/__init__.py +90 -0
  76. glaip_sdk/models/agent.py +47 -0
  77. glaip_sdk/models/agent_runs.py +116 -0
  78. glaip_sdk/models/common.py +42 -0
  79. glaip_sdk/models/mcp.py +33 -0
  80. glaip_sdk/models/tool.py +33 -0
  81. glaip_sdk/payload_schemas/__init__.py +1 -13
  82. glaip_sdk/payload_schemas/agent.py +1 -3
  83. glaip_sdk/registry/__init__.py +55 -0
  84. glaip_sdk/registry/agent.py +164 -0
  85. glaip_sdk/registry/base.py +139 -0
  86. glaip_sdk/registry/mcp.py +253 -0
  87. glaip_sdk/registry/tool.py +232 -0
  88. glaip_sdk/rich_components.py +58 -2
  89. glaip_sdk/runner/__init__.py +59 -0
  90. glaip_sdk/runner/base.py +84 -0
  91. glaip_sdk/runner/deps.py +115 -0
  92. glaip_sdk/runner/langgraph.py +706 -0
  93. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  94. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  95. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
  96. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  97. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  98. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  99. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
  100. glaip_sdk/tools/__init__.py +22 -0
  101. glaip_sdk/tools/base.py +435 -0
  102. glaip_sdk/utils/__init__.py +58 -12
  103. glaip_sdk/utils/a2a/__init__.py +34 -0
  104. glaip_sdk/utils/a2a/event_processor.py +188 -0
  105. glaip_sdk/utils/agent_config.py +4 -14
  106. glaip_sdk/utils/bundler.py +267 -0
  107. glaip_sdk/utils/client.py +111 -0
  108. glaip_sdk/utils/client_utils.py +46 -28
  109. glaip_sdk/utils/datetime_helpers.py +58 -0
  110. glaip_sdk/utils/discovery.py +78 -0
  111. glaip_sdk/utils/display.py +25 -21
  112. glaip_sdk/utils/export.py +143 -0
  113. glaip_sdk/utils/general.py +1 -36
  114. glaip_sdk/utils/import_export.py +15 -16
  115. glaip_sdk/utils/import_resolver.py +492 -0
  116. glaip_sdk/utils/instructions.py +101 -0
  117. glaip_sdk/utils/rendering/__init__.py +115 -1
  118. glaip_sdk/utils/rendering/formatting.py +7 -35
  119. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  120. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +10 -3
  121. glaip_sdk/utils/rendering/{renderer → layout}/progress.py +73 -12
  122. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  123. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  124. glaip_sdk/utils/rendering/models.py +3 -6
  125. glaip_sdk/utils/rendering/renderer/__init__.py +9 -49
  126. glaip_sdk/utils/rendering/renderer/base.py +258 -1577
  127. glaip_sdk/utils/rendering/renderer/config.py +1 -5
  128. glaip_sdk/utils/rendering/renderer/debug.py +30 -34
  129. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  130. glaip_sdk/utils/rendering/renderer/stream.py +10 -51
  131. glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
  132. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  133. glaip_sdk/utils/rendering/renderer/toggle.py +1 -3
  134. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  135. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  136. glaip_sdk/utils/rendering/state.py +204 -0
  137. glaip_sdk/utils/rendering/step_tree_state.py +1 -3
  138. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  139. glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +76 -517
  140. glaip_sdk/utils/rendering/steps/format.py +176 -0
  141. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  142. glaip_sdk/utils/rendering/timing.py +36 -0
  143. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  144. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  145. glaip_sdk/utils/resource_refs.py +29 -26
  146. glaip_sdk/utils/runtime_config.py +425 -0
  147. glaip_sdk/utils/serialization.py +32 -46
  148. glaip_sdk/utils/sync.py +142 -0
  149. glaip_sdk/utils/tool_detection.py +33 -0
  150. glaip_sdk/utils/validation.py +20 -28
  151. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/METADATA +42 -4
  152. glaip_sdk-0.6.10.dist-info/RECORD +159 -0
  153. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/WHEEL +1 -1
  154. glaip_sdk/models.py +0 -259
  155. glaip_sdk-0.1.0.dist-info/RECORD +0 -82
  156. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/entry_points.txt +0 -0
@@ -14,8 +14,10 @@ import click
14
14
  from glaip_sdk.branding import ERROR_STYLE, HINT_PREFIX_STYLE
15
15
  from glaip_sdk.cli.commands.agents import get as agents_get_command
16
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
17
19
  from glaip_sdk.cli.slash.prompt import _HAS_PROMPT_TOOLKIT, FormattedText
18
- from glaip_sdk.cli.utils import format_command_hint
20
+ from glaip_sdk.cli.utils import bind_slash_session_context
19
21
 
20
22
  if TYPE_CHECKING: # pragma: no cover - type checking only
21
23
  from glaip_sdk.cli.slash.session import SlashSession
@@ -36,19 +38,22 @@ class AgentRunSession:
36
38
  self.console = session.console
37
39
  self._agent_id = str(getattr(agent, "id", ""))
38
40
  self._agent_name = getattr(agent, "name", "") or self._agent_id
39
- self._prompt_placeholder: str = "Chat with this agent here; use / for shortcuts. Alt+Enter inserts a newline."
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
+ )
40
45
  self._contextual_completion_help: dict[str, str] = {
41
- "details": "Show this agent's full configuration.",
46
+ "details": "Show this agent's configuration (+ expands prompt).",
42
47
  "help": "Display this context-aware menu.",
48
+ "runs": "✨ NEW · Browse remote run history for this agent.",
43
49
  "exit": "Return to the command palette.",
44
50
  "q": "Return to the command palette.",
45
51
  }
52
+ self._instruction_preview_limit = DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
46
53
 
47
54
  def run(self) -> None:
48
55
  """Run the interactive agent session loop."""
49
- self.session.set_contextual_commands(
50
- self._contextual_completion_help, include_global=False
51
- )
56
+ self.session.set_contextual_commands(self._contextual_completion_help, include_global=False)
52
57
  previous_agent = getattr(self.session, "_current_agent", None)
53
58
  self.session._current_agent = self.agent
54
59
  clear_ready = getattr(self.session, "clear_agent_transcript_ready", None)
@@ -88,6 +93,7 @@ class AgentRunSession:
88
93
  try:
89
94
 
90
95
  def _prompt_message() -> Any:
96
+ """Get formatted prompt message for agent session."""
91
97
  prompt_prefix = f"{self._agent_name} ({self._agent_id}) "
92
98
 
93
99
  # Use FormattedText if prompt_toolkit is available, otherwise use simple string
@@ -124,7 +130,7 @@ class AgentRunSession:
124
130
  if raw in {"/exit", "/back", "/q"}:
125
131
  return self._handle_exit_command()
126
132
 
127
- if raw in {"/details", "/detail"}:
133
+ if raw == "/details":
128
134
  return self._handle_details_command(agent_id)
129
135
 
130
136
  if raw in {"/help", "/?"}:
@@ -153,15 +159,59 @@ class AgentRunSession:
153
159
  self.session.handle_command(raw, invoked_from_agent=True)
154
160
  return not self.session._should_exit
155
161
 
156
- def _show_details(self, agent_id: str) -> None:
162
+ def _show_details(self, agent_id: str, *, enable_prompt: bool = True) -> None:
163
+ """Render the agent's configuration export inside the command palette."""
157
164
  try:
158
- self.session.ctx.invoke(agents_get_command, agent_ref=agent_id)
159
- self.console.print(
160
- f"[{HINT_PREFIX_STYLE}]Tip:[/] Continue the conversation in this prompt, or use {format_command_hint('/help') or '/help'} for shortcuts."
165
+ self.session.ctx.invoke(
166
+ agents_get_command,
167
+ agent_ref=agent_id,
168
+ instruction_preview=self._instruction_preview_limit,
161
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
+ )
162
176
  except click.ClickException as exc:
163
177
  self.console.print(f"[{ERROR_STYLE}]{exc}[/]")
164
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
+
165
215
  def _after_agent_run(self) -> None:
166
216
  """Handle transcript viewer behaviour after a successful run."""
167
217
  payload, manifest = self.session._get_last_transcript()
@@ -173,9 +223,7 @@ class AgentRunSession:
173
223
  mark_ready(self._agent_id, run_id)
174
224
  if self._open_transcript_viewer():
175
225
  return
176
- self.console.print(
177
- "[dim]Transcript viewer is unavailable in this environment.[/dim]"
178
- )
226
+ self.console.print("[dim]Transcript viewer is unavailable in this environment.[/dim]")
179
227
 
180
228
  def _transcript_matches(self, payload: Any, manifest: Any) -> bool:
181
229
  """Return True when the latest transcript belongs to this agent."""
@@ -197,9 +245,7 @@ class AgentRunSession:
197
245
  self.session.console.clear()
198
246
  except Exception: # pragma: no cover - defensive cleanup
199
247
  pass
200
- if (
201
- current_agent is not None
202
- ): # pragma: no cover - UI refresh best effort
248
+ if current_agent is not None: # pragma: no cover - UI refresh best effort
203
249
  try:
204
250
  self.session._render_header(current_agent, focus_agent=True)
205
251
  except Exception: # pragma: no cover - defensive cleanup
@@ -211,21 +257,11 @@ class AgentRunSession:
211
257
  @contextmanager
212
258
  def _bind_session_context(self) -> Any:
213
259
  """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:
260
+ with bind_slash_session_context(self.session.ctx, self.session):
220
261
  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
227
262
 
228
263
  def _run_agent(self, agent_id: str, message: str) -> None:
264
+ """Execute the agents run command for the active agent."""
229
265
  if not message:
230
266
  return
231
267
 
@@ -123,6 +123,11 @@ def _create_key_bindings(_session: SlashSession) -> Any:
123
123
  bindings = KeyBindings()
124
124
 
125
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
+ """
126
131
  text = buffer.document.text_before_cursor or ""
127
132
  if text.startswith("/") and " " not in text:
128
133
  buffer.start_completion(select_first=False)
@@ -157,12 +162,24 @@ def _create_key_bindings(_session: SlashSession) -> Any:
157
162
  if buffer.complete_state is not None:
158
163
  buffer.cancel_completion()
159
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
+
160
176
  return bindings
161
177
 
162
178
 
163
179
  def _iter_command_completions(
164
180
  session: SlashSession, text: str
165
181
  ) -> Iterable[Completion]: # pragma: no cover - thin wrapper
182
+ """Yield completions for global slash commands."""
166
183
  prefix = text[1:]
167
184
  seen: set[str] = set()
168
185
 
@@ -171,22 +188,20 @@ def _iter_command_completions(
171
188
  return []
172
189
 
173
190
  commands = sorted(session._unique_commands.values(), key=lambda c: c.name)
191
+ agent_context = bool(getattr(session, "_current_agent", None))
174
192
 
175
193
  for cmd in commands:
194
+ if getattr(cmd, "agent_only", False) and not agent_context:
195
+ continue
176
196
  yield from _generate_command_completions(cmd, prefix, text, seen)
177
197
 
178
198
 
179
199
  def _should_include_commands(session: SlashSession) -> bool:
180
200
  """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
- )
201
+ return not (session.get_contextual_commands() and not session.should_include_global_commands())
185
202
 
186
203
 
187
- def _generate_command_completions(
188
- cmd: Any, prefix: str, text: str, seen: set[str]
189
- ) -> Iterable[Completion]:
204
+ def _generate_command_completions(cmd: Any, prefix: str, text: str, seen: set[str]) -> Iterable[Completion]:
190
205
  """Generate completion items for a single command."""
191
206
  for alias in (cmd.name, *cmd.aliases):
192
207
  if alias in seen or alias.startswith("?"):
@@ -208,12 +223,11 @@ def _generate_command_completions(
208
223
  def _iter_contextual_completions(
209
224
  session: SlashSession, text: str
210
225
  ) -> Iterable[Completion]: # pragma: no cover - thin wrapper
226
+ """Yield completions for context-specific slash commands."""
211
227
  prefix = text[1:]
212
228
  seen: set[str] = set()
213
229
 
214
- contextual_commands = sorted(
215
- session.get_contextual_commands().items(), key=lambda item: item[0]
216
- )
230
+ contextual_commands = sorted(session.get_contextual_commands().items(), key=lambda item: item[0])
217
231
 
218
232
  for alias, help_text in contextual_commands:
219
233
  if alias in seen: