glaip-sdk 0.6.5b3__py3-none-any.whl → 0.7.17__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 (145) hide show
  1. glaip_sdk/__init__.py +42 -5
  2. glaip_sdk/agents/base.py +362 -39
  3. glaip_sdk/branding.py +113 -2
  4. glaip_sdk/cli/account_store.py +15 -0
  5. glaip_sdk/cli/auth.py +14 -8
  6. glaip_sdk/cli/commands/accounts.py +1 -1
  7. glaip_sdk/cli/commands/agents/__init__.py +116 -0
  8. glaip_sdk/cli/commands/agents/_common.py +562 -0
  9. glaip_sdk/cli/commands/agents/create.py +155 -0
  10. glaip_sdk/cli/commands/agents/delete.py +64 -0
  11. glaip_sdk/cli/commands/agents/get.py +89 -0
  12. glaip_sdk/cli/commands/agents/list.py +129 -0
  13. glaip_sdk/cli/commands/agents/run.py +264 -0
  14. glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
  15. glaip_sdk/cli/commands/agents/update.py +112 -0
  16. glaip_sdk/cli/commands/common_config.py +15 -12
  17. glaip_sdk/cli/commands/configure.py +2 -3
  18. glaip_sdk/cli/commands/mcps/__init__.py +94 -0
  19. glaip_sdk/cli/commands/mcps/_common.py +459 -0
  20. glaip_sdk/cli/commands/mcps/connect.py +82 -0
  21. glaip_sdk/cli/commands/mcps/create.py +152 -0
  22. glaip_sdk/cli/commands/mcps/delete.py +73 -0
  23. glaip_sdk/cli/commands/mcps/get.py +212 -0
  24. glaip_sdk/cli/commands/mcps/list.py +69 -0
  25. glaip_sdk/cli/commands/mcps/tools.py +235 -0
  26. glaip_sdk/cli/commands/mcps/update.py +190 -0
  27. glaip_sdk/cli/commands/models.py +2 -4
  28. glaip_sdk/cli/commands/shared/__init__.py +21 -0
  29. glaip_sdk/cli/commands/shared/formatters.py +91 -0
  30. glaip_sdk/cli/commands/tools/__init__.py +69 -0
  31. glaip_sdk/cli/commands/tools/_common.py +80 -0
  32. glaip_sdk/cli/commands/tools/create.py +228 -0
  33. glaip_sdk/cli/commands/tools/delete.py +61 -0
  34. glaip_sdk/cli/commands/tools/get.py +103 -0
  35. glaip_sdk/cli/commands/tools/list.py +69 -0
  36. glaip_sdk/cli/commands/tools/script.py +49 -0
  37. glaip_sdk/cli/commands/tools/update.py +102 -0
  38. glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
  39. glaip_sdk/cli/commands/transcripts/_common.py +9 -0
  40. glaip_sdk/cli/commands/transcripts/clear.py +5 -0
  41. glaip_sdk/cli/commands/transcripts/detail.py +5 -0
  42. glaip_sdk/cli/commands/{transcripts.py → transcripts_original.py} +2 -1
  43. glaip_sdk/cli/commands/update.py +163 -17
  44. glaip_sdk/cli/config.py +1 -0
  45. glaip_sdk/cli/core/output.py +12 -7
  46. glaip_sdk/cli/entrypoint.py +20 -0
  47. glaip_sdk/cli/main.py +127 -39
  48. glaip_sdk/cli/pager.py +3 -3
  49. glaip_sdk/cli/resolution.py +2 -1
  50. glaip_sdk/cli/slash/accounts_controller.py +112 -32
  51. glaip_sdk/cli/slash/agent_session.py +5 -2
  52. glaip_sdk/cli/slash/prompt.py +11 -0
  53. glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
  54. glaip_sdk/cli/slash/session.py +375 -25
  55. glaip_sdk/cli/slash/tui/__init__.py +28 -1
  56. glaip_sdk/cli/slash/tui/accounts.tcss +97 -6
  57. glaip_sdk/cli/slash/tui/accounts_app.py +1107 -126
  58. glaip_sdk/cli/slash/tui/clipboard.py +195 -0
  59. glaip_sdk/cli/slash/tui/context.py +92 -0
  60. glaip_sdk/cli/slash/tui/indicators.py +341 -0
  61. glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
  62. glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
  63. glaip_sdk/cli/slash/tui/layouts/harlequin.py +184 -0
  64. glaip_sdk/cli/slash/tui/loading.py +43 -21
  65. glaip_sdk/cli/slash/tui/remote_runs_app.py +152 -20
  66. glaip_sdk/cli/slash/tui/terminal.py +407 -0
  67. glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
  68. glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
  69. glaip_sdk/cli/slash/tui/theme/manager.py +112 -0
  70. glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
  71. glaip_sdk/cli/slash/tui/toast.py +388 -0
  72. glaip_sdk/cli/transcript/history.py +1 -1
  73. glaip_sdk/cli/transcript/viewer.py +5 -3
  74. glaip_sdk/cli/tui_settings.py +125 -0
  75. glaip_sdk/cli/update_notifier.py +215 -7
  76. glaip_sdk/cli/validators.py +1 -1
  77. glaip_sdk/client/__init__.py +2 -1
  78. glaip_sdk/client/_schedule_payloads.py +89 -0
  79. glaip_sdk/client/agents.py +290 -16
  80. glaip_sdk/client/base.py +25 -0
  81. glaip_sdk/client/hitl.py +136 -0
  82. glaip_sdk/client/main.py +7 -5
  83. glaip_sdk/client/mcps.py +44 -13
  84. glaip_sdk/client/payloads/agent/__init__.py +23 -0
  85. glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +28 -48
  86. glaip_sdk/client/payloads/agent/responses.py +43 -0
  87. glaip_sdk/client/run_rendering.py +414 -3
  88. glaip_sdk/client/schedules.py +439 -0
  89. glaip_sdk/client/tools.py +57 -26
  90. glaip_sdk/config/constants.py +22 -2
  91. glaip_sdk/guardrails/__init__.py +80 -0
  92. glaip_sdk/guardrails/serializer.py +89 -0
  93. glaip_sdk/hitl/__init__.py +48 -0
  94. glaip_sdk/hitl/base.py +64 -0
  95. glaip_sdk/hitl/callback.py +43 -0
  96. glaip_sdk/hitl/local.py +121 -0
  97. glaip_sdk/hitl/remote.py +523 -0
  98. glaip_sdk/models/__init__.py +47 -1
  99. glaip_sdk/models/_provider_mappings.py +101 -0
  100. glaip_sdk/models/_validation.py +97 -0
  101. glaip_sdk/models/agent.py +2 -1
  102. glaip_sdk/models/agent_runs.py +2 -1
  103. glaip_sdk/models/constants.py +141 -0
  104. glaip_sdk/models/model.py +170 -0
  105. glaip_sdk/models/schedule.py +224 -0
  106. glaip_sdk/payload_schemas/agent.py +1 -0
  107. glaip_sdk/payload_schemas/guardrails.py +34 -0
  108. glaip_sdk/registry/tool.py +273 -66
  109. glaip_sdk/runner/__init__.py +76 -0
  110. glaip_sdk/runner/base.py +84 -0
  111. glaip_sdk/runner/deps.py +115 -0
  112. glaip_sdk/runner/langgraph.py +1055 -0
  113. glaip_sdk/runner/logging_config.py +77 -0
  114. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  115. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  116. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
  117. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +116 -0
  118. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  119. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  120. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +242 -0
  121. glaip_sdk/schedules/__init__.py +22 -0
  122. glaip_sdk/schedules/base.py +291 -0
  123. glaip_sdk/tools/base.py +67 -14
  124. glaip_sdk/utils/__init__.py +1 -0
  125. glaip_sdk/utils/a2a/__init__.py +34 -0
  126. glaip_sdk/utils/a2a/event_processor.py +188 -0
  127. glaip_sdk/utils/agent_config.py +8 -2
  128. glaip_sdk/utils/bundler.py +138 -2
  129. glaip_sdk/utils/import_resolver.py +43 -11
  130. glaip_sdk/utils/rendering/renderer/base.py +58 -0
  131. glaip_sdk/utils/runtime_config.py +120 -0
  132. glaip_sdk/utils/sync.py +31 -11
  133. glaip_sdk/utils/tool_detection.py +301 -0
  134. glaip_sdk/utils/tool_storage_provider.py +140 -0
  135. {glaip_sdk-0.6.5b3.dist-info → glaip_sdk-0.7.17.dist-info}/METADATA +49 -38
  136. glaip_sdk-0.7.17.dist-info/RECORD +224 -0
  137. {glaip_sdk-0.6.5b3.dist-info → glaip_sdk-0.7.17.dist-info}/WHEEL +2 -1
  138. glaip_sdk-0.7.17.dist-info/entry_points.txt +2 -0
  139. glaip_sdk-0.7.17.dist-info/top_level.txt +1 -0
  140. glaip_sdk/cli/commands/agents.py +0 -1509
  141. glaip_sdk/cli/commands/mcps.py +0 -1356
  142. glaip_sdk/cli/commands/tools.py +0 -576
  143. glaip_sdk/cli/utils.py +0 -263
  144. glaip_sdk-0.6.5b3.dist-info/RECORD +0 -145
  145. glaip_sdk-0.6.5b3.dist-info/entry_points.txt +0 -3
@@ -22,13 +22,19 @@ from rich.console import Console
22
22
  from glaip_sdk.branding import (
23
23
  ACCENT_STYLE,
24
24
  ERROR_STYLE,
25
+ INFO_STYLE,
25
26
  SUCCESS_STYLE,
26
27
  WARNING_STYLE,
27
28
  )
28
- from glaip_sdk.cli.commands.update import update_command
29
+ from glaip_sdk.cli.commands.update import (
30
+ PACKAGE_NAME,
31
+ _build_command_parts,
32
+ _build_manual_upgrade_command,
33
+ _is_uv_managed_environment,
34
+ update_command,
35
+ )
29
36
  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
37
+ from glaip_sdk.cli.hints import command_hint, format_command_hint
32
38
  from glaip_sdk.rich_components import AIPPanel
33
39
 
34
40
  FetchLatestVersion = Callable[[], str | None]
@@ -37,6 +43,7 @@ PYPI_JSON_URL = "https://pypi.org/pypi/{package}/json"
37
43
  DEFAULT_TIMEOUT = 1.5 # seconds
38
44
 
39
45
  _LOGGER = logging.getLogger(__name__)
46
+ _UPDATE_VERSIONS_KEY = "_update_notifier_versions"
40
47
 
41
48
 
42
49
  def _parse_version(value: str) -> Version | None:
@@ -146,6 +153,7 @@ def maybe_notify_update(
146
153
  "- choose Update now or Skip to continue."
147
154
  )
148
155
  active_console.print(message)
156
+ _stash_update_versions(ctx, current_version, latest_version)
149
157
  _handle_update_decision(active_console, ctx)
150
158
  return
151
159
 
@@ -161,6 +169,7 @@ def maybe_notify_update(
161
169
  )
162
170
  active_console.print(panel)
163
171
  if should_prompt:
172
+ _stash_update_versions(ctx, current_version, latest_version)
164
173
  _handle_update_decision(active_console, ctx)
165
174
 
166
175
 
@@ -201,7 +210,13 @@ def _prompt_update_decision(console: Console) -> Literal["update", "skip"]:
201
210
 
202
211
  while True:
203
212
  try:
204
- response = console.input("Choice [1/2]: ").strip().lower()
213
+ raw_response = console.input("Choice [1/2]: ")
214
+ # Strip whitespace and convert to lowercase
215
+ response = raw_response.strip().lower()
216
+ # Remove any non-printable control characters (but keep printable chars)
217
+ # This handles cases where ANSI escape sequences or other control chars leak into input
218
+ response = "".join(char for char in response if char.isprintable() or char.isspace())
219
+ response = response.strip() # Strip again after filtering
205
220
  except (KeyboardInterrupt, EOFError):
206
221
  console.print(f"\n[{WARNING_STYLE}]Update skipped.[/]")
207
222
  return "skip"
@@ -214,19 +229,93 @@ def _prompt_update_decision(console: Console) -> Literal["update", "skip"]:
214
229
  console.print(f"[{ERROR_STYLE}]Please enter 1 to update now or 2 to skip.[/]")
215
230
 
216
231
 
232
+ def _get_manual_upgrade_command(is_uv: bool) -> str:
233
+ """Get the manual upgrade command for the given environment type.
234
+
235
+ Args:
236
+ is_uv: True if running in uv tool environment, False for pip environment.
237
+
238
+ Returns:
239
+ Manual upgrade command string.
240
+ """
241
+ try:
242
+ return _build_manual_upgrade_command(include_prerelease=False, is_uv=is_uv)
243
+ except Exception:
244
+ # Fallback: rebuild from shared command parts to avoid hardcoded strings.
245
+ try:
246
+ command_parts, _ = _build_command_parts(
247
+ package_name=PACKAGE_NAME,
248
+ is_uv=is_uv,
249
+ force_reinstall=False,
250
+ include_prerelease=False,
251
+ )
252
+ except Exception:
253
+ command_parts = (
254
+ ["uv", "tool", "install", "--upgrade", PACKAGE_NAME]
255
+ if is_uv
256
+ else ["pip", "install", "--upgrade", PACKAGE_NAME]
257
+ )
258
+ return " ".join(command_parts)
259
+
260
+
261
+ def _show_proactive_uv_guidance(console: Console, is_uv: bool) -> None:
262
+ """Show proactive guidance for uv environments before update attempt.
263
+
264
+ Args:
265
+ console: Rich console for output.
266
+ is_uv: True if running in uv tool environment.
267
+ """
268
+ if not is_uv:
269
+ return
270
+
271
+ manual_cmd = _get_manual_upgrade_command(is_uv=True)
272
+ console.print(
273
+ f"[{INFO_STYLE}]💡 Detected uv tool environment.[/] "
274
+ f"If automatic update fails, run: [{ACCENT_STYLE}]{manual_cmd}[/]"
275
+ )
276
+
277
+
278
+ def _show_error_guidance(console: Console, is_uv: bool) -> None:
279
+ """Show error guidance with correct manual command based on environment.
280
+
281
+ Args:
282
+ console: Rich console for output.
283
+ is_uv: True if running in uv tool environment.
284
+ """
285
+ try:
286
+ manual_cmd = _get_manual_upgrade_command(is_uv=is_uv)
287
+ console.print(f"[{INFO_STYLE}]💡 Tip:[/] Run this command manually:\n [{ACCENT_STYLE}]{manual_cmd}[/]")
288
+ except Exception as exc: # pragma: no cover - defensive guard
289
+ _LOGGER.debug("Failed to render update tip: %s", exc, exc_info=True)
290
+
291
+
217
292
  def _run_update_command(console: Console, ctx: Any) -> None:
218
293
  """Invoke the built-in update command and surface any errors."""
294
+ # Detect uv environment proactively before attempting update
295
+ is_uv = _is_uv_managed_environment()
296
+
297
+ # Provide proactive guidance for uv environments
298
+ # This helps users on older versions (e.g., 0.6.19) that don't have uv detection
299
+ # in their update command
300
+ _show_proactive_uv_guidance(console, is_uv)
301
+
219
302
  try:
220
303
  ctx.invoke(update_command)
221
304
  except click.ClickException as exc:
222
305
  exc.show()
223
306
  console.print(f"[{ERROR_STYLE}]Update command exited with an error.[/]")
307
+ _show_error_guidance(console, is_uv)
224
308
  except click.Abort:
225
309
  console.print(f"[{WARNING_STYLE}]Update aborted by user.[/]")
226
310
  except Exception as exc: # pragma: no cover - defensive guard
227
311
  console.print(f"[{ERROR_STYLE}]Unexpected error while running update: {exc}[/]")
312
+ # Also provide guidance for unexpected errors in uv environments
313
+ if is_uv:
314
+ manual_cmd = _get_manual_upgrade_command(is_uv=True)
315
+ console.print(f"[{INFO_STYLE}]💡 Tip:[/] Try running manually:\n [{ACCENT_STYLE}]{manual_cmd}[/]")
228
316
  else:
229
- _refresh_installed_version(console, ctx)
317
+ new_version = _refresh_installed_version(console, ctx)
318
+ _maybe_retry_update(console, ctx, new_version, is_uv)
230
319
 
231
320
 
232
321
  @contextmanager
@@ -247,7 +336,7 @@ def _suppress_library_logging(
247
336
  logger.setLevel(previous_level)
248
337
 
249
338
 
250
- def _refresh_installed_version(console: Console, ctx: Any) -> None:
339
+ def _refresh_installed_version(console: Console, ctx: Any) -> str | None:
251
340
  """Reload runtime metadata after an in-process upgrade."""
252
341
  new_version: str | None = None
253
342
  branding_module: Any | None = None
@@ -271,12 +360,13 @@ def _refresh_installed_version(console: Console, ctx: Any) -> None:
271
360
  try:
272
361
  branding_cls = getattr(branding_module, "AIPBranding", None) if branding_module else None
273
362
  session.refresh_branding(new_version, branding_cls=branding_cls)
274
- return
363
+ return new_version
275
364
  except Exception as exc: # pragma: no cover - defensive guard
276
365
  _LOGGER.debug("Failed to refresh active slash session: %s", exc, exc_info=True)
277
366
 
278
367
  if new_version:
279
368
  console.print(f"[{SUCCESS_STYLE}]CLI now running glaip-sdk {new_version}.[/]")
369
+ return new_version
280
370
 
281
371
 
282
372
  def _get_slash_session(ctx: Any) -> Any | None:
@@ -287,4 +377,122 @@ def _get_slash_session(ctx: Any) -> Any | None:
287
377
  return None
288
378
 
289
379
 
380
+ def _stash_update_versions(ctx: Any | None, current_version: str, latest_version: str) -> None:
381
+ """Persist update versions in the Click context for post-update checks."""
382
+ if ctx is None:
383
+ return
384
+ ctx_obj = getattr(ctx, "obj", None)
385
+ if isinstance(ctx_obj, dict):
386
+ ctx_obj[_UPDATE_VERSIONS_KEY] = {"current": current_version, "latest": latest_version}
387
+
388
+
389
+ def _get_update_versions(ctx: Any | None) -> tuple[str | None, str | None]:
390
+ """Return current/latest versions captured during the update prompt."""
391
+ if ctx is None:
392
+ return None, None
393
+ ctx_obj = getattr(ctx, "obj", None)
394
+ if not isinstance(ctx_obj, dict):
395
+ return None, None
396
+ payload = ctx_obj.get(_UPDATE_VERSIONS_KEY)
397
+ if not isinstance(payload, dict):
398
+ return None, None
399
+ current = payload.get("current")
400
+ latest = payload.get("latest")
401
+ return (
402
+ current if isinstance(current, str) else None,
403
+ latest if isinstance(latest, str) else None,
404
+ )
405
+
406
+
407
+ def _should_retry_update(
408
+ ctx: Any,
409
+ console: Console,
410
+ new_version: str | None,
411
+ ) -> tuple[str, str, Version, Version, Version] | None:
412
+ """Check if update retry is needed and return parsed versions if so."""
413
+ if ctx is None or not hasattr(ctx, "invoke"):
414
+ return None
415
+ if not _should_prompt_for_action(console, ctx):
416
+ return None
417
+
418
+ current_version, latest_version = _get_update_versions(ctx)
419
+ if not current_version or not latest_version or not new_version:
420
+ return None
421
+
422
+ current = _parse_version(current_version)
423
+ latest = _parse_version(latest_version)
424
+ installed = _parse_version(new_version)
425
+ if current is None or latest is None or installed is None:
426
+ return None
427
+
428
+ if installed >= latest:
429
+ return None
430
+
431
+ # Note: installed > current case is handled in _maybe_retry_update()
432
+ # to allow warning message to be printed before returning
433
+
434
+ return current_version, latest_version, current, latest, installed
435
+
436
+
437
+ def _handle_reinstall_error(console: Console, exc: Exception, is_uv: bool) -> None:
438
+ """Handle errors during reinstall attempt."""
439
+ if isinstance(exc, click.ClickException):
440
+ exc.show()
441
+ console.print(f"[{ERROR_STYLE}]Reinstall attempt failed.[/]")
442
+ _show_error_guidance(console, is_uv)
443
+ elif isinstance(exc, click.Abort):
444
+ console.print(f"[{WARNING_STYLE}]Reinstall skipped by user.[/]")
445
+ else:
446
+ console.print(f"[{ERROR_STYLE}]Unexpected error while reinstalling: {exc}[/]")
447
+ if is_uv:
448
+ manual_cmd = _get_manual_upgrade_command(is_uv=True)
449
+ console.print(f"[{INFO_STYLE}]💡 Tip:[/] Try running manually:\n [{ACCENT_STYLE}]{manual_cmd}[/]")
450
+
451
+
452
+ def _check_final_version(
453
+ console: Console, new_version: str | None, latest_version: str, latest: Version, is_uv: bool
454
+ ) -> None:
455
+ """Check and report final version status after reinstall."""
456
+ installed = _parse_version(new_version) if isinstance(new_version, str) else None
457
+ if installed is None or installed < latest:
458
+ console.print(
459
+ f"[{WARNING_STYLE}]Still on {new_version}. Your package index may not have {latest_version} yet.[/]"
460
+ )
461
+ if is_uv:
462
+ console.print(
463
+ f"[{INFO_STYLE}]💡 Tip:[/] If you need PyPI immediately, set "
464
+ f"[{ACCENT_STYLE}]UV_INDEX_URL=https://pypi.org/simple[/]."
465
+ )
466
+
467
+
468
+ def _maybe_retry_update(
469
+ console: Console,
470
+ ctx: Any,
471
+ new_version: str | None,
472
+ is_uv: bool,
473
+ ) -> None:
474
+ """Retry once with reinstall when the update did not advance versions."""
475
+ versions = _should_retry_update(ctx, console, new_version)
476
+ if versions is None:
477
+ return
478
+
479
+ current_version, latest_version, current, latest, installed = versions
480
+ if installed > current:
481
+ console.print(f"[{WARNING_STYLE}]Update installed {new_version}, but {latest_version} is still available.[/]")
482
+ return
483
+
484
+ console.print(
485
+ f"[{WARNING_STYLE}]Update completed but version stayed at {new_version}. Retrying with reinstall...[/]"
486
+ )
487
+
488
+ try:
489
+ ctx.invoke(update_command, force_reinstall=True)
490
+ except Exception as exc:
491
+ _handle_reinstall_error(console, exc, is_uv)
492
+ return
493
+
494
+ new_version = _refresh_installed_version(console, ctx)
495
+ _check_final_version(console, new_version, latest_version, latest, is_uv)
496
+
497
+
290
498
  __all__ = ["maybe_notify_update"]
@@ -13,7 +13,7 @@ from typing import Any
13
13
 
14
14
  import click
15
15
 
16
- from glaip_sdk.cli.utils import handle_best_effort_check
16
+ from glaip_sdk.cli.core.context import handle_best_effort_check
17
17
  from glaip_sdk.utils.validation import (
18
18
  coerce_timeout,
19
19
  validate_agent_instruction,
@@ -7,5 +7,6 @@ Authors:
7
7
 
8
8
  from glaip_sdk.client.agent_runs import AgentRunsClient
9
9
  from glaip_sdk.client.main import Client
10
+ from glaip_sdk.client.schedules import AgentScheduleManager, ScheduleClient
10
11
 
11
- __all__ = ["AgentRunsClient", "Client"]
12
+ __all__ = ["AgentRunsClient", "AgentScheduleManager", "Client", "ScheduleClient"]
@@ -0,0 +1,89 @@
1
+ """Schedule request payload builders for AIP SDK.
2
+
3
+ Authors:
4
+ Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Any
9
+
10
+ from glaip_sdk.models.schedule import ScheduleConfig
11
+
12
+
13
+ @dataclass
14
+ class ScheduleListParams:
15
+ """Parameters for listing schedules.
16
+
17
+ Args:
18
+ limit: Maximum number of schedules to return (1-100, default 50)
19
+ page: Page number for pagination (default 1)
20
+ agent_id: Filter schedules by agent ID
21
+ """
22
+
23
+ limit: int | None = None
24
+ page: int | None = None
25
+ agent_id: str | None = None
26
+
27
+ def to_query_params(self) -> dict[str, Any]:
28
+ """Convert to query parameters dictionary.
29
+
30
+ Returns:
31
+ Dictionary of non-None parameters for the API request
32
+ """
33
+ params: dict[str, Any] = {}
34
+ if self.limit is not None:
35
+ params["limit"] = self.limit
36
+ if self.page is not None:
37
+ params["page"] = self.page
38
+ if self.agent_id is not None:
39
+ params["agent_id"] = self.agent_id
40
+ return params
41
+
42
+
43
+ def normalize_schedule(
44
+ schedule: ScheduleConfig | dict[str, str] | str | None,
45
+ ) -> dict[str, str] | None:
46
+ """Normalize schedule input to a dictionary for API requests.
47
+
48
+ Accepts multiple input formats for user convenience:
49
+ - ScheduleConfig: Pydantic model with cron fields
50
+ - dict: Dictionary with cron fields (minute, hour, etc.)
51
+ - str: Cron string like "0 9 * * 1-5"
52
+ - None: Returns None
53
+
54
+ Args:
55
+ schedule: Schedule in various formats
56
+
57
+ Returns:
58
+ Dictionary suitable for API request or None
59
+
60
+ Raises:
61
+ ValueError: If cron string format is invalid
62
+ TypeError: If schedule is an unsupported type
63
+
64
+ Examples:
65
+ >>> normalize_schedule(ScheduleConfig(minute="0", hour="9"))
66
+ {'minute': '0', 'hour': '9', 'day_of_month': '*', 'month': '*', 'day_of_week': '*'}
67
+
68
+ >>> normalize_schedule({"minute": "0", "hour": "9"})
69
+ {'minute': '0', 'hour': '9', 'day_of_month': '*', 'month': '*', 'day_of_week': '*'}
70
+
71
+ >>> normalize_schedule("0 9 * * 1-5")
72
+ {'minute': '0', 'hour': '9', 'day_of_month': '*', 'month': '*', 'day_of_week': '1-5'}
73
+ """
74
+ if schedule is None:
75
+ return None
76
+
77
+ if isinstance(schedule, ScheduleConfig):
78
+ return schedule.model_dump()
79
+
80
+ if isinstance(schedule, dict):
81
+ # Validate and merge with defaults
82
+ return ScheduleConfig(**schedule).model_dump()
83
+
84
+ if isinstance(schedule, str):
85
+ # Parse cron string
86
+ config = ScheduleConfig.from_cron_string(schedule)
87
+ return config.model_dump()
88
+
89
+ raise TypeError(f"schedule must be ScheduleConfig, dict, or str, got {type(schedule).__name__}")