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.
Files changed (43) hide show
  1. velophex/__init__.py +38 -0
  2. velophex/_json.py +297 -0
  3. velophex/agents/__init__.py +130 -0
  4. velophex/agents/_errors.py +75 -0
  5. velophex/agents/_loop.py +436 -0
  6. velophex/agents/_tools.py +168 -0
  7. velophex/agents/_tracing.py +87 -0
  8. velophex/agents/testing.py +249 -0
  9. velophex/cli/__init__.py +5 -0
  10. velophex/cli/main.py +548 -0
  11. velophex/cli/publish.py +152 -0
  12. velophex/cli/sync.py +90 -0
  13. velophex/cli/templates.py +126 -0
  14. velophex/contracts/__init__.py +103 -0
  15. velophex/contracts/_model.py +26 -0
  16. velophex/contracts/agents.py +110 -0
  17. velophex/contracts/diagnostics.py +157 -0
  18. velophex/contracts/executor.py +1921 -0
  19. velophex/contracts/package.py +391 -0
  20. velophex/contracts/project.py +1008 -0
  21. velophex/contracts/runtimes.py +226 -0
  22. velophex/contracts/signin.py +53 -0
  23. velophex/contracts/validation.py +158 -0
  24. velophex/entrypoints.py +329 -0
  25. velophex/errors.py +44 -0
  26. velophex/packaging.py +465 -0
  27. velophex/py.typed +0 -0
  28. velophex/runtime/__init__.py +471 -0
  29. velophex/runtime/_session.py +192 -0
  30. velophex/runtime/client.py +957 -0
  31. velophex/runtime/models.py +326 -0
  32. velophex/signing.py +425 -0
  33. velophex/worker/__init__.py +27 -0
  34. velophex/worker/__main__.py +118 -0
  35. velophex/worker/framing.py +248 -0
  36. velophex/worker/loader.py +105 -0
  37. velophex/worker/outbox.py +147 -0
  38. velophex/worker/session.py +519 -0
  39. velophex-0.2.0.dist-info/METADATA +313 -0
  40. velophex-0.2.0.dist-info/RECORD +43 -0
  41. velophex-0.2.0.dist-info/WHEEL +4 -0
  42. velophex-0.2.0.dist-info/entry_points.txt +2 -0
  43. 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)