glaip-sdk 0.0.7__py3-none-any.whl → 0.6.5b6__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 (161) hide show
  1. glaip_sdk/__init__.py +6 -3
  2. glaip_sdk/_version.py +12 -5
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1126 -0
  5. glaip_sdk/branding.py +79 -15
  6. glaip_sdk/cli/account_store.py +540 -0
  7. glaip_sdk/cli/agent_config.py +2 -6
  8. glaip_sdk/cli/auth.py +699 -0
  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 +503 -183
  12. glaip_sdk/cli/commands/common_config.py +101 -0
  13. glaip_sdk/cli/commands/configure.py +774 -137
  14. glaip_sdk/cli/commands/mcps.py +1124 -181
  15. glaip_sdk/cli/commands/models.py +25 -10
  16. glaip_sdk/cli/commands/tools.py +144 -92
  17. glaip_sdk/cli/commands/transcripts.py +755 -0
  18. glaip_sdk/cli/commands/update.py +61 -0
  19. glaip_sdk/cli/config.py +95 -0
  20. glaip_sdk/cli/constants.py +38 -0
  21. glaip_sdk/cli/context.py +150 -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 +143 -53
  28. glaip_sdk/cli/hints.py +57 -0
  29. glaip_sdk/cli/io.py +24 -18
  30. glaip_sdk/cli/main.py +420 -145
  31. glaip_sdk/cli/masking.py +136 -0
  32. glaip_sdk/cli/mcp_validators.py +287 -0
  33. glaip_sdk/cli/pager.py +266 -0
  34. glaip_sdk/cli/parsers/__init__.py +7 -0
  35. glaip_sdk/cli/parsers/json_input.py +177 -0
  36. glaip_sdk/cli/resolution.py +28 -21
  37. glaip_sdk/cli/rich_helpers.py +27 -0
  38. glaip_sdk/cli/slash/__init__.py +15 -0
  39. glaip_sdk/cli/slash/accounts_controller.py +500 -0
  40. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  41. glaip_sdk/cli/slash/agent_session.py +282 -0
  42. glaip_sdk/cli/slash/prompt.py +245 -0
  43. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  44. glaip_sdk/cli/slash/session.py +1679 -0
  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 +872 -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 +31 -0
  52. glaip_sdk/cli/transcript/cache.py +536 -0
  53. glaip_sdk/cli/transcript/capture.py +329 -0
  54. glaip_sdk/cli/transcript/export.py +38 -0
  55. glaip_sdk/cli/transcript/history.py +815 -0
  56. glaip_sdk/cli/transcript/launcher.py +77 -0
  57. glaip_sdk/cli/transcript/viewer.py +372 -0
  58. glaip_sdk/cli/update_notifier.py +290 -0
  59. glaip_sdk/cli/utils.py +247 -1238
  60. glaip_sdk/cli/validators.py +16 -18
  61. glaip_sdk/client/__init__.py +2 -1
  62. glaip_sdk/client/_agent_payloads.py +520 -0
  63. glaip_sdk/client/agent_runs.py +147 -0
  64. glaip_sdk/client/agents.py +940 -574
  65. glaip_sdk/client/base.py +163 -48
  66. glaip_sdk/client/main.py +35 -12
  67. glaip_sdk/client/mcps.py +126 -18
  68. glaip_sdk/client/run_rendering.py +415 -0
  69. glaip_sdk/client/shared.py +21 -0
  70. glaip_sdk/client/tools.py +195 -37
  71. glaip_sdk/client/validators.py +20 -48
  72. glaip_sdk/config/constants.py +15 -5
  73. glaip_sdk/exceptions.py +16 -9
  74. glaip_sdk/icons.py +25 -0
  75. glaip_sdk/mcps/__init__.py +21 -0
  76. glaip_sdk/mcps/base.py +345 -0
  77. glaip_sdk/models/__init__.py +90 -0
  78. glaip_sdk/models/agent.py +47 -0
  79. glaip_sdk/models/agent_runs.py +116 -0
  80. glaip_sdk/models/common.py +42 -0
  81. glaip_sdk/models/mcp.py +33 -0
  82. glaip_sdk/models/tool.py +33 -0
  83. glaip_sdk/payload_schemas/__init__.py +7 -0
  84. glaip_sdk/payload_schemas/agent.py +85 -0
  85. glaip_sdk/registry/__init__.py +55 -0
  86. glaip_sdk/registry/agent.py +164 -0
  87. glaip_sdk/registry/base.py +139 -0
  88. glaip_sdk/registry/mcp.py +253 -0
  89. glaip_sdk/registry/tool.py +231 -0
  90. glaip_sdk/rich_components.py +98 -2
  91. glaip_sdk/runner/__init__.py +59 -0
  92. glaip_sdk/runner/base.py +84 -0
  93. glaip_sdk/runner/deps.py +115 -0
  94. glaip_sdk/runner/langgraph.py +597 -0
  95. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  96. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  97. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +158 -0
  98. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  99. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  100. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  101. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +177 -0
  102. glaip_sdk/tools/__init__.py +22 -0
  103. glaip_sdk/tools/base.py +435 -0
  104. glaip_sdk/utils/__init__.py +59 -13
  105. glaip_sdk/utils/a2a/__init__.py +34 -0
  106. glaip_sdk/utils/a2a/event_processor.py +188 -0
  107. glaip_sdk/utils/agent_config.py +53 -40
  108. glaip_sdk/utils/bundler.py +267 -0
  109. glaip_sdk/utils/client.py +111 -0
  110. glaip_sdk/utils/client_utils.py +58 -26
  111. glaip_sdk/utils/datetime_helpers.py +58 -0
  112. glaip_sdk/utils/discovery.py +78 -0
  113. glaip_sdk/utils/display.py +65 -32
  114. glaip_sdk/utils/export.py +143 -0
  115. glaip_sdk/utils/general.py +1 -36
  116. glaip_sdk/utils/import_export.py +20 -25
  117. glaip_sdk/utils/import_resolver.py +492 -0
  118. glaip_sdk/utils/instructions.py +101 -0
  119. glaip_sdk/utils/rendering/__init__.py +115 -1
  120. glaip_sdk/utils/rendering/formatting.py +85 -43
  121. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  122. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +51 -19
  123. glaip_sdk/utils/rendering/layout/progress.py +202 -0
  124. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  125. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  126. glaip_sdk/utils/rendering/models.py +39 -7
  127. glaip_sdk/utils/rendering/renderer/__init__.py +9 -51
  128. glaip_sdk/utils/rendering/renderer/base.py +672 -759
  129. glaip_sdk/utils/rendering/renderer/config.py +4 -10
  130. glaip_sdk/utils/rendering/renderer/debug.py +75 -22
  131. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  132. glaip_sdk/utils/rendering/renderer/stream.py +13 -54
  133. glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
  134. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  135. glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
  136. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  137. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  138. glaip_sdk/utils/rendering/state.py +204 -0
  139. glaip_sdk/utils/rendering/step_tree_state.py +100 -0
  140. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  141. glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
  142. glaip_sdk/utils/rendering/steps/format.py +176 -0
  143. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  144. glaip_sdk/utils/rendering/timing.py +36 -0
  145. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  146. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  147. glaip_sdk/utils/resource_refs.py +29 -26
  148. glaip_sdk/utils/runtime_config.py +422 -0
  149. glaip_sdk/utils/serialization.py +184 -51
  150. glaip_sdk/utils/sync.py +142 -0
  151. glaip_sdk/utils/tool_detection.py +33 -0
  152. glaip_sdk/utils/validation.py +21 -30
  153. {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/METADATA +58 -12
  154. glaip_sdk-0.6.5b6.dist-info/RECORD +159 -0
  155. {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/WHEEL +1 -1
  156. glaip_sdk/models.py +0 -250
  157. glaip_sdk/utils/rendering/renderer/progress.py +0 -118
  158. glaip_sdk/utils/rendering/steps.py +0 -232
  159. glaip_sdk/utils/rich_utils.py +0 -29
  160. glaip_sdk-0.0.7.dist-info/RECORD +0 -55
  161. {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,290 @@
1
+ """Utility helpers for checking and displaying SDK update notifications.
2
+
3
+ Author:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import importlib
10
+ import logging
11
+ import sys
12
+ from collections.abc import Callable, Iterable, Iterator
13
+ from contextlib import contextmanager
14
+ from typing import Any, Literal
15
+
16
+ import click
17
+ import httpx
18
+ from packaging.version import InvalidVersion, Version
19
+ from rich import box
20
+ from rich.console import Console
21
+
22
+ from glaip_sdk.branding import (
23
+ ACCENT_STYLE,
24
+ ERROR_STYLE,
25
+ SUCCESS_STYLE,
26
+ WARNING_STYLE,
27
+ )
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
32
+ from glaip_sdk.rich_components import AIPPanel
33
+
34
+ FetchLatestVersion = Callable[[], str | None]
35
+
36
+ PYPI_JSON_URL = "https://pypi.org/pypi/{package}/json"
37
+ DEFAULT_TIMEOUT = 1.5 # seconds
38
+
39
+ _LOGGER = logging.getLogger(__name__)
40
+
41
+
42
+ def _parse_version(value: str) -> Version | None:
43
+ """Parse a version string into a `Version`, returning None on failure."""
44
+ try:
45
+ return Version(value)
46
+ except InvalidVersion:
47
+ return None
48
+
49
+
50
+ def _fetch_latest_version(package_name: str) -> str | None:
51
+ """Fetch the latest published version from PyPI."""
52
+ url = PYPI_JSON_URL.format(package=package_name)
53
+ timeout = httpx.Timeout(DEFAULT_TIMEOUT)
54
+
55
+ try:
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)
63
+ return None
64
+ except ValueError as exc:
65
+ _LOGGER.debug("Invalid JSON while checking for updates: %s", exc, exc_info=True)
66
+ return None
67
+
68
+ info = payload.get("info") if isinstance(payload, dict) else None
69
+ latest_version = info.get("version") if isinstance(info, dict) else None
70
+ if isinstance(latest_version, str) and latest_version.strip():
71
+ return latest_version.strip()
72
+ return None
73
+
74
+
75
+ def _should_check_for_updates() -> bool:
76
+ """Return False when update checks are explicitly disabled."""
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
82
+
83
+
84
+ def _build_update_panel(
85
+ current_version: str,
86
+ latest_version: str,
87
+ command_text: str,
88
+ *,
89
+ show_command_hint: bool,
90
+ ) -> AIPPanel:
91
+ """Create a Rich panel that prompts the user to update."""
92
+ command_markup = format_command_hint(command_text) or command_text
93
+ message = (
94
+ f"[{WARNING_STYLE}]✨ Update available![/] "
95
+ f"{current_version} → {latest_version}\n\n"
96
+ "See the latest release notes:\n"
97
+ f"https://pypi.org/project/glaip-sdk/{latest_version}/"
98
+ )
99
+ if show_command_hint:
100
+ message += f"\n\n[{ACCENT_STYLE}]Run[/] {command_markup} to install."
101
+ return AIPPanel(
102
+ message,
103
+ title=f"[{SUCCESS_STYLE}]AIP SDK Update[/]",
104
+ box=box.ROUNDED,
105
+ padding=(0, 3),
106
+ expand=False,
107
+ )
108
+
109
+
110
+ def maybe_notify_update(
111
+ current_version: str,
112
+ *,
113
+ package_name: str = "glaip-sdk",
114
+ console: Console | None = None,
115
+ fetch_latest_version: FetchLatestVersion | None = None,
116
+ ctx: Any | None = None,
117
+ slash_command: str | None = None,
118
+ style: Literal["panel", "inline"] = "panel",
119
+ ) -> None:
120
+ """Check PyPI for a newer version and display a prompt if one exists."""
121
+ if not _should_check_for_updates():
122
+ return
123
+
124
+ fetcher = fetch_latest_version or (lambda: _fetch_latest_version(package_name))
125
+ latest_version = fetcher()
126
+ if not latest_version:
127
+ return
128
+
129
+ current = _parse_version(current_version)
130
+ latest = _parse_version(latest_version)
131
+ if current is None or latest is None or latest <= current:
132
+ return
133
+
134
+ command_text = command_hint("update", slash_command=slash_command, ctx=ctx)
135
+ if command_text is None:
136
+ return
137
+
138
+ active_console = console or Console()
139
+ should_prompt = _should_prompt_for_action(active_console, ctx)
140
+
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
+
152
+ command_markup = format_command_hint(command_text) or command_text
153
+ active_console.print(f"[{WARNING_STYLE}]✨ Update[/] {current_version} → {latest_version} - {command_markup}")
154
+ return
155
+
156
+ panel = _build_update_panel(
157
+ current_version,
158
+ latest_version,
159
+ command_text,
160
+ show_command_hint=not should_prompt,
161
+ )
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
288
+
289
+
290
+ __all__ = ["maybe_notify_update"]