velophex 0.2.0__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.
- velophex/__init__.py +38 -0
- velophex/_json.py +297 -0
- velophex/agents/__init__.py +130 -0
- velophex/agents/_errors.py +75 -0
- velophex/agents/_loop.py +436 -0
- velophex/agents/_tools.py +168 -0
- velophex/agents/_tracing.py +87 -0
- velophex/agents/testing.py +249 -0
- velophex/cli/__init__.py +5 -0
- velophex/cli/main.py +548 -0
- velophex/cli/publish.py +152 -0
- velophex/cli/sync.py +90 -0
- velophex/cli/templates.py +126 -0
- velophex/contracts/__init__.py +103 -0
- velophex/contracts/_model.py +26 -0
- velophex/contracts/agents.py +110 -0
- velophex/contracts/diagnostics.py +157 -0
- velophex/contracts/executor.py +1921 -0
- velophex/contracts/package.py +391 -0
- velophex/contracts/project.py +1008 -0
- velophex/contracts/runtimes.py +226 -0
- velophex/contracts/signin.py +53 -0
- velophex/contracts/validation.py +158 -0
- velophex/entrypoints.py +329 -0
- velophex/errors.py +44 -0
- velophex/packaging.py +465 -0
- velophex/py.typed +0 -0
- velophex/runtime/__init__.py +471 -0
- velophex/runtime/_session.py +192 -0
- velophex/runtime/client.py +957 -0
- velophex/runtime/models.py +326 -0
- velophex/signing.py +425 -0
- velophex/worker/__init__.py +27 -0
- velophex/worker/__main__.py +118 -0
- velophex/worker/framing.py +248 -0
- velophex/worker/loader.py +105 -0
- velophex/worker/outbox.py +147 -0
- velophex/worker/session.py +519 -0
- velophex-0.2.0.dist-info/METADATA +313 -0
- velophex-0.2.0.dist-info/RECORD +43 -0
- velophex-0.2.0.dist-info/WHEEL +4 -0
- velophex-0.2.0.dist-info/entry_points.txt +2 -0
- velophex-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""Responses and requests of the Orchestrator Runtime API (``/api/v1/runtime/*``).
|
|
2
|
+
|
|
3
|
+
Mirrors ``VeloPhex.Platform.Contracts.Runtime.RuntimeApiContracts`` and ``StorageContracts`` and
|
|
4
|
+
the queue and asset contracts in ``VeloPhex.Orchestrator.Contracts``. Unknown members are kept, so a newer server
|
|
5
|
+
never breaks an older automation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Any, Final
|
|
12
|
+
from uuid import UUID
|
|
13
|
+
|
|
14
|
+
from pydantic import ConfigDict, Field, SecretStr
|
|
15
|
+
|
|
16
|
+
from velophex.contracts._model import ContractModel
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"STORAGE_BUCKETS_CAPABILITY",
|
|
20
|
+
"STORAGE_ERROR_CODES",
|
|
21
|
+
"ActionAppSchema",
|
|
22
|
+
"ActionTaskCompleter",
|
|
23
|
+
"ActionTaskResult",
|
|
24
|
+
"AddQueueItemRequest",
|
|
25
|
+
"CancellationState",
|
|
26
|
+
"QueueItem",
|
|
27
|
+
"ResolvedSecret",
|
|
28
|
+
"RuntimeActionTask",
|
|
29
|
+
"RuntimeAsset",
|
|
30
|
+
"RuntimeContext",
|
|
31
|
+
"RuntimeCredential",
|
|
32
|
+
"RuntimeQueue",
|
|
33
|
+
"StartedJob",
|
|
34
|
+
"StorageFile",
|
|
35
|
+
"StorageFileList",
|
|
36
|
+
"StorageLink",
|
|
37
|
+
"StorageLinkRequest",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class RuntimeContext(ContractModel):
|
|
42
|
+
"""What the Orchestrator knows about the attempt this process is running."""
|
|
43
|
+
|
|
44
|
+
job_id: UUID = Field(alias="jobId")
|
|
45
|
+
attempt_id: UUID = Field(alias="attemptId")
|
|
46
|
+
attempt_number: int = Field(alias="attemptNumber")
|
|
47
|
+
tenant_id: UUID = Field(alias="tenantId")
|
|
48
|
+
workspace_id: UUID = Field(alias="workspaceId")
|
|
49
|
+
automation_id: UUID = Field(alias="automationId")
|
|
50
|
+
automation_name: str = Field(alias="automationName")
|
|
51
|
+
entry_point_id: str = Field(alias="entryPointId")
|
|
52
|
+
inputs: Any | None = None
|
|
53
|
+
execution_deadline: datetime = Field(alias="executionDeadline")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CancellationState(ContractModel):
|
|
57
|
+
"""Whether a stop was requested for this attempt, and why."""
|
|
58
|
+
|
|
59
|
+
cancel_requested: bool = Field(alias="cancelRequested")
|
|
60
|
+
reason: str | None = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
#: Settings for a model that carries a decrypted secret.
|
|
64
|
+
#:
|
|
65
|
+
#: * ``hide_input_in_errors``: a :class:`pydantic.ValidationError` otherwise prints the whole
|
|
66
|
+
#: input document - every sibling member, the secret included - in its message, and that
|
|
67
|
+
#: message is exactly what ends up in a log or a fault.
|
|
68
|
+
#: * ``extra="ignore"``: unlike other contract models, an unknown member is *dropped*. A newer
|
|
69
|
+
#: server may add a confidential member this SDK does not know to wrap, and a plain ``str``
|
|
70
|
+
#: in the extras would print in every ``repr``.
|
|
71
|
+
_SECRET_BEARING = ConfigDict(hide_input_in_errors=True, extra="ignore")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class RuntimeAsset(ContractModel):
|
|
75
|
+
"""An asset of the job's workspace, with the secret or password already decrypted.
|
|
76
|
+
|
|
77
|
+
``secret`` and ``password`` are :class:`pydantic.SecretStr`: ``repr``, ``str`` and
|
|
78
|
+
``model_dump`` show ``**********``. Read the plain text deliberately, with
|
|
79
|
+
``asset.secret.get_secret_value()`` or :meth:`value`.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
model_config = _SECRET_BEARING
|
|
83
|
+
|
|
84
|
+
name: str
|
|
85
|
+
type: str
|
|
86
|
+
text: str | None = None
|
|
87
|
+
integer: int | None = None
|
|
88
|
+
boolean: bool | None = None
|
|
89
|
+
secret: SecretStr | None = None
|
|
90
|
+
user_name: str | None = Field(default=None, alias="userName")
|
|
91
|
+
password: SecretStr | None = None
|
|
92
|
+
|
|
93
|
+
def value(self) -> Any:
|
|
94
|
+
"""The one member the asset's type carries, as plain text for a secret or credential.
|
|
95
|
+
|
|
96
|
+
This is the explicit way to read a confidential value; keep the result out of logs.
|
|
97
|
+
"""
|
|
98
|
+
return {
|
|
99
|
+
"Text": self.text,
|
|
100
|
+
"Integer": self.integer,
|
|
101
|
+
"Boolean": self.boolean,
|
|
102
|
+
"Secret": _reveal(self.secret),
|
|
103
|
+
"Credential": _reveal(self.password),
|
|
104
|
+
}.get(self.type)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ResolvedSecret(ContractModel):
|
|
108
|
+
"""One confidential value: a secret asset, or a credential's password.
|
|
109
|
+
|
|
110
|
+
``value`` is a :class:`pydantic.SecretStr`, so the model can be printed, logged or put in
|
|
111
|
+
an exception without the secret; :meth:`get_secret_value` returns the plain text.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
model_config = _SECRET_BEARING
|
|
115
|
+
|
|
116
|
+
name: str
|
|
117
|
+
value: SecretStr
|
|
118
|
+
user_name: str | None = Field(default=None, alias="userName")
|
|
119
|
+
|
|
120
|
+
def get_secret_value(self) -> str:
|
|
121
|
+
"""The plain text of the secret. Keep it out of logs, outputs and exceptions."""
|
|
122
|
+
return self.value.get_secret_value()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _reveal(value: SecretStr | None) -> str | None:
|
|
126
|
+
return value.get_secret_value() if value is not None else None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class RuntimeCredential(ContractModel):
|
|
130
|
+
"""A credential asset without its password."""
|
|
131
|
+
|
|
132
|
+
name: str
|
|
133
|
+
user_name: str = Field(alias="userName")
|
|
134
|
+
has_password: bool = Field(alias="hasPassword")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class RuntimeQueue(ContractModel):
|
|
138
|
+
"""Queue metadata by name, inside the job's workspace."""
|
|
139
|
+
|
|
140
|
+
id: UUID
|
|
141
|
+
number: int
|
|
142
|
+
name: str
|
|
143
|
+
description: str | None = None
|
|
144
|
+
max_retries: int = Field(alias="maxRetries")
|
|
145
|
+
enforce_unique_reference: bool = Field(alias="enforceUniqueReference")
|
|
146
|
+
is_active: bool = Field(alias="isActive")
|
|
147
|
+
sla_minutes: int | None = Field(default=None, alias="slaMinutes")
|
|
148
|
+
risk_sla_minutes: int | None = Field(default=None, alias="riskSlaMinutes")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class AddQueueItemRequest(ContractModel):
|
|
152
|
+
"""A new queue item."""
|
|
153
|
+
|
|
154
|
+
reference: str | None = None
|
|
155
|
+
priority: int | None = None
|
|
156
|
+
payload: Any | None = None
|
|
157
|
+
deadline: datetime | None = None
|
|
158
|
+
defer_until: datetime | None = Field(default=None, alias="deferUntil")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class QueueItem(ContractModel):
|
|
162
|
+
"""A queue item as the Orchestrator reports it.
|
|
163
|
+
|
|
164
|
+
Only the members an automation acts on are typed; everything else survives in the model's
|
|
165
|
+
extras, because the queue projection grows more often than the SDK ships.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
id: UUID
|
|
169
|
+
number: int
|
|
170
|
+
queue_id: UUID = Field(alias="queueId")
|
|
171
|
+
reference: str | None = None
|
|
172
|
+
status: str
|
|
173
|
+
priority: int
|
|
174
|
+
payload: Any | None = None
|
|
175
|
+
output: Any | None = None
|
|
176
|
+
error_kind: str | None = Field(default=None, alias="errorKind")
|
|
177
|
+
error_reason: str | None = Field(default=None, alias="errorReason")
|
|
178
|
+
retry_count: int = Field(alias="retryCount")
|
|
179
|
+
deadline: datetime | None = None
|
|
180
|
+
defer_until: datetime | None = Field(default=None, alias="deferUntil")
|
|
181
|
+
progress: str | None = None
|
|
182
|
+
lease_expires_at: datetime | None = Field(default=None, alias="leaseExpiresAt")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class StartedJob(ContractModel):
|
|
186
|
+
"""A child job started from a running automation."""
|
|
187
|
+
|
|
188
|
+
job_id: UUID = Field(alias="jobId")
|
|
189
|
+
job_number: int = Field(alias="jobNumber")
|
|
190
|
+
automation_id: UUID = Field(alias="automationId")
|
|
191
|
+
automation_name: str = Field(alias="automationName")
|
|
192
|
+
status: str
|
|
193
|
+
chain_depth: int = Field(alias="chainDepth")
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class RuntimeJob(ContractModel):
|
|
197
|
+
"""A job of the running automation's workspace (``GET /runtime/jobs``)."""
|
|
198
|
+
|
|
199
|
+
job_id: UUID = Field(alias="jobId")
|
|
200
|
+
job_number: int = Field(alias="jobNumber")
|
|
201
|
+
automation_id: UUID | None = Field(default=None, alias="automationId")
|
|
202
|
+
automation_name: str = Field(alias="automationName")
|
|
203
|
+
status: str
|
|
204
|
+
priority: int
|
|
205
|
+
created_at: datetime = Field(alias="createdAt")
|
|
206
|
+
started_at: datetime | None = Field(default=None, alias="startedAt")
|
|
207
|
+
finished_at: datetime | None = Field(default=None, alias="finishedAt")
|
|
208
|
+
parent_job_id: UUID | None = Field(default=None, alias="parentJobId")
|
|
209
|
+
fault_message: str | None = Field(default=None, alias="faultMessage")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class ActionTaskCompleter(ContractModel):
|
|
213
|
+
"""Who completed an action task."""
|
|
214
|
+
|
|
215
|
+
user_id: UUID = Field(alias="userId")
|
|
216
|
+
name: str | None = None
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class RuntimeActionTask(ContractModel):
|
|
220
|
+
"""An action task as this job reads it (``ActionTaskState``).
|
|
221
|
+
|
|
222
|
+
``status`` is ``unassigned``, ``in_progress``, ``completed`` or ``canceled``; ``outcome`` is
|
|
223
|
+
the action the completer chose and ``output`` their answer, both ``None`` until completed.
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
task_id: UUID = Field(alias="taskId")
|
|
227
|
+
app_key: str = Field(alias="appKey")
|
|
228
|
+
version: int
|
|
229
|
+
title: str
|
|
230
|
+
status: str
|
|
231
|
+
outcome: str | None = None
|
|
232
|
+
output: Any | None = None
|
|
233
|
+
completed_by: ActionTaskCompleter | None = Field(default=None, alias="completedBy")
|
|
234
|
+
completed_at: datetime | None = Field(default=None, alias="completedAt")
|
|
235
|
+
late: bool = False
|
|
236
|
+
automation_gone: bool = Field(default=False, alias="automationGone")
|
|
237
|
+
cancel_reason: str | None = Field(default=None, alias="cancelReason")
|
|
238
|
+
created_at: datetime | None = Field(default=None, alias="createdAt")
|
|
239
|
+
due_at: datetime | None = Field(default=None, alias="dueAt")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class ActionTaskResult(ContractModel):
|
|
243
|
+
"""What waiting for an action task ends with (``ActionTaskWaitResult``).
|
|
244
|
+
|
|
245
|
+
``status`` is ``completed``, ``canceled`` or ``timedOut``; on ``timedOut`` the task stays open.
|
|
246
|
+
``output`` may be ``None`` for an action that does not require a valid form.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
task_id: UUID = Field(alias="taskId")
|
|
250
|
+
status: str
|
|
251
|
+
outcome: str | None = None
|
|
252
|
+
output: Any | None = None
|
|
253
|
+
completed_by: ActionTaskCompleter | None = Field(default=None, alias="completedBy")
|
|
254
|
+
completed_at: datetime | None = Field(default=None, alias="completedAt")
|
|
255
|
+
late: bool = False
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class ActionAppSchema(ContractModel):
|
|
259
|
+
"""The input and output JSON Schema of a published action app version."""
|
|
260
|
+
|
|
261
|
+
app_key: str = Field(alias="appKey")
|
|
262
|
+
version: int
|
|
263
|
+
name: str
|
|
264
|
+
input_schema: dict[str, Any] = Field(alias="inputSchema")
|
|
265
|
+
output_schema: dict[str, Any] = Field(alias="outputSchema")
|
|
266
|
+
actions: list[dict[str, Any]] = Field(default_factory=list)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# ---------------------------------------------------------------------------------------------
|
|
270
|
+
# Storage buckets (contracts 0.8.35, schemas/runtime-storage.schema.json)
|
|
271
|
+
# ---------------------------------------------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
#: The runtime capability of the storage routes (``RuntimeCapabilities.StorageBuckets``).
|
|
274
|
+
STORAGE_BUCKETS_CAPABILITY: Final = "storage-buckets/1"
|
|
275
|
+
|
|
276
|
+
#: The problem ``code`` values of the storage routes (``StorageErrorCodes``), in contract order.
|
|
277
|
+
STORAGE_ERROR_CODES: Final = (
|
|
278
|
+
"storage_bucket_not_found",
|
|
279
|
+
"storage_file_not_found",
|
|
280
|
+
"storage_bucket_read_only",
|
|
281
|
+
"storage_file_too_large",
|
|
282
|
+
"storage_quota_exceeded",
|
|
283
|
+
"storage_connection_failed",
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class StorageFile(ContractModel):
|
|
288
|
+
"""One file of a storage bucket (``RuntimeStorageFile``).
|
|
289
|
+
|
|
290
|
+
``path`` is ``/``-separated with no leading slash; ``sha256`` and ``version`` are set where the
|
|
291
|
+
bucket knows them (internal buckets keep versions).
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
path: str
|
|
295
|
+
name: str
|
|
296
|
+
size_bytes: int = Field(alias="sizeBytes")
|
|
297
|
+
content_type: str = Field(alias="contentType")
|
|
298
|
+
last_modified: datetime = Field(alias="lastModified")
|
|
299
|
+
sha256: str | None = None
|
|
300
|
+
version: int | None = None
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class StorageFileList(ContractModel):
|
|
304
|
+
"""A page of a bucket listing (``RuntimeStorageFileList``); ``folders`` on the first page only."""
|
|
305
|
+
|
|
306
|
+
files: list[StorageFile] = Field(default_factory=list)
|
|
307
|
+
folders: list[str] = Field(default_factory=list)
|
|
308
|
+
next_cursor: str | None = Field(default=None, alias="nextCursor")
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class StorageLinkRequest(ContractModel):
|
|
312
|
+
"""Asks for a short-lived URL for one file (``CreateRuntimeStorageLinkRequest``)."""
|
|
313
|
+
|
|
314
|
+
path: str
|
|
315
|
+
verb: str
|
|
316
|
+
expires_in_seconds: int | None = Field(default=None, alias="expiresInSeconds")
|
|
317
|
+
content_type: str | None = Field(default=None, alias="contentType")
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class StorageLink(ContractModel):
|
|
321
|
+
"""A short-lived URL for one file (``RuntimeStorageLinkResponse``); send ``headers`` with it."""
|
|
322
|
+
|
|
323
|
+
url: str
|
|
324
|
+
verb: str
|
|
325
|
+
expires_at: datetime = Field(alias="expiresAt")
|
|
326
|
+
headers: dict[str, str] = Field(default_factory=dict)
|