@oddington/sdk 0.1.41 → 0.1.42

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 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.select({ invoices: { $: { limit: 20 } } });
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. Reads return the successful Web
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.select({
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.select({
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 `select`, `pages` and `all` values from normalized
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 `select` for a manual `after` cursor or explicit historical selection.
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
- Selection and authority failures propagate unchanged, with no retry or restart.
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 `select`.
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.select({ invoices: { $: { where: {
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: "File media type must name one valid type/subtype.",
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: "Enrichment name must be a safe filename and cannot be original or original.*.",
10
- invalid_replacement: "Enrichment replacement must be a SHA-256 hash of 64 lowercase hexadecimal characters.",
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,57 @@ function parseFileErrorDetail(value) {
28
36
  };
29
37
  }
30
38
  function fileErrorExplanation(detail) {
31
- return detail.reason === "size_mismatch" ? `File byte count mismatch: expected ${detail.expectedBytes} bytes; received ${detail.receivedAtLeast ? "at least " : ""}${detail.receivedBytes} bytes.` : detail.reason === "size_limit" ? `File size exceeds the ${detail.limitBytes}-byte limit; received ${detail.receivedAtLeast ? "at least " : ""}${detail.receivedBytes} bytes.` : fileValidationReasons[detail.reason];
32
- }
33
- 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.", FileOperationError = class extends Error {
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 "request_too_large":
67
+ return "The file request exceeds the size limit.";
68
+ case "conflict":
69
+ return "The saved file or enrichment doesn\u2019t match this request. Reload it before trying again.";
70
+ case "local_only":
71
+ return "This file is stored on a computer and isn\u2019t available through remote storage.";
72
+ case "integrity":
73
+ return FILE_INTEGRITY_MESSAGE;
74
+ case "capacity":
75
+ return FILE_CAPACITY_MESSAGE;
76
+ case "method_not_allowed":
77
+ return "This request uses an unsupported file operation.";
78
+ case "unavailable":
79
+ return `OS couldn\u2019t complete the file operation.${error.retryable ? " Try again." : ""}`;
80
+ }
81
+ }
82
+ var FileOperationError = class extends Error {
34
83
  constructor(code, status, retryable = !1, detail) {
35
- let safe = ["invalid_input", "request_too_large"].includes(code) ? parseFileErrorDetail(detail) : void 0;
36
- super(safe ? `${code}: ${fileErrorExplanation(safe)}` : code === "capacity" ? FILE_CAPACITY_MESSAGE : code === "integrity" ? FILE_INTEGRITY_MESSAGE : code);
84
+ let safe = fileErrorDetailForCode(code, detail);
85
+ super(fileErrorMessage({ code, retryable, detail: safe }));
37
86
  this.code = code;
38
87
  this.status = status;
39
88
  this.retryable = retryable;
40
- safe && (this.detail = Object.freeze(safe));
89
+ safe && (this.detail = Object.freeze(safe)), safe && Object.hasOwn(localStorageReasons, safe.reason) && (this.retryable = !1);
41
90
  }
42
91
  code;
43
92
  status;
@@ -666,7 +715,7 @@ function parseRoutineDeclaration(value) {
666
715
  (!routineAlias(key) || !["read", "write"].includes(repositories[key])) && invalid(`access.repositories.${key}`), grants[key] = repositories[key];
667
716
  access.files !== void 0 && !["read", "read-write"].includes(access.files) && invalid("access.files");
668
717
  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 !== "15m" && invalid("runtime.maxDuration");
718
+ 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
719
  let retry = object2(v.retry === void 0 ? {} : v.retry, ["maxAttempts"], "retry");
671
720
  retry.maxAttempts !== void 0 && retry.maxAttempts !== 1 && invalid("retry.maxAttempts");
672
721
  let assets = v.assets === void 0 ? [] : v.assets;
@@ -679,7 +728,11 @@ function parseRoutineDeclaration(value) {
679
728
  ...access.files === void 0 ? {} : { files: access.files },
680
729
  publish: list(access.publish, "access.publish", ["asks", "digests"])
681
730
  },
682
- runtime: { features: [], resources: "standard", maxDuration: "15m" },
731
+ runtime: {
732
+ features: [],
733
+ resources: "standard",
734
+ maxDuration: runtime.maxDuration ?? "60m"
735
+ },
683
736
  retry: { maxAttempts: 1 },
684
737
  assets: [...assets].sort()
685
738
  };
@@ -732,7 +785,7 @@ function parseRoutineAccess(value) {
732
785
  function parseRoutineAIOutputIssue(value) {
733
786
  if (!value || typeof value != "object" || Array.isArray(value)) return;
734
787
  let v = value;
735
- if (["invalid_json", "invalid_response", "empty_text"].includes(String(v.reason)))
788
+ if (["invalid_json", "invalid_response", "empty_text", "structured_output_limit"].includes(String(v.reason)))
736
789
  return Object.keys(v).join(",") === "reason" ? { reason: v.reason } : void 0;
737
790
  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
791
  if (v.reason === "out_of_bounds") {
@@ -748,6 +801,7 @@ function parseRoutineAIOutputIssue(value) {
748
801
  }
749
802
  }
750
803
  function routineAIOutputMessage(issue) {
804
+ if (issue.reason === "structured_output_limit") return "AI couldn\u2019t produce an answer matching the requested format within its submission limit.";
751
805
  if (issue.reason === "invalid_json") return "AI replied, but its answer could not be read as JSON.";
752
806
  if (issue.reason === "invalid_response") return "AI returned a response OS could not read.";
753
807
  if (issue.reason === "empty_text") return "AI returned an empty answer.";
@@ -789,11 +843,83 @@ function routineAIOutputMessage(issue) {
789
843
  ${field}: expected ${expected}; received ${received}.`;
790
844
  }
791
845
 
846
+ function parseRoutineAIErrorDetail(code, value) {
847
+ if (code === "invalid_output") return parseRoutineAIOutputIssue(value);
848
+ if (!value || typeof value != "object" || Array.isArray(value)) return;
849
+ let v = value, only2 = (...keys4) => Object.keys(v).every((key) => ["reason", ...keys4].includes(key));
850
+ if (code === "provider_failure") {
851
+ if (v.reason === "turn_limit" && only2("limit") && Number.isSafeInteger(v.limit) && Number(v.limit) >= 1 && Number(v.limit) <= 1024)
852
+ return { reason: "turn_limit", limit: v.limit };
853
+ if (v.reason === "runner_exit" && only2("exitCode") && (v.exitCode === void 0 || Number.isSafeInteger(v.exitCode) && Number(v.exitCode) >= 1 && Number(v.exitCode) <= 255))
854
+ return { reason: "runner_exit", ...v.exitCode === void 0 ? {} : { exitCode: v.exitCode } };
855
+ if (only2() && ["runner_error", "invalid_runner_response", "unexpected_runner_configuration"].includes(String(v.reason)))
856
+ return { reason: v.reason };
857
+ }
858
+ if (code === "invalid_input" && only2() && ["invalid_tools", "tools_require_file"].includes(String(v.reason)))
859
+ return { reason: v.reason };
860
+ if (code === "unsupported_file" && v.reason === "unsupported_media_type" && only2("mediaType") && typeof v.mediaType == "string" && isCanonicalOriginalMediaType(v.mediaType))
861
+ return { reason: "unsupported_media_type", mediaType: v.mediaType };
862
+ }
863
+ function routineAIErrorMessage(code, value) {
864
+ let detail = parseRoutineAIErrorDetail(code, value);
865
+ if (code === "invalid_output" && detail) return routineAIOutputMessage(detail);
866
+ if (detail) switch (detail.reason) {
867
+ case "turn_limit":
868
+ return `AI stopped before finishing the request.
869
+ It reached the ${detail.limit}-turn limit.`;
870
+ case "runner_exit":
871
+ return `AI couldn\u2019t complete the request.
872
+ The local AI runner stopped before returning a usable answer.`;
873
+ case "runner_error":
874
+ return `AI couldn\u2019t complete the request.
875
+ The local AI runner reported an error. No more specific reason was available.`;
876
+ case "invalid_runner_response":
877
+ return `AI couldn\u2019t complete the request.
878
+ The local AI runner did not return a complete, readable response.`;
879
+ case "unexpected_runner_configuration":
880
+ return `OS couldn\u2019t accept the AI response.
881
+ The runner\u2019s model, tools or configuration did not match the permitted setup.`;
882
+ case "invalid_tools":
883
+ return `The AI request is invalid.
884
+ Set tools to true or false.`;
885
+ case "tools_require_file":
886
+ return `The AI request is invalid.
887
+ Using tools requires a File input.`;
888
+ case "unsupported_media_type":
889
+ return `AI couldn\u2019t read this file.
890
+ Its media type is ${detail.mediaType}, which this input path doesn\u2019t support.
891
+
892
+ Check the file\u2019s stored media type before retrying. The model was not called.`;
893
+ }
894
+ switch (code) {
895
+ case "access_denied":
896
+ return "This routine doesn\u2019t currently have permission to use AI.";
897
+ case "capability_unavailable":
898
+ return "AI is unavailable in this execution environment.";
899
+ case "invalid_input":
900
+ return "The AI request is invalid. Check its input, instructions and schema.";
901
+ case "unsupported_file":
902
+ return "AI couldn\u2019t read this file in the requested format.";
903
+ case "file_unavailable":
904
+ return "AI couldn\u2019t read or verify the file.";
905
+ case "input_too_large":
906
+ return "The AI input exceeds the size limit for this execution path.";
907
+ case "output_too_large":
908
+ return "The AI response exceeds the size limit for this execution path.";
909
+ case "provider_failure":
910
+ return "AI couldn\u2019t complete the request. No more specific reason was recorded.";
911
+ case "invalid_output":
912
+ return "AI returned an answer OS could not accept.";
913
+ case "cancelled":
914
+ return "The AI operation was stopped before it completed.";
915
+ }
916
+ }
917
+
792
918
  var ROUTINE_AI_VALUE_BYTES = 256 * 1024, ROUTINE_AI_RESULT_BYTES = 6 * ROUTINE_AI_VALUE_BYTES + 1024, ROUTINE_AI_EVIDENCE_BYTES = 4 * 1024;
793
919
  var AIError = class extends Error {
794
920
  constructor(code, options = {}) {
795
- let detail = code === "invalid_output" ? parseRoutineAIOutputIssue(options.detail) : void 0;
796
- super(detail ? routineAIOutputMessage(detail) : code);
921
+ let detail = parseRoutineAIErrorDetail(code, options.detail);
922
+ super(routineAIErrorMessage(code, detail));
797
923
  this.code = code;
798
924
  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
925
  }
@@ -813,13 +939,18 @@ function capture(value, invalid9, oversized, limit = ROUTINE_AI_VALUE_BYTES) {
813
939
  function parseRoutineAIRequest(value) {
814
940
  let v = capture(value, "invalid_input", "input_too_large");
815
941
  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();
942
+ 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
943
  let input = typeof v.input == "string" ? v.input : parseFileDescriptor(v.input);
818
- if (v.kind === "generateText") return freezeRoutineMetadata({ kind: v.kind, instructions: v.instructions, input });
944
+ if (Object.hasOwn(v, "tools") && typeof v.tools != "boolean")
945
+ throw new AIError("invalid_input", { detail: { reason: "invalid_tools" } });
946
+ if (v.tools === !0 && typeof input == "string")
947
+ throw new AIError("invalid_input", { detail: { reason: "tools_require_file" } });
948
+ let tools = v.tools === !0 ? { tools: !0 } : {};
949
+ if (v.kind === "generateText") return freezeRoutineMetadata({ kind: v.kind, instructions: v.instructions, input, ...tools });
819
950
  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");
951
+ return freezeRoutineMetadata({ kind: v.kind, instructions: v.instructions, input, schema: schema2, ...tools });
952
+ } catch (error) {
953
+ throw error instanceof AIError ? error : new AIError("invalid_input");
823
954
  }
824
955
  }
825
956
  function parseRoutineAIResult(request2, value) {
@@ -995,11 +1126,12 @@ async function executeRoutineDefinition(definition, input, runtime) {
995
1126
  if (!runtime.ai) throw new AIError("capability_unavailable");
996
1127
  let request2;
997
1128
  try {
998
- assertRoutineFields(value, kind === "generateObject" ? ["instructions", "input", "schema"] : ["instructions", "input"]), request2 = parseRoutineAIRequest({
1129
+ assertRoutineFields(value, kind === "generateObject" ? ["instructions", "input", "schema", "tools"] : ["instructions", "input", "tools"]), request2 = parseRoutineAIRequest({
999
1130
  kind,
1000
1131
  instructions: value.instructions,
1001
1132
  input: value.input,
1002
- ...kind === "generateObject" ? { schema: schemaMetadata(value.schema).schema } : {}
1133
+ ...kind === "generateObject" ? { schema: schemaMetadata(value.schema).schema } : {},
1134
+ ...value.tools === void 0 ? {} : { tools: value.tools }
1003
1135
  });
1004
1136
  } catch (error) {
1005
1137
  throw error instanceof AIError ? error : new AIError("invalid_input");
@@ -1021,10 +1153,16 @@ async function executeRoutineDefinition(definition, input, runtime) {
1021
1153
  }) : value, validateHandlers = (value) => {
1022
1154
  for (let question2 of value.questions ?? []) if (question2.handler && !contract.responses.some((handler) => handler.id === question2.handler.id)) return invalid3();
1023
1155
  return value;
1024
- }, os2 = Object.freeze({
1156
+ }, activeStep, progress2 = /* @__PURE__ */ new Map(), os2 = Object.freeze({
1025
1157
  ...guarded({
1026
1158
  ...runtime,
1027
- run: freezeRoutineMetadata({ ...runtime.run }),
1159
+ run: freezeRoutineMetadata({ id: runtime.run.id, async progress(value) {
1160
+ assertRoutineFields(value, ["completed", "total"]);
1161
+ let { completed, total } = value, prior = activeStep ? progress2.get(activeStep) : void 0;
1162
+ if (!active2 || broken || !activeStep || !Number.isSafeInteger(completed) || !Number.isSafeInteger(total) || completed < 1 || completed > total || prior && (prior.total !== total || completed <= prior.completed)) return invalid3();
1163
+ let accepted = freezeRoutineMetadata({ step: activeStep, completed, total });
1164
+ await runtime.run.progress(accepted), progress2.set(activeStep, accepted);
1165
+ } }),
1028
1166
  ai: {
1029
1167
  generateText: (value) => generate("generateText", value),
1030
1168
  generateObject: (value) => generate("generateObject", value)
@@ -1049,13 +1187,13 @@ async function executeRoutineDefinition(definition, input, runtime) {
1049
1187
  if (broken) return invalid3();
1050
1188
  reached.add(id3);
1051
1189
  try {
1052
- await runtime.step(id3, { status: "running" });
1190
+ await runtime.step(id3, { status: "running" }), activeStep = id3;
1053
1191
  let output = await steps[id3].run(saved, os2);
1054
1192
  if (assertRoutineFields(output, ["summary", "data"]), !Object.hasOwn(output, "summary") || !Object.hasOwn(output, "data")) return invalid3();
1055
1193
  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 });
1194
+ return await runtime.step(id3, { status: "completed", summary: summary2, data: data3 }), activeStep = void 0, freezeRoutineMetadata({ summary: summary2, data: data3 });
1057
1195
  } catch (error) {
1058
- throw broken = !0, await runtime.step(id3, { status: "failed" }), error;
1196
+ throw broken = !0, activeStep = void 0, await runtime.step(id3, { status: "failed" }), error;
1059
1197
  }
1060
1198
  });
1061
1199
  return work = next.then(() => {
@@ -1541,7 +1679,7 @@ function capture3(value, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
1541
1679
  ancestors.delete(value);
1542
1680
  }
1543
1681
  }
1544
- function recordPagination(select) {
1682
+ function recordPagination(queryRecords) {
1545
1683
  function pages(query) {
1546
1684
  let captured = capture3(query), names = Object.keys(historyProperties(captured));
1547
1685
  for (let name2 of names) {
@@ -1563,7 +1701,7 @@ function recordPagination(select) {
1563
1701
  let request2 = Object.fromEntries(active2.map((name2) => {
1564
1702
  let selection2 = captured[name2], next = state.get(name2).next;
1565
1703
  return [name2, next === void 0 ? selection2 : { ...selection2, $: { ...selection2.$, after: next } }];
1566
- })), response2 = await select(request2), page;
1704
+ })), response2 = await queryRecords(request2), page;
1567
1705
  try {
1568
1706
  page = capture3(response2);
1569
1707
  let keys4 = Object.keys(historyProperties(page));
@@ -1647,7 +1785,7 @@ var genericData = {
1647
1785
  let refs = captureRecordsReadRefs(typeof input == "string" ? [input] : input), captured = captureRecordsReadOptions(options), result2 = await send3({ operation: "records.read", refs, ...options === void 0 ? {} : { options: captured } });
1648
1786
  return typeof input == "string" ? result2.records[0] : result2;
1649
1787
  }
1650
- let select = (query) => send3({ operation: "records.select", query });
1788
+ let queryRecords = (query) => send3({ operation: "records.query", query });
1651
1789
  return {
1652
1790
  collections: {
1653
1791
  list: () => send3({ operation: "collections.list" }),
@@ -1655,8 +1793,8 @@ var genericData = {
1655
1793
  },
1656
1794
  records: {
1657
1795
  read: read2,
1658
- select,
1659
- ...recordPagination(select),
1796
+ query: queryRecords,
1797
+ ...recordPagination(queryRecords),
1660
1798
  search: async (query, options) => send3({ operation: "records.search", ...captureRecordsSearchInput(query, options) })
1661
1799
  },
1662
1800
  history: {
@@ -2301,16 +2439,16 @@ function parseConnectionLease(raw, now = Date.now()) {
2301
2439
  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
2440
  throw new Error("Invalid connection credential");
2303
2441
  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 + 6e4 || Date.parse(v.expiresAt) > now + 36e5)
2442
+ 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
2443
  throw new Error("Invalid connection credential");
2306
2444
  return v;
2307
2445
  }
2308
2446
  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 + 6e4 || Date.parse(v.expiresAt) > now + 36e5)
2447
+ 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
2448
  throw new Error("Invalid connection credential");
2311
2449
  return v;
2312
2450
  }
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 + 6e4 && 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 + 6e4 && Date.parse(v.expiresAt) <= now + 144e5)
2451
+ 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
2452
  return v;
2315
2453
  throw new Error("Invalid connection credential");
2316
2454
  }
@@ -4173,6 +4311,11 @@ function times(value) {
4173
4311
  function bounded(value) {
4174
4312
  return assertRoutineJson(value), new TextEncoder().encode(JSON.stringify(value)).byteLength > 256 * 1024 ? invalid8() : value;
4175
4313
  }
4314
+ function progress(value) {
4315
+ if (value === void 0) return {};
4316
+ let v = record3(value);
4317
+ 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 } };
4318
+ }
4176
4319
  function parseRunStep(value, expected) {
4177
4320
  let v = record3(value);
4178
4321
  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 +4323,7 @@ function parseRunStep(value, expected) {
4180
4323
  title: v.title,
4181
4324
  status: v.status,
4182
4325
  ...times(v),
4326
+ ...progress(v.progress),
4183
4327
  ...Object.hasOwn(v, "summary") ? { summary: bounded(v.summary) } : {},
4184
4328
  ...Object.hasOwn(v, "data") ? { data: bounded(v.data) } : {}
4185
4329
  };
@@ -4283,12 +4427,12 @@ async function request(key, suffix, init2, maximum = SUMMARY_BYTES, retained, re
4283
4427
  function summary(value, key, context, id3) {
4284
4428
  if (context.kind !== "local")
4285
4429
  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 });
4430
+ 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
4431
  } catch {
4288
4432
  return fail3("execution_unavailable");
4289
4433
  }
4290
4434
  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;
4435
+ 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
4436
  }
4293
4437
  function only(value, allowed) {
4294
4438
  return !!value && typeof value == "object" && !Array.isArray(value) && Object.keys(value).every((key) => allowed.split(",").includes(key));
@@ -4298,7 +4442,7 @@ function optionalTimes(value) {
4298
4442
  return (value.startedAt === void 0 || timestamp(value.startedAt)) && (value.finishedAt === void 0 || timestamp(value.finishedAt));
4299
4443
  }
4300
4444
  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));
4445
+ 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
4446
  }
4303
4447
  function validLocalActivity(value) {
4304
4448
  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 +4483,11 @@ function handle(key, id3, context) {
4339
4483
  let value = await request(key, `runs/${id3}/steps/${step}`, void 0, 514 * 1024, context, !0);
4340
4484
  if (context.kind !== "local")
4341
4485
  try {
4342
- return only(value, "id,title,status,startedAt,finishedAt,summary,data") || fail3("execution_unavailable"), parseRunStep(value, step);
4486
+ return only(value, "id,title,status,startedAt,finishedAt,progress,summary,data") || fail3("execution_unavailable"), parseRunStep(value, step);
4343
4487
  } catch {
4344
4488
  return fail3("execution_unavailable");
4345
4489
  }
4346
- return (!only(value, "id,title,status,startedAt,finishedAt,summary,data") || value.id !== step || !validStep(value)) && fail3("execution_unavailable"), value;
4490
+ return (!only(value, "id,title,status,startedAt,finishedAt,progress,summary,data") || value.id !== step || !validStep(value)) && fail3("execution_unavailable"), value;
4347
4491
  }
4348
4492
  });
4349
4493
  }