glaip-sdk 0.1.2__py3-none-any.whl → 0.6.5b3__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 (129) hide show
  1. glaip_sdk/__init__.py +5 -2
  2. glaip_sdk/_version.py +9 -0
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1090 -0
  5. glaip_sdk/branding.py +13 -0
  6. glaip_sdk/cli/account_store.py +540 -0
  7. glaip_sdk/cli/auth.py +254 -15
  8. glaip_sdk/cli/commands/__init__.py +2 -2
  9. glaip_sdk/cli/commands/accounts.py +746 -0
  10. glaip_sdk/cli/commands/agents.py +214 -74
  11. glaip_sdk/cli/commands/common_config.py +101 -0
  12. glaip_sdk/cli/commands/configure.py +729 -113
  13. glaip_sdk/cli/commands/mcps.py +241 -72
  14. glaip_sdk/cli/commands/models.py +11 -5
  15. glaip_sdk/cli/commands/tools.py +49 -57
  16. glaip_sdk/cli/commands/transcripts.py +755 -0
  17. glaip_sdk/cli/config.py +48 -4
  18. glaip_sdk/cli/constants.py +38 -0
  19. glaip_sdk/cli/context.py +8 -0
  20. glaip_sdk/cli/core/__init__.py +79 -0
  21. glaip_sdk/cli/core/context.py +124 -0
  22. glaip_sdk/cli/core/output.py +846 -0
  23. glaip_sdk/cli/core/prompting.py +649 -0
  24. glaip_sdk/cli/core/rendering.py +187 -0
  25. glaip_sdk/cli/display.py +41 -20
  26. glaip_sdk/cli/hints.py +57 -0
  27. glaip_sdk/cli/io.py +6 -3
  28. glaip_sdk/cli/main.py +228 -119
  29. glaip_sdk/cli/masking.py +21 -33
  30. glaip_sdk/cli/pager.py +9 -10
  31. glaip_sdk/cli/parsers/__init__.py +1 -3
  32. glaip_sdk/cli/slash/__init__.py +0 -9
  33. glaip_sdk/cli/slash/accounts_controller.py +500 -0
  34. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  35. glaip_sdk/cli/slash/agent_session.py +58 -20
  36. glaip_sdk/cli/slash/prompt.py +10 -0
  37. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  38. glaip_sdk/cli/slash/session.py +736 -134
  39. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  40. glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
  41. glaip_sdk/cli/slash/tui/accounts_app.py +872 -0
  42. glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
  43. glaip_sdk/cli/slash/tui/loading.py +58 -0
  44. glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
  45. glaip_sdk/cli/transcript/__init__.py +12 -52
  46. glaip_sdk/cli/transcript/cache.py +255 -44
  47. glaip_sdk/cli/transcript/capture.py +66 -1
  48. glaip_sdk/cli/transcript/history.py +815 -0
  49. glaip_sdk/cli/transcript/viewer.py +70 -463
  50. glaip_sdk/cli/update_notifier.py +14 -5
  51. glaip_sdk/cli/utils.py +243 -1258
  52. glaip_sdk/cli/validators.py +5 -6
  53. glaip_sdk/client/__init__.py +2 -1
  54. glaip_sdk/client/_agent_payloads.py +45 -9
  55. glaip_sdk/client/agent_runs.py +147 -0
  56. glaip_sdk/client/agents.py +287 -29
  57. glaip_sdk/client/base.py +1 -0
  58. glaip_sdk/client/main.py +19 -10
  59. glaip_sdk/client/mcps.py +122 -12
  60. glaip_sdk/client/run_rendering.py +133 -90
  61. glaip_sdk/client/shared.py +21 -0
  62. glaip_sdk/client/tools.py +153 -10
  63. glaip_sdk/config/constants.py +11 -0
  64. glaip_sdk/mcps/__init__.py +21 -0
  65. glaip_sdk/mcps/base.py +345 -0
  66. glaip_sdk/models/__init__.py +90 -0
  67. glaip_sdk/models/agent.py +47 -0
  68. glaip_sdk/models/agent_runs.py +116 -0
  69. glaip_sdk/models/common.py +42 -0
  70. glaip_sdk/models/mcp.py +33 -0
  71. glaip_sdk/models/tool.py +33 -0
  72. glaip_sdk/payload_schemas/__init__.py +1 -13
  73. glaip_sdk/registry/__init__.py +55 -0
  74. glaip_sdk/registry/agent.py +164 -0
  75. glaip_sdk/registry/base.py +139 -0
  76. glaip_sdk/registry/mcp.py +253 -0
  77. glaip_sdk/registry/tool.py +238 -0
  78. glaip_sdk/rich_components.py +58 -2
  79. glaip_sdk/tools/__init__.py +22 -0
  80. glaip_sdk/tools/base.py +435 -0
  81. glaip_sdk/utils/__init__.py +58 -12
  82. glaip_sdk/utils/bundler.py +267 -0
  83. glaip_sdk/utils/client.py +111 -0
  84. glaip_sdk/utils/client_utils.py +39 -7
  85. glaip_sdk/utils/datetime_helpers.py +58 -0
  86. glaip_sdk/utils/discovery.py +78 -0
  87. glaip_sdk/utils/display.py +23 -15
  88. glaip_sdk/utils/export.py +143 -0
  89. glaip_sdk/utils/general.py +0 -33
  90. glaip_sdk/utils/import_export.py +12 -7
  91. glaip_sdk/utils/import_resolver.py +492 -0
  92. glaip_sdk/utils/instructions.py +101 -0
  93. glaip_sdk/utils/rendering/__init__.py +115 -1
  94. glaip_sdk/utils/rendering/formatting.py +5 -30
  95. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  96. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
  97. glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
  98. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  99. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  100. glaip_sdk/utils/rendering/models.py +1 -0
  101. glaip_sdk/utils/rendering/renderer/__init__.py +9 -47
  102. glaip_sdk/utils/rendering/renderer/base.py +241 -1434
  103. glaip_sdk/utils/rendering/renderer/config.py +1 -5
  104. glaip_sdk/utils/rendering/renderer/debug.py +26 -20
  105. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  106. glaip_sdk/utils/rendering/renderer/stream.py +4 -33
  107. glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
  108. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  109. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  110. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  111. glaip_sdk/utils/rendering/state.py +204 -0
  112. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  113. glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -440
  114. glaip_sdk/utils/rendering/steps/format.py +176 -0
  115. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  116. glaip_sdk/utils/rendering/timing.py +36 -0
  117. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  118. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  119. glaip_sdk/utils/resource_refs.py +25 -13
  120. glaip_sdk/utils/runtime_config.py +306 -0
  121. glaip_sdk/utils/serialization.py +18 -0
  122. glaip_sdk/utils/sync.py +142 -0
  123. glaip_sdk/utils/validation.py +16 -24
  124. {glaip_sdk-0.1.2.dist-info → glaip_sdk-0.6.5b3.dist-info}/METADATA +39 -4
  125. glaip_sdk-0.6.5b3.dist-info/RECORD +145 -0
  126. {glaip_sdk-0.1.2.dist-info → glaip_sdk-0.6.5b3.dist-info}/WHEEL +1 -1
  127. glaip_sdk/models.py +0 -240
  128. glaip_sdk-0.1.2.dist-info/RECORD +0 -82
  129. {glaip_sdk-0.1.2.dist-info → glaip_sdk-0.6.5b3.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,75 @@
1
+ """Shared helpers for palette `/accounts`.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from typing import Any
11
+
12
+ from glaip_sdk.cli.masking import mask_api_key_display
13
+
14
+
15
+ def build_account_status_string(row: dict[str, Any], *, use_markup: bool = False) -> str:
16
+ """Build status string for an account row (active/env-lock).
17
+
18
+ When `use_markup` is True, returns Rich markup strings for Textual/Rich rendering;
19
+ when False, returns plain text for console output.
20
+
21
+ Example:
22
+ build_account_status_string({"active": True, "env_lock": True}, use_markup=True)
23
+ returns "[bold green]● active[/] · [yellow]🔒 env-lock[/]"
24
+ use_markup=False returns "● active · 🔒 env-lock"
25
+ """
26
+ status_parts: list[str] = []
27
+ if row.get("active"):
28
+ status_parts.append("[bold green]● active[/]" if use_markup else "● active")
29
+ if row.get("env_lock"):
30
+ status_parts.append("[yellow]🔒 env-lock[/]" if use_markup else "🔒 env-lock")
31
+ return " · ".join(status_parts)
32
+
33
+
34
+ def env_credentials_present(*, partial: bool = False) -> bool:
35
+ """Return True when env credentials are present.
36
+
37
+ Args:
38
+ partial: When True, treat either AIP_API_URL or AIP_API_KEY as present
39
+ (used by UIs that should lock on any env override). When False,
40
+ require both to be non-empty (used for context display).
41
+ """
42
+ api_url = (os.getenv("AIP_API_URL") or "").strip()
43
+ api_key = (os.getenv("AIP_API_KEY") or "").strip()
44
+ if partial:
45
+ return bool(api_url or api_key)
46
+ return bool(api_url and api_key)
47
+
48
+
49
+ def build_account_rows(
50
+ accounts: dict[str, dict[str, str]],
51
+ active_account: str | None,
52
+ env_lock: bool,
53
+ ) -> list[dict[str, str | bool]]:
54
+ """Build account rows for display from accounts dict.
55
+
56
+ Args:
57
+ accounts: Dictionary mapping account names to account data.
58
+ active_account: Name of the currently active account.
59
+ env_lock: Whether environment credentials are locking account switching.
60
+
61
+ Returns:
62
+ List of account row dictionaries with name, api_url, masked_key, active, and env_lock.
63
+ """
64
+ rows: list[dict[str, str | bool]] = []
65
+ for name, account in sorted(accounts.items()):
66
+ rows.append(
67
+ {
68
+ "name": name,
69
+ "api_url": account.get("api_url", ""),
70
+ "masked_key": mask_api_key_display(account.get("api_key", "")),
71
+ "active": name == active_account,
72
+ "env_lock": env_lock,
73
+ }
74
+ )
75
+ return rows
@@ -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
@@ -38,11 +40,13 @@ class AgentRunSession:
38
40
  self._agent_name = getattr(agent, "name", "") or self._agent_id
39
41
  self._prompt_placeholder: str = "Chat with this agent here; use / for shortcuts. Alt+Enter inserts a newline."
40
42
  self._contextual_completion_help: dict[str, str] = {
41
- "details": "Show this agent's full configuration.",
43
+ "details": "Show this agent's configuration (+ expands prompt).",
42
44
  "help": "Display this context-aware menu.",
45
+ "runs": "✨ NEW · Browse remote run history for this agent.",
43
46
  "exit": "Return to the command palette.",
44
47
  "q": "Return to the command palette.",
45
48
  }
49
+ self._instruction_preview_limit = DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
46
50
 
47
51
  def run(self) -> None:
48
52
  """Run the interactive agent session loop."""
@@ -86,6 +90,7 @@ class AgentRunSession:
86
90
  try:
87
91
 
88
92
  def _prompt_message() -> Any:
93
+ """Get formatted prompt message for agent session."""
89
94
  prompt_prefix = f"{self._agent_name} ({self._agent_id}) "
90
95
 
91
96
  # Use FormattedText if prompt_toolkit is available, otherwise use simple string
@@ -122,7 +127,7 @@ class AgentRunSession:
122
127
  if raw in {"/exit", "/back", "/q"}:
123
128
  return self._handle_exit_command()
124
129
 
125
- if raw in {"/details", "/detail"}:
130
+ if raw == "/details":
126
131
  return self._handle_details_command(agent_id)
127
132
 
128
133
  if raw in {"/help", "/?"}:
@@ -151,16 +156,59 @@ class AgentRunSession:
151
156
  self.session.handle_command(raw, invoked_from_agent=True)
152
157
  return not self.session._should_exit
153
158
 
154
- def _show_details(self, agent_id: str) -> None:
159
+ def _show_details(self, agent_id: str, *, enable_prompt: bool = True) -> None:
160
+ """Render the agent's configuration export inside the command palette."""
155
161
  try:
156
- self.session.ctx.invoke(agents_get_command, agent_ref=agent_id)
157
- self.console.print(
158
- f"[{HINT_PREFIX_STYLE}]Tip:[/] Continue the conversation in this prompt, or use "
159
- f"{format_command_hint('/help') or '/help'} for shortcuts."
162
+ self.session.ctx.invoke(
163
+ agents_get_command,
164
+ agent_ref=agent_id,
165
+ instruction_preview=self._instruction_preview_limit,
160
166
  )
167
+ if enable_prompt:
168
+ self._prompt_instruction_view_toggle(agent_id)
169
+ self.console.print(
170
+ f"[{HINT_PREFIX_STYLE}]Tip:[/] Continue the conversation in this prompt, or use "
171
+ f"{format_command_hint('/help') or '/help'} for shortcuts."
172
+ )
161
173
  except click.ClickException as exc:
162
174
  self.console.print(f"[{ERROR_STYLE}]{exc}[/]")
163
175
 
176
+ def _prompt_instruction_view_toggle(self, agent_id: str) -> None:
177
+ """Offer a prompt to expand or collapse the instruction preview after details."""
178
+ if not getattr(self.console, "is_terminal", False):
179
+ return
180
+
181
+ while True:
182
+ mode = "expanded" if self._instruction_preview_limit == 0 else "trimmed"
183
+ self.console.print(f"[dim]Instruction view is {mode}. Press Ctrl+T to toggle, Enter to continue.[/dim]")
184
+ try:
185
+ ch = click.getchar()
186
+ except (EOFError, KeyboardInterrupt): # pragma: no cover - defensive guard
187
+ return
188
+
189
+ if not self._handle_instruction_toggle_input(agent_id, ch):
190
+ break
191
+
192
+ if self._instruction_preview_limit == 0:
193
+ self._instruction_preview_limit = DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
194
+ self.console.print("")
195
+
196
+ def _handle_instruction_toggle_input(self, agent_id: str, ch: str) -> bool:
197
+ """Process a single toggle keypress; return False when the loop should exit."""
198
+ if ch in {"\r", "\n"}:
199
+ return False
200
+
201
+ lowered = ch.lower()
202
+ if lowered == "t" or ch == "\x14": # support literal 't' or Ctrl+T
203
+ self._instruction_preview_limit = (
204
+ DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT if self._instruction_preview_limit == 0 else 0
205
+ )
206
+ self._show_details(agent_id, enable_prompt=False)
207
+ return True
208
+
209
+ # Ignore other keys and continue prompting.
210
+ return True
211
+
164
212
  def _after_agent_run(self) -> None:
165
213
  """Handle transcript viewer behaviour after a successful run."""
166
214
  payload, manifest = self.session._get_last_transcript()
@@ -206,21 +254,11 @@ class AgentRunSession:
206
254
  @contextmanager
207
255
  def _bind_session_context(self) -> Any:
208
256
  """Temporarily attach this slash session to the Click context."""
209
- ctx_obj = getattr(self.session.ctx, "obj", None)
210
- has_context = isinstance(ctx_obj, dict)
211
- previous_session = ctx_obj.get("_slash_session") if has_context else None
212
- if has_context:
213
- ctx_obj["_slash_session"] = self.session
214
- try:
257
+ with bind_slash_session_context(self.session.ctx, self.session):
215
258
  yield
216
- finally:
217
- if has_context:
218
- if previous_session is None:
219
- ctx_obj.pop("_slash_session", None)
220
- else:
221
- ctx_obj["_slash_session"] = previous_session
222
259
 
223
260
  def _run_agent(self, agent_id: str, message: str) -> None:
261
+ """Execute the agents run command for the active agent."""
224
262
  if not message:
225
263
  return
226
264
 
@@ -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)
@@ -163,6 +168,7 @@ def _create_key_bindings(_session: SlashSession) -> Any:
163
168
  def _iter_command_completions(
164
169
  session: SlashSession, text: str
165
170
  ) -> Iterable[Completion]: # pragma: no cover - thin wrapper
171
+ """Yield completions for global slash commands."""
166
172
  prefix = text[1:]
167
173
  seen: set[str] = set()
168
174
 
@@ -171,8 +177,11 @@ def _iter_command_completions(
171
177
  return []
172
178
 
173
179
  commands = sorted(session._unique_commands.values(), key=lambda c: c.name)
180
+ agent_context = bool(getattr(session, "_current_agent", None))
174
181
 
175
182
  for cmd in commands:
183
+ if getattr(cmd, "agent_only", False) and not agent_context:
184
+ continue
176
185
  yield from _generate_command_completions(cmd, prefix, text, seen)
177
186
 
178
187
 
@@ -203,6 +212,7 @@ def _generate_command_completions(cmd: Any, prefix: str, text: str, seen: set[st
203
212
  def _iter_contextual_completions(
204
213
  session: SlashSession, text: str
205
214
  ) -> Iterable[Completion]: # pragma: no cover - thin wrapper
215
+ """Yield completions for context-specific slash commands."""
206
216
  prefix = text[1:]
207
217
  seen: set[str] = set()
208
218