glaip-sdk 0.6.5b6__py3-none-any.whl → 0.7.12__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 +42 -5
- glaip_sdk/agents/base.py +217 -42
- glaip_sdk/branding.py +113 -2
- glaip_sdk/cli/account_store.py +15 -0
- glaip_sdk/cli/auth.py +14 -8
- glaip_sdk/cli/commands/accounts.py +1 -1
- 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 +15 -12
- glaip_sdk/cli/commands/configure.py +2 -3
- 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 +2 -4
- 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.py → transcripts_original.py} +2 -1
- glaip_sdk/cli/commands/update.py +163 -17
- glaip_sdk/cli/config.py +1 -0
- glaip_sdk/cli/core/output.py +12 -7
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/main.py +127 -39
- glaip_sdk/cli/pager.py +3 -3
- glaip_sdk/cli/resolution.py +2 -1
- glaip_sdk/cli/slash/accounts_controller.py +112 -32
- glaip_sdk/cli/slash/agent_session.py +5 -2
- glaip_sdk/cli/slash/prompt.py +11 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
- glaip_sdk/cli/slash/session.py +369 -23
- glaip_sdk/cli/slash/tui/__init__.py +26 -1
- glaip_sdk/cli/slash/tui/accounts.tcss +79 -5
- glaip_sdk/cli/slash/tui/accounts_app.py +1027 -88
- glaip_sdk/cli/slash/tui/clipboard.py +195 -0
- glaip_sdk/cli/slash/tui/context.py +87 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
- glaip_sdk/cli/slash/tui/layouts/harlequin.py +160 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +119 -12
- glaip_sdk/cli/slash/tui/terminal.py +407 -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 +112 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +374 -0
- glaip_sdk/cli/transcript/history.py +1 -1
- glaip_sdk/cli/transcript/viewer.py +5 -3
- glaip_sdk/cli/tui_settings.py +125 -0
- glaip_sdk/cli/update_notifier.py +215 -7
- glaip_sdk/cli/validators.py +1 -1
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agents.py +50 -8
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +7 -1
- glaip_sdk/client/mcps.py +44 -13
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +22 -47
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +414 -3
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/tools.py +57 -26
- glaip_sdk/guardrails/__init__.py +80 -0
- glaip_sdk/guardrails/serializer.py +89 -0
- 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/models/__init__.py +17 -0
- glaip_sdk/models/agent_runs.py +2 -1
- glaip_sdk/models/schedule.py +224 -0
- glaip_sdk/payload_schemas/agent.py +1 -0
- glaip_sdk/payload_schemas/guardrails.py +34 -0
- glaip_sdk/registry/tool.py +273 -59
- glaip_sdk/runner/__init__.py +20 -3
- glaip_sdk/runner/deps.py +5 -8
- glaip_sdk/runner/langgraph.py +318 -42
- glaip_sdk/runner/logging_config.py +77 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +104 -5
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +72 -7
- glaip_sdk/schedules/__init__.py +22 -0
- glaip_sdk/schedules/base.py +291 -0
- glaip_sdk/tools/base.py +67 -14
- glaip_sdk/utils/__init__.py +1 -0
- glaip_sdk/utils/bundler.py +138 -2
- glaip_sdk/utils/import_resolver.py +43 -11
- glaip_sdk/utils/rendering/renderer/base.py +58 -0
- glaip_sdk/utils/runtime_config.py +15 -12
- glaip_sdk/utils/sync.py +31 -11
- glaip_sdk/utils/tool_detection.py +274 -6
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- {glaip_sdk-0.6.5b6.dist-info → glaip_sdk-0.7.12.dist-info}/METADATA +49 -37
- glaip_sdk-0.7.12.dist-info/RECORD +219 -0
- {glaip_sdk-0.6.5b6.dist-info → glaip_sdk-0.7.12.dist-info}/WHEEL +2 -1
- glaip_sdk-0.7.12.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.7.12.dist-info/top_level.txt +1 -0
- glaip_sdk/cli/commands/agents.py +0 -1509
- glaip_sdk/cli/commands/mcps.py +0 -1356
- glaip_sdk/cli/commands/tools.py +0 -576
- glaip_sdk/cli/utils.py +0 -263
- glaip_sdk-0.6.5b6.dist-info/RECORD +0 -159
- glaip_sdk-0.6.5b6.dist-info/entry_points.txt +0 -3
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"""Schedule client for AIP SDK.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
5
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from glaip_sdk.client._schedule_payloads import ScheduleListParams, normalize_schedule
|
|
11
|
+
from glaip_sdk.client.base import BaseClient
|
|
12
|
+
from glaip_sdk.exceptions import APIError, NotFoundError
|
|
13
|
+
from glaip_sdk.models.agent_runs import RunStatus
|
|
14
|
+
from glaip_sdk.models.schedule import (
|
|
15
|
+
ScheduleConfig,
|
|
16
|
+
ScheduleResponse,
|
|
17
|
+
ScheduleRunResponse,
|
|
18
|
+
ScheduleRunResult,
|
|
19
|
+
)
|
|
20
|
+
from glaip_sdk.schedules import (
|
|
21
|
+
Schedule,
|
|
22
|
+
ScheduleListResult,
|
|
23
|
+
ScheduleRun,
|
|
24
|
+
ScheduleRunListResult,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from glaip_sdk.models import Agent
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ScheduleClient(BaseClient):
|
|
32
|
+
"""Client for managing agent schedules.
|
|
33
|
+
|
|
34
|
+
Provides CRUD operations for scheduled agent executions.
|
|
35
|
+
Schedules allow agents to run automatically at specified times
|
|
36
|
+
using cron-like configurations.
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
>>> from glaip_sdk import Client
|
|
40
|
+
>>> from glaip_sdk.models.schedule import ScheduleConfig
|
|
41
|
+
>>>
|
|
42
|
+
>>> client = Client()
|
|
43
|
+
>>> # List all schedules
|
|
44
|
+
>>> result = client.schedules.list()
|
|
45
|
+
>>> for schedule in result:
|
|
46
|
+
... print(f"{schedule.id}: {schedule.next_run_time}")
|
|
47
|
+
>>>
|
|
48
|
+
>>> # Get a specific schedule
|
|
49
|
+
>>> schedule = client.schedules.get("schedule-id")
|
|
50
|
+
>>>
|
|
51
|
+
>>> # Create a schedule for an agent
|
|
52
|
+
>>> schedule = client.schedules.create(
|
|
53
|
+
... agent_id="agent-id",
|
|
54
|
+
... input="Generate daily report",
|
|
55
|
+
... schedule=ScheduleConfig(minute="0", hour="9", day_of_week="1-5")
|
|
56
|
+
... )
|
|
57
|
+
>>>
|
|
58
|
+
>>> # Update a schedule
|
|
59
|
+
>>> schedule = client.schedules.update(
|
|
60
|
+
... schedule_id="schedule-id",
|
|
61
|
+
... input="Updated report input"
|
|
62
|
+
... )
|
|
63
|
+
>>>
|
|
64
|
+
>>> # Delete a schedule
|
|
65
|
+
>>> client.schedules.delete("schedule-id")
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def list(
|
|
69
|
+
self,
|
|
70
|
+
*,
|
|
71
|
+
limit: int | None = None,
|
|
72
|
+
page: int | None = None,
|
|
73
|
+
agent_id: str | None = None,
|
|
74
|
+
) -> ScheduleListResult:
|
|
75
|
+
"""List schedules with optional filtering and pagination.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
limit: Maximum number of schedules to return (1-100)
|
|
79
|
+
page: Page number for pagination
|
|
80
|
+
agent_id: Filter schedules by agent ID
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
ScheduleListResult containing schedules and pagination metadata
|
|
84
|
+
"""
|
|
85
|
+
params = ScheduleListParams(limit=limit, page=page, agent_id=agent_id)
|
|
86
|
+
|
|
87
|
+
response = self._request_with_envelope(
|
|
88
|
+
"GET",
|
|
89
|
+
"/agents/schedules",
|
|
90
|
+
params=params.to_query_params(),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Parse schedules from response data
|
|
94
|
+
schedules = [
|
|
95
|
+
Schedule.from_response(ScheduleResponse.model_validate(item), client=self)
|
|
96
|
+
for item in (response.get("data") or [])
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
return ScheduleListResult(
|
|
100
|
+
items=schedules,
|
|
101
|
+
total=response.get("total"),
|
|
102
|
+
page=response.get("page"),
|
|
103
|
+
limit=response.get("limit"),
|
|
104
|
+
has_next=response.get("has_next"),
|
|
105
|
+
has_prev=response.get("has_prev"),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def get(self, schedule_id: str) -> Schedule:
|
|
109
|
+
"""Get a schedule by ID.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
schedule_id: The schedule ID to retrieve
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Schedule instance
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
NotFoundError: If schedule is not found
|
|
119
|
+
AuthenticationError: If API key is invalid
|
|
120
|
+
APIError: If the API request fails
|
|
121
|
+
"""
|
|
122
|
+
data = self._request("GET", f"/agents/schedules/{schedule_id}")
|
|
123
|
+
|
|
124
|
+
if data is None:
|
|
125
|
+
raise NotFoundError(f"Schedule not found: {schedule_id}")
|
|
126
|
+
return Schedule.from_response(ScheduleResponse.model_validate(data), client=self)
|
|
127
|
+
|
|
128
|
+
def create(
|
|
129
|
+
self,
|
|
130
|
+
*,
|
|
131
|
+
agent_id: str,
|
|
132
|
+
input: str,
|
|
133
|
+
schedule: ScheduleConfig | dict[str, str] | str,
|
|
134
|
+
) -> Schedule:
|
|
135
|
+
"""Create a new schedule for an agent.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
agent_id: The agent ID to schedule
|
|
139
|
+
input: Input text for scheduled execution
|
|
140
|
+
schedule: Schedule configuration (ScheduleConfig, dict, or cron string)
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Created Schedule instance
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
ValueError: If schedule format is invalid
|
|
147
|
+
NotFoundError: If agent is not found
|
|
148
|
+
ValidationError: If schedule configuration is invalid
|
|
149
|
+
AuthenticationError: If API key is invalid
|
|
150
|
+
APIError: If the API request fails
|
|
151
|
+
"""
|
|
152
|
+
schedule_dict = normalize_schedule(schedule)
|
|
153
|
+
if schedule_dict is None:
|
|
154
|
+
raise ValueError("schedule is required")
|
|
155
|
+
|
|
156
|
+
payload = {
|
|
157
|
+
"input": input,
|
|
158
|
+
"schedule": schedule_dict,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
response = self._request("POST", f"/agents/{agent_id}/schedule", json=payload)
|
|
162
|
+
|
|
163
|
+
# Response contains schedule_id, fetch the full schedule
|
|
164
|
+
schedule_id = response.get("schedule_id")
|
|
165
|
+
if not schedule_id:
|
|
166
|
+
raise APIError("Missing schedule_id in create response")
|
|
167
|
+
|
|
168
|
+
return self.get(schedule_id)
|
|
169
|
+
|
|
170
|
+
def update(
|
|
171
|
+
self,
|
|
172
|
+
schedule_id: str,
|
|
173
|
+
*,
|
|
174
|
+
input: str | None = None,
|
|
175
|
+
schedule: ScheduleConfig | dict[str, str] | str | None = None,
|
|
176
|
+
) -> Schedule:
|
|
177
|
+
"""Update an existing schedule.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
schedule_id: The schedule ID to update
|
|
181
|
+
input: New input text for scheduled execution
|
|
182
|
+
schedule: New schedule configuration (ScheduleConfig, dict, or cron string)
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Updated Schedule instance
|
|
186
|
+
|
|
187
|
+
Raises:
|
|
188
|
+
NotFoundError: If schedule is not found
|
|
189
|
+
ValueError: If schedule config is required but not provided
|
|
190
|
+
ValidationError: If schedule configuration is invalid
|
|
191
|
+
AuthenticationError: If API key is invalid
|
|
192
|
+
APIError: If the API request fails
|
|
193
|
+
|
|
194
|
+
Note:
|
|
195
|
+
Updates use explicit replacement (not merge). The SDK normalizes partial
|
|
196
|
+
schedule dicts by filling missing cron fields with "*". This is intentional
|
|
197
|
+
for predictability - what you provide is what you get (plus wildcard defaults).
|
|
198
|
+
If the current schedule metadata is missing and no schedule parameter is
|
|
199
|
+
provided, a ValueError is raised.
|
|
200
|
+
"""
|
|
201
|
+
# Get current schedule to merge with updates
|
|
202
|
+
current = self.get(schedule_id)
|
|
203
|
+
|
|
204
|
+
# Handle input - ensure we have valid input data
|
|
205
|
+
if current.input is None and input is None:
|
|
206
|
+
raise ValueError(
|
|
207
|
+
f"Schedule {schedule_id} has missing input metadata and no input parameter provided. "
|
|
208
|
+
"Please provide an input value to update this schedule."
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Handle schedule config - ensure we have complete schedule data
|
|
212
|
+
if current.schedule_config is None and schedule is None:
|
|
213
|
+
raise ValueError(
|
|
214
|
+
f"Schedule {schedule_id} has missing metadata and no schedule parameter provided. "
|
|
215
|
+
"Please provide a full schedule configuration to update this schedule."
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
current_input = current.input or ""
|
|
219
|
+
current_schedule = current.schedule_config.model_dump() if current.schedule_config else {}
|
|
220
|
+
|
|
221
|
+
payload: dict[str, Any] = {
|
|
222
|
+
"input": input if input is not None else current_input,
|
|
223
|
+
"schedule": normalize_schedule(schedule) or current_schedule,
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
data = self._request("PUT", f"/agents/schedules/{schedule_id}", json=payload)
|
|
227
|
+
return Schedule.from_response(ScheduleResponse.model_validate(data), client=self)
|
|
228
|
+
|
|
229
|
+
def delete(self, schedule_id: str) -> None:
|
|
230
|
+
"""Delete a schedule.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
schedule_id: The schedule ID to delete
|
|
234
|
+
|
|
235
|
+
Raises:
|
|
236
|
+
NotFoundError: If schedule is not found
|
|
237
|
+
AuthenticationError: If API key is invalid
|
|
238
|
+
APIError: If the API request fails
|
|
239
|
+
"""
|
|
240
|
+
self._request("DELETE", f"/agents/schedules/{schedule_id}")
|
|
241
|
+
|
|
242
|
+
def list_runs(
|
|
243
|
+
self,
|
|
244
|
+
agent_id: str,
|
|
245
|
+
*,
|
|
246
|
+
schedule_id: str | None = None,
|
|
247
|
+
status: RunStatus | None = None,
|
|
248
|
+
limit: int | None = None,
|
|
249
|
+
page: int | None = None,
|
|
250
|
+
) -> ScheduleRunListResult:
|
|
251
|
+
"""List runs for an agent, optionally filtered by schedule ID.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
agent_id: The agent ID to list runs for
|
|
255
|
+
schedule_id: Optional schedule ID to filter by
|
|
256
|
+
status: Optional status filter
|
|
257
|
+
limit: Maximum number of runs to return (1-100)
|
|
258
|
+
page: Page number for pagination
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
ScheduleRunListResult containing runs and pagination metadata
|
|
262
|
+
"""
|
|
263
|
+
params: dict[str, Any] = {"run_type": "schedule"}
|
|
264
|
+
if schedule_id is not None:
|
|
265
|
+
params["schedule_id"] = schedule_id
|
|
266
|
+
if status is not None:
|
|
267
|
+
params["status"] = status
|
|
268
|
+
if limit is not None:
|
|
269
|
+
params["limit"] = limit
|
|
270
|
+
if page is not None:
|
|
271
|
+
params["page"] = page
|
|
272
|
+
|
|
273
|
+
response = self._request_with_envelope(
|
|
274
|
+
"GET",
|
|
275
|
+
f"/agents/{agent_id}/runs",
|
|
276
|
+
params=params,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Parse runs from response data
|
|
280
|
+
runs = [
|
|
281
|
+
ScheduleRun.from_response(ScheduleRunResponse.model_validate(item), client=self)
|
|
282
|
+
for item in (response.get("data") or [])
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
return ScheduleRunListResult(
|
|
286
|
+
items=runs,
|
|
287
|
+
total=response.get("total"),
|
|
288
|
+
page=response.get("page"),
|
|
289
|
+
limit=response.get("limit"),
|
|
290
|
+
has_next=response.get("has_next"),
|
|
291
|
+
has_prev=response.get("has_prev"),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
def get_run_result(self, agent_id: str, run_id: str) -> ScheduleRunResult:
|
|
295
|
+
"""Get the full output payload for an agent run.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
agent_id: The agent ID the run belongs to
|
|
299
|
+
run_id: The run ID to retrieve
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
ScheduleRunResult containing run details and optional output
|
|
303
|
+
"""
|
|
304
|
+
data = self._request("GET", f"/agents/{agent_id}/runs/{run_id}")
|
|
305
|
+
return ScheduleRunResult.model_validate(data)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class AgentScheduleManager:
|
|
309
|
+
"""Facade for agent-scoped schedule operations.
|
|
310
|
+
|
|
311
|
+
Provides a convenient interface for managing schedules through
|
|
312
|
+
an Agent instance, automatically scoping operations to that agent.
|
|
313
|
+
|
|
314
|
+
Example:
|
|
315
|
+
>>> agent = client.get_agent_by_id("agent-id")
|
|
316
|
+
>>> # List schedules for this agent
|
|
317
|
+
>>> schedules = agent.schedule.list()
|
|
318
|
+
>>> # Create a schedule for this agent
|
|
319
|
+
>>> schedule = agent.schedule.create(
|
|
320
|
+
... input="Daily task",
|
|
321
|
+
... schedule="0 9 * * 1-5"
|
|
322
|
+
... )
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
def __init__(self, agent: "Agent", client: ScheduleClient) -> None:
|
|
326
|
+
"""Initialize the schedule manager.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
agent: The agent to manage schedules for
|
|
330
|
+
client: The ScheduleClient for API operations
|
|
331
|
+
"""
|
|
332
|
+
self._agent = agent
|
|
333
|
+
self._client = client
|
|
334
|
+
|
|
335
|
+
def list(
|
|
336
|
+
self,
|
|
337
|
+
*,
|
|
338
|
+
limit: int | None = None,
|
|
339
|
+
page: int | None = None,
|
|
340
|
+
) -> ScheduleListResult:
|
|
341
|
+
"""List schedules for this agent.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
limit: Maximum number of schedules to return (1-100)
|
|
345
|
+
page: Page number for pagination
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
ScheduleListResult containing schedules for this agent
|
|
349
|
+
"""
|
|
350
|
+
return self._client.list(limit=limit, page=page, agent_id=self._agent.id)
|
|
351
|
+
|
|
352
|
+
def create(
|
|
353
|
+
self,
|
|
354
|
+
*,
|
|
355
|
+
input: str,
|
|
356
|
+
schedule: ScheduleConfig | dict[str, str] | str,
|
|
357
|
+
) -> Schedule:
|
|
358
|
+
"""Create a schedule for this agent.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
input: Input text for scheduled execution
|
|
362
|
+
schedule: Schedule configuration (ScheduleConfig, dict, or cron string)
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
Created Schedule instance
|
|
366
|
+
"""
|
|
367
|
+
return self._client.create(
|
|
368
|
+
agent_id=self._agent.id,
|
|
369
|
+
input=input,
|
|
370
|
+
schedule=schedule,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
def get(self, schedule_id: str) -> Schedule:
|
|
374
|
+
"""Get a schedule by ID.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
schedule_id: The schedule ID to retrieve
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Schedule instance
|
|
381
|
+
|
|
382
|
+
Raises:
|
|
383
|
+
NotFoundError: If schedule is not found
|
|
384
|
+
"""
|
|
385
|
+
return self._client.get(schedule_id)
|
|
386
|
+
|
|
387
|
+
def update(
|
|
388
|
+
self,
|
|
389
|
+
schedule_id: str,
|
|
390
|
+
*,
|
|
391
|
+
input: str | None = None,
|
|
392
|
+
schedule: ScheduleConfig | dict[str, str] | str | None = None,
|
|
393
|
+
) -> Schedule:
|
|
394
|
+
"""Update a schedule.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
schedule_id: The schedule ID to update
|
|
398
|
+
input: New input text for scheduled execution
|
|
399
|
+
schedule: New schedule configuration
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
Updated Schedule instance
|
|
403
|
+
"""
|
|
404
|
+
return self._client.update(schedule_id, input=input, schedule=schedule)
|
|
405
|
+
|
|
406
|
+
def delete(self, schedule_id: str) -> None:
|
|
407
|
+
"""Delete a schedule.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
schedule_id: The schedule ID to delete
|
|
411
|
+
"""
|
|
412
|
+
self._client.delete(schedule_id)
|
|
413
|
+
|
|
414
|
+
def list_runs(
|
|
415
|
+
self,
|
|
416
|
+
schedule_id: str | None = None,
|
|
417
|
+
*,
|
|
418
|
+
status: RunStatus | None = None,
|
|
419
|
+
limit: int | None = None,
|
|
420
|
+
page: int | None = None,
|
|
421
|
+
) -> ScheduleRunListResult:
|
|
422
|
+
"""List runs for this agent.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
schedule_id: Optional schedule ID to filter by
|
|
426
|
+
status: Optional status filter
|
|
427
|
+
limit: Maximum number of runs to return (1-100)
|
|
428
|
+
page: Page number for pagination
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
ScheduleRunListResult containing runs for this agent
|
|
432
|
+
"""
|
|
433
|
+
return self._client.list_runs(
|
|
434
|
+
self._agent.id,
|
|
435
|
+
schedule_id=schedule_id,
|
|
436
|
+
status=status,
|
|
437
|
+
limit=limit,
|
|
438
|
+
page=page,
|
|
439
|
+
)
|
glaip_sdk/client/tools.py
CHANGED
|
@@ -103,6 +103,9 @@ class ToolClient(BaseClient):
|
|
|
103
103
|
def _prepare_upload_data(self, name: str, framework: str, description: str | None = None, **kwargs) -> dict:
|
|
104
104
|
"""Prepare upload data dictionary.
|
|
105
105
|
|
|
106
|
+
Uses the same payload building logic as _build_create_payload to ensure
|
|
107
|
+
consistency between upload and metadata-only tool creation.
|
|
108
|
+
|
|
106
109
|
Args:
|
|
107
110
|
name: Tool name
|
|
108
111
|
framework: Tool framework
|
|
@@ -112,28 +115,19 @@ class ToolClient(BaseClient):
|
|
|
112
115
|
Returns:
|
|
113
116
|
dict: Upload data dictionary
|
|
114
117
|
"""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
"framework": framework,
|
|
118
|
-
"type": kwargs.pop("tool_type", DEFAULT_TOOL_TYPE), # Default to custom
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if description:
|
|
122
|
-
data["description"] = description
|
|
123
|
-
|
|
124
|
-
# Handle tags if provided in kwargs
|
|
125
|
-
if kwargs.get("tags"):
|
|
126
|
-
if isinstance(kwargs["tags"], list):
|
|
127
|
-
data["tags"] = ",".join(kwargs["tags"])
|
|
128
|
-
else:
|
|
129
|
-
data["tags"] = kwargs["tags"]
|
|
118
|
+
# Extract tool_type from kwargs if present, defaulting to DEFAULT_TOOL_TYPE
|
|
119
|
+
tool_type = kwargs.pop("tool_type", DEFAULT_TOOL_TYPE)
|
|
130
120
|
|
|
131
|
-
#
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
121
|
+
# Use _build_create_payload to build the payload consistently
|
|
122
|
+
payload = self._build_create_payload(
|
|
123
|
+
name=name,
|
|
124
|
+
description=description,
|
|
125
|
+
framework=framework,
|
|
126
|
+
tool_type=tool_type,
|
|
127
|
+
**kwargs,
|
|
128
|
+
)
|
|
135
129
|
|
|
136
|
-
return
|
|
130
|
+
return payload
|
|
137
131
|
|
|
138
132
|
def _upload_tool_file(self, file_path: str, upload_data: dict) -> Tool:
|
|
139
133
|
"""Upload tool file to server.
|
|
@@ -439,9 +433,44 @@ class ToolClient(BaseClient):
|
|
|
439
433
|
except OSError:
|
|
440
434
|
pass # Ignore cleanup errors
|
|
441
435
|
|
|
442
|
-
def update_tool(self, tool_id: str, **kwargs) -> Tool:
|
|
443
|
-
"""Update an existing tool.
|
|
444
|
-
|
|
436
|
+
def update_tool(self, tool_id: str | Tool, **kwargs) -> Tool:
|
|
437
|
+
"""Update an existing tool.
|
|
438
|
+
|
|
439
|
+
Notes:
|
|
440
|
+
- Payload construction is centralized via ``_build_update_payload`` to keep metadata
|
|
441
|
+
update and upload update flows consistent.
|
|
442
|
+
- Accepts either a tool ID or a ``Tool`` instance (avoids an extra fetch when callers
|
|
443
|
+
already have the current tool).
|
|
444
|
+
"""
|
|
445
|
+
# Backward-compatible: allow passing a Tool instance to avoid an extra fetch.
|
|
446
|
+
if isinstance(tool_id, Tool):
|
|
447
|
+
current_tool = tool_id
|
|
448
|
+
if not current_tool.id:
|
|
449
|
+
raise ValueError("Tool instance has no id; cannot update.")
|
|
450
|
+
tool_id_value = str(current_tool.id)
|
|
451
|
+
else:
|
|
452
|
+
current_tool = None
|
|
453
|
+
tool_id_value = tool_id
|
|
454
|
+
|
|
455
|
+
if not kwargs:
|
|
456
|
+
data = self._request("PUT", f"{TOOLS_ENDPOINT}{tool_id_value}", json={})
|
|
457
|
+
response = ToolResponse(**data)
|
|
458
|
+
return Tool.from_response(response, client=self)
|
|
459
|
+
|
|
460
|
+
if current_tool is None:
|
|
461
|
+
current_tool = self.get_tool_by_id(tool_id_value)
|
|
462
|
+
|
|
463
|
+
payload_kwargs = kwargs.copy()
|
|
464
|
+
name = payload_kwargs.pop("name", None)
|
|
465
|
+
description = payload_kwargs.pop("description", None)
|
|
466
|
+
update_payload = self._build_update_payload(
|
|
467
|
+
current_tool=current_tool,
|
|
468
|
+
name=name,
|
|
469
|
+
description=description,
|
|
470
|
+
**payload_kwargs,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
data = self._request("PUT", f"{TOOLS_ENDPOINT}{tool_id_value}", json=update_payload)
|
|
445
474
|
response = ToolResponse(**data)
|
|
446
475
|
return Tool.from_response(response, client=self)
|
|
447
476
|
|
|
@@ -562,12 +591,14 @@ class ToolClient(BaseClient):
|
|
|
562
591
|
) -> Tool:
|
|
563
592
|
"""Find tool by name and update, or create if not found."""
|
|
564
593
|
existing = self.find_tools(name)
|
|
594
|
+
name_lower = name.lower()
|
|
595
|
+
exact_matches = [tool for tool in existing if tool.name and tool.name.lower() == name_lower]
|
|
565
596
|
|
|
566
|
-
if len(
|
|
597
|
+
if len(exact_matches) == 1:
|
|
567
598
|
logger.info("Updating existing tool: %s", name)
|
|
568
|
-
return self._do_tool_upsert_update(
|
|
599
|
+
return self._do_tool_upsert_update(exact_matches[0].id, name, code, description, framework, **kwargs)
|
|
569
600
|
|
|
570
|
-
if len(
|
|
601
|
+
if len(exact_matches) > 1:
|
|
571
602
|
raise ValueError(f"Multiple tools found with name '{name}'")
|
|
572
603
|
|
|
573
604
|
# Create new tool - code is required
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Guardrails package for content filtering and safety checks.
|
|
2
|
+
|
|
3
|
+
This package provides modular guardrail engines and managers for filtering
|
|
4
|
+
harmful content in AI agent interactions. All components support lazy loading
|
|
5
|
+
from aip-agents to maintain Principle VII compliance.
|
|
6
|
+
|
|
7
|
+
Authors:
|
|
8
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from enum import StrEnum
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from aip_agents.guardrails.engines.nemo import NemoGuardrailEngine
|
|
16
|
+
from aip_agents.guardrails.engines.phrase_matcher import PhraseMatcherEngine
|
|
17
|
+
from aip_agents.guardrails.manager import GuardrailManager
|
|
18
|
+
from aip_agents.guardrails.schemas import GuardrailMode
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ImportableName(StrEnum):
|
|
22
|
+
"""Names of the importable attributes."""
|
|
23
|
+
|
|
24
|
+
GUARDRAIL_MANAGER = "GuardrailManager"
|
|
25
|
+
PHRASE_MATCHER_ENGINE = "PhraseMatcherEngine"
|
|
26
|
+
NEMO_GUARDRAIL_ENGINE = "NemoGuardrailEngine"
|
|
27
|
+
GUARDRAIL_MODE = "GuardrailMode"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Lazy loading support - components are only imported when actually used
|
|
31
|
+
_LAZY_IMPORTS = {}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def __getattr__(name: str) -> Any:
|
|
35
|
+
"""Lazy import to avoid eager loading of optional aip-agents dependency.
|
|
36
|
+
|
|
37
|
+
This function is called by Python when an attribute is not found in the module.
|
|
38
|
+
It performs the import from aip_agents.guardrails at runtime.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
name: The name of the attribute to get.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
The attribute value from aip_agents.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
AttributeError: If the attribute doesn't exist.
|
|
48
|
+
ImportError: If aip-agents is not installed but a component is accessed.
|
|
49
|
+
"""
|
|
50
|
+
if name in _LAZY_IMPORTS:
|
|
51
|
+
return _LAZY_IMPORTS[name]
|
|
52
|
+
|
|
53
|
+
if name == ImportableName.GUARDRAIL_MANAGER:
|
|
54
|
+
from aip_agents.guardrails.manager import GuardrailManager # noqa: PLC0415
|
|
55
|
+
|
|
56
|
+
_LAZY_IMPORTS[name] = GuardrailManager
|
|
57
|
+
return GuardrailManager
|
|
58
|
+
|
|
59
|
+
if name == ImportableName.PHRASE_MATCHER_ENGINE:
|
|
60
|
+
from aip_agents.guardrails.engines.phrase_matcher import ( # noqa: PLC0415
|
|
61
|
+
PhraseMatcherEngine,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
_LAZY_IMPORTS[name] = PhraseMatcherEngine
|
|
65
|
+
return PhraseMatcherEngine
|
|
66
|
+
|
|
67
|
+
if name == ImportableName.NEMO_GUARDRAIL_ENGINE:
|
|
68
|
+
from aip_agents.guardrails.engines.nemo import NemoGuardrailEngine # noqa: PLC0415
|
|
69
|
+
|
|
70
|
+
_LAZY_IMPORTS[name] = NemoGuardrailEngine
|
|
71
|
+
return NemoGuardrailEngine
|
|
72
|
+
|
|
73
|
+
if name == ImportableName.GUARDRAIL_MODE:
|
|
74
|
+
from aip_agents.guardrails.schemas import GuardrailMode # noqa: PLC0415
|
|
75
|
+
|
|
76
|
+
_LAZY_IMPORTS[name] = GuardrailMode
|
|
77
|
+
return GuardrailMode
|
|
78
|
+
|
|
79
|
+
msg = f"module {__name__!r} has no attribute {name!r}"
|
|
80
|
+
raise AttributeError(msg)
|