@oddington/sdk 0.1.41 → 0.1.43
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.
- package/README.md +47 -9
- package/dist/browser.js +187 -40
- package/dist/index.js +17 -4
- package/dist/types/packages/sdk/src/data-schema-types.d.ts +2 -2
- package/dist/types/packages/sdk/src/public.d.ts +5 -0
- package/dist/types/packages/sdk/src/routine-definition-types.d.ts +7 -0
- package/dist/types/shared/data-contract.d.ts +1 -1
- package/dist/types/shared/files-contract.d.ts +16 -2
- package/dist/types/shared/routine-ai-contract.d.ts +6 -4
- package/dist/types/shared/routine-ai-error-detail.d.ts +19 -0
- package/dist/types/shared/routine-ai-output-detail.d.ts +2 -0
- package/dist/types/shared/routine-deployment-contract.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,11 @@ declared `review` handler crosses the Inbox publication seam as `review`; Inbox
|
|
|
86
86
|
retains the canonical `invoice-import` routine identity separately, without
|
|
87
87
|
deploying or executing that handler.
|
|
88
88
|
|
|
89
|
+
Long customer computation inside a remote step can report advancing work with
|
|
90
|
+
`await os.run.progress({ completed, total })`. Report progress after meaningful
|
|
91
|
+
batches of work rather than after every individual item. OS capability calls and
|
|
92
|
+
step transitions already count as activity automatically.
|
|
93
|
+
|
|
89
94
|
Microsoft Connection configuration has a separate, optional `access` description.
|
|
90
95
|
It preserves supplied strings, including existing `read` and `read-write` labels,
|
|
91
96
|
and does not grant or restrict Microsoft permissions. Management results omit
|
|
@@ -98,6 +103,13 @@ With `access: { ai: true }`, `os.ai.generateText` returns non-empty `{ text }`.
|
|
|
98
103
|
with code `invalid_output`, a clear bounded `detail`, and the original final
|
|
99
104
|
answer as optional `text` for routine-owned recovery. The text is bounded at
|
|
100
105
|
256 KiB and excluded from the error message and ordinary JSON serialization.
|
|
106
|
+
AI failures have plain-language messages and keep their structured `code`.
|
|
107
|
+
Optional `detail` explains a known runner limit, exit, response failure or request
|
|
108
|
+
validation rule. Use these facts rather than parsing messages. A code-only legacy
|
|
109
|
+
failure does not establish a more specific cause or justify a retry. Local Run
|
|
110
|
+
activity and AI inspection preserve these diagnostics even when the routine
|
|
111
|
+
catches the error and continues; no provider logs or private answer text are
|
|
112
|
+
included in the ordinary activity summary.
|
|
101
113
|
The routine can inspect it explicitly, apply its own recovery rules, report
|
|
102
114
|
an item failure and continue; it must not cast a rejected answer to the promised
|
|
103
115
|
type. OS does not coerce, drop fields, repair or retry automatically.
|
|
@@ -107,6 +119,29 @@ type. OS does not coerce, drop fields, repair or retry automatically.
|
|
|
107
119
|
extract JSON from explanatory prose or repair the answer. `RoutineAIError`
|
|
108
120
|
remains a compatibility alias of `AIError`, so existing `instanceof` checks work.
|
|
109
121
|
|
|
122
|
+
For a File that needs local tool-assisted inspection, explicitly enable tools
|
|
123
|
+
on that call:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const { object } = await os.ai.generateObject({
|
|
127
|
+
input: document,
|
|
128
|
+
instructions: 'Read this document and report only grounded facts.',
|
|
129
|
+
schema: documentFacts,
|
|
130
|
+
tools: true,
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Omitting `tools` or setting it to `false` keeps the existing tool-free behavior.
|
|
135
|
+
OS supplies and sandboxes the tools; routines do not choose CLI flags or shell
|
|
136
|
+
commands. This first version requires a File input and local macOS execution
|
|
137
|
+
with Claude Code 2.1.285 or newer. Unsupported hosts reject with
|
|
138
|
+
`AIError('capability_unavailable')` rather than silently ignoring the option.
|
|
139
|
+
The original stays read-only, temporary processing is disposable, and tool
|
|
140
|
+
network access is blocked. A tool-assisted answer still has to satisfy the
|
|
141
|
+
requested schema and the routine's own completeness checks.
|
|
142
|
+
Tool-enabled object generation uses the runner's structured final submission;
|
|
143
|
+
OS validates that value without extracting JSON from surrounding commentary.
|
|
144
|
+
|
|
110
145
|
Preserve safe AI error facts when reporting a per-document failure. Keep the
|
|
111
146
|
original answer (`error.text`) out of step data, business records and logs:
|
|
112
147
|
|
|
@@ -244,7 +279,7 @@ os.init({ auth }); // OS-owned startup, once before app modules run.
|
|
|
244
279
|
// App code uses the installed capabilities directly.
|
|
245
280
|
const general = data.repository('general');
|
|
246
281
|
try {
|
|
247
|
-
const selected = await general.records.
|
|
282
|
+
const selected = await general.records.query({ invoices: { $: { limit: 20 } } });
|
|
248
283
|
const found = await general.records.search('acme overdue', {
|
|
249
284
|
collections: ['invoices'], limit: 20,
|
|
250
285
|
});
|
|
@@ -473,7 +508,10 @@ and numeric byte counts; its message explains the rejected field or expected
|
|
|
473
508
|
versus received size. Streaming rejection marks partial received counts with
|
|
474
509
|
`receivedAtLeast: true`. These facts survive local Run summaries and saved
|
|
475
510
|
failure evidence without raw names, keys, file content or provider text. Older
|
|
476
|
-
errors without `detail` remain valid.
|
|
511
|
+
errors without `detail` remain valid. Messages use the known reason in plain
|
|
512
|
+
language; use `code`, `status`, `retryable` and `detail` for programmatic decisions
|
|
513
|
+
rather than parsing the message. Replacement errors explain that `replace`
|
|
514
|
+
expects the current enrichment's SHA-256 value. Reads return the successful Web
|
|
477
515
|
Response without consuming its body. Local transfer is bounded and buffered
|
|
478
516
|
before constructing that Response. Current workspace members can use every File in that workspace. A
|
|
479
517
|
routine's `files: "read"` permits get/read; `files: "read-write"` also permits
|
|
@@ -581,12 +619,12 @@ import { data, DataOperationError } from '@oddington/sdk';
|
|
|
581
619
|
const general = data.repository('general');
|
|
582
620
|
const collections = await general.collections.list();
|
|
583
621
|
const schema = await general.collections.schema('invoices');
|
|
584
|
-
const first = await general.records.
|
|
622
|
+
const first = await general.records.query({
|
|
585
623
|
invoices: { $: { limit: 50 }, supplier: { $: { fields: ['name'] } } },
|
|
586
624
|
suppliers: {},
|
|
587
625
|
});
|
|
588
626
|
const next = first.$meta.collections.invoices.next;
|
|
589
|
-
if (next) await general.records.
|
|
627
|
+
if (next) await general.records.query({
|
|
590
628
|
invoices: { $: { limit: 50, after: next }, supplier: { $: { fields: ['name'] } } },
|
|
591
629
|
});
|
|
592
630
|
```
|
|
@@ -623,7 +661,7 @@ unedited first-release generated output and preserves every other alias; edited
|
|
|
623
661
|
or malformed generated files remain protected.
|
|
624
662
|
|
|
625
663
|
Known repository literals then constrain collection/field/link names and query
|
|
626
|
-
operands and infer projected `
|
|
664
|
+
operands and infer projected `query`, `pages` and `all` values from normalized
|
|
627
665
|
schemas. Multiple aliases share the root declaration. Refresh explicitly with
|
|
628
666
|
the same CLI command after publishing schema changes; app builds and launches
|
|
629
667
|
never refresh it. Missing metadata and dynamic repository strings retain generic
|
|
@@ -651,7 +689,7 @@ Both helpers preserve literal collection arrays and selected-link inference.
|
|
|
651
689
|
prefetches after a consumer stops. Independent collection cursors pin the first
|
|
652
690
|
snapshot. Completed collections yield empty arrays on later pages and retain
|
|
653
691
|
requested counts. Empty matches yield one versioned page. Helpers own continuation;
|
|
654
|
-
use `
|
|
692
|
+
use `query` for a manual `after` cursor or explicit historical query.
|
|
655
693
|
|
|
656
694
|
`all` consumes the same iterator and returns only after successful exhaustion.
|
|
657
695
|
Its required positive safe-integer `maxRecords` caps top-level Record occurrences
|
|
@@ -661,7 +699,7 @@ or increase `maxRecords`, and rejects with nonretryable `capacity` without a
|
|
|
661
699
|
partial or truncated result.
|
|
662
700
|
Invalid helper arguments use `invalid_query`; malformed pages, cursor cycles,
|
|
663
701
|
snapshot changes or inconsistent requested counts use nonretryable `unavailable`.
|
|
664
|
-
|
|
702
|
+
Query and authority failures propagate unchanged, with no retry or restart.
|
|
665
703
|
|
|
666
704
|
Read known stable refs directly, including refs from different collections:
|
|
667
705
|
|
|
@@ -674,7 +712,7 @@ const batch = await general.records.read(['invoices/one', 'suppliers/acme']);
|
|
|
674
712
|
|
|
675
713
|
An array accepts 1–100 distinct valid refs; a one-element array keeps the batch
|
|
676
714
|
shape, and all-missing batches still carry their version. Both forms send one
|
|
677
|
-
bounded operation through the same app/routine authority and snapshot as `
|
|
715
|
+
bounded operation through the same app/routine authority and snapshot as `query`.
|
|
678
716
|
Records include `$ref`, `$version` and all canonical structured fields; links
|
|
679
717
|
remain refs, omitted and null fields stay distinct, and Markdown bodies and
|
|
680
718
|
`$filing` are excluded. Invalid refs, exceeded bounds, denied access and service
|
|
@@ -743,7 +781,7 @@ and `$in`, declared one-valued `integer`, `number` and `date` fields accept
|
|
|
743
781
|
`$gt`, `$gte`, `$lt` and `$lte`:
|
|
744
782
|
|
|
745
783
|
```ts
|
|
746
|
-
await general.records.
|
|
784
|
+
await general.records.query({ invoices: { $: { where: {
|
|
747
785
|
amount: { $gte: 10, $lte: 20 },
|
|
748
786
|
date: { $gte: '2026-10-01', $lt: '2026-11-01' },
|
|
749
787
|
month: { $gte: '2026-10', $lte: '2026-10' },
|
package/dist/browser.js
CHANGED
|
@@ -3,19 +3,27 @@ var fileValidationReasons = {
|
|
|
3
3
|
invalid_name: "File name must be 1\u2013255 characters, with no reserved names, path separators, control characters or trailing spaces/dots.",
|
|
4
4
|
invalid_path: "File path must contain safe relative folder names and cannot start with .os.",
|
|
5
5
|
invalid_storage: "File storage must be local or remote.",
|
|
6
|
-
invalid_media_type: "
|
|
6
|
+
invalid_media_type: "The media type must use a value such as text/markdown or application/json.",
|
|
7
7
|
invalid_size: "File size must be a non-negative safe integer byte count.",
|
|
8
8
|
invalid_request_key: "File request key must contain 1\u2013256 characters, with no surrounding whitespace or control characters.",
|
|
9
|
-
invalid_enrichment_name: "
|
|
10
|
-
|
|
9
|
+
invalid_enrichment_name: "The enrichment name must be a filename without folders, such as read.md. Names such as original and original.pdf are reserved.",
|
|
10
|
+
invalid_enrichment_request: "The save request has invalid content or metadata.",
|
|
11
|
+
invalid_replacement: "To replace an enrichment, pass its current SHA-256 value as replace.",
|
|
11
12
|
invalid_bytes: "File content must contain byte chunks.",
|
|
12
13
|
original_changed: "The selected file changed while OS was reading it. Wait for it to finish saving, then try again."
|
|
14
|
+
}, localStorageReasons = {
|
|
15
|
+
local_storage_legacy: `OS found an older local file storage layout: the internal metadata folder is still named .oddington-files.
|
|
16
|
+
|
|
17
|
+
Stop any OS routines using this workspace, then rename .oddington-files to .os in the same directory and rerun your command. Keep the folder\u2019s contents intact.`,
|
|
18
|
+
local_storage_ambiguous: "OS found both .oddington-files and .os metadata folders in local file storage and could not safely choose one. Reconcile their metadata before continuing; do not overwrite either folder. Your stored files have been left in place.",
|
|
19
|
+
local_storage_unrecognized: "OS could not open its local file storage because the folder contains files but has no recognized metadata. The storage folder needs to be checked before continuing. Your stored files have been left in place.",
|
|
20
|
+
local_storage_tracked: "OS could not use its local file storage because it contains Git-tracked files. Local file storage must be outside tracked repository content. Your stored files have been left in place."
|
|
13
21
|
};
|
|
14
22
|
function parseFileErrorDetail(value) {
|
|
15
23
|
if (!value || typeof value != "object" || Array.isArray(value)) return;
|
|
16
24
|
let v = value;
|
|
17
25
|
if (typeof v.reason != "string") return;
|
|
18
|
-
if (Object.hasOwn(fileValidationReasons, v.reason))
|
|
26
|
+
if (Object.hasOwn(fileValidationReasons, v.reason) || Object.hasOwn(localStorageReasons, v.reason))
|
|
19
27
|
return Object.keys(v).length === 1 ? { reason: v.reason } : void 0;
|
|
20
28
|
if (v.reason !== "size_mismatch" && v.reason !== "size_limit") return;
|
|
21
29
|
let count = v.reason === "size_mismatch" ? "expectedBytes" : "limitBytes";
|
|
@@ -28,16 +36,59 @@ function parseFileErrorDetail(value) {
|
|
|
28
36
|
};
|
|
29
37
|
}
|
|
30
38
|
function fileErrorExplanation(detail) {
|
|
31
|
-
return detail.reason === "size_mismatch" ? `
|
|
32
|
-
}
|
|
33
|
-
|
|
39
|
+
return detail.reason === "size_mismatch" ? `The request declared ${detail.expectedBytes} bytes, but contained ${detail.receivedAtLeast ? "at least " : ""}${detail.receivedBytes} bytes.` : detail.reason === "size_limit" ? `The file content exceeds the ${fileSizeLabel(detail.limitBytes)} limit; received ${detail.receivedAtLeast ? "at least " : ""}${fileSizeLabel(detail.receivedBytes)}.` : Object.hasOwn(localStorageReasons, detail.reason) ? localStorageReasons[detail.reason] : fileValidationReasons[detail.reason];
|
|
40
|
+
}
|
|
41
|
+
function fileSizeLabel(bytes) {
|
|
42
|
+
for (let [unit, size] of [["GiB", 1024 ** 3], ["MiB", 1024 ** 2], ["KiB", 1024]])
|
|
43
|
+
if (bytes >= size && bytes % size === 0) return `${bytes / size} ${unit}`;
|
|
44
|
+
return `${bytes} bytes`;
|
|
45
|
+
}
|
|
46
|
+
function fileErrorDetailForCode(code, detail) {
|
|
47
|
+
let parsed = parseFileErrorDetail(detail);
|
|
48
|
+
if (!parsed) return;
|
|
49
|
+
if (Object.hasOwn(localStorageReasons, parsed.reason) ? code === "unavailable" : ["invalid_input", "request_too_large"].includes(code)) return parsed;
|
|
50
|
+
}
|
|
51
|
+
var FILE_CAPACITY_MESSAGE = "OS reached its file-processing limit. Try again in a moment. If this keeps happening with a large batch, split the files into smaller groups.", FILE_INTEGRITY_MESSAGE = "OS could not verify this File against its saved record. Try again. If the problem continues, add the original file again.";
|
|
52
|
+
function fileErrorMessage(error) {
|
|
53
|
+
let detail = fileErrorDetailForCode(error.code, error.detail);
|
|
54
|
+
if (detail)
|
|
55
|
+
return `${["invalid_enrichment_name", "invalid_enrichment_request", "invalid_replacement"].includes(detail.reason) ? `Couldn\u2019t save the file\u2019s enrichment.
|
|
56
|
+
` : ""}${fileErrorExplanation(detail)}`;
|
|
57
|
+
switch (error.code) {
|
|
58
|
+
case "invalid_input":
|
|
59
|
+
return "The file request is invalid.";
|
|
60
|
+
case "unsupported_context":
|
|
61
|
+
return "This environment does not support this file operation.";
|
|
62
|
+
case "unauthenticated":
|
|
63
|
+
return "Sign in to OS before accessing files.";
|
|
64
|
+
case "access_denied":
|
|
65
|
+
return "You don\u2019t have access to this file operation.";
|
|
66
|
+
case "not_found":
|
|
67
|
+
return "This file isn\u2019t available in this workspace.";
|
|
68
|
+
case "request_too_large":
|
|
69
|
+
return "The file request exceeds the size limit.";
|
|
70
|
+
case "conflict":
|
|
71
|
+
return "The saved file or enrichment doesn\u2019t match this request. Reload it before trying again.";
|
|
72
|
+
case "local_only":
|
|
73
|
+
return "This file is stored on a computer and isn\u2019t available through remote storage.";
|
|
74
|
+
case "integrity":
|
|
75
|
+
return FILE_INTEGRITY_MESSAGE;
|
|
76
|
+
case "capacity":
|
|
77
|
+
return FILE_CAPACITY_MESSAGE;
|
|
78
|
+
case "method_not_allowed":
|
|
79
|
+
return "This request uses an unsupported file operation.";
|
|
80
|
+
case "unavailable":
|
|
81
|
+
return `OS couldn\u2019t complete the file operation.${error.retryable ? " Try again." : ""}`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
var FileOperationError = class extends Error {
|
|
34
85
|
constructor(code, status, retryable = !1, detail) {
|
|
35
|
-
let safe =
|
|
36
|
-
super(
|
|
86
|
+
let safe = fileErrorDetailForCode(code, detail);
|
|
87
|
+
super(fileErrorMessage({ code, retryable, detail: safe }));
|
|
37
88
|
this.code = code;
|
|
38
89
|
this.status = status;
|
|
39
90
|
this.retryable = retryable;
|
|
40
|
-
safe && (this.detail = Object.freeze(safe));
|
|
91
|
+
safe && (this.detail = Object.freeze(safe)), safe && Object.hasOwn(localStorageReasons, safe.reason) && (this.retryable = !1);
|
|
41
92
|
}
|
|
42
93
|
code;
|
|
43
94
|
status;
|
|
@@ -666,7 +717,7 @@ function parseRoutineDeclaration(value) {
|
|
|
666
717
|
(!routineAlias(key) || !["read", "write"].includes(repositories[key])) && invalid(`access.repositories.${key}`), grants[key] = repositories[key];
|
|
667
718
|
access.files !== void 0 && !["read", "read-write"].includes(access.files) && invalid("access.files");
|
|
668
719
|
let runtime = object2(v.runtime === void 0 ? {} : v.runtime, ["features", "resources", "maxDuration"], "runtime");
|
|
669
|
-
runtime.features !== void 0 && (!Array.isArray(runtime.features) || runtime.features.length !== 0) && invalid("runtime.features"), runtime.resources !== void 0 && runtime.resources !== "standard" && invalid("runtime.resources"), runtime.maxDuration !== void 0 && runtime.maxDuration
|
|
720
|
+
runtime.features !== void 0 && (!Array.isArray(runtime.features) || runtime.features.length !== 0) && invalid("runtime.features"), runtime.resources !== void 0 && runtime.resources !== "standard" && invalid("runtime.resources"), runtime.maxDuration !== void 0 && !["15m", "60m"].includes(runtime.maxDuration) && invalid("runtime.maxDuration");
|
|
670
721
|
let retry = object2(v.retry === void 0 ? {} : v.retry, ["maxAttempts"], "retry");
|
|
671
722
|
retry.maxAttempts !== void 0 && retry.maxAttempts !== 1 && invalid("retry.maxAttempts");
|
|
672
723
|
let assets = v.assets === void 0 ? [] : v.assets;
|
|
@@ -679,7 +730,11 @@ function parseRoutineDeclaration(value) {
|
|
|
679
730
|
...access.files === void 0 ? {} : { files: access.files },
|
|
680
731
|
publish: list(access.publish, "access.publish", ["asks", "digests"])
|
|
681
732
|
},
|
|
682
|
-
runtime: {
|
|
733
|
+
runtime: {
|
|
734
|
+
features: [],
|
|
735
|
+
resources: "standard",
|
|
736
|
+
maxDuration: runtime.maxDuration ?? "60m"
|
|
737
|
+
},
|
|
683
738
|
retry: { maxAttempts: 1 },
|
|
684
739
|
assets: [...assets].sort()
|
|
685
740
|
};
|
|
@@ -732,7 +787,7 @@ function parseRoutineAccess(value) {
|
|
|
732
787
|
function parseRoutineAIOutputIssue(value) {
|
|
733
788
|
if (!value || typeof value != "object" || Array.isArray(value)) return;
|
|
734
789
|
let v = value;
|
|
735
|
-
if (["invalid_json", "invalid_response", "empty_text"].includes(String(v.reason)))
|
|
790
|
+
if (["invalid_json", "invalid_response", "empty_text", "structured_output_limit"].includes(String(v.reason)))
|
|
736
791
|
return Object.keys(v).join(",") === "reason" ? { reason: v.reason } : void 0;
|
|
737
792
|
if (!(!["missing_field", "unexpected_field", "wrong_type", "out_of_bounds", "invalid_file"].includes(String(v.reason)) || typeof v.path != "string" || v.path.length > 256 || !/^\$(?:\.[a-z][A-Za-z0-9]*|\[(?:0|[1-9][0-9]*)\])*$/.test(v.path) || !["object", "array", "boolean", "string", "integer", "number", "file"].includes(String(v.expected)) || !["missing", "null", "object", "array", "boolean", "string", "number", "other"].includes(String(v.received)) || Object.keys(v).some((key) => !["reason", "path", "expected", "received", "constraint", "limit"].includes(key)))) {
|
|
738
793
|
if (v.reason === "out_of_bounds") {
|
|
@@ -748,6 +803,7 @@ function parseRoutineAIOutputIssue(value) {
|
|
|
748
803
|
}
|
|
749
804
|
}
|
|
750
805
|
function routineAIOutputMessage(issue) {
|
|
806
|
+
if (issue.reason === "structured_output_limit") return "AI couldn\u2019t produce an answer matching the requested format within its submission limit.";
|
|
751
807
|
if (issue.reason === "invalid_json") return "AI replied, but its answer could not be read as JSON.";
|
|
752
808
|
if (issue.reason === "invalid_response") return "AI returned a response OS could not read.";
|
|
753
809
|
if (issue.reason === "empty_text") return "AI returned an empty answer.";
|
|
@@ -789,11 +845,83 @@ function routineAIOutputMessage(issue) {
|
|
|
789
845
|
${field}: expected ${expected}; received ${received}.`;
|
|
790
846
|
}
|
|
791
847
|
|
|
848
|
+
function parseRoutineAIErrorDetail(code, value) {
|
|
849
|
+
if (code === "invalid_output") return parseRoutineAIOutputIssue(value);
|
|
850
|
+
if (!value || typeof value != "object" || Array.isArray(value)) return;
|
|
851
|
+
let v = value, only2 = (...keys4) => Object.keys(v).every((key) => ["reason", ...keys4].includes(key));
|
|
852
|
+
if (code === "provider_failure") {
|
|
853
|
+
if (v.reason === "turn_limit" && only2("limit") && Number.isSafeInteger(v.limit) && Number(v.limit) >= 1 && Number(v.limit) <= 1024)
|
|
854
|
+
return { reason: "turn_limit", limit: v.limit };
|
|
855
|
+
if (v.reason === "runner_exit" && only2("exitCode") && (v.exitCode === void 0 || Number.isSafeInteger(v.exitCode) && Number(v.exitCode) >= 1 && Number(v.exitCode) <= 255))
|
|
856
|
+
return { reason: "runner_exit", ...v.exitCode === void 0 ? {} : { exitCode: v.exitCode } };
|
|
857
|
+
if (only2() && ["runner_error", "invalid_runner_response", "unexpected_runner_configuration"].includes(String(v.reason)))
|
|
858
|
+
return { reason: v.reason };
|
|
859
|
+
}
|
|
860
|
+
if (code === "invalid_input" && only2() && ["invalid_tools", "tools_require_file"].includes(String(v.reason)))
|
|
861
|
+
return { reason: v.reason };
|
|
862
|
+
if (code === "unsupported_file" && v.reason === "unsupported_media_type" && only2("mediaType") && typeof v.mediaType == "string" && isCanonicalOriginalMediaType(v.mediaType))
|
|
863
|
+
return { reason: "unsupported_media_type", mediaType: v.mediaType };
|
|
864
|
+
}
|
|
865
|
+
function routineAIErrorMessage(code, value) {
|
|
866
|
+
let detail = parseRoutineAIErrorDetail(code, value);
|
|
867
|
+
if (code === "invalid_output" && detail) return routineAIOutputMessage(detail);
|
|
868
|
+
if (detail) switch (detail.reason) {
|
|
869
|
+
case "turn_limit":
|
|
870
|
+
return `AI stopped before finishing the request.
|
|
871
|
+
It reached the ${detail.limit}-turn limit.`;
|
|
872
|
+
case "runner_exit":
|
|
873
|
+
return `AI couldn\u2019t complete the request.
|
|
874
|
+
The local AI runner stopped before returning a usable answer.`;
|
|
875
|
+
case "runner_error":
|
|
876
|
+
return `AI couldn\u2019t complete the request.
|
|
877
|
+
The local AI runner reported an error. No more specific reason was available.`;
|
|
878
|
+
case "invalid_runner_response":
|
|
879
|
+
return `AI couldn\u2019t complete the request.
|
|
880
|
+
The local AI runner did not return a complete, readable response.`;
|
|
881
|
+
case "unexpected_runner_configuration":
|
|
882
|
+
return `OS couldn\u2019t accept the AI response.
|
|
883
|
+
The runner\u2019s model, tools or configuration did not match the permitted setup.`;
|
|
884
|
+
case "invalid_tools":
|
|
885
|
+
return `The AI request is invalid.
|
|
886
|
+
Set tools to true or false.`;
|
|
887
|
+
case "tools_require_file":
|
|
888
|
+
return `The AI request is invalid.
|
|
889
|
+
Using tools requires a File input.`;
|
|
890
|
+
case "unsupported_media_type":
|
|
891
|
+
return `AI couldn\u2019t read this file.
|
|
892
|
+
Its media type is ${detail.mediaType}, which this input path doesn\u2019t support.
|
|
893
|
+
|
|
894
|
+
Check the file\u2019s stored media type before retrying. The model was not called.`;
|
|
895
|
+
}
|
|
896
|
+
switch (code) {
|
|
897
|
+
case "access_denied":
|
|
898
|
+
return "This routine doesn\u2019t currently have permission to use AI.";
|
|
899
|
+
case "capability_unavailable":
|
|
900
|
+
return "AI is unavailable in this execution environment.";
|
|
901
|
+
case "invalid_input":
|
|
902
|
+
return "The AI request is invalid. Check its input, instructions and schema.";
|
|
903
|
+
case "unsupported_file":
|
|
904
|
+
return "AI couldn\u2019t read this file in the requested format.";
|
|
905
|
+
case "file_unavailable":
|
|
906
|
+
return "AI couldn\u2019t read or verify the file.";
|
|
907
|
+
case "input_too_large":
|
|
908
|
+
return "The AI input exceeds the size limit for this execution path.";
|
|
909
|
+
case "output_too_large":
|
|
910
|
+
return "The AI response exceeds the size limit for this execution path.";
|
|
911
|
+
case "provider_failure":
|
|
912
|
+
return "AI couldn\u2019t complete the request. No more specific reason was recorded.";
|
|
913
|
+
case "invalid_output":
|
|
914
|
+
return "AI returned an answer OS could not accept.";
|
|
915
|
+
case "cancelled":
|
|
916
|
+
return "The AI operation was stopped before it completed.";
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
792
920
|
var ROUTINE_AI_VALUE_BYTES = 256 * 1024, ROUTINE_AI_RESULT_BYTES = 6 * ROUTINE_AI_VALUE_BYTES + 1024, ROUTINE_AI_EVIDENCE_BYTES = 4 * 1024;
|
|
793
921
|
var AIError = class extends Error {
|
|
794
922
|
constructor(code, options = {}) {
|
|
795
|
-
let detail = code
|
|
796
|
-
super(
|
|
923
|
+
let detail = parseRoutineAIErrorDetail(code, options.detail);
|
|
924
|
+
super(routineAIErrorMessage(code, detail));
|
|
797
925
|
this.code = code;
|
|
798
926
|
this.name = "AIError", detail && (this.detail = Object.freeze(detail)), code === "invalid_output" && typeof options.text == "string" && new TextEncoder().encode(options.text).byteLength <= ROUTINE_AI_VALUE_BYTES && Object.defineProperty(this, "text", { value: options.text });
|
|
799
927
|
}
|
|
@@ -813,13 +941,18 @@ function capture(value, invalid9, oversized, limit = ROUTINE_AI_VALUE_BYTES) {
|
|
|
813
941
|
function parseRoutineAIRequest(value) {
|
|
814
942
|
let v = capture(value, "invalid_input", "input_too_large");
|
|
815
943
|
try {
|
|
816
|
-
if (assertRoutineFields(v, v.kind === "generateObject" ? ["kind", "instructions", "input", "schema"] : ["kind", "instructions", "input"]), !["generateText", "generateObject"].includes(v.kind) || typeof v.instructions != "string" || !v.instructions.trim()) throw new Error();
|
|
944
|
+
if (assertRoutineFields(v, v.kind === "generateObject" ? ["kind", "instructions", "input", "schema", "tools"] : ["kind", "instructions", "input", "tools"]), !["generateText", "generateObject"].includes(v.kind) || typeof v.instructions != "string" || !v.instructions.trim()) throw new Error();
|
|
817
945
|
let input = typeof v.input == "string" ? v.input : parseFileDescriptor(v.input);
|
|
818
|
-
if (v
|
|
946
|
+
if (Object.hasOwn(v, "tools") && typeof v.tools != "boolean")
|
|
947
|
+
throw new AIError("invalid_input", { detail: { reason: "invalid_tools" } });
|
|
948
|
+
if (v.tools === !0 && typeof input == "string")
|
|
949
|
+
throw new AIError("invalid_input", { detail: { reason: "tools_require_file" } });
|
|
950
|
+
let tools = v.tools === !0 ? { tools: !0 } : {};
|
|
951
|
+
if (v.kind === "generateText") return freezeRoutineMetadata({ kind: v.kind, instructions: v.instructions, input, ...tools });
|
|
819
952
|
let schema2 = parseAppRoutineContract({ routine: "ai", input: v.schema, steps: [{ id: "generate" }] }).input;
|
|
820
|
-
return freezeRoutineMetadata({ kind: v.kind, instructions: v.instructions, input, schema: schema2 });
|
|
821
|
-
} catch {
|
|
822
|
-
throw new AIError("invalid_input");
|
|
953
|
+
return freezeRoutineMetadata({ kind: v.kind, instructions: v.instructions, input, schema: schema2, ...tools });
|
|
954
|
+
} catch (error) {
|
|
955
|
+
throw error instanceof AIError ? error : new AIError("invalid_input");
|
|
823
956
|
}
|
|
824
957
|
}
|
|
825
958
|
function parseRoutineAIResult(request2, value) {
|
|
@@ -995,11 +1128,12 @@ async function executeRoutineDefinition(definition, input, runtime) {
|
|
|
995
1128
|
if (!runtime.ai) throw new AIError("capability_unavailable");
|
|
996
1129
|
let request2;
|
|
997
1130
|
try {
|
|
998
|
-
assertRoutineFields(value, kind === "generateObject" ? ["instructions", "input", "schema"] : ["instructions", "input"]), request2 = parseRoutineAIRequest({
|
|
1131
|
+
assertRoutineFields(value, kind === "generateObject" ? ["instructions", "input", "schema", "tools"] : ["instructions", "input", "tools"]), request2 = parseRoutineAIRequest({
|
|
999
1132
|
kind,
|
|
1000
1133
|
instructions: value.instructions,
|
|
1001
1134
|
input: value.input,
|
|
1002
|
-
...kind === "generateObject" ? { schema: schemaMetadata(value.schema).schema } : {}
|
|
1135
|
+
...kind === "generateObject" ? { schema: schemaMetadata(value.schema).schema } : {},
|
|
1136
|
+
...value.tools === void 0 ? {} : { tools: value.tools }
|
|
1003
1137
|
});
|
|
1004
1138
|
} catch (error) {
|
|
1005
1139
|
throw error instanceof AIError ? error : new AIError("invalid_input");
|
|
@@ -1021,10 +1155,16 @@ async function executeRoutineDefinition(definition, input, runtime) {
|
|
|
1021
1155
|
}) : value, validateHandlers = (value) => {
|
|
1022
1156
|
for (let question2 of value.questions ?? []) if (question2.handler && !contract.responses.some((handler) => handler.id === question2.handler.id)) return invalid3();
|
|
1023
1157
|
return value;
|
|
1024
|
-
}, os2 = Object.freeze({
|
|
1158
|
+
}, activeStep, progress2 = /* @__PURE__ */ new Map(), os2 = Object.freeze({
|
|
1025
1159
|
...guarded({
|
|
1026
1160
|
...runtime,
|
|
1027
|
-
run: freezeRoutineMetadata({
|
|
1161
|
+
run: freezeRoutineMetadata({ id: runtime.run.id, async progress(value) {
|
|
1162
|
+
assertRoutineFields(value, ["completed", "total"]);
|
|
1163
|
+
let { completed, total } = value, prior = activeStep ? progress2.get(activeStep) : void 0;
|
|
1164
|
+
if (!active2 || broken || !activeStep || !Number.isSafeInteger(completed) || !Number.isSafeInteger(total) || completed < 1 || completed > total || prior && (prior.total !== total || completed <= prior.completed)) return invalid3();
|
|
1165
|
+
let accepted = freezeRoutineMetadata({ step: activeStep, completed, total });
|
|
1166
|
+
await runtime.run.progress(accepted), progress2.set(activeStep, accepted);
|
|
1167
|
+
} }),
|
|
1028
1168
|
ai: {
|
|
1029
1169
|
generateText: (value) => generate("generateText", value),
|
|
1030
1170
|
generateObject: (value) => generate("generateObject", value)
|
|
@@ -1049,13 +1189,13 @@ async function executeRoutineDefinition(definition, input, runtime) {
|
|
|
1049
1189
|
if (broken) return invalid3();
|
|
1050
1190
|
reached.add(id3);
|
|
1051
1191
|
try {
|
|
1052
|
-
await runtime.step(id3, { status: "running" });
|
|
1192
|
+
await runtime.step(id3, { status: "running" }), activeStep = id3;
|
|
1053
1193
|
let output = await steps[id3].run(saved, os2);
|
|
1054
1194
|
if (assertRoutineFields(output, ["summary", "data"]), !Object.hasOwn(output, "summary") || !Object.hasOwn(output, "data")) return invalid3();
|
|
1055
1195
|
let summary2 = captureJson(output.summary), data3 = captureJson(output.data);
|
|
1056
|
-
return await runtime.step(id3, { status: "completed", summary: summary2, data: data3 }), freezeRoutineMetadata({ summary: summary2, data: data3 });
|
|
1196
|
+
return await runtime.step(id3, { status: "completed", summary: summary2, data: data3 }), activeStep = void 0, freezeRoutineMetadata({ summary: summary2, data: data3 });
|
|
1057
1197
|
} catch (error) {
|
|
1058
|
-
throw broken = !0, await runtime.step(id3, { status: "failed" }), error;
|
|
1198
|
+
throw broken = !0, activeStep = void 0, await runtime.step(id3, { status: "failed" }), error;
|
|
1059
1199
|
}
|
|
1060
1200
|
});
|
|
1061
1201
|
return work = next.then(() => {
|
|
@@ -1541,7 +1681,7 @@ function capture3(value, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
|
|
|
1541
1681
|
ancestors.delete(value);
|
|
1542
1682
|
}
|
|
1543
1683
|
}
|
|
1544
|
-
function recordPagination(
|
|
1684
|
+
function recordPagination(queryRecords) {
|
|
1545
1685
|
function pages(query) {
|
|
1546
1686
|
let captured = capture3(query), names = Object.keys(historyProperties(captured));
|
|
1547
1687
|
for (let name2 of names) {
|
|
@@ -1563,7 +1703,7 @@ function recordPagination(select) {
|
|
|
1563
1703
|
let request2 = Object.fromEntries(active2.map((name2) => {
|
|
1564
1704
|
let selection2 = captured[name2], next = state.get(name2).next;
|
|
1565
1705
|
return [name2, next === void 0 ? selection2 : { ...selection2, $: { ...selection2.$, after: next } }];
|
|
1566
|
-
})), response2 = await
|
|
1706
|
+
})), response2 = await queryRecords(request2), page;
|
|
1567
1707
|
try {
|
|
1568
1708
|
page = capture3(response2);
|
|
1569
1709
|
let keys4 = Object.keys(historyProperties(page));
|
|
@@ -1647,7 +1787,7 @@ var genericData = {
|
|
|
1647
1787
|
let refs = captureRecordsReadRefs(typeof input == "string" ? [input] : input), captured = captureRecordsReadOptions(options), result2 = await send3({ operation: "records.read", refs, ...options === void 0 ? {} : { options: captured } });
|
|
1648
1788
|
return typeof input == "string" ? result2.records[0] : result2;
|
|
1649
1789
|
}
|
|
1650
|
-
let
|
|
1790
|
+
let queryRecords = (query) => send3({ operation: "records.query", query });
|
|
1651
1791
|
return {
|
|
1652
1792
|
collections: {
|
|
1653
1793
|
list: () => send3({ operation: "collections.list" }),
|
|
@@ -1655,8 +1795,8 @@ var genericData = {
|
|
|
1655
1795
|
},
|
|
1656
1796
|
records: {
|
|
1657
1797
|
read: read2,
|
|
1658
|
-
|
|
1659
|
-
...recordPagination(
|
|
1798
|
+
query: queryRecords,
|
|
1799
|
+
...recordPagination(queryRecords),
|
|
1660
1800
|
search: async (query, options) => send3({ operation: "records.search", ...captureRecordsSearchInput(query, options) })
|
|
1661
1801
|
},
|
|
1662
1802
|
history: {
|
|
@@ -1786,6 +1926,7 @@ var MAX_BYTES = 16 * 1024 * 1024, PART_BYTES = 8 * 1024 * 1024, METADATA_BYTES =
|
|
|
1786
1926
|
400: ["invalid_input", "unsupported_context"],
|
|
1787
1927
|
401: ["unauthenticated"],
|
|
1788
1928
|
403: ["access_denied"],
|
|
1929
|
+
404: ["not_found"],
|
|
1789
1930
|
405: ["method_not_allowed"],
|
|
1790
1931
|
409: ["conflict", "local_only"],
|
|
1791
1932
|
413: ["request_too_large"],
|
|
@@ -2301,16 +2442,16 @@ function parseConnectionLease(raw, now = Date.now()) {
|
|
|
2301
2442
|
if (!v || typeof v != "object" || Array.isArray(v) || !connectionUuid(v.connectionId) || !connectionUuid(v.runId) || typeof v.name != "string" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(v.name) || typeof v.accessToken != "string" || !/^[A-Za-z0-9._~+/-]{1,8192}={0,2}$/.test(v.accessToken))
|
|
2302
2443
|
throw new Error("Invalid connection credential");
|
|
2303
2444
|
if (v.provider === "outlook" || v.provider === "sharepoint") {
|
|
2304
|
-
if (!(v.provider === "outlook" ? Object.keys(v).sort().join(",") === "accessToken,connectionId,expiresAt,mailboxEmail,name,provider,runId" && validMicrosoftMailbox(v.mailboxEmail) : Object.keys(v).sort().join(",") === "accessToken,connectionId,expiresAt,name,provider,runId,siteId,siteUrl" && typeof v.siteUrl == "string" && canonicalSharePointSiteUrl(v.siteUrl) === v.siteUrl && validSharePointSiteId(v.siteId, v.siteUrl)) || typeof v.expiresAt != "string" || !Number.isFinite(Date.parse(v.expiresAt)) || Date.parse(v.expiresAt) <= now
|
|
2445
|
+
if (!(v.provider === "outlook" ? Object.keys(v).sort().join(",") === "accessToken,connectionId,expiresAt,mailboxEmail,name,provider,runId" && validMicrosoftMailbox(v.mailboxEmail) : Object.keys(v).sort().join(",") === "accessToken,connectionId,expiresAt,name,provider,runId,siteId,siteUrl" && typeof v.siteUrl == "string" && canonicalSharePointSiteUrl(v.siteUrl) === v.siteUrl && validSharePointSiteId(v.siteId, v.siteUrl)) || typeof v.expiresAt != "string" || !Number.isFinite(Date.parse(v.expiresAt)) || Date.parse(v.expiresAt) <= now || Date.parse(v.expiresAt) > now + 36e5)
|
|
2305
2446
|
throw new Error("Invalid connection credential");
|
|
2306
2447
|
return v;
|
|
2307
2448
|
}
|
|
2308
2449
|
if (v.provider === "gmail") {
|
|
2309
|
-
if (Object.keys(v).sort().join(",") !== "accessToken,accountEmail,connectionId,expiresAt,name,provider,runId,scopes" || typeof v.accountEmail != "string" || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.accountEmail) || !validGoogleScopes(v.scopes) || typeof v.expiresAt != "string" || !Number.isFinite(Date.parse(v.expiresAt)) || Date.parse(v.expiresAt) <= now
|
|
2450
|
+
if (Object.keys(v).sort().join(",") !== "accessToken,accountEmail,connectionId,expiresAt,name,provider,runId,scopes" || typeof v.accountEmail != "string" || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.accountEmail) || !validGoogleScopes(v.scopes) || typeof v.expiresAt != "string" || !Number.isFinite(Date.parse(v.expiresAt)) || Date.parse(v.expiresAt) <= now || Date.parse(v.expiresAt) > now + 36e5)
|
|
2310
2451
|
throw new Error("Invalid connection credential");
|
|
2311
2452
|
return v;
|
|
2312
2453
|
}
|
|
2313
|
-
if (v.provider === "airtable" && Object.keys(v).sort().join(",") === "accessToken,baseId,connectionId,name,provider,runExpiresAt,runId" && typeof v.baseId == "string" && /^app[A-Za-z0-9]+$/.test(v.baseId) && typeof v.runExpiresAt == "string" && Number.isFinite(Date.parse(v.runExpiresAt)) && Date.parse(v.runExpiresAt) > now
|
|
2454
|
+
if (v.provider === "airtable" && Object.keys(v).sort().join(",") === "accessToken,baseId,connectionId,name,provider,runExpiresAt,runId" && typeof v.baseId == "string" && /^app[A-Za-z0-9]+$/.test(v.baseId) && typeof v.runExpiresAt == "string" && Number.isFinite(Date.parse(v.runExpiresAt)) && Date.parse(v.runExpiresAt) > now && Date.parse(v.runExpiresAt) <= now + 15 * 6e4 || v.provider === "dropbox" && Object.keys(v).sort().join(",") === "access,accessToken,accountId,connectionId,expiresAt,name,provider,rootPath,runId,scopes" && typeof v.accountId == "string" && /^dbid:[A-Za-z0-9_-]{1,128}$/.test(v.accountId) && v.access === "full_dropbox" && validDropboxRootPath(v.rootPath) && validDropboxFileScopes(v.scopes) && typeof v.expiresAt == "string" && Number.isFinite(Date.parse(v.expiresAt)) && Date.parse(v.expiresAt) > now && Date.parse(v.expiresAt) <= now + 144e5)
|
|
2314
2455
|
return v;
|
|
2315
2456
|
throw new Error("Invalid connection credential");
|
|
2316
2457
|
}
|
|
@@ -4173,6 +4314,11 @@ function times(value) {
|
|
|
4173
4314
|
function bounded(value) {
|
|
4174
4315
|
return assertRoutineJson(value), new TextEncoder().encode(JSON.stringify(value)).byteLength > 256 * 1024 ? invalid8() : value;
|
|
4175
4316
|
}
|
|
4317
|
+
function progress(value) {
|
|
4318
|
+
if (value === void 0) return {};
|
|
4319
|
+
let v = record3(value);
|
|
4320
|
+
return Object.keys(v).sort().join(",") !== "completed,total" || !Number.isSafeInteger(v.completed) || !Number.isSafeInteger(v.total) || v.completed < 1 || v.completed > v.total ? invalid8() : { progress: { completed: v.completed, total: v.total } };
|
|
4321
|
+
}
|
|
4176
4322
|
function parseRunStep(value, expected) {
|
|
4177
4323
|
let v = record3(value);
|
|
4178
4324
|
return typeof v.id != "string" || !name.test(v.id) || expected !== void 0 && v.id !== expected || typeof v.title != "string" || !v.title.trim() || v.title.length > 256 || !["pending", "running", "completed", "failed", "skipped"].includes(v.status) ? invalid8() : {
|
|
@@ -4180,6 +4326,7 @@ function parseRunStep(value, expected) {
|
|
|
4180
4326
|
title: v.title,
|
|
4181
4327
|
status: v.status,
|
|
4182
4328
|
...times(v),
|
|
4329
|
+
...progress(v.progress),
|
|
4183
4330
|
...Object.hasOwn(v, "summary") ? { summary: bounded(v.summary) } : {},
|
|
4184
4331
|
...Object.hasOwn(v, "data") ? { data: bounded(v.data) } : {}
|
|
4185
4332
|
};
|
|
@@ -4283,12 +4430,12 @@ async function request(key, suffix, init2, maximum = SUMMARY_BYTES, retained, re
|
|
|
4283
4430
|
function summary(value, key, context, id3) {
|
|
4284
4431
|
if (context.kind !== "local")
|
|
4285
4432
|
try {
|
|
4286
|
-
return (!only(value, "id,routine,placement,status,createdAt,startedAt,finishedAt,steps,result,failure") || !Array.isArray(value.steps) || value.steps.some((step) => !only(step, "id,title,status,startedAt,finishedAt,summary,detailAvailable")) || value.failure !== void 0 && !only(value.failure, "message")) && fail3("execution_unavailable"), parseRemoteRun(value, { routine: key, id: id3 });
|
|
4433
|
+
return (!only(value, "id,routine,placement,status,createdAt,startedAt,finishedAt,steps,result,failure") || !Array.isArray(value.steps) || value.steps.some((step) => !only(step, "id,title,status,startedAt,finishedAt,progress,summary,detailAvailable")) || value.failure !== void 0 && !only(value.failure, "message")) && fail3("execution_unavailable"), parseRemoteRun(value, { routine: key, id: id3 });
|
|
4287
4434
|
} catch {
|
|
4288
4435
|
return fail3("execution_unavailable");
|
|
4289
4436
|
}
|
|
4290
4437
|
let run = value;
|
|
4291
|
-
return (!only(run, "id,routine,placement,status,createdAt,startedAt,finishedAt,steps,result,failure,local") || typeof run.id != "string" || !uuid2.test(run.id) || id3 !== void 0 && run.id !== id3 || run.routine !== key || run.placement !== "local" || !["queued", "running", "succeeded", "failed"].includes(run.status) || !timestamp(run.createdAt) || !optionalTimes(run) || !Array.isArray(run.steps) || run.steps.length > 64 || run.steps.some((step) => !only(step, "id,title,status,startedAt,finishedAt,summary,detailAvailable") || typeof step.detailAvailable != "boolean" || !validStep(step)) || new Set(run.steps.map((step) => step.id)).size !== run.steps.length || Object.hasOwn(run, "result") && (run.status !== "succeeded" || !bounded2(run.result)) || run.failure !== void 0 && (run.status !== "failed" || !only(run.failure, "message") || !message(run.failure.message)) || run.local !== void 0 && !validLocalActivity(run.local)) && fail3("execution_unavailable"), run;
|
|
4438
|
+
return (!only(run, "id,routine,placement,status,createdAt,startedAt,finishedAt,steps,result,failure,local") || typeof run.id != "string" || !uuid2.test(run.id) || id3 !== void 0 && run.id !== id3 || run.routine !== key || run.placement !== "local" || !["queued", "running", "succeeded", "failed"].includes(run.status) || !timestamp(run.createdAt) || !optionalTimes(run) || !Array.isArray(run.steps) || run.steps.length > 64 || run.steps.some((step) => !only(step, "id,title,status,startedAt,finishedAt,progress,summary,detailAvailable") || typeof step.detailAvailable != "boolean" || !validStep(step)) || new Set(run.steps.map((step) => step.id)).size !== run.steps.length || Object.hasOwn(run, "result") && (run.status !== "succeeded" || !bounded2(run.result)) || run.failure !== void 0 && (run.status !== "failed" || !only(run.failure, "message") || !message(run.failure.message)) || run.local !== void 0 && !validLocalActivity(run.local)) && fail3("execution_unavailable"), run;
|
|
4292
4439
|
}
|
|
4293
4440
|
function only(value, allowed) {
|
|
4294
4441
|
return !!value && typeof value == "object" && !Array.isArray(value) && Object.keys(value).every((key) => allowed.split(",").includes(key));
|
|
@@ -4298,7 +4445,7 @@ function optionalTimes(value) {
|
|
|
4298
4445
|
return (value.startedAt === void 0 || timestamp(value.startedAt)) && (value.finishedAt === void 0 || timestamp(value.finishedAt));
|
|
4299
4446
|
}
|
|
4300
4447
|
function validStep(step) {
|
|
4301
|
-
return typeof step.id == "string" && identifier.test(step.id) && typeof step.title == "string" && step.title.length > 0 && step.title.length <= 256 && ["pending", "running", "completed", "failed", "skipped"].includes(step.status) && optionalTimes(step) && (!Object.hasOwn(step, "summary") || bounded2(step.summary)) && (!Object.hasOwn(step, "data") || bounded2(step.data));
|
|
4448
|
+
return typeof step.id == "string" && identifier.test(step.id) && typeof step.title == "string" && step.title.length > 0 && step.title.length <= 256 && ["pending", "running", "completed", "failed", "skipped"].includes(step.status) && optionalTimes(step) && (step.progress === void 0 || only(step.progress, "completed,total") && Number.isSafeInteger(step.progress.completed) && Number.isSafeInteger(step.progress.total) && step.progress.completed > 0 && step.progress.completed <= step.progress.total) && (!Object.hasOwn(step, "summary") || bounded2(step.summary)) && (!Object.hasOwn(step, "data") || bounded2(step.data));
|
|
4302
4449
|
}
|
|
4303
4450
|
function validLocalActivity(value) {
|
|
4304
4451
|
let paths = (value2) => Array.isArray(value2) && value2.every((path) => typeof path == "string" && path.length > 0 && path.length <= 4096 && !path.startsWith("/") && !path.includes("\\") && !path.split("/").some((part) => !part || part === "." || part === "..") && !/[\x00-\x1f\x7f]/.test(path));
|
|
@@ -4339,11 +4486,11 @@ function handle(key, id3, context) {
|
|
|
4339
4486
|
let value = await request(key, `runs/${id3}/steps/${step}`, void 0, 514 * 1024, context, !0);
|
|
4340
4487
|
if (context.kind !== "local")
|
|
4341
4488
|
try {
|
|
4342
|
-
return only(value, "id,title,status,startedAt,finishedAt,summary,data") || fail3("execution_unavailable"), parseRunStep(value, step);
|
|
4489
|
+
return only(value, "id,title,status,startedAt,finishedAt,progress,summary,data") || fail3("execution_unavailable"), parseRunStep(value, step);
|
|
4343
4490
|
} catch {
|
|
4344
4491
|
return fail3("execution_unavailable");
|
|
4345
4492
|
}
|
|
4346
|
-
return (!only(value, "id,title,status,startedAt,finishedAt,summary,data") || value.id !== step || !validStep(value)) && fail3("execution_unavailable"), value;
|
|
4493
|
+
return (!only(value, "id,title,status,startedAt,finishedAt,progress,summary,data") || value.id !== step || !validStep(value)) && fail3("execution_unavailable"), value;
|
|
4347
4494
|
}
|
|
4348
4495
|
});
|
|
4349
4496
|
}
|