glaip-sdk 0.0.20__py3-none-any.whl → 0.7.7__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.
- glaip_sdk/__init__.py +44 -4
- glaip_sdk/_version.py +10 -3
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1250 -0
- glaip_sdk/branding.py +15 -6
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +271 -45
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents/__init__.py +119 -0
- glaip_sdk/cli/commands/agents/_common.py +561 -0
- glaip_sdk/cli/commands/agents/create.py +151 -0
- glaip_sdk/cli/commands/agents/delete.py +64 -0
- glaip_sdk/cli/commands/agents/get.py +89 -0
- glaip_sdk/cli/commands/agents/list.py +129 -0
- glaip_sdk/cli/commands/agents/run.py +264 -0
- glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
- glaip_sdk/cli/commands/agents/update.py +112 -0
- glaip_sdk/cli/commands/common_config.py +104 -0
- glaip_sdk/cli/commands/configure.py +734 -143
- glaip_sdk/cli/commands/mcps/__init__.py +94 -0
- glaip_sdk/cli/commands/mcps/_common.py +459 -0
- glaip_sdk/cli/commands/mcps/connect.py +82 -0
- glaip_sdk/cli/commands/mcps/create.py +152 -0
- glaip_sdk/cli/commands/mcps/delete.py +73 -0
- glaip_sdk/cli/commands/mcps/get.py +212 -0
- glaip_sdk/cli/commands/mcps/list.py +69 -0
- glaip_sdk/cli/commands/mcps/tools.py +235 -0
- glaip_sdk/cli/commands/mcps/update.py +190 -0
- glaip_sdk/cli/commands/models.py +14 -12
- glaip_sdk/cli/commands/shared/__init__.py +21 -0
- glaip_sdk/cli/commands/shared/formatters.py +91 -0
- glaip_sdk/cli/commands/tools/__init__.py +69 -0
- glaip_sdk/cli/commands/tools/_common.py +80 -0
- glaip_sdk/cli/commands/tools/create.py +228 -0
- glaip_sdk/cli/commands/tools/delete.py +61 -0
- glaip_sdk/cli/commands/tools/get.py +103 -0
- glaip_sdk/cli/commands/tools/list.py +69 -0
- glaip_sdk/cli/commands/tools/script.py +49 -0
- glaip_sdk/cli/commands/tools/update.py +102 -0
- glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
- glaip_sdk/cli/commands/transcripts/_common.py +9 -0
- glaip_sdk/cli/commands/transcripts/clear.py +5 -0
- glaip_sdk/cli/commands/transcripts/detail.py +5 -0
- glaip_sdk/cli/commands/transcripts_original.py +756 -0
- glaip_sdk/cli/commands/update.py +164 -23
- glaip_sdk/cli/config.py +49 -7
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +8 -0
- glaip_sdk/cli/core/__init__.py +79 -0
- glaip_sdk/cli/core/context.py +124 -0
- glaip_sdk/cli/core/output.py +851 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +45 -32
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +14 -17
- glaip_sdk/cli/main.py +344 -167
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/mcp_validators.py +5 -15
- glaip_sdk/cli/pager.py +15 -22
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/parsers/json_input.py +11 -22
- glaip_sdk/cli/resolution.py +5 -10
- glaip_sdk/cli/rich_helpers.py +1 -3
- glaip_sdk/cli/slash/__init__.py +0 -9
- glaip_sdk/cli/slash/accounts_controller.py +580 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +65 -29
- glaip_sdk/cli/slash/prompt.py +24 -10
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +827 -232
- glaip_sdk/cli/slash/tui/__init__.py +34 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +88 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +933 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/clipboard.py +147 -0
- glaip_sdk/cli/slash/tui/context.py +59 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/slash/tui/terminal.py +402 -0
- glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
- glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
- glaip_sdk/cli/slash/tui/theme/manager.py +86 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +123 -0
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +258 -60
- glaip_sdk/cli/transcript/capture.py +72 -21
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/launcher.py +1 -3
- glaip_sdk/cli/transcript/viewer.py +79 -329
- glaip_sdk/cli/update_notifier.py +385 -24
- glaip_sdk/cli/validators.py +16 -18
- glaip_sdk/client/__init__.py +3 -1
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +370 -100
- glaip_sdk/client/base.py +78 -35
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +25 -10
- glaip_sdk/client/mcps.py +166 -27
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +65 -74
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +583 -79
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +214 -56
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/config/constants.py +11 -0
- glaip_sdk/exceptions.py +1 -3
- glaip_sdk/hitl/__init__.py +48 -0
- glaip_sdk/hitl/base.py +64 -0
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +121 -0
- glaip_sdk/hitl/remote.py +523 -0
- glaip_sdk/icons.py +9 -3
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +107 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +117 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/schedule.py +224 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +1 -13
- glaip_sdk/payload_schemas/agent.py +1 -3
- glaip_sdk/registry/__init__.py +55 -0
- glaip_sdk/registry/agent.py +164 -0
- glaip_sdk/registry/base.py +139 -0
- glaip_sdk/registry/mcp.py +253 -0
- glaip_sdk/registry/tool.py +445 -0
- glaip_sdk/rich_components.py +58 -2
- glaip_sdk/runner/__init__.py +76 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +112 -0
- glaip_sdk/runner/langgraph.py +872 -0
- glaip_sdk/runner/logging_config.py +77 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +242 -0
- glaip_sdk/schedules/__init__.py +22 -0
- glaip_sdk/schedules/base.py +291 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +468 -0
- glaip_sdk/utils/__init__.py +59 -12
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +4 -14
- glaip_sdk/utils/bundler.py +403 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +46 -28
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +25 -21
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +1 -36
- glaip_sdk/utils/import_export.py +15 -16
- glaip_sdk/utils/import_resolver.py +524 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -1
- glaip_sdk/utils/rendering/formatting.py +38 -23
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +10 -3
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +73 -12
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +18 -8
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -51
- glaip_sdk/utils/rendering/renderer/base.py +534 -882
- glaip_sdk/utils/rendering/renderer/config.py +4 -10
- glaip_sdk/utils/rendering/renderer/debug.py +30 -34
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +13 -54
- glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
- glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
- glaip_sdk/utils/rendering/state.py +204 -0
- glaip_sdk/utils/rendering/step_tree_state.py +100 -0
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/{steps.py → steps/manager.py} +122 -26
- glaip_sdk/utils/rendering/timing.py +36 -0
- glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
- glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
- glaip_sdk/utils/resource_refs.py +29 -26
- glaip_sdk/utils/runtime_config.py +425 -0
- glaip_sdk/utils/serialization.py +32 -46
- glaip_sdk/utils/sync.py +162 -0
- glaip_sdk/utils/tool_detection.py +301 -0
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- glaip_sdk/utils/validation.py +20 -28
- {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.7.7.dist-info}/METADATA +78 -23
- glaip_sdk-0.7.7.dist-info/RECORD +213 -0
- {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.7.7.dist-info}/WHEEL +2 -1
- glaip_sdk-0.7.7.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.7.7.dist-info/top_level.txt +1 -0
- glaip_sdk/cli/commands/agents.py +0 -1412
- glaip_sdk/cli/commands/mcps.py +0 -1225
- glaip_sdk/cli/commands/tools.py +0 -597
- glaip_sdk/cli/utils.py +0 -1330
- glaip_sdk/models.py +0 -259
- glaip_sdk-0.0.20.dist-info/RECORD +0 -80
- glaip_sdk-0.0.20.dist-info/entry_points.txt +0 -3
glaip_sdk/cli/update_notifier.py
CHANGED
|
@@ -6,10 +6,14 @@ Author:
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
import
|
|
10
|
-
|
|
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,20 @@ from rich.console import Console
|
|
|
17
21
|
|
|
18
22
|
from glaip_sdk.branding import (
|
|
19
23
|
ACCENT_STYLE,
|
|
24
|
+
ERROR_STYLE,
|
|
25
|
+
INFO_STYLE,
|
|
20
26
|
SUCCESS_STYLE,
|
|
21
27
|
WARNING_STYLE,
|
|
22
28
|
)
|
|
23
|
-
from glaip_sdk.cli.
|
|
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
|
+
)
|
|
36
|
+
from glaip_sdk.cli.constants import UPDATE_CHECK_ENABLED
|
|
37
|
+
from glaip_sdk.cli.hints import command_hint, format_command_hint
|
|
24
38
|
from glaip_sdk.rich_components import AIPPanel
|
|
25
39
|
|
|
26
40
|
FetchLatestVersion = Callable[[], str | None]
|
|
@@ -28,6 +42,9 @@ FetchLatestVersion = Callable[[], str | None]
|
|
|
28
42
|
PYPI_JSON_URL = "https://pypi.org/pypi/{package}/json"
|
|
29
43
|
DEFAULT_TIMEOUT = 1.5 # seconds
|
|
30
44
|
|
|
45
|
+
_LOGGER = logging.getLogger(__name__)
|
|
46
|
+
_UPDATE_VERSIONS_KEY = "_update_notifier_versions"
|
|
47
|
+
|
|
31
48
|
|
|
32
49
|
def _parse_version(value: str) -> Version | None:
|
|
33
50
|
"""Parse a version string into a `Version`, returning None on failure."""
|
|
@@ -43,13 +60,16 @@ def _fetch_latest_version(package_name: str) -> str | None:
|
|
|
43
60
|
timeout = httpx.Timeout(DEFAULT_TIMEOUT)
|
|
44
61
|
|
|
45
62
|
try:
|
|
46
|
-
with
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
with _suppress_library_logging():
|
|
64
|
+
with httpx.Client(timeout=timeout) as client:
|
|
65
|
+
response = client.get(url, headers={"Accept": "application/json"})
|
|
66
|
+
response.raise_for_status()
|
|
67
|
+
payload = response.json()
|
|
68
|
+
except httpx.HTTPError as exc:
|
|
69
|
+
_LOGGER.debug("Update check failed: %s", exc, exc_info=True)
|
|
51
70
|
return None
|
|
52
|
-
except ValueError:
|
|
71
|
+
except ValueError as exc:
|
|
72
|
+
_LOGGER.debug("Invalid JSON while checking for updates: %s", exc, exc_info=True)
|
|
53
73
|
return None
|
|
54
74
|
|
|
55
75
|
info = payload.get("info") if isinstance(payload, dict) else None
|
|
@@ -61,13 +81,19 @@ def _fetch_latest_version(package_name: str) -> str | None:
|
|
|
61
81
|
|
|
62
82
|
def _should_check_for_updates() -> bool:
|
|
63
83
|
"""Return False when update checks are explicitly disabled."""
|
|
64
|
-
|
|
84
|
+
# Check module attribute first (for test overrides), then fall back to imported constant
|
|
85
|
+
module = sys.modules.get(__name__)
|
|
86
|
+
if module and hasattr(module, "UPDATE_CHECK_ENABLED"):
|
|
87
|
+
return getattr(module, "UPDATE_CHECK_ENABLED")
|
|
88
|
+
return UPDATE_CHECK_ENABLED
|
|
65
89
|
|
|
66
90
|
|
|
67
91
|
def _build_update_panel(
|
|
68
92
|
current_version: str,
|
|
69
93
|
latest_version: str,
|
|
70
94
|
command_text: str,
|
|
95
|
+
*,
|
|
96
|
+
show_command_hint: bool,
|
|
71
97
|
) -> AIPPanel:
|
|
72
98
|
"""Create a Rich panel that prompts the user to update."""
|
|
73
99
|
command_markup = format_command_hint(command_text) or command_text
|
|
@@ -75,9 +101,10 @@ def _build_update_panel(
|
|
|
75
101
|
f"[{WARNING_STYLE}]✨ Update available![/] "
|
|
76
102
|
f"{current_version} → {latest_version}\n\n"
|
|
77
103
|
"See the latest release notes:\n"
|
|
78
|
-
f"https://pypi.org/project/glaip-sdk/{latest_version}
|
|
79
|
-
f"[{ACCENT_STYLE}]Run[/] {command_markup} to install."
|
|
104
|
+
f"https://pypi.org/project/glaip-sdk/{latest_version}/"
|
|
80
105
|
)
|
|
106
|
+
if show_command_hint:
|
|
107
|
+
message += f"\n\n[{ACCENT_STYLE}]Run[/] {command_markup} to install."
|
|
81
108
|
return AIPPanel(
|
|
82
109
|
message,
|
|
83
110
|
title=f"[{SUCCESS_STYLE}]AIP SDK Update[/]",
|
|
@@ -97,11 +124,7 @@ def maybe_notify_update(
|
|
|
97
124
|
slash_command: str | None = None,
|
|
98
125
|
style: Literal["panel", "inline"] = "panel",
|
|
99
126
|
) -> 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
|
-
"""
|
|
127
|
+
"""Check PyPI for a newer version and display a prompt if one exists."""
|
|
105
128
|
if not _should_check_for_updates():
|
|
106
129
|
return
|
|
107
130
|
|
|
@@ -120,18 +143,356 @@ def maybe_notify_update(
|
|
|
120
143
|
return
|
|
121
144
|
|
|
122
145
|
active_console = console or Console()
|
|
146
|
+
should_prompt = _should_prompt_for_action(active_console, ctx)
|
|
147
|
+
|
|
123
148
|
if style == "inline":
|
|
149
|
+
if should_prompt:
|
|
150
|
+
message = (
|
|
151
|
+
f"[{WARNING_STYLE}]✨ Update[/] "
|
|
152
|
+
f"{current_version} → {latest_version} "
|
|
153
|
+
"- choose Update now or Skip to continue."
|
|
154
|
+
)
|
|
155
|
+
active_console.print(message)
|
|
156
|
+
_stash_update_versions(ctx, current_version, latest_version)
|
|
157
|
+
_handle_update_decision(active_console, ctx)
|
|
158
|
+
return
|
|
159
|
+
|
|
124
160
|
command_markup = format_command_hint(command_text) or command_text
|
|
125
|
-
|
|
126
|
-
f"[{WARNING_STYLE}]✨ Update[/] "
|
|
127
|
-
f"{current_version} → {latest_version} "
|
|
128
|
-
f"- {command_markup}"
|
|
129
|
-
)
|
|
130
|
-
active_console.print(message)
|
|
161
|
+
active_console.print(f"[{WARNING_STYLE}]✨ Update[/] {current_version} → {latest_version} - {command_markup}")
|
|
131
162
|
return
|
|
132
163
|
|
|
133
|
-
panel = _build_update_panel(
|
|
164
|
+
panel = _build_update_panel(
|
|
165
|
+
current_version,
|
|
166
|
+
latest_version,
|
|
167
|
+
command_text,
|
|
168
|
+
show_command_hint=not should_prompt,
|
|
169
|
+
)
|
|
134
170
|
active_console.print(panel)
|
|
171
|
+
if should_prompt:
|
|
172
|
+
_stash_update_versions(ctx, current_version, latest_version)
|
|
173
|
+
_handle_update_decision(active_console, ctx)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _handle_update_decision(console: Console, ctx: Any) -> None:
|
|
177
|
+
"""Prompt the user to take action on the available update."""
|
|
178
|
+
choice = _prompt_update_decision(console)
|
|
179
|
+
if choice == "skip":
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
_run_update_command(console, ctx)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _should_prompt_for_action(console: Console, ctx: Any | None) -> bool:
|
|
186
|
+
"""Return True when we can safely block for interactive input."""
|
|
187
|
+
if ctx is None or not hasattr(ctx, "invoke"):
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
is_interactive = getattr(console, "is_interactive", False)
|
|
191
|
+
if not isinstance(is_interactive, bool) or not is_interactive:
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
is_terminal = getattr(console, "is_terminal", False)
|
|
195
|
+
if not isinstance(is_terminal, bool) or not is_terminal:
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
input_method = getattr(console, "input", None)
|
|
199
|
+
return callable(input_method)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _prompt_update_decision(console: Console) -> Literal["update", "skip"]:
|
|
203
|
+
"""Ask the user to choose between updating now or skipping."""
|
|
204
|
+
console.print(
|
|
205
|
+
f"[{ACCENT_STYLE}]Select an option to continue:[/]\n"
|
|
206
|
+
f" [{SUCCESS_STYLE}]1.[/] Update now\n"
|
|
207
|
+
f" [{WARNING_STYLE}]2.[/] Skip\n"
|
|
208
|
+
)
|
|
209
|
+
console.print("[dim]Press Enter after typing your choice.[/]")
|
|
210
|
+
|
|
211
|
+
while True:
|
|
212
|
+
try:
|
|
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
|
|
220
|
+
except (KeyboardInterrupt, EOFError):
|
|
221
|
+
console.print(f"\n[{WARNING_STYLE}]Update skipped.[/]")
|
|
222
|
+
return "skip"
|
|
223
|
+
|
|
224
|
+
if response in {"1", "update", "u"}:
|
|
225
|
+
return "update"
|
|
226
|
+
if response in {"2", "skip", "s"}:
|
|
227
|
+
return "skip"
|
|
228
|
+
|
|
229
|
+
console.print(f"[{ERROR_STYLE}]Please enter 1 to update now or 2 to skip.[/]")
|
|
230
|
+
|
|
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
|
+
|
|
292
|
+
def _run_update_command(console: Console, ctx: Any) -> None:
|
|
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
|
+
|
|
302
|
+
try:
|
|
303
|
+
ctx.invoke(update_command)
|
|
304
|
+
except click.ClickException as exc:
|
|
305
|
+
exc.show()
|
|
306
|
+
console.print(f"[{ERROR_STYLE}]Update command exited with an error.[/]")
|
|
307
|
+
_show_error_guidance(console, is_uv)
|
|
308
|
+
except click.Abort:
|
|
309
|
+
console.print(f"[{WARNING_STYLE}]Update aborted by user.[/]")
|
|
310
|
+
except Exception as exc: # pragma: no cover - defensive guard
|
|
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}[/]")
|
|
316
|
+
else:
|
|
317
|
+
new_version = _refresh_installed_version(console, ctx)
|
|
318
|
+
_maybe_retry_update(console, ctx, new_version, is_uv)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@contextmanager
|
|
322
|
+
def _suppress_library_logging(
|
|
323
|
+
logger_names: Iterable[str] | None = None, *, level: int = logging.WARNING
|
|
324
|
+
) -> Iterator[None]:
|
|
325
|
+
"""Temporarily raise log level for selected libraries during update checks."""
|
|
326
|
+
names = tuple(logger_names) if logger_names is not None else ("httpx",)
|
|
327
|
+
captured: list[tuple[logging.Logger, int]] = []
|
|
328
|
+
try:
|
|
329
|
+
for name in names:
|
|
330
|
+
logger = logging.getLogger(name)
|
|
331
|
+
captured.append((logger, logger.level))
|
|
332
|
+
logger.setLevel(level)
|
|
333
|
+
yield
|
|
334
|
+
finally:
|
|
335
|
+
for logger, previous_level in captured:
|
|
336
|
+
logger.setLevel(previous_level)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _refresh_installed_version(console: Console, ctx: Any) -> str | None:
|
|
340
|
+
"""Reload runtime metadata after an in-process upgrade."""
|
|
341
|
+
new_version: str | None = None
|
|
342
|
+
branding_module: Any | None = None
|
|
343
|
+
|
|
344
|
+
try:
|
|
345
|
+
version_module = importlib.reload(importlib.import_module("glaip_sdk._version"))
|
|
346
|
+
new_version = getattr(version_module, "__version__", None)
|
|
347
|
+
except Exception as exc: # pragma: no cover - defensive guard
|
|
348
|
+
_LOGGER.debug("Failed to reload glaip_sdk._version: %s", exc, exc_info=True)
|
|
349
|
+
|
|
350
|
+
try:
|
|
351
|
+
branding_module = importlib.reload(importlib.import_module("glaip_sdk.branding"))
|
|
352
|
+
if new_version:
|
|
353
|
+
branding_module.SDK_VERSION = new_version
|
|
354
|
+
except Exception as exc: # pragma: no cover - defensive guard
|
|
355
|
+
_LOGGER.debug("Failed to update branding metadata: %s", exc, exc_info=True)
|
|
356
|
+
branding_module = None
|
|
357
|
+
|
|
358
|
+
session = _get_slash_session(ctx)
|
|
359
|
+
if session and hasattr(session, "refresh_branding"):
|
|
360
|
+
try:
|
|
361
|
+
branding_cls = getattr(branding_module, "AIPBranding", None) if branding_module else None
|
|
362
|
+
session.refresh_branding(new_version, branding_cls=branding_cls)
|
|
363
|
+
return new_version
|
|
364
|
+
except Exception as exc: # pragma: no cover - defensive guard
|
|
365
|
+
_LOGGER.debug("Failed to refresh active slash session: %s", exc, exc_info=True)
|
|
366
|
+
|
|
367
|
+
if new_version:
|
|
368
|
+
console.print(f"[{SUCCESS_STYLE}]CLI now running glaip-sdk {new_version}.[/]")
|
|
369
|
+
return new_version
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _get_slash_session(ctx: Any) -> Any | None:
|
|
373
|
+
"""Return active slash session from the Click context if present."""
|
|
374
|
+
ctx_obj = getattr(ctx, "obj", None)
|
|
375
|
+
if isinstance(ctx_obj, dict):
|
|
376
|
+
return ctx_obj.get("_slash_session")
|
|
377
|
+
return None
|
|
378
|
+
|
|
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)
|
|
135
496
|
|
|
136
497
|
|
|
137
498
|
__all__ = ["maybe_notify_update"]
|
glaip_sdk/cli/validators.py
CHANGED
|
@@ -13,6 +13,7 @@ from typing import Any
|
|
|
13
13
|
|
|
14
14
|
import click
|
|
15
15
|
|
|
16
|
+
from glaip_sdk.cli.core.context import handle_best_effort_check
|
|
16
17
|
from glaip_sdk.utils.validation import (
|
|
17
18
|
coerce_timeout,
|
|
18
19
|
validate_agent_instruction,
|
|
@@ -42,7 +43,7 @@ def validate_agent_name_cli(name: str) -> str:
|
|
|
42
43
|
try:
|
|
43
44
|
return validate_agent_name(name)
|
|
44
45
|
except ValueError as e:
|
|
45
|
-
raise click.ClickException(str(e))
|
|
46
|
+
raise click.ClickException(str(e)) from e
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
def validate_agent_instruction_cli(instruction: str) -> str:
|
|
@@ -60,7 +61,7 @@ def validate_agent_instruction_cli(instruction: str) -> str:
|
|
|
60
61
|
try:
|
|
61
62
|
return validate_agent_instruction(instruction)
|
|
62
63
|
except ValueError as e:
|
|
63
|
-
raise click.ClickException(str(e))
|
|
64
|
+
raise click.ClickException(str(e)) from e
|
|
64
65
|
|
|
65
66
|
|
|
66
67
|
def validate_timeout_cli(timeout: int) -> int:
|
|
@@ -78,7 +79,7 @@ def validate_timeout_cli(timeout: int) -> int:
|
|
|
78
79
|
try:
|
|
79
80
|
return validate_timeout(timeout)
|
|
80
81
|
except ValueError as e:
|
|
81
|
-
raise click.ClickException(str(e))
|
|
82
|
+
raise click.ClickException(str(e)) from e
|
|
82
83
|
|
|
83
84
|
|
|
84
85
|
def validate_tool_name_cli(name: str) -> str:
|
|
@@ -96,7 +97,7 @@ def validate_tool_name_cli(name: str) -> str:
|
|
|
96
97
|
try:
|
|
97
98
|
return validate_tool_name(name)
|
|
98
99
|
except ValueError as e:
|
|
99
|
-
raise click.ClickException(str(e))
|
|
100
|
+
raise click.ClickException(str(e)) from e
|
|
100
101
|
|
|
101
102
|
|
|
102
103
|
def validate_mcp_name_cli(name: str) -> str:
|
|
@@ -114,7 +115,7 @@ def validate_mcp_name_cli(name: str) -> str:
|
|
|
114
115
|
try:
|
|
115
116
|
return validate_mcp_name(name)
|
|
116
117
|
except ValueError as e:
|
|
117
|
-
raise click.ClickException(str(e))
|
|
118
|
+
raise click.ClickException(str(e)) from e
|
|
118
119
|
|
|
119
120
|
|
|
120
121
|
def validate_file_path_cli(file_path: str | Path, must_exist: bool = True) -> Path:
|
|
@@ -133,7 +134,7 @@ def validate_file_path_cli(file_path: str | Path, must_exist: bool = True) -> Pa
|
|
|
133
134
|
try:
|
|
134
135
|
return validate_file_path(file_path, must_exist)
|
|
135
136
|
except ValueError as e:
|
|
136
|
-
raise click.ClickException(str(e))
|
|
137
|
+
raise click.ClickException(str(e)) from e
|
|
137
138
|
|
|
138
139
|
|
|
139
140
|
def validate_directory_path_cli(dir_path: str | Path, must_exist: bool = True) -> Path:
|
|
@@ -152,7 +153,7 @@ def validate_directory_path_cli(dir_path: str | Path, must_exist: bool = True) -
|
|
|
152
153
|
try:
|
|
153
154
|
return validate_directory_path(dir_path, must_exist)
|
|
154
155
|
except ValueError as e:
|
|
155
|
-
raise click.ClickException(str(e))
|
|
156
|
+
raise click.ClickException(str(e)) from e
|
|
156
157
|
|
|
157
158
|
|
|
158
159
|
def validate_url_cli(url: str) -> str:
|
|
@@ -170,7 +171,7 @@ def validate_url_cli(url: str) -> str:
|
|
|
170
171
|
try:
|
|
171
172
|
return validate_url(url)
|
|
172
173
|
except ValueError as e:
|
|
173
|
-
raise click.ClickException(str(e))
|
|
174
|
+
raise click.ClickException(str(e)) from e
|
|
174
175
|
|
|
175
176
|
|
|
176
177
|
def validate_api_key_cli(api_key: str) -> str:
|
|
@@ -188,7 +189,7 @@ def validate_api_key_cli(api_key: str) -> str:
|
|
|
188
189
|
try:
|
|
189
190
|
return validate_api_key(api_key)
|
|
190
191
|
except ValueError as e:
|
|
191
|
-
raise click.ClickException(str(e))
|
|
192
|
+
raise click.ClickException(str(e)) from e
|
|
192
193
|
|
|
193
194
|
|
|
194
195
|
def coerce_timeout_cli(value: int | float | str) -> int:
|
|
@@ -206,7 +207,7 @@ def coerce_timeout_cli(value: int | float | str) -> int:
|
|
|
206
207
|
try:
|
|
207
208
|
return coerce_timeout(value)
|
|
208
209
|
except ValueError as e:
|
|
209
|
-
raise click.ClickException(str(e))
|
|
210
|
+
raise click.ClickException(str(e)) from e
|
|
210
211
|
|
|
211
212
|
|
|
212
213
|
def validate_name_uniqueness_cli(
|
|
@@ -226,15 +227,12 @@ def validate_name_uniqueness_cli(
|
|
|
226
227
|
Raises:
|
|
227
228
|
click.ClickException: If name is not unique
|
|
228
229
|
"""
|
|
229
|
-
|
|
230
|
+
|
|
231
|
+
def _check_duplicate() -> None:
|
|
230
232
|
existing = finder_func(name=name)
|
|
231
233
|
if existing:
|
|
232
234
|
raise click.ClickException(
|
|
233
|
-
f"A {resource_type.lower()} named '{name}' already exists. "
|
|
234
|
-
"Please choose a unique name."
|
|
235
|
+
f"A {resource_type.lower()} named '{name}' already exists. Please choose a unique name."
|
|
235
236
|
)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
except Exception:
|
|
239
|
-
# Non-fatal: best-effort duplicate check
|
|
240
|
-
pass
|
|
237
|
+
|
|
238
|
+
handle_best_effort_check(_check_duplicate)
|
glaip_sdk/client/__init__.py
CHANGED
|
@@ -5,6 +5,8 @@ Authors:
|
|
|
5
5
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
from glaip_sdk.client.agent_runs import AgentRunsClient
|
|
8
9
|
from glaip_sdk.client.main import Client
|
|
10
|
+
from glaip_sdk.client.schedules import AgentScheduleManager, ScheduleClient
|
|
9
11
|
|
|
10
|
-
__all__ = ["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__}")
|