@oddington/sdk 0.1.24 → 0.1.26

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
@@ -40,7 +40,7 @@ import { defineRoutine, file, schema } from '@oddington/sdk';
40
40
 
41
41
  export default defineRoutine({
42
42
  id: 'invoice-import',
43
- input: schema.object({ document: file() }),
43
+ input: schema.object({ document: file({ storage: 'local', path: 'Statements' }) }),
44
44
  access: { files: 'read' },
45
45
  steps: {
46
46
  read: {
@@ -67,8 +67,10 @@ skipped. Omitted input normalizes to `{}`. A top-level schema can supply a valid
67
67
  immutable default with `.default(value)`, such as
68
68
  `schema.object({ files: schema.array(file(), { maxItems: 32 }) }).default({ files: [] })`.
69
69
  Nested defaults are unsupported and rejected. Omitted steps permit no step call;
70
- omitted result permits no returned value. `file()` declares a complete OS File descriptor;
71
- the trusted launcher separately verifies current File-use authority. The supplied
70
+ omitted result permits no returned value. `file()` declares a complete OS File descriptor.
71
+ Its optional `storage` selects local or remote placement and its optional `path`
72
+ selects a managed OS parent such as `Statements`; it is never a machine or provider
73
+ path. The trusted launcher separately verifies current File-use authority. The supplied
72
74
  runtime contains `run.id`, `data`, `files`, `connections`, `inbox`, and `step`.
73
75
  Accepted input and saved values are immutable snapshots. Access declarations set
74
76
  maximum named capabilities and grant no authority; the trusted host checks current
package/dist/browser.js CHANGED
@@ -413,7 +413,14 @@ function parseFileDescriptor(value) {
413
413
  if (Object.keys(f).sort().join(",") !== "details,enrichments,id,mediaType,name,path,sha256,size" || typeof f.id != "string" || !FILE_ID_PATTERN.test(f.id) || !safeFileName(f.name) || !safeFilePath(f.path) || !Number.isSafeInteger(f.size) || f.size < 0 || f.size > FILE_ORIGINAL_MAX_BYTES || typeof f.sha256 != "string" || !/^[0-9a-f]{64}$/.test(f.sha256) || !f.enrichments || typeof f.enrichments != "object" || Array.isArray(f.enrichments) || !f.details || typeof f.details != "object" || Array.isArray(f.details)) return invalid8();
414
414
  let d = f.details;
415
415
  if (d.kind === "other") {
416
- if (Object.keys(d).length !== 1 || f.mediaType !== "application/octet-stream") return invalid8();
416
+ if (Object.keys(d).length !== 1 || ![
417
+ "application/octet-stream",
418
+ "text/plain",
419
+ "text/markdown",
420
+ "application/json",
421
+ "image/png",
422
+ "image/jpeg"
423
+ ].includes(f.mediaType)) return invalid8();
417
424
  } else if (d.kind === "csv") {
418
425
  if (Object.keys(d).sort().join(",") !== "dataRows,delimiter,encoding,headerRow,kind" || f.mediaType !== "text/csv" || !["utf-8", "windows-1252"].includes(d.encoding) || ![",", ";", " "].includes(d.delimiter) || !Number.isSafeInteger(d.headerRow) || d.headerRow < 1 || !Number.isSafeInteger(d.dataRows) || d.dataRows < 0) return invalid8();
419
426
  } else if (d.kind === "pdf") {
@@ -452,6 +459,14 @@ function safeFileName(value) {
452
459
  function safeFilePath(value) {
453
460
  return typeof value == "string" && value.length <= 1024 && value.split("/").every(safeFileName);
454
461
  }
462
+ function safeManagedFilePath(value) {
463
+ return safeFilePath(value) && value.split("/")[0].toLowerCase() !== ".os";
464
+ }
465
+ var ORIGINAL_MEDIA_TYPES = ["application/pdf", "text/plain", "text/markdown", "application/json", "image/png", "image/jpeg"];
466
+ function declaredOriginalMediaType(value) {
467
+ let normalized = value.split(";", 1)[0].trim().toLowerCase();
468
+ return ORIGINAL_MEDIA_TYPES.includes(normalized) ? normalized : void 0;
469
+ }
455
470
  function parseFileCreateMetadata(value, maxBytes) {
456
471
  let invalid8 = () => {
457
472
  throw new FileOperationError("invalid_input", 400);
@@ -460,7 +475,7 @@ function parseFileCreateMetadata(value, maxBytes) {
460
475
  let input = value;
461
476
  if (Object.keys(input).some((key) => !["name", "path", "storage", "csv", "inputMediaType", "size", "requestKey"].includes(key))) return invalid8();
462
477
  let path = input.path === void 0 ? "Files" : input.path;
463
- if (!safeFileName(input.name) || !safeFilePath(path) || path.split("/")[0].toLowerCase() === ".os" || input.storage !== void 0 && input.storage !== "remote" && input.storage !== "local" || input.inputMediaType !== void 0 && input.inputMediaType !== "application/pdf" || !Number.isSafeInteger(input.size) || input.size < 0) return invalid8();
478
+ if (!safeFileName(input.name) || !safeManagedFilePath(path) || input.storage !== void 0 && input.storage !== "remote" && input.storage !== "local" || input.inputMediaType !== void 0 && (typeof input.inputMediaType != "string" || !ORIGINAL_MEDIA_TYPES.includes(input.inputMediaType)) || !Number.isSafeInteger(input.size) || input.size < 0) return invalid8();
464
479
  if (input.size > maxBytes) throw new FileOperationError("request_too_large", 413);
465
480
  if (typeof input.requestKey != "string" || input.requestKey.length < 1 || input.requestKey.length > 256 || input.requestKey !== input.requestKey.trim() || /[\u0000-\u001f\u007f]/.test(input.requestKey)) return invalid8();
466
481
  let csv;
@@ -476,7 +491,7 @@ function parseFileCreateMetadata(value, maxBytes) {
476
491
  name: input.name,
477
492
  path,
478
493
  ...input.storage ? { storage: input.storage } : {},
479
- ...input.inputMediaType === "application/pdf" ? { inputMediaType: input.inputMediaType } : {},
494
+ ...input.inputMediaType === void 0 ? {} : { inputMediaType: input.inputMediaType },
480
495
  ...csv ? { csv } : {},
481
496
  size: input.size,
482
497
  requestKey: input.requestKey
@@ -533,7 +548,11 @@ function parseAppRoutineContract(value) {
533
548
  case "boolean":
534
549
  return fields2(value2, ["type"]) ? { type: value2.type } : invalidContract();
535
550
  case "file":
536
- return !fields2(value2, ["type"], ["storage"]) || owns(value2, "storage") && value2.storage !== "local" && value2.storage !== "remote" ? invalidContract() : { type: "file", ...owns(value2, "storage") ? { storage: value2.storage } : {} };
551
+ return !fields2(value2, ["type"], ["storage", "path"]) || owns(value2, "storage") && value2.storage !== "local" && value2.storage !== "remote" || owns(value2, "path") && !safeManagedFilePath(value2.path) ? invalidContract() : {
552
+ type: "file",
553
+ ...owns(value2, "storage") ? { storage: value2.storage } : {},
554
+ ...owns(value2, "path") ? { path: value2.path } : {}
555
+ };
537
556
  case "string":
538
557
  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 } : {} };
539
558
  case "integer":
@@ -580,7 +599,9 @@ function validateRoutineSchemaValue(schema2, value) {
580
599
  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;
581
600
  case "file":
582
601
  try {
583
- jsonData(value), parseFileDescriptor(value);
602
+ jsonData(value);
603
+ let file2 = parseFileDescriptor(value);
604
+ if (schema2.path !== void 0 && file2.path !== schema2.path) return invalidValue();
584
605
  } catch {
585
606
  return invalidValue();
586
607
  }
@@ -590,14 +611,16 @@ function validateRoutineSchemaValue(schema2, value) {
590
611
  var ROUTINE_DECLARATION_SCHEMA = "https://schemas.oddington.ai/routine/v1.json", ROUTINE_SOURCE_LIMITS = { files: 2e4, bytes: 256 * 1024 * 1024, declarationBytes: 64 * 1024 };
591
612
  var invalid = (field) => {
592
613
  throw new Error(`Invalid routine declaration: ${field}`);
593
- }, object2 = (value, fields5, field) => ((!value || typeof value != "object" || Array.isArray(value) || Object.keys(value).some((key) => !fields5.includes(key))) && invalid(field), value), routineAlias = (value) => typeof value == "string" && /^[a-z][a-z0-9-]{0,62}$/.test(value), list = (value, field, allowed, qualified = !1) => value === void 0 ? [] : ((!Array.isArray(value) || value.length > 64 || value.some((item) => !(routineAlias(item) || qualified && typeof item == "string" && item.split("/").length === 2 && item.split("/").every(routineAlias)) || allowed && !allowed.includes(item)) || new Set(value).size !== value.length) && invalid(field), [...value].sort());
614
+ }, object2 = (value, fields5, field) => ((!value || typeof value != "object" || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null || Reflect.ownKeys(value).some((key) => typeof key != "string" || !fields5.includes(key) || !Object.getOwnPropertyDescriptor(value, key).enumerable || !Object.hasOwn(Object.getOwnPropertyDescriptor(value, key), "value"))) && invalid(field), value), routineAlias = (value) => typeof value == "string" && /^[a-z][a-z0-9-]{0,62}$/.test(value), list = (value, field, allowed, qualified = !1) => value === void 0 ? [] : ((!Array.isArray(value) || value.length > 64 || value.some((item) => !(routineAlias(item) || qualified && typeof item == "string" && item.split("/").length === 2 && item.split("/").every(routineAlias)) || allowed && !allowed.includes(item)) || new Set(value).size !== value.length) && invalid(field), [...value].sort());
594
615
  function routineAssetPattern(value) {
595
616
  return typeof value == "string" && value.length <= 512 && !value.startsWith("/") && !value.includes("\\") && !value.includes(":") && value.split("/").every((part) => !!part && part !== "." && part !== ".." && /^[a-zA-Z0-9_.*-]+$/.test(part));
596
617
  }
597
618
  function parseRoutineDeclaration(value) {
598
619
  let v = object2(value, ["$schema", "access", "runtime", "retry", "assets"], "fields");
599
620
  v.$schema !== ROUTINE_DECLARATION_SCHEMA && invalid("$schema");
600
- let access = object2(v.access === void 0 ? {} : v.access, ["repositories", "connections", "models", "files", "publish"], "access"), rawRepositories = access.repositories === void 0 ? {} : access.repositories, repositories = object2(rawRepositories, rawRepositories && typeof rawRepositories == "object" ? Object.keys(rawRepositories) : [], "access.repositories");
621
+ let access = object2(v.access === void 0 ? {} : v.access, ["repositories", "connections", "ai", "files", "publish"], "access");
622
+ Object.hasOwn(access, "ai") && access.ai !== !0 && invalid("access.ai");
623
+ let rawRepositories = access.repositories === void 0 ? {} : access.repositories, repositories = object2(rawRepositories, rawRepositories && typeof rawRepositories == "object" ? Object.keys(rawRepositories) : [], "access.repositories");
601
624
  Object.keys(repositories).length > 64 && invalid("access.repositories");
602
625
  let grants = {};
603
626
  for (let key of Object.keys(repositories).sort())
@@ -615,7 +638,7 @@ function parseRoutineDeclaration(value) {
615
638
  access: {
616
639
  repositories: grants,
617
640
  connections: list(access.connections, "access.connections", void 0, !0),
618
- models: list(access.models, "access.models"),
641
+ ...access.ai === !0 ? { ai: !0 } : {},
619
642
  ...access.files === void 0 ? {} : { files: access.files },
620
643
  publish: list(access.publish, "access.publish", ["asks", "digests"])
621
644
  },
@@ -663,7 +686,7 @@ function assertRoutineFields(value, allowed) {
663
686
  }
664
687
  function parseRoutineAccess(value) {
665
688
  try {
666
- value !== void 0 && assertRoutineJson(value), assertRoutineFields(value ?? {}, ["repositories", "connections", "models", "files", "publish"]);
689
+ value !== void 0 && assertRoutineJson(value), assertRoutineFields(value ?? {}, ["repositories", "connections", "ai", "files", "publish"]);
667
690
  let access = value, names = access?.connections ?? [];
668
691
  if (!Array.isArray(names) || names.length > 64 || new Set(names).size !== names.length || names.some((name) => typeof name != "string" || name.split("/").length !== 2 || name.split("/").some((part) => !isAppRoutineName(part)))) return invalidContract2();
669
692
  let parsed = parseRoutineDeclaration({ $schema: ROUTINE_DECLARATION_SCHEMA, access: { ...access, connections: [] } }).access;
@@ -673,6 +696,52 @@ function parseRoutineAccess(value) {
673
696
  }
674
697
  }
675
698
 
699
+ var ROUTINE_AI_VALUE_BYTES = 256 * 1024, ROUTINE_AI_EVIDENCE_BYTES = 4 * 1024;
700
+ var RoutineAIError = class extends Error {
701
+ constructor(code) {
702
+ super(code);
703
+ this.code = code;
704
+ this.name = "RoutineAIError";
705
+ }
706
+ code;
707
+ };
708
+ function capture(value, invalid8, oversized) {
709
+ try {
710
+ assertRoutineJson(value);
711
+ let encoded = JSON.stringify(value);
712
+ if (new TextEncoder().encode(encoded).byteLength > ROUTINE_AI_VALUE_BYTES) throw new RoutineAIError(oversized);
713
+ return freezeRoutineMetadata(JSON.parse(encoded));
714
+ } catch (error) {
715
+ throw error instanceof RoutineAIError ? error : new RoutineAIError(invalid8);
716
+ }
717
+ }
718
+ function parseRoutineAIRequest(value) {
719
+ let v = capture(value, "invalid_input", "input_too_large");
720
+ try {
721
+ 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();
722
+ let input = typeof v.input == "string" ? v.input : parseFileDescriptor(v.input);
723
+ if (v.kind === "generateText") return freezeRoutineMetadata({ kind: v.kind, instructions: v.instructions, input });
724
+ let schema2 = parseAppRoutineContract({ routine: "ai", input: v.schema, steps: [{ id: "generate" }] }).input;
725
+ return freezeRoutineMetadata({ kind: v.kind, instructions: v.instructions, input, schema: schema2 });
726
+ } catch {
727
+ throw new RoutineAIError("invalid_input");
728
+ }
729
+ }
730
+ function parseRoutineAIResult(request2, value) {
731
+ let v = capture(value, "invalid_output", "output_too_large");
732
+ try {
733
+ if (request2.kind === "generateText") {
734
+ if (assertRoutineFields(v, ["text"]), typeof v.text != "string" || !v.text.trim()) throw new Error();
735
+ return freezeRoutineMetadata({ text: v.text });
736
+ } else {
737
+ if (assertRoutineFields(v, ["object"]), !Object.hasOwn(v, "object")) throw new Error();
738
+ return validateRoutineSchemaValue(request2.schema, v.object), freezeRoutineMetadata({ object: v.object });
739
+ }
740
+ } catch {
741
+ throw new RoutineAIError("invalid_output");
742
+ }
743
+ }
744
+
676
745
  var LIMIT = 256 * 1024, invalid3 = () => {
677
746
  throw new AppRoutineContractError("invalid_value");
678
747
  }, invalidContract3 = () => {
@@ -683,7 +752,7 @@ function captureJson(value) {
683
752
  let encoded = JSON.stringify(value);
684
753
  return new TextEncoder().encode(encoded).byteLength > LIMIT ? invalid3() : freezeRoutineMetadata(JSON.parse(encoded));
685
754
  }
686
- function capture(schema2, value) {
755
+ function capture2(schema2, value) {
687
756
  let captured = captureJson(value);
688
757
  return validateRoutineSchemaValue(schema2, captured), captured;
689
758
  }
@@ -709,7 +778,7 @@ function declaration(value, defaults) {
709
778
  } catch {
710
779
  return invalidContract3();
711
780
  }
712
- let parsed = freezeRoutineMetadata(parseAppRoutineContract({ routine: "definition", input: value, steps: [{ id: "inspect" }] }).input), captured = defaults ? capture(parsed, defaults.value) : void 0, builder = Object.defineProperty({ ...parsed }, "default", { value: (value2) => declaration(parsed, { value: value2 }) });
781
+ let parsed = freezeRoutineMetadata(parseAppRoutineContract({ routine: "definition", input: value, steps: [{ id: "inspect" }] }).input), captured = defaults ? capture2(parsed, defaults.value) : void 0, builder = Object.defineProperty({ ...parsed }, "default", { value: (value2) => declaration(parsed, { value: value2 }) });
713
782
  return schemas.set(builder, { schema: parsed, ...defaults ? { default: captured } : {} }), Object.freeze(builder);
714
783
  }
715
784
  var schema = {
@@ -731,7 +800,7 @@ var schema = {
731
800
  boolean() {
732
801
  return declaration({ type: "boolean" });
733
802
  }
734
- }, file = (options = {}) => (assertRoutineFields(options, ["storage"]), declaration({ type: "file", ...options })), definitions = /* @__PURE__ */ new WeakMap(), defineRoutine = (definition) => {
803
+ }, file = (options = {}) => (assertRoutineFields(options, ["storage", "path"]), declaration({ type: "file", ...options })), definitions = /* @__PURE__ */ new WeakMap(), defineRoutine = (definition) => {
735
804
  if (assertRoutineFields(definition, ["id", "title", "input", "steps", "result", "access", "responses", "run"]), !isAppRoutineName(definition.id) || definition.title !== void 0 && !routineTitle(definition.title) || typeof definition.run != "function") return invalidContract3();
736
805
  let input = definition.input === void 0 ? { schema: empty, default: {} } : schemaMetadata(definition.input), result2 = definition.result === void 0 ? void 0 : schemaMetadata(definition.result).schema, access = parseRoutineAccess(definition.access);
737
806
  assertRoutineFields(definition.steps ?? {});
@@ -794,10 +863,32 @@ function inspectRoutineDefinition(definition) {
794
863
  }
795
864
  function captureRoutineInput(definition, input) {
796
865
  let contract = inspectRoutineDefinition(definition);
797
- return capture(contract.input, input === void 0 && Object.hasOwn(contract, "inputDefault") ? contract.inputDefault : input);
866
+ return capture2(contract.input, input === void 0 && Object.hasOwn(contract, "inputDefault") ? contract.inputDefault : input);
798
867
  }
799
868
  async function executeRoutineDefinition(definition, input, runtime) {
800
- let captured = captureRoutineInput(definition, input), { contract, steps, run } = definitions.get(definition), active2 = !0, accepting = !0, broken = !1, work = Promise.resolve(), called = /* @__PURE__ */ new Set(), reached = /* @__PURE__ */ new Set(), guarded = (value, receiver) => typeof value == "function" ? (...args) => {
869
+ 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) => {
870
+ let operation2 = (async () => {
871
+ if (!active2 || broken || contract.access.ai !== !0) throw new RoutineAIError("access_denied");
872
+ if (!runtime.ai) throw new RoutineAIError("capability_unavailable");
873
+ let request2;
874
+ try {
875
+ assertRoutineFields(value, kind === "generateObject" ? ["instructions", "input", "schema"] : ["instructions", "input"]), request2 = parseRoutineAIRequest({
876
+ kind,
877
+ instructions: value.instructions,
878
+ input: value.input,
879
+ ...kind === "generateObject" ? { schema: schemaMetadata(value.schema).schema } : {}
880
+ });
881
+ } catch (error) {
882
+ throw error instanceof RoutineAIError ? error : new RoutineAIError("invalid_input");
883
+ }
884
+ let result3 = await runtime.ai[kind](request2);
885
+ if (!active2 || broken) throw new RoutineAIError("access_denied");
886
+ return parseRoutineAIResult(request2, result3);
887
+ })();
888
+ return aiWork.add(operation2), operation2.then(() => aiWork.delete(operation2), (error) => {
889
+ aiFailure ??= error, broken = !0, aiWork.delete(operation2);
890
+ }), operation2;
891
+ }, called = /* @__PURE__ */ new Set(), reached = /* @__PURE__ */ new Set(), guarded = (value, receiver) => typeof value == "function" ? (...args) => {
801
892
  if (!active2 || broken) throw new Error("Run authority ended");
802
893
  return guarded(Reflect.apply(value, receiver, args));
803
894
  } : value && typeof value == "object" && !(value instanceof Promise) ? new Proxy({ ...value }, {
@@ -810,6 +901,10 @@ async function executeRoutineDefinition(definition, input, runtime) {
810
901
  ...guarded({
811
902
  ...runtime,
812
903
  run: freezeRoutineMetadata({ ...runtime.run }),
904
+ ai: {
905
+ generateText: (value) => generate("generateText", value),
906
+ generateObject: (value) => generate("generateObject", value)
907
+ },
813
908
  connections: { ...runtime.connections, get: async (identity) => {
814
909
  if (!active2 || broken || !contract.access.connections?.includes(identity)) throw new Error("Connection unavailable");
815
910
  let value = await runtime.connections.get(identity);
@@ -850,10 +945,11 @@ async function executeRoutineDefinition(definition, input, runtime) {
850
945
  } catch (error) {
851
946
  failure = error, broken = !0;
852
947
  }
853
- accepting = !1, await work, active2 = !1;
948
+ for (accepting = !1, await work; aiWork.size; ) await Promise.allSettled([...aiWork]);
949
+ active2 = !1;
854
950
  for (let step of contract.steps) reached.has(step.id) || await runtime.step(step.id, { status: "skipped" });
855
- if (broken) throw failure ?? new AppRoutineContractError("invalid_value");
856
- if (contract.result !== void 0) return capture(contract.result, result2);
951
+ if (broken) throw failure ?? aiFailure ?? new AppRoutineContractError("invalid_value");
952
+ if (contract.result !== void 0) return capture2(contract.result, result2);
857
953
  if (result2 !== void 0) return invalid3();
858
954
  }
859
955
 
@@ -1133,19 +1229,19 @@ var invalid6 = () => {
1133
1229
  }, unavailable = () => {
1134
1230
  throw new DataOperationError("unavailable");
1135
1231
  }, text2 = (value) => typeof value == "string" && value.length > 0;
1136
- function capture2(value, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
1232
+ function capture3(value, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
1137
1233
  if (value === null || typeof value == "string" || typeof value == "boolean" || typeof value == "number" && Number.isFinite(value)) return value;
1138
1234
  if (!value || typeof value != "object" || ancestors.has(value) || depth > 32) return invalid6();
1139
1235
  ancestors.add(value);
1140
1236
  try {
1141
- return Array.isArray(value) ? historyArray(value).map((item) => capture2(item, ancestors, depth + 1)) : Object.fromEntries(Object.entries(historyProperties(value)).map(([key, descriptor]) => [key, capture2(descriptor.value, ancestors, depth + 1)]));
1237
+ return Array.isArray(value) ? historyArray(value).map((item) => capture3(item, ancestors, depth + 1)) : Object.fromEntries(Object.entries(historyProperties(value)).map(([key, descriptor]) => [key, capture3(descriptor.value, ancestors, depth + 1)]));
1142
1238
  } finally {
1143
1239
  ancestors.delete(value);
1144
1240
  }
1145
1241
  }
1146
1242
  function recordPagination(select) {
1147
1243
  function pages(query) {
1148
- let captured = capture2(query), names = Object.keys(historyProperties(captured));
1244
+ let captured = capture3(query), names = Object.keys(historyProperties(captured));
1149
1245
  for (let name of names) {
1150
1246
  let selection2 = historyProperties(captured[name]);
1151
1247
  if (Object.hasOwn(selection2, "$")) {
@@ -1167,7 +1263,7 @@ function recordPagination(select) {
1167
1263
  return [name, next === void 0 ? selection2 : { ...selection2, $: { ...selection2.$, after: next } }];
1168
1264
  })), response2 = await select(request2), page;
1169
1265
  try {
1170
- page = capture2(response2);
1266
+ page = capture3(response2);
1171
1267
  let keys4 = Object.keys(historyProperties(page));
1172
1268
  (keys4.length !== active2.length + 1 || keys4.some((key) => key !== "$meta" && !active2.includes(key))) && unavailable();
1173
1269
  let meta = page.$meta;
@@ -1645,13 +1741,13 @@ var files = {
1645
1741
  if (intrinsic !== void 0 && input.size !== void 0 && input.size !== intrinsic) throw invalid7();
1646
1742
  let name = input.name;
1647
1743
  name === void 0 && blob && "name" in input.file && typeof input.file.name == "string" && (name = typeof globalThis.File < "u" && input.file instanceof globalThis.File ? input.file.name : input.file.name.split(/[\\/]/).at(-1));
1648
- let metadata = parseFileCreateMetadata({
1744
+ let inputMediaType = blob ? declaredOriginalMediaType(input.file.type) : void 0, metadata = parseFileCreateMetadata({
1649
1745
  name,
1650
1746
  size: intrinsic ?? input.size,
1651
1747
  path: input.path,
1652
1748
  storage: input.storage,
1653
1749
  csv: input.csv,
1654
- ...blob && input.file.type === "application/pdf" ? { inputMediaType: "application/pdf" } : {},
1750
+ ...inputMediaType === void 0 ? {} : { inputMediaType },
1655
1751
  requestKey: options?.requestKey === void 0 ? routineTransport()?.key("files") ?? crypto.randomUUID() : options.requestKey
1656
1752
  }, FILE_ORIGINAL_MAX_BYTES);
1657
1753
  if (!context2.local && !context2.localFiles && metadata.storage === "local") throw new FileOperationError("unsupported_context", 400);
@@ -1733,29 +1829,58 @@ var files = {
1733
1829
  read
1734
1830
  };
1735
1831
 
1832
+ function validDropboxRootPath(value) {
1833
+ return typeof value == "string" && value.length <= 2048 && value.startsWith("/") && !/[\\\x00-\x1f\x7f]/.test(value) && value.slice(1).split("/").every((segment) => segment.length > 0 && segment.trim() === segment && segment !== "." && segment !== "..");
1834
+ }
1736
1835
  function validGoogleScopes(value) {
1737
1836
  return Array.isArray(value) && value.length > 0 && value.length <= 32 && value.every((scope) => typeof scope == "string" && scope.length <= 256 && /^(https:\/\/www\.googleapis\.com\/auth\/[A-Za-z0-9._-]+|https:\/\/mail\.google\.com\/|openid|email|profile)$/.test(scope)) && new Set(value).size === value.length;
1738
1837
  }
1838
+ var DROPBOX_FILE_SCOPES = ["account_info.read", "files.metadata.read", "files.content.read", "files.content.write"];
1839
+ function validDropboxFileScopes(value) {
1840
+ return Array.isArray(value) && value.length === DROPBOX_FILE_SCOPES.length && new Set(value).size === value.length && DROPBOX_FILE_SCOPES.every((scope) => value.includes(scope));
1841
+ }
1739
1842
 
1740
1843
  var ROUTINE_CONNECTION_CREDENTIALS_ENV = "OS_ROUTINE_CONNECTION_CREDENTIALS";
1844
+ function parseRoutineConnectionNames(value) {
1845
+ if (!Array.isArray(value) || value.some((name) => typeof name != "string" || !/^(gmail|airtable|dropbox)\/[a-z0-9][a-z0-9-]{0,62}$/.test(name)) || new Set(value).size !== value.length)
1846
+ throw new Error("Invalid routine connections");
1847
+ return value;
1848
+ }
1741
1849
  var connectionUuid = (v) => typeof v == "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(v);
1742
1850
  function runtimeConnection(lease) {
1743
- return lease.provider === "gmail" ? {
1744
- id: `gmail/${lease.name}`,
1745
- provider: lease.provider,
1746
- name: lease.name,
1747
- accountEmail: lease.accountEmail,
1748
- scopes: [...lease.scopes],
1749
- accessToken: lease.accessToken,
1750
- expiresAt: lease.expiresAt
1751
- } : {
1752
- id: `airtable/${lease.name}`,
1753
- provider: lease.provider,
1754
- name: lease.name,
1755
- baseId: lease.baseId,
1756
- accessToken: lease.accessToken,
1757
- runExpiresAt: lease.runExpiresAt
1758
- };
1851
+ switch (lease.provider) {
1852
+ case "gmail":
1853
+ return {
1854
+ id: `gmail/${lease.name}`,
1855
+ provider: lease.provider,
1856
+ name: lease.name,
1857
+ accountEmail: lease.accountEmail,
1858
+ scopes: [...lease.scopes],
1859
+ accessToken: lease.accessToken,
1860
+ expiresAt: lease.expiresAt
1861
+ };
1862
+ case "airtable":
1863
+ return {
1864
+ id: `airtable/${lease.name}`,
1865
+ provider: lease.provider,
1866
+ name: lease.name,
1867
+ baseId: lease.baseId,
1868
+ accessToken: lease.accessToken,
1869
+ runExpiresAt: lease.runExpiresAt
1870
+ };
1871
+ case "dropbox":
1872
+ return {
1873
+ id: `dropbox/${lease.name}`,
1874
+ provider: lease.provider,
1875
+ name: lease.name,
1876
+ accountId: lease.accountId,
1877
+ access: lease.access,
1878
+ rootPath: lease.rootPath,
1879
+ scopes: [...lease.scopes],
1880
+ accessToken: lease.accessToken,
1881
+ expiresAt: lease.expiresAt
1882
+ };
1883
+ }
1759
1884
  }
1760
1885
  function parseConnectionLease(raw, now = Date.now()) {
1761
1886
  let v = raw;
@@ -1766,7 +1891,7 @@ function parseConnectionLease(raw, now = Date.now()) {
1766
1891
  throw new Error("Invalid connection credential");
1767
1892
  return v;
1768
1893
  }
1769
- 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)
1894
+ 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)
1770
1895
  return v;
1771
1896
  throw new Error("Invalid connection credential");
1772
1897
  }
@@ -2305,9 +2430,9 @@ function parseAnswerableQuestion(value) {
2305
2430
  let entry = object4(json(value));
2306
2431
  keys3(
2307
2432
  entry,
2308
- ["itemId", "revision", "title", "inboxUrl", "questionId", "routine", "prompt", "response", "answerable", "tags", "recordRefs", "instructions"],
2309
- ["itemId", "revision", "title", "inboxUrl", "questionId", "routine", "prompt", "response", "answerable"]
2310
- ), assert(id2(entry.itemId) && integer(entry.revision) && text4(entry.title) && text4(entry.inboxUrl));
2433
+ ["itemId", "number", "revision", "title", "inboxUrl", "questionId", "routine", "prompt", "response", "answerable", "tags", "recordRefs", "instructions"],
2434
+ ["itemId", "number", "revision", "title", "inboxUrl", "questionId", "routine", "prompt", "response", "answerable"]
2435
+ ), assert(id2(entry.itemId) && integer(entry.number) && integer(entry.revision) && text4(entry.title) && text4(entry.inboxUrl));
2311
2436
  let link = new URL(entry.inboxUrl), slug = link.hostname.slice(0, -13);
2312
2437
  return assert(link.hostname.endsWith(".oddington.ai") && slug.length <= 63 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug) && !["auth", "www", "api", "os"].includes(slug) && entry.inboxUrl === `https://${slug}.oddington.ai/inbox/${entry.itemId}`), assert(isAppRoutineName(entry.routine) && text4(entry.questionId) && entry.questionId === entry.questionId.trim() && text4(entry.prompt) && (!("instructions" in entry) || text4(entry.instructions, !0)) && typeof entry.answerable == "boolean"), responseDefinition(entry.response), askMetadata(entry), entry;
2313
2438
  }
@@ -2841,10 +2966,10 @@ var unavailable3 = () => new InboxOperationError(503, "unavailable"), stale = ()
2841
2966
  function hostFailure(error, context2) {
2842
2967
  return !context2.isCurrent() || context2.expiresAt <= Date.now() || error instanceof InboxContextError && error.status === 401 ? stale() : error instanceof InboxContextError && error.status === 403 ? new InboxOperationError(403, "access_denied") : unavailable3();
2843
2968
  }
2844
- async function send2(operation2, input) {
2969
+ async function send2(operation2, input, admitted) {
2845
2970
  let envelope = parseInboxRequest({ operation: operation2, input }), context2;
2846
2971
  try {
2847
- context2 = await resolveAppContext();
2972
+ context2 = admitted ?? await resolveAppContext();
2848
2973
  } catch {
2849
2974
  throw stale();
2850
2975
  }
@@ -3004,7 +3129,7 @@ var inbox = {
3004
3129
  let page = await send2("questions.list", {
3005
3130
  routines,
3006
3131
  ...beforeNumber === void 0 ? {} : { beforeNumber }
3007
- });
3132
+ }, initial);
3008
3133
  if (check(), page.questions.some((question2) => question2.inboxUrl !== `https://${initial.workspace}.oddington.ai/inbox/${question2.itemId}` || !routines.includes(question2.routine)) || beforeNumber !== void 0 && page.nextBeforeNumber !== null && page.nextBeforeNumber >= beforeNumber) throw unavailable3();
3009
3134
  if (questions.push(...page.questions), page.nextBeforeNumber === null) return questions;
3010
3135
  beforeNumber = page.nextBeforeNumber;