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
@@ -6,10 +6,14 @@ Author:
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- import os
10
- from collections.abc import Callable
9
+ import importlib
10
+ import logging
11
+ import sys
12
+ from collections.abc import Callable, Iterable, Iterator
13
+ from contextlib import contextmanager
11
14
  from typing import Any, Literal
12
15
 
16
+ import click
13
17
  import httpx
14
18
  from packaging.version import InvalidVersion, Version
15
19
  from rich import box
@@ -17,10 +21,14 @@ from rich.console import Console
17
21
 
18
22
  from glaip_sdk.branding import (
19
23
  ACCENT_STYLE,
24
+ ERROR_STYLE,
20
25
  SUCCESS_STYLE,
21
26
  WARNING_STYLE,
22
27
  )
23
- from glaip_sdk.cli.utils import command_hint, format_command_hint
28
+ from glaip_sdk.cli.commands.update import update_command
29
+ from glaip_sdk.cli.constants import UPDATE_CHECK_ENABLED
30
+ from glaip_sdk.cli.hints import format_command_hint
31
+ from glaip_sdk.cli.utils import command_hint
24
32
  from glaip_sdk.rich_components import AIPPanel
25
33
 
26
34
  FetchLatestVersion = Callable[[], str | None]
@@ -28,6 +36,8 @@ FetchLatestVersion = Callable[[], str | None]
28
36
  PYPI_JSON_URL = "https://pypi.org/pypi/{package}/json"
29
37
  DEFAULT_TIMEOUT = 1.5 # seconds
30
38
 
39
+ _LOGGER = logging.getLogger(__name__)
40
+
31
41
 
32
42
  def _parse_version(value: str) -> Version | None:
33
43
  """Parse a version string into a `Version`, returning None on failure."""
@@ -43,13 +53,16 @@ def _fetch_latest_version(package_name: str) -> str | None:
43
53
  timeout = httpx.Timeout(DEFAULT_TIMEOUT)
44
54
 
45
55
  try:
46
- with httpx.Client(timeout=timeout) as client:
47
- response = client.get(url, headers={"Accept": "application/json"})
48
- response.raise_for_status()
49
- payload = response.json()
50
- except httpx.HTTPError:
56
+ with _suppress_library_logging():
57
+ with httpx.Client(timeout=timeout) as client:
58
+ response = client.get(url, headers={"Accept": "application/json"})
59
+ response.raise_for_status()
60
+ payload = response.json()
61
+ except httpx.HTTPError as exc:
62
+ _LOGGER.debug("Update check failed: %s", exc, exc_info=True)
51
63
  return None
52
- except ValueError:
64
+ except ValueError as exc:
65
+ _LOGGER.debug("Invalid JSON while checking for updates: %s", exc, exc_info=True)
53
66
  return None
54
67
 
55
68
  info = payload.get("info") if isinstance(payload, dict) else None
@@ -61,13 +74,19 @@ def _fetch_latest_version(package_name: str) -> str | None:
61
74
 
62
75
  def _should_check_for_updates() -> bool:
63
76
  """Return False when update checks are explicitly disabled."""
64
- return os.getenv("AIP_NO_UPDATE_CHECK") is None
77
+ # Check module attribute first (for test overrides), then fall back to imported constant
78
+ module = sys.modules.get(__name__)
79
+ if module and hasattr(module, "UPDATE_CHECK_ENABLED"):
80
+ return getattr(module, "UPDATE_CHECK_ENABLED")
81
+ return UPDATE_CHECK_ENABLED
65
82
 
66
83
 
67
84
  def _build_update_panel(
68
85
  current_version: str,
69
86
  latest_version: str,
70
87
  command_text: str,
88
+ *,
89
+ show_command_hint: bool,
71
90
  ) -> AIPPanel:
72
91
  """Create a Rich panel that prompts the user to update."""
73
92
  command_markup = format_command_hint(command_text) or command_text
@@ -75,9 +94,10 @@ def _build_update_panel(
75
94
  f"[{WARNING_STYLE}]✨ Update available![/] "
76
95
  f"{current_version} → {latest_version}\n\n"
77
96
  "See the latest release notes:\n"
78
- f"https://pypi.org/project/glaip-sdk/{latest_version}/\n\n"
79
- f"[{ACCENT_STYLE}]Run[/] {command_markup} to install."
97
+ f"https://pypi.org/project/glaip-sdk/{latest_version}/"
80
98
  )
99
+ if show_command_hint:
100
+ message += f"\n\n[{ACCENT_STYLE}]Run[/] {command_markup} to install."
81
101
  return AIPPanel(
82
102
  message,
83
103
  title=f"[{SUCCESS_STYLE}]AIP SDK Update[/]",
@@ -97,11 +117,7 @@ def maybe_notify_update(
97
117
  slash_command: str | None = None,
98
118
  style: Literal["panel", "inline"] = "panel",
99
119
  ) -> None:
100
- """Check PyPI for a newer version and display a prompt if one exists.
101
-
102
- This function deliberately swallows network errors to avoid impacting CLI
103
- startup time when offline or when PyPI is unavailable.
104
- """
120
+ """Check PyPI for a newer version and display a prompt if one exists."""
105
121
  if not _should_check_for_updates():
106
122
  return
107
123
 
@@ -120,18 +136,155 @@ def maybe_notify_update(
120
136
  return
121
137
 
122
138
  active_console = console or Console()
139
+ should_prompt = _should_prompt_for_action(active_console, ctx)
140
+
123
141
  if style == "inline":
142
+ if should_prompt:
143
+ message = (
144
+ f"[{WARNING_STYLE}]✨ Update[/] "
145
+ f"{current_version} → {latest_version} "
146
+ "- choose Update now or Skip to continue."
147
+ )
148
+ active_console.print(message)
149
+ _handle_update_decision(active_console, ctx)
150
+ return
151
+
124
152
  command_markup = format_command_hint(command_text) or command_text
125
- message = (
126
- f"[{WARNING_STYLE}]✨ Update[/] "
127
- f"{current_version} → {latest_version} "
128
- f"- {command_markup}"
129
- )
130
- active_console.print(message)
153
+ active_console.print(f"[{WARNING_STYLE}]✨ Update[/] {current_version} → {latest_version} - {command_markup}")
131
154
  return
132
155
 
133
- panel = _build_update_panel(current_version, latest_version, command_text)
156
+ panel = _build_update_panel(
157
+ current_version,
158
+ latest_version,
159
+ command_text,
160
+ show_command_hint=not should_prompt,
161
+ )
134
162
  active_console.print(panel)
163
+ if should_prompt:
164
+ _handle_update_decision(active_console, ctx)
165
+
166
+
167
+ def _handle_update_decision(console: Console, ctx: Any) -> None:
168
+ """Prompt the user to take action on the available update."""
169
+ choice = _prompt_update_decision(console)
170
+ if choice == "skip":
171
+ return
172
+
173
+ _run_update_command(console, ctx)
174
+
175
+
176
+ def _should_prompt_for_action(console: Console, ctx: Any | None) -> bool:
177
+ """Return True when we can safely block for interactive input."""
178
+ if ctx is None or not hasattr(ctx, "invoke"):
179
+ return False
180
+
181
+ is_interactive = getattr(console, "is_interactive", False)
182
+ if not isinstance(is_interactive, bool) or not is_interactive:
183
+ return False
184
+
185
+ is_terminal = getattr(console, "is_terminal", False)
186
+ if not isinstance(is_terminal, bool) or not is_terminal:
187
+ return False
188
+
189
+ input_method = getattr(console, "input", None)
190
+ return callable(input_method)
191
+
192
+
193
+ def _prompt_update_decision(console: Console) -> Literal["update", "skip"]:
194
+ """Ask the user to choose between updating now or skipping."""
195
+ console.print(
196
+ f"[{ACCENT_STYLE}]Select an option to continue:[/]\n"
197
+ f" [{SUCCESS_STYLE}]1.[/] Update now\n"
198
+ f" [{WARNING_STYLE}]2.[/] Skip\n"
199
+ )
200
+ console.print("[dim]Press Enter after typing your choice.[/]")
201
+
202
+ while True:
203
+ try:
204
+ response = console.input("Choice [1/2]: ").strip().lower()
205
+ except (KeyboardInterrupt, EOFError):
206
+ console.print(f"\n[{WARNING_STYLE}]Update skipped.[/]")
207
+ return "skip"
208
+
209
+ if response in {"1", "update", "u"}:
210
+ return "update"
211
+ if response in {"2", "skip", "s"}:
212
+ return "skip"
213
+
214
+ console.print(f"[{ERROR_STYLE}]Please enter 1 to update now or 2 to skip.[/]")
215
+
216
+
217
+ def _run_update_command(console: Console, ctx: Any) -> None:
218
+ """Invoke the built-in update command and surface any errors."""
219
+ try:
220
+ ctx.invoke(update_command)
221
+ except click.ClickException as exc:
222
+ exc.show()
223
+ console.print(f"[{ERROR_STYLE}]Update command exited with an error.[/]")
224
+ except click.Abort:
225
+ console.print(f"[{WARNING_STYLE}]Update aborted by user.[/]")
226
+ except Exception as exc: # pragma: no cover - defensive guard
227
+ console.print(f"[{ERROR_STYLE}]Unexpected error while running update: {exc}[/]")
228
+ else:
229
+ _refresh_installed_version(console, ctx)
230
+
231
+
232
+ @contextmanager
233
+ def _suppress_library_logging(
234
+ logger_names: Iterable[str] | None = None, *, level: int = logging.WARNING
235
+ ) -> Iterator[None]:
236
+ """Temporarily raise log level for selected libraries during update checks."""
237
+ names = tuple(logger_names) if logger_names is not None else ("httpx",)
238
+ captured: list[tuple[logging.Logger, int]] = []
239
+ try:
240
+ for name in names:
241
+ logger = logging.getLogger(name)
242
+ captured.append((logger, logger.level))
243
+ logger.setLevel(level)
244
+ yield
245
+ finally:
246
+ for logger, previous_level in captured:
247
+ logger.setLevel(previous_level)
248
+
249
+
250
+ def _refresh_installed_version(console: Console, ctx: Any) -> None:
251
+ """Reload runtime metadata after an in-process upgrade."""
252
+ new_version: str | None = None
253
+ branding_module: Any | None = None
254
+
255
+ try:
256
+ version_module = importlib.reload(importlib.import_module("glaip_sdk._version"))
257
+ new_version = getattr(version_module, "__version__", None)
258
+ except Exception as exc: # pragma: no cover - defensive guard
259
+ _LOGGER.debug("Failed to reload glaip_sdk._version: %s", exc, exc_info=True)
260
+
261
+ try:
262
+ branding_module = importlib.reload(importlib.import_module("glaip_sdk.branding"))
263
+ if new_version:
264
+ branding_module.SDK_VERSION = new_version
265
+ except Exception as exc: # pragma: no cover - defensive guard
266
+ _LOGGER.debug("Failed to update branding metadata: %s", exc, exc_info=True)
267
+ branding_module = None
268
+
269
+ session = _get_slash_session(ctx)
270
+ if session and hasattr(session, "refresh_branding"):
271
+ try:
272
+ branding_cls = getattr(branding_module, "AIPBranding", None) if branding_module else None
273
+ session.refresh_branding(new_version, branding_cls=branding_cls)
274
+ return
275
+ except Exception as exc: # pragma: no cover - defensive guard
276
+ _LOGGER.debug("Failed to refresh active slash session: %s", exc, exc_info=True)
277
+
278
+ if new_version:
279
+ console.print(f"[{SUCCESS_STYLE}]CLI now running glaip-sdk {new_version}.[/]")
280
+
281
+
282
+ def _get_slash_session(ctx: Any) -> Any | None:
283
+ """Return active slash session from the Click context if present."""
284
+ ctx_obj = getattr(ctx, "obj", None)
285
+ if isinstance(ctx_obj, dict):
286
+ return ctx_obj.get("_slash_session")
287
+ return None
135
288
 
136
289
 
137
290
  __all__ = ["maybe_notify_update"]