@oddington/sdk 0.1.34 → 0.1.36

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/dist/browser.js CHANGED
@@ -1,14 +1,49 @@
1
+ var fileValidationReasons = {
2
+ invalid_metadata: "File metadata must contain only the supported fields.",
3
+ invalid_name: "File name must be 1\u2013255 characters, with no reserved names, path separators, control characters or trailing spaces/dots.",
4
+ invalid_path: "File path must contain safe relative folder names and cannot start with .os.",
5
+ invalid_storage: "File storage must be local or remote.",
6
+ invalid_media_type: "File media type must name one valid type/subtype.",
7
+ invalid_size: "File size must be a non-negative safe integer byte count.",
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.",
11
+ invalid_bytes: "File content must contain byte chunks."
12
+ };
13
+ function parseFileErrorDetail(value) {
14
+ if (!value || typeof value != "object" || Array.isArray(value)) return;
15
+ let v = value;
16
+ if (typeof v.reason != "string") return;
17
+ if (Object.hasOwn(fileValidationReasons, v.reason))
18
+ return Object.keys(v).length === 1 ? { reason: v.reason } : void 0;
19
+ if (v.reason !== "size_mismatch" && v.reason !== "size_limit") return;
20
+ let count = v.reason === "size_mismatch" ? "expectedBytes" : "limitBytes";
21
+ if (!(Object.keys(v).some((key) => !["reason", count, "receivedBytes", "receivedAtLeast"].includes(key)) || !Number.isSafeInteger(v[count]) || v[count] < 0 || !Number.isSafeInteger(v.receivedBytes) || v.receivedBytes < 0 || v.receivedAtLeast !== void 0 && v.receivedAtLeast !== !0))
22
+ return {
23
+ reason: v.reason,
24
+ [count]: v[count],
25
+ receivedBytes: v.receivedBytes,
26
+ ...v.receivedAtLeast ? { receivedAtLeast: !0 } : {}
27
+ };
28
+ }
29
+ function fileErrorExplanation(detail) {
30
+ 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];
31
+ }
1
32
  var FileOperationError = class extends Error {
2
- constructor(code, status, retryable = !1) {
3
- super(code);
33
+ constructor(code, status, retryable = !1, detail) {
34
+ let safe = ["invalid_input", "request_too_large"].includes(code) ? parseFileErrorDetail(detail) : void 0;
35
+ super(safe ? `${code}: ${fileErrorExplanation(safe)}` : code);
4
36
  this.code = code;
5
37
  this.status = status;
6
38
  this.retryable = retryable;
39
+ safe && (this.detail = Object.freeze(safe));
7
40
  }
8
41
  code;
9
42
  status;
10
43
  retryable;
11
- }, FILE_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/, FILE_ORIGINAL_MAX_BYTES = 5 * 1024 ** 3;
44
+ detail;
45
+ };
46
+ var FILE_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/, FILE_ORIGINAL_MAX_BYTES = 5 * 1024 ** 3;
12
47
  function parseFileDescriptor(value) {
13
48
  let invalid9 = () => {
14
49
  throw new FileOperationError("unavailable", 503, !0);
@@ -34,13 +69,22 @@ function safeEnrichmentName(value) {
34
69
  return safeFileName(value) && !/^original(?:\.|$)/i.test(value);
35
70
  }
36
71
  function parseFileEnrichMetadata(value, maxBytes) {
37
- let invalid9 = () => {
38
- throw new FileOperationError("invalid_input", 400);
72
+ let invalid9 = (reason = "invalid_metadata") => {
73
+ throw new FileOperationError("invalid_input", 400, !1, { reason });
39
74
  };
40
75
  if (!value || typeof value != "object" || Array.isArray(value)) return invalid9();
41
76
  let d = value;
42
- if (Object.keys(d).some((key) => !["name", "size", "mediaType", "replace"].includes(key)) || !safeEnrichmentName(d.name) || !Number.isSafeInteger(d.size) || d.size < 0 || d.mediaType !== void 0 && (typeof d.mediaType != "string" || d.mediaType.length > 255 || /[\u0000-\u001f\u007f]/.test(d.mediaType)) || d.replace !== void 0 && (typeof d.replace != "string" || !/^[0-9a-f]{64}$/.test(d.replace))) return invalid9();
43
- if (d.size > maxBytes) throw new FileOperationError("request_too_large", 413);
77
+ if (Object.keys(d).some((key) => !["name", "size", "mediaType", "replace"].includes(key))) return invalid9();
78
+ if (!safeEnrichmentName(d.name)) return invalid9("invalid_enrichment_name");
79
+ if (!Number.isSafeInteger(d.size) || d.size < 0) return invalid9("invalid_size");
80
+ if (d.mediaType !== void 0 && (typeof d.mediaType != "string" || d.mediaType.length > 255 || /[\u0000-\u001f\u007f]/.test(d.mediaType))) return invalid9("invalid_media_type");
81
+ if (d.replace !== void 0 && (typeof d.replace != "string" || !/^[0-9a-f]{64}$/.test(d.replace))) return invalid9("invalid_replacement");
82
+ if (d.size > maxBytes) throw new FileOperationError(
83
+ "request_too_large",
84
+ 413,
85
+ !1,
86
+ { reason: "size_limit", limitBytes: maxBytes, receivedBytes: d.size }
87
+ );
44
88
  return d;
45
89
  }
46
90
  function safeFileName(value) {
@@ -54,9 +98,9 @@ function safeManagedFilePath(value) {
54
98
  }
55
99
  function declaredOriginalMediaType(value) {
56
100
  if (typeof value != "string" || value.length > 255 || /[\u0000-\u001f\u007f-\u009f]/.test(value))
57
- throw new FileOperationError("invalid_input", 400);
101
+ throw new FileOperationError("invalid_input", 400, !1, { reason: "invalid_media_type" });
58
102
  let normalized = value.split(";", 1)[0].trim().toLowerCase(), token = "[a-z0-9!#$%&'+.^_`|~-]+";
59
- if (!new RegExp(`^${token}/${token}$`).test(normalized)) throw new FileOperationError("invalid_input", 400);
103
+ if (!new RegExp(`^${token}/${token}$`).test(normalized)) throw new FileOperationError("invalid_input", 400, !1, { reason: "invalid_media_type" });
60
104
  return normalized;
61
105
  }
62
106
  function isCanonicalOriginalMediaType(value) {
@@ -67,18 +111,25 @@ function isCanonicalOriginalMediaType(value) {
67
111
  }
68
112
  }
69
113
  function parseFileCreateMetadata(value, maxBytes) {
70
- let invalid9 = () => {
71
- throw new FileOperationError("invalid_input", 400);
114
+ let invalid9 = (reason = "invalid_metadata") => {
115
+ throw new FileOperationError("invalid_input", 400, !1, { reason });
72
116
  };
73
117
  if (!value || typeof value != "object" || Array.isArray(value)) return invalid9();
74
118
  let input = value;
75
119
  if (Object.keys(input).some((key) => !["name", "path", "storage", "mediaType", "size", "requestKey"].includes(key))) return invalid9();
76
120
  let path = input.path === void 0 ? "Files" : input.path;
77
- if (!safeFileName(input.name) || !safeManagedFilePath(path) || input.storage !== void 0 && input.storage !== "remote" && input.storage !== "local") return invalid9();
121
+ if (!safeFileName(input.name)) return invalid9("invalid_name");
122
+ if (!safeManagedFilePath(path)) return invalid9("invalid_path");
123
+ if (input.storage !== void 0 && input.storage !== "remote" && input.storage !== "local") return invalid9("invalid_storage");
78
124
  let mediaType = input.mediaType === void 0 ? "application/octet-stream" : declaredOriginalMediaType(input.mediaType);
79
- if (!Number.isSafeInteger(input.size) || input.size < 0) return invalid9();
80
- if (input.size > maxBytes) throw new FileOperationError("request_too_large", 413);
81
- return typeof input.requestKey != "string" || input.requestKey.length < 1 || input.requestKey.length > 256 || input.requestKey !== input.requestKey.trim() || /[\u0000-\u001f\u007f]/.test(input.requestKey) ? invalid9() : {
125
+ if (!Number.isSafeInteger(input.size) || input.size < 0) return invalid9("invalid_size");
126
+ if (input.size > maxBytes) throw new FileOperationError(
127
+ "request_too_large",
128
+ 413,
129
+ !1,
130
+ { reason: "size_limit", limitBytes: maxBytes, receivedBytes: input.size }
131
+ );
132
+ return typeof input.requestKey != "string" || input.requestKey.length < 1 || input.requestKey.length > 256 || input.requestKey !== input.requestKey.trim() || /[\u0000-\u001f\u007f]/.test(input.requestKey) ? invalid9("invalid_request_key") : {
82
133
  name: input.name,
83
134
  path,
84
135
  ...input.storage ? { storage: input.storage } : {},
@@ -88,6 +139,129 @@ function parseFileCreateMetadata(value, maxBytes) {
88
139
  };
89
140
  }
90
141
 
142
+ var AppRoutineContractError = class extends Error {
143
+ constructor(code) {
144
+ super(code);
145
+ this.code = code;
146
+ }
147
+ code;
148
+ }, propertyName = /^[a-z][A-Za-z0-9]*(?![\s\S])/, stepId = /^[a-z0-9]+(?:-[a-z0-9]+)*(?![\s\S])/, routineName = /^[a-z0-9][a-z0-9-]{0,62}(?![\s\S])/;
149
+ function isAppRoutineName(value) {
150
+ return typeof value == "string" && routineName.test(value);
151
+ }
152
+ var owns = (value, key) => Object.hasOwn(value, key), invalidContract = () => {
153
+ throw new AppRoutineContractError("invalid_contract");
154
+ }, invalidValue = () => {
155
+ throw new AppRoutineContractError("invalid_value");
156
+ };
157
+ function record(value) {
158
+ return value !== null && typeof value == "object" && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
159
+ }
160
+ function fields(value, required, optional = []) {
161
+ return required.every((key) => owns(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
162
+ }
163
+ function denseArray(value) {
164
+ return Array.isArray(value) && Object.keys(value).length === value.length && Array.from({ length: value.length }, (_, index) => owns(value, String(index))).every(Boolean);
165
+ }
166
+ function parseAppRoutineContract(value) {
167
+ let properties = 0;
168
+ function schema2(value2, depth = 1) {
169
+ if (depth > 8 || !record(value2)) return invalidContract();
170
+ switch (value2.type) {
171
+ case "object": {
172
+ if (!fields(value2, ["type", "properties", "additionalProperties"], ["required"]) || value2.additionalProperties !== !1 || !record(value2.properties)) return invalidContract();
173
+ let entries = Object.entries(value2.properties);
174
+ if (properties += entries.length, properties > 64 || entries.some(([key]) => !propertyName.test(key))) return invalidContract();
175
+ let required;
176
+ if (owns(value2, "required")) {
177
+ if (!denseArray(value2.required) || value2.required.some((key) => typeof key != "string" || !owns(value2.properties, key)) || new Set(value2.required).size !== value2.required.length) return invalidContract();
178
+ required = [...value2.required];
179
+ }
180
+ return {
181
+ type: "object",
182
+ properties: Object.fromEntries(entries.map(([key, child]) => [key, schema2(child, depth + 1)])),
183
+ ...required === void 0 ? {} : { required },
184
+ additionalProperties: !1
185
+ };
186
+ }
187
+ case "array":
188
+ return !fields(value2, ["type", "items", "maxItems"]) || !Number.isSafeInteger(value2.maxItems) || value2.maxItems < 1 || value2.maxItems > 64 ? invalidContract() : { type: "array", items: schema2(value2.items, depth + 1), maxItems: value2.maxItems };
189
+ case "boolean":
190
+ return fields(value2, ["type"]) ? { type: value2.type } : invalidContract();
191
+ case "file":
192
+ return !fields(value2, ["type"], ["storage", "path"]) || owns(value2, "storage") && value2.storage !== "local" && value2.storage !== "remote" || owns(value2, "path") && !safeManagedFilePath(value2.path) ? invalidContract() : {
193
+ type: "file",
194
+ ...owns(value2, "storage") ? { storage: value2.storage } : {},
195
+ ...owns(value2, "path") ? { path: value2.path } : {}
196
+ };
197
+ case "string":
198
+ return !fields(value2, ["type"], ["maxLength"]) || owns(value2, "maxLength") && (!Number.isSafeInteger(value2.maxLength) || value2.maxLength < 0 || value2.maxLength > 4096) ? invalidContract() : { type: "string", ...owns(value2, "maxLength") ? { maxLength: value2.maxLength } : {} };
199
+ case "integer":
200
+ case "number": {
201
+ let validNumber = value2.type === "integer" ? Number.isSafeInteger : Number.isFinite;
202
+ return !fields(value2, ["type"], ["minimum", "maximum"]) || owns(value2, "minimum") && !validNumber(value2.minimum) || owns(value2, "maximum") && !validNumber(value2.maximum) || owns(value2, "minimum") && owns(value2, "maximum") && value2.minimum > value2.maximum ? invalidContract() : {
203
+ type: value2.type,
204
+ ...owns(value2, "minimum") ? { minimum: value2.minimum } : {},
205
+ ...owns(value2, "maximum") ? { maximum: value2.maximum } : {}
206
+ };
207
+ }
208
+ default:
209
+ return invalidContract();
210
+ }
211
+ }
212
+ if (!record(value) || !fields(value, ["routine", "input", "steps"], ["result"]) || !isAppRoutineName(value.routine) || !denseArray(value.steps) || value.steps.length < 1 || value.steps.length > 64) return invalidContract();
213
+ let input = schema2(value.input), result2 = owns(value, "result") ? schema2(value.result) : void 0, ids = /* @__PURE__ */ new Set(), steps = value.steps.map((step) => !record(step) || !fields(step, ["id"], ["summary"]) || typeof step.id != "string" || !stepId.test(step.id) || ids.has(step.id) ? invalidContract() : (ids.add(step.id), { id: step.id, ...owns(step, "summary") ? { summary: schema2(step.summary) } : {} }));
214
+ return { routine: value.routine, input, steps, ...result2 === void 0 ? {} : { result: result2 } };
215
+ }
216
+ function jsonData(value, ancestors = /* @__PURE__ */ new Set()) {
217
+ if (!(value === null || typeof value == "string" || typeof value == "boolean" || typeof value == "number" && Number.isFinite(value))) {
218
+ if (!record(value) && !denseArray(value) || ancestors.has(value)) return invalidValue();
219
+ ancestors.add(value);
220
+ for (let child of Object.values(value)) jsonData(child, ancestors);
221
+ ancestors.delete(value);
222
+ }
223
+ }
224
+ function validateRoutineSchemaValue(schema2, value, rejected) {
225
+ let type = (value2) => value2 === null ? "null" : Array.isArray(value2) ? "array" : ["object", "boolean", "string", "number"].includes(typeof value2) ? typeof value2 : "other";
226
+ function check(schema3, value2, path) {
227
+ let fail4 = (reason, extra = {}) => (rejected?.({ reason, path: path.length <= 256 ? path : "$", expected: schema3.type, received: type(value2), ...extra }), invalidValue());
228
+ switch (schema3.type) {
229
+ case "object":
230
+ if (!record(value2)) return fail4("wrong_type");
231
+ for (let key of schema3.required ?? []) if (!owns(value2, key)) return fail4("missing_field", {
232
+ path: `${path}.${key}`.length <= 256 ? `${path}.${key}` : "$",
233
+ expected: schema3.properties[key].type,
234
+ received: "missing"
235
+ });
236
+ if (Object.keys(value2).some((key) => !owns(schema3.properties, key))) return fail4("unexpected_field");
237
+ for (let [key, child] of Object.entries(value2)) check(schema3.properties[key], child, `${path}.${key}`);
238
+ return;
239
+ case "array":
240
+ if (!denseArray(value2)) return fail4("wrong_type");
241
+ if (value2.length > schema3.maxItems) return fail4("out_of_bounds", { constraint: "maxItems", limit: schema3.maxItems });
242
+ value2.forEach((child, index) => check(schema3.items, child, `${path}[${index}]`));
243
+ return;
244
+ case "boolean":
245
+ return typeof value2 != "boolean" ? fail4("wrong_type") : void 0;
246
+ case "string":
247
+ return typeof value2 != "string" ? fail4("wrong_type") : Array.from(value2).length > (schema3.maxLength ?? 4096) ? fail4("out_of_bounds", { constraint: "maxLength", limit: schema3.maxLength ?? 4096 }) : void 0;
248
+ case "integer":
249
+ case "number":
250
+ return typeof value2 != "number" || !(schema3.type === "integer" ? Number.isSafeInteger(value2) : Number.isFinite(value2)) ? fail4("wrong_type") : schema3.minimum !== void 0 && value2 < schema3.minimum ? fail4("out_of_bounds", { constraint: "minimum", limit: schema3.minimum }) : schema3.maximum !== void 0 && value2 > schema3.maximum ? fail4("out_of_bounds", { constraint: "maximum", limit: schema3.maximum }) : void 0;
251
+ case "file": {
252
+ let file2;
253
+ try {
254
+ jsonData(value2), file2 = parseFileDescriptor(value2);
255
+ } catch {
256
+ return fail4("invalid_file");
257
+ }
258
+ if (schema3.path !== void 0 && file2.path !== schema3.path) return fail4("invalid_file");
259
+ }
260
+ }
261
+ }
262
+ check(schema2, value, "$");
263
+ }
264
+
91
265
  var InboxContractError = class extends Error {
92
266
  constructor(message2) {
93
267
  super(message2), this.name = "InboxContractError";
@@ -157,7 +331,7 @@ function validateInboxFile(value) {
157
331
  let size = natural(v.size, "file size");
158
332
  return size > FILE_ORIGINAL_MAX_BYTES && fail("File exceeds supported size"), { id: fileId, name: name2, mediaType, size, sha256: v.sha256 };
159
333
  }
160
- function fields(value) {
334
+ function fields2(value) {
161
335
  let result2 = array(value, "selected fields").map((entry) => {
162
336
  let field = object(entry, "field");
163
337
  return keys(field, ["label", "value"], "field"), { label: text(field.label, "field label"), value: text(field.value, "field value") };
@@ -184,7 +358,7 @@ function validateBlock(value) {
184
358
  label,
185
359
  ...target,
186
360
  ...expanded,
187
- record: { ref: id(r.ref, "record reference"), title: text(r.title, "record title"), fields: fields(r.fields) }
361
+ record: { ref: id(r.ref, "record reference"), title: text(r.title, "record title"), fields: fields2(r.fields) }
188
362
  };
189
363
  }
190
364
  if (b.kind === "records") {
@@ -194,7 +368,7 @@ function validateBlock(value) {
194
368
  ref: id(r.ref, "record reference"),
195
369
  title: text(r.title, "record title"),
196
370
  ...r.description === void 0 ? {} : { description: text(r.description, "record description") },
197
- ...r.facts === void 0 ? {} : { facts: fields(r.facts) },
371
+ ...r.facts === void 0 ? {} : { facts: fields2(r.facts) },
198
372
  ...r.view === void 0 ? {} : { view: id(r.view, "row View target") }
199
373
  };
200
374
  });
@@ -452,138 +626,6 @@ function createInboxPublisher(transport) {
452
626
  };
453
627
  }
454
628
 
455
- var REPOSITORY = /^[a-z0-9][a-z0-9._/-]{0,127}$/;
456
- function validRepositoryAlias(value) {
457
- return typeof value == "string" && REPOSITORY.test(value) && !value.includes("//") && !value.includes("..");
458
- }
459
-
460
- var RECORD_READ_INPUT_LIMIT = 1024 * 1024, RECORD_CAPTURE_OUTPUT_LIMIT = 25 * 1024 * 1024;
461
- function validRecordRef(value) {
462
- return typeof value == "string" && value.length <= 512 && /^[_a-zA-Z0-9][a-zA-Z0-9_-]*(\/[_a-zA-Z0-9][a-zA-Z0-9_-]*)+$/.test(value);
463
- }
464
- function validateRecordRead(value) {
465
- if (!value || typeof value != "object" || Array.isArray(value))
466
- throw new InboxContractError("Invalid selected-record read");
467
- let input = value;
468
- if (Object.keys(input).some((key) => !["repository", "refs", "atCommit"].includes(key)) || !validRepositoryAlias(input.repository) || !Array.isArray(input.refs) || !input.refs.length || input.refs.length > 1e4 || input.refs.some((ref) => !validRecordRef(ref)) || new Set(input.refs).size !== input.refs.length || input.atCommit !== void 0 && (typeof input.atCommit != "string" || !/^[0-9a-f]{40}$/.test(input.atCommit)))
469
- throw new InboxContractError("Invalid selected-record read");
470
- return {
471
- repository: input.repository,
472
- refs: [...input.refs],
473
- ...input.atCommit === void 0 ? {} : { atCommit: input.atCommit }
474
- };
475
- }
476
-
477
- var AppRoutineContractError = class extends Error {
478
- constructor(code) {
479
- super(code);
480
- this.code = code;
481
- }
482
- code;
483
- }, propertyName = /^[a-z][A-Za-z0-9]*(?![\s\S])/, stepId = /^[a-z0-9]+(?:-[a-z0-9]+)*(?![\s\S])/, routineName = /^[a-z0-9][a-z0-9-]{0,62}(?![\s\S])/;
484
- function isAppRoutineName(value) {
485
- return typeof value == "string" && routineName.test(value);
486
- }
487
- var owns = (value, key) => Object.hasOwn(value, key), invalidContract = () => {
488
- throw new AppRoutineContractError("invalid_contract");
489
- }, invalidValue = () => {
490
- throw new AppRoutineContractError("invalid_value");
491
- };
492
- function record(value) {
493
- return value !== null && typeof value == "object" && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
494
- }
495
- function fields2(value, required, optional = []) {
496
- return required.every((key) => owns(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
497
- }
498
- function denseArray(value) {
499
- return Array.isArray(value) && Object.keys(value).length === value.length && Array.from({ length: value.length }, (_, index) => owns(value, String(index))).every(Boolean);
500
- }
501
- function parseAppRoutineContract(value) {
502
- let properties = 0;
503
- function schema2(value2, depth = 1) {
504
- if (depth > 8 || !record(value2)) return invalidContract();
505
- switch (value2.type) {
506
- case "object": {
507
- if (!fields2(value2, ["type", "properties", "additionalProperties"], ["required"]) || value2.additionalProperties !== !1 || !record(value2.properties)) return invalidContract();
508
- let entries = Object.entries(value2.properties);
509
- if (properties += entries.length, properties > 64 || entries.some(([key]) => !propertyName.test(key))) return invalidContract();
510
- let required;
511
- if (owns(value2, "required")) {
512
- if (!denseArray(value2.required) || value2.required.some((key) => typeof key != "string" || !owns(value2.properties, key)) || new Set(value2.required).size !== value2.required.length) return invalidContract();
513
- required = [...value2.required];
514
- }
515
- return {
516
- type: "object",
517
- properties: Object.fromEntries(entries.map(([key, child]) => [key, schema2(child, depth + 1)])),
518
- ...required === void 0 ? {} : { required },
519
- additionalProperties: !1
520
- };
521
- }
522
- case "array":
523
- return !fields2(value2, ["type", "items", "maxItems"]) || !Number.isSafeInteger(value2.maxItems) || value2.maxItems < 1 || value2.maxItems > 64 ? invalidContract() : { type: "array", items: schema2(value2.items, depth + 1), maxItems: value2.maxItems };
524
- case "boolean":
525
- return fields2(value2, ["type"]) ? { type: value2.type } : invalidContract();
526
- case "file":
527
- return !fields2(value2, ["type"], ["storage", "path"]) || owns(value2, "storage") && value2.storage !== "local" && value2.storage !== "remote" || owns(value2, "path") && !safeManagedFilePath(value2.path) ? invalidContract() : {
528
- type: "file",
529
- ...owns(value2, "storage") ? { storage: value2.storage } : {},
530
- ...owns(value2, "path") ? { path: value2.path } : {}
531
- };
532
- case "string":
533
- return !fields2(value2, ["type"], ["maxLength"]) || owns(value2, "maxLength") && (!Number.isSafeInteger(value2.maxLength) || value2.maxLength < 0 || value2.maxLength > 4096) ? invalidContract() : { type: "string", ...owns(value2, "maxLength") ? { maxLength: value2.maxLength } : {} };
534
- case "integer":
535
- case "number": {
536
- let validNumber = value2.type === "integer" ? Number.isSafeInteger : Number.isFinite;
537
- return !fields2(value2, ["type"], ["minimum", "maximum"]) || owns(value2, "minimum") && !validNumber(value2.minimum) || owns(value2, "maximum") && !validNumber(value2.maximum) || owns(value2, "minimum") && owns(value2, "maximum") && value2.minimum > value2.maximum ? invalidContract() : {
538
- type: value2.type,
539
- ...owns(value2, "minimum") ? { minimum: value2.minimum } : {},
540
- ...owns(value2, "maximum") ? { maximum: value2.maximum } : {}
541
- };
542
- }
543
- default:
544
- return invalidContract();
545
- }
546
- }
547
- if (!record(value) || !fields2(value, ["routine", "input", "steps"], ["result"]) || !isAppRoutineName(value.routine) || !denseArray(value.steps) || value.steps.length < 1 || value.steps.length > 64) return invalidContract();
548
- let input = schema2(value.input), result2 = owns(value, "result") ? schema2(value.result) : void 0, ids = /* @__PURE__ */ new Set(), steps = value.steps.map((step) => !record(step) || !fields2(step, ["id"], ["summary"]) || typeof step.id != "string" || !stepId.test(step.id) || ids.has(step.id) ? invalidContract() : (ids.add(step.id), { id: step.id, ...owns(step, "summary") ? { summary: schema2(step.summary) } : {} }));
549
- return { routine: value.routine, input, steps, ...result2 === void 0 ? {} : { result: result2 } };
550
- }
551
- function jsonData(value, ancestors = /* @__PURE__ */ new Set()) {
552
- if (!(value === null || typeof value == "string" || typeof value == "boolean" || typeof value == "number" && Number.isFinite(value))) {
553
- if (!record(value) && !denseArray(value) || ancestors.has(value)) return invalidValue();
554
- ancestors.add(value);
555
- for (let child of Object.values(value)) jsonData(child, ancestors);
556
- ancestors.delete(value);
557
- }
558
- }
559
- function validateRoutineSchemaValue(schema2, value) {
560
- switch (schema2.type) {
561
- case "object":
562
- if (!record(value) || (schema2.required ?? []).some((key) => !owns(value, key)) || Object.keys(value).some((key) => !owns(schema2.properties, key))) return invalidValue();
563
- for (let [key, child] of Object.entries(value)) validateRoutineSchemaValue(schema2.properties[key], child);
564
- return;
565
- case "array":
566
- if (!denseArray(value) || value.length > schema2.maxItems) return invalidValue();
567
- for (let child of value) validateRoutineSchemaValue(schema2.items, child);
568
- return;
569
- case "boolean":
570
- return typeof value != "boolean" ? invalidValue() : void 0;
571
- case "string":
572
- return typeof value != "string" || Array.from(value).length > (schema2.maxLength ?? 4096) ? invalidValue() : void 0;
573
- case "integer":
574
- case "number":
575
- return typeof value != "number" || !(schema2.type === "integer" ? Number.isSafeInteger(value) : Number.isFinite(value)) || schema2.minimum !== void 0 && value < schema2.minimum || schema2.maximum !== void 0 && value > schema2.maximum ? invalidValue() : void 0;
576
- case "file":
577
- try {
578
- jsonData(value);
579
- let file2 = parseFileDescriptor(value);
580
- if (schema2.path !== void 0 && file2.path !== schema2.path) return invalidValue();
581
- } catch {
582
- return invalidValue();
583
- }
584
- }
585
- }
586
-
587
629
  var ROUTINE_DECLARATION_SCHEMA = "https://schemas.oddington.ai/routine/v1.json", ROUTINE_SOURCE_LIMITS = { files: 2e4, bytes: 256 * 1024 * 1024, declarationBytes: 64 * 1024 };
588
630
  var invalid = (field) => {
589
631
  throw new Error(`Invalid routine declaration: ${field}`);
@@ -603,11 +645,9 @@ function parseRoutineDeclaration(value) {
603
645
  (!routineAlias(key) || !["read", "write"].includes(repositories[key])) && invalid(`access.repositories.${key}`), grants[key] = repositories[key];
604
646
  access.files !== void 0 && !["read", "read-write"].includes(access.files) && invalid("access.files");
605
647
  let runtime = object2(v.runtime === void 0 ? {} : v.runtime, ["features", "resources", "maxDuration"], "runtime");
606
- runtime.resources !== void 0 && !["standard", "large"].includes(runtime.resources) && invalid("runtime.resources");
607
- let duration = runtime.maxDuration === void 0 ? "15m" : runtime.maxDuration;
608
- (typeof duration != "string" || !/^(?:[1-5][0-9]|60)m$/.test(duration) || Number(duration.slice(0, -1)) < 15) && invalid("runtime.maxDuration");
609
- let retry = object2(v.retry === void 0 ? {} : v.retry, ["maxAttempts"], "retry"), maxAttempts = retry.maxAttempts === void 0 ? 1 : retry.maxAttempts;
610
- (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 3) && invalid("retry.maxAttempts");
648
+ 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");
649
+ let retry = object2(v.retry === void 0 ? {} : v.retry, ["maxAttempts"], "retry");
650
+ retry.maxAttempts !== void 0 && retry.maxAttempts !== 1 && invalid("retry.maxAttempts");
611
651
  let assets = v.assets === void 0 ? [] : v.assets;
612
652
  return (!Array.isArray(assets) || assets.length > 64 || assets.some((item) => !routineAssetPattern(item)) || new Set(assets).size !== assets.length) && invalid("assets"), {
613
653
  $schema: ROUTINE_DECLARATION_SCHEMA,
@@ -618,12 +658,8 @@ function parseRoutineDeclaration(value) {
618
658
  ...access.files === void 0 ? {} : { files: access.files },
619
659
  publish: list(access.publish, "access.publish", ["asks", "digests"])
620
660
  },
621
- runtime: {
622
- features: list(runtime.features, "runtime.features", ["python", "documents", "browser"]),
623
- resources: runtime.resources ?? "standard",
624
- maxDuration: duration
625
- },
626
- retry: { maxAttempts },
661
+ runtime: { features: [], resources: "standard", maxDuration: "15m" },
662
+ retry: { maxAttempts: 1 },
627
663
  assets: [...assets].sort()
628
664
  };
629
665
  }
@@ -672,20 +708,82 @@ function parseRoutineAccess(value) {
672
708
  }
673
709
  }
674
710
 
675
- var ROUTINE_AI_VALUE_BYTES = 256 * 1024, ROUTINE_AI_EVIDENCE_BYTES = 4 * 1024;
711
+ function parseRoutineAIOutputIssue(value) {
712
+ if (!value || typeof value != "object" || Array.isArray(value)) return;
713
+ let v = value;
714
+ if (["invalid_json", "invalid_response", "empty_text"].includes(String(v.reason)))
715
+ return Object.keys(v).join(",") === "reason" ? { reason: v.reason } : void 0;
716
+ 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)))) {
717
+ if (v.reason === "out_of_bounds") {
718
+ if (!["maxLength", "maxItems", "minimum", "maximum"].includes(String(v.constraint)) || typeof v.limit != "number" || !Number.isFinite(v.limit)) return;
719
+ } else if (Object.hasOwn(v, "constraint") || Object.hasOwn(v, "limit")) return;
720
+ return {
721
+ reason: v.reason,
722
+ path: v.path,
723
+ expected: v.expected,
724
+ received: v.received,
725
+ ...v.reason === "out_of_bounds" ? { constraint: v.constraint, limit: v.limit } : {}
726
+ };
727
+ }
728
+ }
729
+ function routineAIOutputMessage(issue) {
730
+ if (issue.reason === "invalid_json") return "AI replied, but its answer could not be read as JSON.";
731
+ if (issue.reason === "invalid_response") return "AI returned a response OS could not read.";
732
+ if (issue.reason === "empty_text") return "AI returned an empty answer.";
733
+ let field = issue.path === "$" ? "response" : issue.path.slice(issue.path.startsWith("$.") ? 2 : 1), expected = {
734
+ object: "an object",
735
+ array: "a list",
736
+ boolean: "true or false",
737
+ string: "text",
738
+ integer: "a whole number",
739
+ number: "a number",
740
+ file: "a File"
741
+ }[issue.expected], received = {
742
+ missing: "a missing field",
743
+ null: "null",
744
+ object: "an object",
745
+ array: "a list",
746
+ boolean: "true or false",
747
+ string: "text",
748
+ number: "a number",
749
+ other: "an unsupported value"
750
+ }[issue.received];
751
+ if (issue.reason === "missing_field") return `AI response is missing a required field.
752
+ ${field}: expected ${expected}; the field was missing.`;
753
+ if (issue.reason === "unexpected_field") return `AI response includes an unexpected field.
754
+ ${field}: only the fields declared by the routine are allowed.`;
755
+ if (issue.reason === "invalid_file") return `AI response contains an invalid File.
756
+ ${field}: expected a valid File matching the routine's requirements.`;
757
+ if (issue.reason === "out_of_bounds") {
758
+ let constraint = {
759
+ maxLength: `at most ${issue.limit} characters`,
760
+ maxItems: `at most ${issue.limit} items`,
761
+ minimum: `a value of at least ${issue.limit}`,
762
+ maximum: `a value of at most ${issue.limit}`
763
+ }[issue.constraint];
764
+ return `AI response is outside a permitted limit.
765
+ ${field}: expected ${constraint}.`;
766
+ }
767
+ return `AI response has the wrong type.
768
+ ${field}: expected ${expected}; received ${received}.`;
769
+ }
770
+
771
+ var ROUTINE_AI_VALUE_BYTES = 256 * 1024, ROUTINE_AI_RESULT_BYTES = 6 * ROUTINE_AI_VALUE_BYTES + 1024, ROUTINE_AI_EVIDENCE_BYTES = 4 * 1024;
676
772
  var RoutineAIError = class extends Error {
677
- constructor(code) {
678
- super(code);
773
+ constructor(code, options = {}) {
774
+ let detail = code === "invalid_output" ? parseRoutineAIOutputIssue(options.detail) : void 0;
775
+ super(detail ? routineAIOutputMessage(detail) : code);
679
776
  this.code = code;
680
- this.name = "RoutineAIError";
777
+ this.name = "RoutineAIError", 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 });
681
778
  }
682
779
  code;
780
+ detail;
683
781
  };
684
- function capture(value, invalid9, oversized) {
782
+ function capture(value, invalid9, oversized, limit = ROUTINE_AI_VALUE_BYTES) {
685
783
  try {
686
784
  assertRoutineJson(value);
687
785
  let encoded = JSON.stringify(value);
688
- if (new TextEncoder().encode(encoded).byteLength > ROUTINE_AI_VALUE_BYTES) throw new RoutineAIError(oversized);
786
+ if (new TextEncoder().encode(encoded).byteLength > limit) throw new RoutineAIError(oversized);
689
787
  return freezeRoutineMetadata(JSON.parse(encoded));
690
788
  } catch (error) {
691
789
  throw error instanceof RoutineAIError ? error : new RoutineAIError(invalid9);
@@ -704,19 +802,47 @@ function parseRoutineAIRequest(value) {
704
802
  }
705
803
  }
706
804
  function parseRoutineAIResult(request2, value) {
707
- let v = capture(value, "invalid_output", "output_too_large");
805
+ let v;
708
806
  try {
709
- if (request2.kind === "generateText") {
710
- if (assertRoutineFields(v, ["text"]), typeof v.text != "string" || !v.text.trim()) throw new Error();
711
- return freezeRoutineMetadata({ text: v.text });
712
- } else {
713
- if (assertRoutineFields(v, ["object"]), !Object.hasOwn(v, "object")) throw new Error();
714
- return validateRoutineSchemaValue(request2.schema, v.object), freezeRoutineMetadata({ object: v.object });
807
+ if (v = capture(
808
+ value,
809
+ "invalid_output",
810
+ "output_too_large",
811
+ request2.kind === "generateText" ? ROUTINE_AI_RESULT_BYTES : ROUTINE_AI_VALUE_BYTES
812
+ ), assertRoutineFields(v, request2.kind === "generateText" ? ["text"] : ["object"]), request2.kind === "generateText") {
813
+ if (typeof v.text != "string") throw new Error();
814
+ return acceptText(request2, v.text);
715
815
  }
816
+ if (!Object.hasOwn(v, "object")) throw new Error();
817
+ } catch (error) {
818
+ throw error instanceof RoutineAIError ? error : new RoutineAIError("invalid_output", { detail: { reason: "invalid_response" } });
819
+ }
820
+ return validateObject(request2.schema, v.object), freezeRoutineMetadata({ object: v.object });
821
+ }
822
+ function validateObject(schema2, object5, text5) {
823
+ let detail = { reason: "invalid_response" };
824
+ try {
825
+ validateRoutineSchemaValue(schema2, object5, (issue) => {
826
+ detail = issue;
827
+ });
716
828
  } catch {
717
- throw new RoutineAIError("invalid_output");
829
+ throw new RoutineAIError("invalid_output", { ...text5 === void 0 ? {} : { text: text5 }, detail });
718
830
  }
719
831
  }
832
+ function acceptText(request2, text5) {
833
+ if (new TextEncoder().encode(text5).byteLength > ROUTINE_AI_VALUE_BYTES) throw new RoutineAIError("output_too_large");
834
+ if (request2.kind === "generateText") {
835
+ if (!text5.trim()) throw new RoutineAIError("invalid_output", { text: text5, detail: { reason: "empty_text" } });
836
+ return freezeRoutineMetadata({ text: text5 });
837
+ }
838
+ let object5;
839
+ try {
840
+ object5 = JSON.parse(text5);
841
+ } catch {
842
+ throw new RoutineAIError("invalid_output", { text: text5, detail: { reason: "invalid_json" } });
843
+ }
844
+ return validateObject(request2.schema, object5, text5), capture({ object: object5 }, "invalid_output", "output_too_large");
845
+ }
720
846
 
721
847
  var LIMIT = 256 * 1024, invalid3 = () => {
722
848
  throw new AppRoutineContractError("invalid_value");
@@ -842,7 +968,7 @@ function captureRoutineInput(definition, input) {
842
968
  return capture2(contract.input, input === void 0 && Object.hasOwn(contract, "inputDefault") ? contract.inputDefault : input);
843
969
  }
844
970
  async function executeRoutineDefinition(definition, input, runtime) {
845
- let captured = captureRoutineInput(definition, input), { contract, steps, run } = definitions.get(definition), active2 = !0, accepting = !0, broken = !1, work = Promise.resolve(), aiFailure, aiWork = /* @__PURE__ */ new Set(), generate = (kind, value) => {
971
+ let captured = captureRoutineInput(definition, input), { contract, steps, run } = definitions.get(definition), active2 = !0, accepting = !0, broken = !1, work = Promise.resolve(), aiWork = /* @__PURE__ */ new Set(), generate = (kind, value) => {
846
972
  let operation2 = (async () => {
847
973
  if (!active2 || broken || contract.access.ai !== !0) throw new RoutineAIError("access_denied");
848
974
  if (!runtime.ai) throw new RoutineAIError("capability_unavailable");
@@ -861,9 +987,7 @@ async function executeRoutineDefinition(definition, input, runtime) {
861
987
  if (!active2 || broken) throw new RoutineAIError("access_denied");
862
988
  return parseRoutineAIResult(request2, result3);
863
989
  })();
864
- return aiWork.add(operation2), operation2.then(() => aiWork.delete(operation2), (error) => {
865
- aiFailure ??= error, broken = !0, aiWork.delete(operation2);
866
- }), operation2;
990
+ return aiWork.add(operation2), operation2.then(() => aiWork.delete(operation2), () => aiWork.delete(operation2)), operation2;
867
991
  }, called = /* @__PURE__ */ new Set(), reached = /* @__PURE__ */ new Set(), guarded = (value, receiver) => typeof value == "function" ? (...args) => {
868
992
  if (!active2 || broken) throw new Error("Run authority ended");
869
993
  return guarded(Reflect.apply(value, receiver, args));
@@ -927,7 +1051,7 @@ async function executeRoutineDefinition(definition, input, runtime) {
927
1051
  for (accepting = !1, await work; aiWork.size; ) await Promise.allSettled([...aiWork]);
928
1052
  active2 = !1;
929
1053
  for (let step of contract.steps) reached.has(step.id) || await runtime.step(step.id, { status: "skipped" });
930
- if (broken) throw failure ?? aiFailure ?? new AppRoutineContractError("invalid_value");
1054
+ if (broken) throw failure ?? new AppRoutineContractError("invalid_value");
931
1055
  if (contract.result !== void 0) return capture2(contract.result, result2);
932
1056
  if (result2 !== void 0) return invalid3();
933
1057
  }
@@ -947,6 +1071,11 @@ var DataOperationError = class extends Error {
947
1071
  component;
948
1072
  };
949
1073
 
1074
+ var RECORD_READ_INPUT_LIMIT = 1024 * 1024, RECORD_CAPTURE_OUTPUT_LIMIT = 25 * 1024 * 1024;
1075
+ function validRecordRef(value) {
1076
+ return typeof value == "string" && value.length <= 512 && /^[_a-zA-Z0-9][a-zA-Z0-9_-]*(\/[_a-zA-Z0-9][a-zA-Z0-9_-]*)+$/.test(value);
1077
+ }
1078
+
950
1079
  var invalid4 = () => {
951
1080
  throw new DataOperationError("invalid_query");
952
1081
  }, historyText = (value) => typeof value == "string" && !/[\u0000\uD800-\uDFFF]/u.test(value);
@@ -1471,8 +1600,13 @@ async function boundedBytes(stream, limit, signal) {
1471
1600
  let { done, value } = await reader.read();
1472
1601
  if (signal?.aborted) throw unavailable2();
1473
1602
  if (done) break;
1474
- if (!(value instanceof Uint8Array)) throw invalid7();
1475
- if (size += value.byteLength, size > limit) throw new FileOperationError("request_too_large", 413);
1603
+ if (!(value instanceof Uint8Array)) throw new FileOperationError("invalid_input", 400, !1, { reason: "invalid_bytes" });
1604
+ if (size += value.byteLength, size > limit) throw new FileOperationError(
1605
+ "request_too_large",
1606
+ 413,
1607
+ !1,
1608
+ { reason: "size_limit", limitBytes: limit, receivedBytes: size, receivedAtLeast: !0 }
1609
+ );
1476
1610
  bytes.set(value, size - value.byteLength);
1477
1611
  }
1478
1612
  } catch (error) {
@@ -1524,7 +1658,12 @@ async function send(suffix, init2, descriptor, initial, parent) {
1524
1658
  })();
1525
1659
  if (!response2.ok) {
1526
1660
  let value2 = await result(response2), error = value2 && typeof value2 == "object" && "error" in value2 ? value2.error : void 0, received = error && typeof error == "object" && "code" in error ? error.code : void 0, code = failures[response2.status]?.find((code2) => code2 === received);
1527
- throw code ? new FileOperationError(code, response2.status, code === "capacity" || code === "unavailable") : unavailable2();
1661
+ throw code ? new FileOperationError(
1662
+ code,
1663
+ response2.status,
1664
+ code === "capacity" || code === "unavailable",
1665
+ parseFileErrorDetail(error?.detail)
1666
+ ) : unavailable2();
1528
1667
  }
1529
1668
  if (response2.status !== (descriptor === 204 ? 204 : 200) || descriptor === 204 && response2.body !== null)
1530
1669
  throw response2.body?.cancel().catch(() => {
@@ -1588,7 +1727,7 @@ async function multipartCreate(input, metadata, exchange, release = () => {
1588
1727
  };
1589
1728
  for (let sent = 0, partNumber = 1; sent < metadata.size || metadata.size === 0 && partNumber === 1; partNumber++) {
1590
1729
  let length = Math.min(PART_BYTES, metadata.size - sent), part;
1591
- if (input.file instanceof Uint8Array) part = input.file.subarray(sent, sent + length).slice();
1730
+ if (input.file instanceof Uint8Array) part = new Uint8Array(input.file.subarray(sent, sent + length));
1592
1731
  else if (input.file instanceof Blob) {
1593
1732
  let slice = input.file.slice(sent, sent + length);
1594
1733
  part = new Uint8Array(await request2(() => slice.arrayBuffer()));
@@ -1717,7 +1856,13 @@ var files = {
1717
1856
  let blob = input.file instanceof Blob, array3 = input.file instanceof Uint8Array, stream = input.file instanceof ReadableStream;
1718
1857
  if (!blob && !array3 && !stream) throw invalid7();
1719
1858
  let intrinsic = blob ? input.file.size : array3 ? input.file.byteLength : void 0;
1720
- if (intrinsic !== void 0 && input.size !== void 0 && input.size !== intrinsic) throw invalid7();
1859
+ if (intrinsic !== void 0 && input.size !== void 0 && input.size !== intrinsic)
1860
+ throw !Number.isSafeInteger(input.size) || input.size < 0 ? new FileOperationError("invalid_input", 400, !1, { reason: "invalid_size" }) : new FileOperationError(
1861
+ "invalid_input",
1862
+ 400,
1863
+ !1,
1864
+ { reason: "size_mismatch", expectedBytes: input.size, receivedBytes: intrinsic }
1865
+ );
1721
1866
  let name2 = input.name;
1722
1867
  name2 === void 0 && blob && "name" in input.file && typeof input.file.name == "string" && (name2 = typeof globalThis.File < "u" && input.file instanceof globalThis.File ? input.file.name : input.file.name.split(/[\\/]/).at(-1));
1723
1868
  let mediaType = declaredOriginalMediaType(input.mediaType === void 0 ? blob && input.file.type ? input.file.type : "application/octet-stream" : input.mediaType), metadata = parseFileCreateMetadata({
@@ -1754,12 +1899,17 @@ var files = {
1754
1899
  }
1755
1900
  return metadata.size > MAX_BYTES ? multipartCreate(input, metadata, (suffix, init2, kind, signal) => kind === !0 ? send(suffix, init2, !0, context, signal) : send(suffix, init2, "control", context, signal)) : timed(async (signal) => {
1756
1901
  try {
1757
- let bytes = array3 ? input.file.slice() : await boundedBytes(
1902
+ let bytes = array3 ? new Uint8Array(input.file) : await boundedBytes(
1758
1903
  blob ? input.file.stream() : input.file,
1759
1904
  metadata.size,
1760
1905
  signal
1761
1906
  );
1762
- if (bytes.byteLength !== metadata.size) throw invalid7();
1907
+ if (bytes.byteLength !== metadata.size) throw new FileOperationError(
1908
+ "invalid_input",
1909
+ 400,
1910
+ !1,
1911
+ { reason: "size_mismatch", expectedBytes: metadata.size, receivedBytes: bytes.byteLength }
1912
+ );
1763
1913
  if (signal.aborted) throw unavailable2();
1764
1914
  return await send("", {
1765
1915
  method: "POST",
@@ -1785,7 +1935,7 @@ var files = {
1785
1935
  let buffer = new Uint8Array(Math.min(MAX_BYTES, input.content.length * 3)), encoded = new TextEncoder().encodeInto(input.content, buffer);
1786
1936
  if (encoded.read !== input.content.length) throw new FileOperationError("request_too_large", 413);
1787
1937
  bytes = buffer.slice(0, encoded.written);
1788
- } else bytes = input.content.slice();
1938
+ } else bytes = new Uint8Array(input.content);
1789
1939
  let metadata = parseFileEnrichMetadata({
1790
1940
  name: input.name,
1791
1941
  size: bytes.byteLength,
@@ -2130,68 +2280,6 @@ var connections = /* @__PURE__ */ runtimeConnections();
2130
2280
 
2131
2281
  var LOCAL_ROUTINE_VALUE_BYTES = 256 * 1024;
2132
2282
 
2133
- var RECORD_WRITE_INPUT_LIMIT = 8 * 1024 * 1024, RoutineRecordWriteError = class extends Error {
2134
- constructor(message2, effect) {
2135
- super(message2);
2136
- this.effect = effect;
2137
- this.name = "RoutineRecordWriteError";
2138
- }
2139
- effect;
2140
- };
2141
- function validateRecordWrite(value) {
2142
- let input = serializable(value);
2143
- if (!input || Array.isArray(input) || typeof input != "object" || Object.keys(input).sort().join(",") !== "records,repository" || !validRepositoryAlias(input.repository) || !Array.isArray(input.records) || !input.records.length || input.records.length > 1e4 || new Set(input.records.map((edit) => edit?.ref)).size !== input.records.length)
2144
- throw new InboxContractError("Invalid record update batch");
2145
- for (let edit of input.records) {
2146
- if (!edit || Array.isArray(edit) || typeof edit != "object" || !validRecordRef(edit.ref))
2147
- throw new InboxContractError("Invalid record edit");
2148
- if ("expectedVersion" in edit) {
2149
- if (Object.keys(edit).sort().join(",") !== "expectedVersion,ref,set" || typeof edit.expectedVersion != "string" || !/^record-v1:[0-9a-f]{64}$/.test(edit.expectedVersion) || !edit.set || Array.isArray(edit.set) || typeof edit.set != "object" || !Object.keys(edit.set).length)
2150
- throw new InboxContractError("Invalid record edit");
2151
- continue;
2152
- }
2153
- if (Object.keys(edit).sort().join(",") !== "fields,path,ref" || typeof edit.path != "string" || !validCreatePath(edit.path) || !edit.fields || Array.isArray(edit.fields) || typeof edit.fields != "object" || !Object.keys(edit.fields).length)
2154
- throw new InboxContractError("Invalid record creation");
2155
- }
2156
- return structuredClone(input);
2157
- }
2158
- function validateRecordUpdate(value) {
2159
- let input = validateRecordWrite(value);
2160
- if (input.records.some((record4) => !("expectedVersion" in record4)))
2161
- throw new InboxContractError("Record creation is unavailable for this operation");
2162
- return input;
2163
- }
2164
- function validateRecordDelete(value) {
2165
- let input = serializable(value);
2166
- if (!input || Array.isArray(input) || typeof input != "object" || Object.keys(input).sort().join(",") !== "records,repository" || !validRepositoryAlias(input.repository) || !Array.isArray(input.records) || !input.records.length || input.records.length > 1e4 || new Set(input.records.map((record4) => record4?.ref)).size !== input.records.length)
2167
- throw new InboxContractError("Invalid record deletion batch");
2168
- for (let record4 of input.records)
2169
- if (!record4 || Array.isArray(record4) || typeof record4 != "object" || Object.keys(record4).sort().join(",") !== "expectedVersion,ref" || !validRecordRef(record4.ref) || typeof record4.expectedVersion != "string" || !/^record-v1:[0-9a-f]{64}$/.test(record4.expectedVersion))
2170
- throw new InboxContractError("Invalid record deletion");
2171
- return structuredClone(input);
2172
- }
2173
- function validateRecordChange(value) {
2174
- let input = serializable(value);
2175
- if (!input || Array.isArray(input) || typeof input != "object" || Object.keys(input).sort().join(",") !== "records,repository" || !validRepositoryAlias(input.repository) || !Array.isArray(input.records) || !input.records.length || input.records.length > 1e4 || new Set(input.records.map((record4) => record4?.ref)).size !== input.records.length || new TextEncoder().encode(JSON.stringify(input)).byteLength > RECORD_WRITE_INPUT_LIMIT)
2176
- throw new InboxContractError("Invalid record change batch");
2177
- let writes = [], deletes = [];
2178
- for (let record4 of input.records) {
2179
- if (!record4 || typeof record4 != "object" || Array.isArray(record4))
2180
- throw new InboxContractError("Invalid record change");
2181
- "set" in record4 || "fields" in record4 ? writes.push(record4) : deletes.push(record4);
2182
- }
2183
- return writes.length && validateRecordWrite({ repository: input.repository, records: writes }), deletes.length && validateRecordDelete({ repository: input.repository, records: deletes }), structuredClone(input);
2184
- }
2185
- function validateRoutineRecordWrite(value) {
2186
- let body = serializable(value);
2187
- if (!body || typeof body != "object" || Array.isArray(body) || Object.keys(body).sort().join(",") !== "input,requestKey" || typeof body.requestKey != "string" || !body.requestKey.trim() || body.requestKey !== body.requestKey.trim() || body.requestKey.length > 512)
2188
- throw new InboxContractError("A stable routine request key is required");
2189
- return { input: validateRecordChange(body.input), requestKey: body.requestKey };
2190
- }
2191
- function validCreatePath(value) {
2192
- return value === value.trim() && value.endsWith(".md") && !value.startsWith("/") && value.split("/").every((segment) => !!segment && ![".", ".."].includes(segment) && /^[a-zA-Z0-9_.-]+$/.test(segment));
2193
- }
2194
-
2195
2283
  function locationIdentity(location) {
2196
2284
  let coordinates = location.provider === "os" ? [location.file] : location.provider === "gmail" ? [location.connection, location.message, location.attachment] : location.provider === "dropbox" ? [location.connection, location.file] : void 0;
2197
2285
  if (coordinates?.every((value) => typeof value == "string" && value.length > 0))
@@ -3008,7 +3096,15 @@ function validPair(value) {
3008
3096
  return validProof(pair.credential) && validProof(pair.renewalCredential) && pair.credential !== pair.renewalCredential && validBinding(pair.context);
3009
3097
  }
3010
3098
 
3011
- var unavailable3 = () => new InboxOperationError(503, "unavailable"), stale = () => new InboxOperationError(401, "authority_unavailable");
3099
+ var WorkspaceAdmissionError = class extends Error {
3100
+ constructor(status) {
3101
+ super("Workspace admission unavailable");
3102
+ this.status = status;
3103
+ }
3104
+ status;
3105
+ };
3106
+
3107
+ var unavailable3 = () => new InboxOperationError(503, "unavailable"), stale = () => new InboxOperationError(401, "authority_unavailable"), contextFailure = (error) => error instanceof WorkspaceAdmissionError ? new InboxOperationError(error.status, error.status === 401 ? "authority_unavailable" : error.status === 403 ? "access_denied" : "unavailable") : stale();
3012
3108
  function hostFailure(error, context) {
3013
3109
  return !context.isCurrent() || context.expiresAt <= Date.now() || error instanceof InboxContextError && error.status === 401 ? stale() : error instanceof InboxContextError && error.status === 403 ? new InboxOperationError(403, "access_denied") : unavailable3();
3014
3110
  }
@@ -3016,8 +3112,8 @@ async function send2(operation2, input, admitted) {
3016
3112
  let envelope = parseInboxRequest({ operation: operation2, input }), context;
3017
3113
  try {
3018
3114
  context = admitted ?? await resolveAppContext();
3019
- } catch {
3020
- throw stale();
3115
+ } catch (error) {
3116
+ throw contextFailure(error);
3021
3117
  }
3022
3118
  if (context.kind === "local") {
3023
3119
  if (!isAppQuestionOperation(operation2)) throw new InboxOperationError(403, "access_denied");
@@ -3125,8 +3221,8 @@ var inbox = {
3125
3221
  let items = [], beforeNumber, initial;
3126
3222
  try {
3127
3223
  initial = await resolveAppContext();
3128
- } catch {
3129
- throw stale();
3224
+ } catch (error) {
3225
+ throw contextFailure(error);
3130
3226
  }
3131
3227
  let host;
3132
3228
  try {
@@ -3164,8 +3260,8 @@ var inbox = {
3164
3260
  let routines = Object.freeze(supplied.routines), questions = [], beforeNumber, initial;
3165
3261
  try {
3166
3262
  initial = await resolveAppContext();
3167
- } catch {
3168
- throw stale();
3263
+ } catch (error) {
3264
+ throw contextFailure(error);
3169
3265
  }
3170
3266
  let check = () => {
3171
3267
  if (!initial.isCurrent() || initial.expiresAt <= Date.now()) throw stale();
@@ -3215,14 +3311,15 @@ function init({ auth: input }) {
3215
3311
  }
3216
3312
  let active2 = !0, revision = 0, person, token, binding = {}, local2 = localOrigin ? appContext(localOrigin, () => active2) : void 0, subscription, stopDisposal, resolve = async () => {
3217
3313
  if (local2) return local2.resolve();
3218
- if (!active2 || !endpoint || !workspace || globalThis.location.origin !== endpoint) throw new Error("Hosted Data context unavailable.");
3314
+ if (registered && await registered.ready(), !active2 || !endpoint || !workspace || globalThis.location.origin !== endpoint) throw new Error("Hosted Data context unavailable.");
3219
3315
  let started = revision, { data: { session }, error } = await auth2.getSession();
3220
3316
  if (!active2 || globalThis.location.origin !== endpoint || started !== revision || error || !session || person !== void 0 && person !== session.user.id)
3221
3317
  throw new Error("Hosted Data context unavailable.");
3222
3318
  let expiresAt = (session.expires_at ?? 0) * 1e3;
3223
3319
  if (!Number.isFinite(expiresAt) || expiresAt <= Date.now() || !session.user.id)
3224
3320
  throw new Error("Hosted Data context unavailable.");
3225
- person = session.user.id;
3321
+ if (person = session.user.id, registered && await registered.admitWorkspace(), !active2 || globalThis.location.origin !== endpoint || started !== revision || person !== session.user.id)
3322
+ throw new Error("Hosted Data context unavailable.");
3226
3323
  let isCurrent = () => active2 && globalThis.location.origin === endpoint && revision === started && person === session.user.id;
3227
3324
  return {
3228
3325
  workspace,
@@ -3369,6 +3466,36 @@ function createBrowserAuth(runtime) {
3369
3466
  function current2(c, origin, revision) {
3370
3467
  return c.session && !validSession(c.session) && expire(c), c.active && c.revision === revision && globalThis.location?.origin === origin;
3371
3468
  }
3469
+ async function admitWorkspace(c, origin) {
3470
+ if (origin === "https://auth.oddington.ai" || origin === "http://127.0.0.1:5176") return;
3471
+ let revision = c.revision;
3472
+ if (!current2(c, origin, revision) || !c.session || c.state.pending === "sign_out") throw runtime.failure("stale");
3473
+ if (c.admission?.revision !== revision) {
3474
+ let token = c.session.access_token, result2 = (async () => {
3475
+ let status;
3476
+ try {
3477
+ let response2 = await fetch(`${origin}/api/auth`, {
3478
+ method: "POST",
3479
+ headers: { authorization: `Bearer ${token}` },
3480
+ credentials: "same-origin",
3481
+ redirect: "error",
3482
+ cache: "no-store",
3483
+ signal: AbortSignal.timeout(15e3)
3484
+ });
3485
+ status = response2.status;
3486
+ let value = status === 200 ? await response2.json() : null;
3487
+ status === 200 && value?.admitted !== !0 && (status = 503), status !== 200 && response2.body?.cancel().catch(() => {
3488
+ });
3489
+ } catch {
3490
+ status = 503;
3491
+ }
3492
+ if (!current2(c, origin, revision) || c.state.pending === "sign_out") throw runtime.failure("stale");
3493
+ if (status !== 200) throw new WorkspaceAdmissionError(status === 401 ? 401 : status === 403 ? 403 : 503);
3494
+ })();
3495
+ c.admission = { revision, result: result2 };
3496
+ }
3497
+ if (await c.admission.result, !current2(c, origin, revision)) throw runtime.failure("stale");
3498
+ }
3372
3499
  return { create() {
3373
3500
  let origin = globalThis.location?.origin ?? "", c = coordinators.get(origin);
3374
3501
  c || (c = createCoordinator(origin), coordinators.set(origin, c));
@@ -3458,7 +3585,9 @@ function createBrowserAuth(runtime) {
3458
3585
  async sendCode(email) {
3459
3586
  if (assertActive(), !validEmail(email)) throw runtime.failure("invalid_input");
3460
3587
  await action("send_code", async (provider2) => {
3461
- let { error } = await provider2.signInWithOtp({ email: email.trim(), options: { shouldCreateUser: !1 } });
3588
+ let { error } = await provider2.signInWithOtp({ email: email.trim(), options: {
3589
+ shouldCreateUser: origin !== "https://auth.oddington.ai" && origin !== "http://127.0.0.1:5176"
3590
+ } });
3462
3591
  if (error && !accountRefusal(error)) throw runtime.failure("unavailable");
3463
3592
  });
3464
3593
  },
@@ -3470,6 +3599,11 @@ function createBrowserAuth(runtime) {
3470
3599
  if (!data3.session || !validSession(data3.session)) throw runtime.failure("invalid_code");
3471
3600
  if (!current2(coordinator, origin, revision) && (coordinator.session?.access_token !== data3.session.access_token || coordinator.revision !== revision + 1 || coordinator.state.pending !== "verify_code")) throw runtime.failure("stale");
3472
3601
  accept(coordinator, "SIGNED_IN", data3.session);
3602
+ try {
3603
+ await admitWorkspace(coordinator, origin);
3604
+ } catch (error2) {
3605
+ if (!(error2 instanceof WorkspaceAdmissionError)) throw error2;
3606
+ }
3473
3607
  });
3474
3608
  },
3475
3609
  async signOut() {
@@ -3495,6 +3629,12 @@ function createBrowserAuth(runtime) {
3495
3629
  }
3496
3630
  });
3497
3631
  return registerBrowserAuth(handle2, {
3632
+ async ready() {
3633
+ assertActive(), await coordinator.ready, assertActive();
3634
+ },
3635
+ async admitWorkspace() {
3636
+ assertActive(), await admitWorkspace(coordinator, origin), assertActive();
3637
+ },
3498
3638
  onDispose(callback) {
3499
3639
  return active2 ? (disposalObservers.add(callback), () => {
3500
3640
  disposalObservers.delete(callback);
@@ -3926,18 +4066,11 @@ function validateFileAddOptions(value) {
3926
4066
  return v;
3927
4067
  }
3928
4068
 
3929
- var PREPARED_RECORD_INPUT_LIMIT = 64 * 1024 * 1024;
3930
-
3931
- function routineDataEnvelope(command) {
3932
- return "apply" in command ? { operation: "changes.publish", captured: command.apply } : { operation: "changes.publish", request: command };
3933
- }
3934
-
3935
4069
  function routinePublicationClient2(options = {}) {
3936
4070
  let environment = {}, installed2 = routineTransport(), local2 = installed2?.request ?? void 0, workspace = installed2 ? "installed" : options.workspace ?? environment.OS_ROUTINE_WORKSPACE, capability = options.capability ?? environment.OS_ROUTINE_RUN;
3937
4071
  if (!workspace || !/^[a-z0-9][a-z0-9-]*$/.test(workspace) || capability !== void 0 && !/^[0-9a-f]{64}$/.test(capability))
3938
4072
  throw new Error("A workspace and valid optional routine capability are required");
3939
4073
  let workingRoot = options.workingRoot ?? environment.OS_INBOX_WORKING_ROOT ?? void 0, headers = capability ? { "x-os-run": capability } : {}, request2 = options.request ?? (capability ? async (path, init2) => {
3940
- if (path === "/api/data") return fetch(`https://${workspace}.oddington.ai/api/data`, { ...init2, redirect: "error" });
3941
4074
  let origin = `https://${workspace}.oddington.ai`, target = new URL(path, `${origin}/`);
3942
4075
  if (workspace.length > 63 || ["auth", "www", "api", "os"].includes(workspace) || target.origin !== origin || target.search || target.hash || target.username || target.password || !/^\/api\/inbox\/(check|publish|resolve|files)$/.test(path) || init2?.method !== "POST")
3943
4076
  throw new Error("Invalid routine endpoint");
@@ -3947,49 +4080,23 @@ function routinePublicationClient2(options = {}) {
3947
4080
  });
3948
4081
  async function send3(operation2, body) {
3949
4082
  if (local2) return await local2(operation2, body);
3950
- let writing = operation2 === "records/update", reading = operation2 === "records/read", response2 = await request2(writing || reading ? "/api/data" : `/api/inbox/${operation2}`, {
4083
+ let response2 = await request2(`/api/inbox/${operation2}`, {
3951
4084
  method: "POST",
3952
- headers: {
3953
- "content-type": "application/json",
3954
- ...headers,
3955
- ...writing || reading ? { "x-os-workspace": workspace, "x-os-captured-records": "1" } : {}
3956
- },
3957
- body: JSON.stringify(writing ? routineDataEnvelope(validateRoutineRecordWrite(body)) : reading ? { operation: "records.select", request: validateRecordRead(body) } : body)
4085
+ headers: { "content-type": "application/json", ...headers },
4086
+ body: JSON.stringify(body)
3958
4087
  }), result2 = await response2.json();
3959
- if (!response2.ok && operation2 === "records/update")
3960
- throw new RoutineRecordWriteError(result2.error ?? "Could not confirm routine preparation", result2.effect ?? "uncertain");
3961
4088
  if (!response2.ok) throw new Error(result2.error ?? "Could not confirm routine publication");
3962
4089
  return result2;
3963
4090
  }
3964
- let transport = {
3965
- async publish(body) {
3966
- return await send3("publish", body);
3967
- },
3968
- async resolve(body) {
3969
- await send3("resolve", body);
3970
- }
3971
- };
3972
- async function readRecords(input) {
3973
- return await send3("records/read", validateRecordRead(input));
3974
- }
3975
4091
  return {
3976
- ...createInboxPublisher(transport),
3977
- readRecords,
3978
- repository: {
3979
- readRecords,
3980
- async writeRecords(input, options2) {
3981
- if (!capability && !installed2) throw new Error("A trusted routine launch is required");
3982
- return await send3("records/update", validateRoutineRecordWrite({ input, requestKey: options2?.requestKey }));
3983
- },
3984
- async updateRecords(input, options2) {
3985
- if (!capability && !installed2) throw new Error("A trusted routine launch is required");
3986
- return await send3("records/update", validateRoutineRecordWrite({ input: validateRecordUpdate(input), requestKey: options2?.requestKey }));
4092
+ ...createInboxPublisher({
4093
+ async publish(body) {
4094
+ return await send3("publish", body);
3987
4095
  },
3988
- async deleteRecords(input, options2) {
3989
- if (!capability && !installed2) throw new Error("A trusted routine launch is required");
3990
- return await send3("records/update", validateRoutineRecordWrite({ input: validateRecordDelete(input), requestKey: options2?.requestKey }));
4096
+ async resolve(body) {
4097
+ await send3("resolve", body);
3991
4098
  }
3992
- },
4099
+ }),
3993
4100
  files: {
3994
4101
  async add(value) {
3995
4102
  let input = validateFileAddOptions(value);
@@ -4010,7 +4117,7 @@ export {
4010
4117
  FileOperationError,
4011
4118
  InboxContractError,
4012
4119
  InboxOperationError,
4013
- RoutineRecordWriteError,
4120
+ RoutineAIError,
4014
4121
  app,
4015
4122
  asksBlock,
4016
4123
  auth,