@zapier/kitcore 0.7.0 → 0.9.0

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/index.mjs CHANGED
@@ -1,6 +1,3 @@
1
- // src/registry.ts
2
- import { z } from "zod";
3
-
4
1
  // src/utils/string-utils.ts
5
2
  function toTitleCase(input) {
6
3
  return input.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_\-]+/g, " ").replace(/\s+/g, " ").trim().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
@@ -24,6 +21,65 @@ function pluralizeLastWord(title) {
24
21
  return [...words.slice(0, -1), pluralize(words[words.length - 1])].join(" ");
25
22
  }
26
23
 
24
+ // src/utils/schema-utils.ts
25
+ import { z } from "zod";
26
+ function canonicalInputSchema(schema) {
27
+ if (schema instanceof z.ZodUnion) {
28
+ return schema.options[0];
29
+ }
30
+ return schema;
31
+ }
32
+ function getOutputSchema(inputSchema) {
33
+ return inputSchema._zod.def.outputSchema;
34
+ }
35
+ function withOutputSchema(inputSchema, outputSchema) {
36
+ Object.assign(inputSchema._zod.def, {
37
+ outputSchema
38
+ });
39
+ return inputSchema;
40
+ }
41
+ function withResolver(schema, config) {
42
+ schema._zod.def.resolverMeta = config;
43
+ return schema;
44
+ }
45
+ function getSchemaDescription(schema) {
46
+ return schema.description;
47
+ }
48
+ function getFieldDescriptions(schema) {
49
+ const descriptions = {};
50
+ const shape = schema.shape;
51
+ for (const [key, fieldSchema] of Object.entries(shape)) {
52
+ if (fieldSchema instanceof z.ZodType && fieldSchema.description) {
53
+ descriptions[key] = fieldSchema.description;
54
+ }
55
+ }
56
+ return descriptions;
57
+ }
58
+ function withPositional(schema) {
59
+ Object.assign(schema._zod.def, {
60
+ positionalMeta: { positional: true }
61
+ });
62
+ return schema;
63
+ }
64
+ function schemaHasPositionalMeta(schema) {
65
+ return "positionalMeta" in schema._zod.def;
66
+ }
67
+ function isPositional(schema) {
68
+ if (schemaHasPositionalMeta(schema) && schema._zod.def.positionalMeta?.positional) {
69
+ return true;
70
+ }
71
+ if (schema instanceof z.ZodOptional) {
72
+ return isPositional(schema._zod.def.innerType);
73
+ }
74
+ if (schema instanceof z.ZodDefault) {
75
+ return isPositional(schema._zod.def.innerType);
76
+ }
77
+ return false;
78
+ }
79
+ function openEnum(values, description) {
80
+ return z.union([z.enum(values), z.string()]).describe(description);
81
+ }
82
+
27
83
  // src/registry.ts
28
84
  function resolveCategoryDefinition(ref) {
29
85
  const def = typeof ref === "string" ? { key: ref } : ref;
@@ -34,30 +90,25 @@ function resolveCategoryDefinition(ref) {
34
90
  titlePlural: def.titlePlural ?? pluralizeLastWord(title)
35
91
  };
36
92
  }
37
- function canonicalInputSchema(schema) {
38
- if (schema instanceof z.ZodUnion) {
39
- return schema.options[0];
40
- }
41
- return schema;
42
- }
43
93
  function buildRegistry({
44
94
  sdk,
45
95
  meta,
46
96
  formatters,
47
- boundResolvers,
97
+ resolvers,
48
98
  positional,
99
+ skipInputValidation,
49
100
  packageFilter
50
101
  }) {
51
102
  const definitionsByKey = /* @__PURE__ */ new Map();
52
103
  const objectDeclaredKeys = /* @__PURE__ */ new Set();
53
104
  for (const m of Object.values(meta)) {
54
105
  for (const ref of m.categories ?? []) {
55
- const key2 = typeof ref === "string" ? ref : ref.key;
106
+ const key = typeof ref === "string" ? ref : ref.key;
56
107
  if (typeof ref === "object") {
57
- objectDeclaredKeys.add(key2);
58
- definitionsByKey.set(key2, resolveCategoryDefinition(ref));
59
- } else if (!objectDeclaredKeys.has(key2)) {
60
- definitionsByKey.set(key2, resolveCategoryDefinition(ref));
108
+ objectDeclaredKeys.add(key);
109
+ definitionsByKey.set(key, resolveCategoryDefinition(ref));
110
+ } else if (!objectDeclaredKeys.has(key)) {
111
+ definitionsByKey.set(key, resolveCategoryDefinition(ref));
61
112
  }
62
113
  }
63
114
  }
@@ -65,30 +116,29 @@ function buildRegistry({
65
116
  definitionsByKey.set("other", resolveCategoryDefinition("other"));
66
117
  }
67
118
  const knownCategories = Array.from(definitionsByKey.keys());
68
- const functions = Object.keys(meta).filter((key2) => {
69
- const property = sdk[key2];
119
+ const functions = Object.keys(meta).filter((key) => {
120
+ const property = sdk[key];
70
121
  if (typeof property === "function") return true;
71
- const [rootKey] = key2.split(".");
122
+ const [rootKey] = key.split(".");
72
123
  const rootProperty = sdk[rootKey];
73
124
  return typeof rootProperty === "object" && rootProperty !== null;
74
- }).map((key2) => {
75
- const m = meta[key2];
125
+ }).map((key) => {
126
+ const m = meta[key];
76
127
  return {
77
- name: key2,
128
+ name: key,
78
129
  description: m.description,
79
130
  type: m.type,
80
131
  itemType: m.itemType,
81
132
  returnType: m.returnType,
82
133
  inputSchema: canonicalInputSchema(m.inputSchema),
83
- inputParameters: m.inputParameters,
84
134
  outputSchema: m.outputSchema,
85
- positional: positional?.[key2],
135
+ positional: positional?.[key],
136
+ skipInputValidation: skipInputValidation?.[key],
86
137
  categories: (m.categories ?? []).map(
87
138
  (c) => typeof c === "string" ? c : c.key
88
139
  ),
89
- resolvers: m.resolvers,
90
- boundResolvers: boundResolvers?.[key2],
91
- formatter: formatters?.[key2],
140
+ resolvers: resolvers?.[key],
141
+ formatter: formatters?.[key],
92
142
  experimental: m.experimental,
93
143
  packages: m.packages,
94
144
  confirm: m.confirm ?? (m.type === "delete" ? "delete" : void 0),
@@ -445,53 +495,49 @@ function decodeConcatCursor(incoming) {
445
495
  }
446
496
  return { index: 0, cursor: incoming };
447
497
  }
448
- function concatPaginated({
498
+ async function concatLists({
449
499
  sources,
450
500
  pageSize = 100,
451
501
  cursor
452
502
  }) {
453
503
  if (sources.length === 0) {
454
- const empty = { data: [] };
455
- return Object.assign(Promise.resolve(empty), {
456
- [Symbol.asyncIterator]: async function* () {
457
- yield empty;
458
- }
459
- });
504
+ return { data: [] };
460
505
  }
461
506
  const pageFunction = async (options) => {
462
- let { index, cursor: sourceCursor } = decodeConcatCursor(options.cursor);
507
+ let { index, cursor: listCursor } = decodeConcatCursor(options.cursor);
463
508
  while (index < sources.length) {
464
- const page = await sources[index]({ cursor: sourceCursor });
465
- const hasMoreInSource = page.nextCursor != null;
466
- if (page.data.length === 0 && !hasMoreInSource) {
509
+ const page = await sources[index]({ cursor: listCursor });
510
+ const hasMoreInList = page.nextCursor != null;
511
+ if (page.data.length === 0 && !hasMoreInList) {
467
512
  index++;
468
- sourceCursor = void 0;
513
+ listCursor = void 0;
469
514
  continue;
470
515
  }
471
516
  return {
472
517
  data: page.data,
473
- nextCursor: hasMoreInSource ? encodeConcatCursor(index, page.nextCursor) : index < sources.length - 1 ? encodeConcatCursor(index + 1, void 0) : void 0
518
+ nextCursor: hasMoreInList ? encodeConcatCursor(index, page.nextCursor) : index < sources.length - 1 ? encodeConcatCursor(index + 1, void 0) : void 0
474
519
  };
475
520
  }
476
521
  return { data: [] };
477
522
  };
478
- const iterator = paginateBuffered(pageFunction, { pageSize, cursor });
479
- const firstPagePromise = iterator.next().then((result) => {
480
- if (result.done) {
481
- return { data: [] };
482
- }
483
- return result.value;
484
- });
485
- return Object.assign(firstPagePromise, {
486
- [Symbol.asyncIterator]: async function* () {
487
- yield await firstPagePromise;
488
- for await (const page of { [Symbol.asyncIterator]: () => iterator }) {
489
- yield page;
490
- }
491
- }
492
- });
523
+ const result = await paginateBuffered(pageFunction, {
524
+ pageSize,
525
+ cursor
526
+ }).next();
527
+ return result.done ? { data: [] } : result.value;
528
+ }
529
+ function concatPaginated({
530
+ sources,
531
+ pageSize,
532
+ cursor
533
+ }) {
534
+ logDeprecation("concatPaginated() is deprecated. Use concatLists() instead.");
535
+ return concatLists({ sources, pageSize, cursor });
493
536
  }
494
537
  function toIterable(source) {
538
+ logDeprecation(
539
+ "toIterable() is deprecated. Call .pages() on the paginated result instead."
540
+ );
495
541
  return { [Symbol.asyncIterator]: () => source[Symbol.asyncIterator]() };
496
542
  }
497
543
 
@@ -581,6 +627,52 @@ function runInMethodScope(fn) {
581
627
  var runWithTelemetryContext = runInMethodScope;
582
628
  var isTelemetryNested = isNestedMethodCall;
583
629
 
630
+ // src/utils/call-context.ts
631
+ var CALL_CONTEXT_BRAND = Symbol("kitcore.callContext");
632
+ function isCallContext(value) {
633
+ return typeof value === "object" && value !== null && value[CALL_CONTEXT_BRAND] === true;
634
+ }
635
+ function generateCallId() {
636
+ try {
637
+ const webCrypto = globalThis.crypto;
638
+ if (webCrypto?.randomUUID) {
639
+ return webCrypto.randomUUID();
640
+ }
641
+ if (webCrypto?.getRandomValues) {
642
+ const bytes = webCrypto.getRandomValues(new Uint8Array(16));
643
+ const hex = Array.from(bytes, (byte, i) => {
644
+ const value = i === 6 ? byte & 15 | 64 : i === 8 ? byte & 63 | 128 : byte;
645
+ return value.toString(16).padStart(2, "0");
646
+ });
647
+ return [
648
+ hex.slice(0, 4).join(""),
649
+ hex.slice(4, 6).join(""),
650
+ hex.slice(6, 8).join(""),
651
+ hex.slice(8, 10).join(""),
652
+ hex.slice(10, 16).join("")
653
+ ].join("-");
654
+ }
655
+ } catch {
656
+ }
657
+ return null;
658
+ }
659
+ function rootCallContext() {
660
+ return {
661
+ callId: generateCallId(),
662
+ depth: 0,
663
+ annotations: {},
664
+ [CALL_CONTEXT_BRAND]: true
665
+ };
666
+ }
667
+ function childCallContext(parent) {
668
+ return {
669
+ callId: parent.callId,
670
+ depth: parent.depth + 1,
671
+ annotations: {},
672
+ [CALL_CONTEXT_BRAND]: true
673
+ };
674
+ }
675
+
584
676
  // src/utils/core-options.ts
585
677
  function defaultLogDeprecation({
586
678
  methodName,
@@ -599,6 +691,9 @@ function resolveCoreOptions(context) {
599
691
  return context.core;
600
692
  }
601
693
  var INTERNAL_CALL = Symbol("kitcore.internalCall");
694
+ function resolveCallContext(secondArg) {
695
+ return isCallContext(secondArg) ? secondArg : rootCallContext();
696
+ }
602
697
  function signalDeprecation(context, methodName, getDeprecation) {
603
698
  if (isInsideObserver()) return;
604
699
  const deprecation = getDeprecation?.();
@@ -628,14 +723,16 @@ function createFunction(coreFn, options) {
628
723
  const functionName = name || coreFn.name;
629
724
  const namedFunctions = {
630
725
  [functionName]: async function(callOptions) {
631
- if (arguments[1] !== INTERNAL_CALL) {
726
+ const internal = arguments[1];
727
+ const context = resolveCallContext(internal);
728
+ if (!isCallContext(internal) && internal !== INTERNAL_CALL) {
632
729
  signalDeprecation(sdk.context, functionName, getDeprecation);
633
730
  }
634
731
  return runInMethodScope(async () => {
635
732
  const startTime = Date.now();
636
733
  const normalizedOptions = callOptions ?? {};
637
734
  const args = [normalizedOptions];
638
- const depth = getCurrentDepth();
735
+ const depth = Math.max(context.depth, getCurrentDepth());
639
736
  const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
640
737
  const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
641
738
  hooks?.onMethodStart?.({
@@ -654,12 +751,15 @@ function createFunction(coreFn, options) {
654
751
  adaptError
655
752
  }
656
753
  );
657
- result = await coreFn({
658
- ...normalizedOptions,
659
- ...validatedOptions
660
- });
754
+ result = await coreFn(
755
+ {
756
+ ...normalizedOptions,
757
+ ...validatedOptions
758
+ },
759
+ context
760
+ );
661
761
  } else {
662
- result = await coreFn(normalizedOptions);
762
+ result = await coreFn(normalizedOptions, context);
663
763
  }
664
764
  hooks?.onMethodEnd?.({
665
765
  methodName: functionName,
@@ -689,17 +789,19 @@ function createFunction(coreFn, options) {
689
789
  function createRawFunction(coreFn, options) {
690
790
  const { sdk, name, schema, positional, getDeprecation } = options;
691
791
  return function(rawInput) {
692
- if (arguments[1] !== INTERNAL_CALL) {
792
+ const internal = arguments[1];
793
+ const context = resolveCallContext(internal);
794
+ if (!isCallContext(internal) && internal !== INTERNAL_CALL) {
693
795
  signalDeprecation(sdk.context, name, getDeprecation);
694
796
  }
695
797
  return runInMethodScope(() => {
696
798
  const startTime = Date.now();
697
- const depth = getCurrentDepth();
799
+ const depth = Math.max(context.depth, getCurrentDepth());
698
800
  const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
699
801
  const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
700
802
  const input = schema ? rawInput ?? {} : rawInput;
701
803
  const record = input;
702
- const args = positional ? positional.filter((key2) => record?.[key2] !== void 0).map((key2) => record?.[key2]) : [input];
804
+ const args = positional ? positional.filter((key) => record?.[key] !== void 0).map((key) => record?.[key]) : [input];
703
805
  hooks?.onMethodStart?.({
704
806
  methodName: name,
705
807
  args,
@@ -718,7 +820,7 @@ function createRawFunction(coreFn, options) {
718
820
  };
719
821
  try {
720
822
  const parsed = schema ? validateOptions(schema, input, { adaptError }) : input;
721
- const result = coreFn(parsed);
823
+ const result = coreFn(parsed, context);
722
824
  if (result !== null && typeof result === "object" && typeof result.then === "function") {
723
825
  return result.then(
724
826
  (value) => {
@@ -757,9 +859,9 @@ function createPageFunction(coreFn, {
757
859
  }) {
758
860
  const functionName = coreFn.name + "Page";
759
861
  const namedFunctions = {
760
- [functionName]: async function(options) {
862
+ [functionName]: async function(options, callContext) {
761
863
  try {
762
- const response = await coreFn(options);
864
+ const response = await coreFn(options, callContext);
763
865
  const page = adaptPage ? adaptPage(response) : response;
764
866
  if (!isSdkPage(page)) {
765
867
  throw new Error(
@@ -783,14 +885,16 @@ function createPaginatedFunction(coreFn, options) {
783
885
  const functionName = name || coreFn.name;
784
886
  const namedFunctions = {
785
887
  [functionName]: function(callOptions) {
786
- if (arguments[1] !== INTERNAL_CALL) {
888
+ const internal = arguments[1];
889
+ const context = resolveCallContext(internal);
890
+ if (!isCallContext(internal) && internal !== INTERNAL_CALL) {
787
891
  signalDeprecation(sdk.context, functionName, getDeprecation);
788
892
  }
789
893
  return runInMethodScope(() => {
790
894
  const startTime = Date.now();
791
895
  const normalizedOptions = callOptions ?? {};
792
896
  const args = [normalizedOptions];
793
- const depth = getCurrentDepth();
897
+ const depth = Math.max(context.depth, getCurrentDepth());
794
898
  const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
795
899
  const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
796
900
  hooks?.onMethodStart?.({
@@ -809,7 +913,11 @@ function createPaginatedFunction(coreFn, options) {
809
913
  ...validatedOptions,
810
914
  pageSize
811
915
  };
812
- const iterator = paginate(pageFunction, optimizedOptions);
916
+ const iterator = paginate(
917
+ (pageOptions) => pageFunction(pageOptions, context),
918
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
919
+ optimizedOptions
920
+ );
813
921
  const firstPagePromise = iterator.next().then((result) => {
814
922
  if (result.done) {
815
923
  throw new Error("Paginate should always iterate at least once");
@@ -846,6 +954,13 @@ function createPaginatedFunction(coreFn, options) {
846
954
  [Symbol.asyncIterator]() {
847
955
  return pageStream;
848
956
  },
957
+ pages: function() {
958
+ return {
959
+ [Symbol.asyncIterator]() {
960
+ return pageStream;
961
+ }
962
+ };
963
+ },
849
964
  items: function() {
850
965
  return {
851
966
  [Symbol.asyncIterator]: async function* () {
@@ -953,11 +1068,11 @@ var RESERVED_ROOT_KEYS = /* @__PURE__ */ new Set([
953
1068
  "context",
954
1069
  "getRegistry"
955
1070
  ]);
956
- function hasOwn(obj, key2) {
957
- return Object.prototype.hasOwnProperty.call(obj, key2);
1071
+ function hasOwn(obj, key) {
1072
+ return Object.prototype.hasOwnProperty.call(obj, key);
958
1073
  }
959
- function setOwn(target, key2, value) {
960
- Object.defineProperty(target, key2, {
1074
+ function setOwn(target, key, value) {
1075
+ Object.defineProperty(target, key, {
961
1076
  value,
962
1077
  enumerable: true,
963
1078
  configurable: true,
@@ -969,31 +1084,31 @@ function checkCollisions(target, source, kind, callerLabel, override) {
969
1084
  checkRootKeyCollisions(target, Object.keys(source), override, callerLabel);
970
1085
  return;
971
1086
  }
972
- for (const key2 of Object.keys(source)) {
973
- if (!override && hasOwn(target, key2)) {
1087
+ for (const key of Object.keys(source)) {
1088
+ if (!override && hasOwn(target, key)) {
974
1089
  throw new Error(
975
- `${callerLabel}: duplicate ${kind} "${key2}". If the override is intentional, pass { override: true } in the options.`
1090
+ `${callerLabel}: duplicate ${kind} "${key}". If the override is intentional, pass { override: true } in the options.`
976
1091
  );
977
1092
  }
978
1093
  }
979
1094
  }
980
1095
  function checkRootKeyCollisions(target, keys, override, callerLabel) {
981
- for (const key2 of keys) {
982
- if (RESERVED_ROOT_KEYS.has(key2)) {
1096
+ for (const key of keys) {
1097
+ if (RESERVED_ROOT_KEYS.has(key)) {
983
1098
  throw new Error(
984
- `${callerLabel}: plugin attempted to register reserved root key "${key2}". The SDK uses this key for its own accessor; rename the plugin's method.`
1099
+ `${callerLabel}: plugin attempted to register reserved root key "${key}". The SDK uses this key for its own accessor; rename the plugin's method.`
985
1100
  );
986
1101
  }
987
- if (!override && hasOwn(target, key2)) {
1102
+ if (!override && hasOwn(target, key)) {
988
1103
  throw new Error(
989
- `${callerLabel}: duplicate root key "${key2}". If the override is intentional, pass { override: true } in the options.`
1104
+ `${callerLabel}: duplicate root key "${key}". If the override is intentional, pass { override: true } in the options.`
990
1105
  );
991
1106
  }
992
1107
  }
993
1108
  }
994
1109
  function applyOwnProperties(target, source) {
995
- for (const key2 of Object.keys(source)) {
996
- setOwn(target, key2, source[key2]);
1110
+ for (const key of Object.keys(source)) {
1111
+ setOwn(target, key, source[key]);
997
1112
  }
998
1113
  }
999
1114
  function createPluginAccumulator(initialProperties = {}, initialContext = {}) {
@@ -1211,7 +1326,6 @@ var LEAF_META_KEYS = [
1211
1326
  "itemType",
1212
1327
  "returnType",
1213
1328
  "outputSchema",
1214
- "inputParameters",
1215
1329
  "packages",
1216
1330
  "experimental",
1217
1331
  "confirm",
@@ -1251,8 +1365,8 @@ function normalizeImports(deps) {
1251
1365
  }
1252
1366
  function collectLeafMeta(config) {
1253
1367
  let meta;
1254
- for (const key2 of LEAF_META_KEYS) {
1255
- if (config[key2] !== void 0) (meta ?? (meta = {}))[key2] = config[key2];
1368
+ for (const key of LEAF_META_KEYS) {
1369
+ if (config[key] !== void 0) (meta ?? (meta = {}))[key] = config[key];
1256
1370
  }
1257
1371
  return meta;
1258
1372
  }
@@ -1335,7 +1449,8 @@ function defineResolver(config) {
1335
1449
  type: "object",
1336
1450
  properties: config.properties,
1337
1451
  definitions: config.definitions,
1338
- getProperties: config.getProperties
1452
+ getProperties: config.getProperties,
1453
+ additionalKeys: config.additionalKeys
1339
1454
  };
1340
1455
  case "array":
1341
1456
  return {
@@ -1647,7 +1762,7 @@ function normalizeFormatter(entry, sdk) {
1647
1762
  const legacy = entry.meta?.formatter;
1648
1763
  return legacy ? adaptLegacyFormatter(legacy, sdk) : void 0;
1649
1764
  }
1650
- function normalizeBoundResolvers(entry) {
1765
+ function normalizeResolvers(entry) {
1651
1766
  if (entry.pluginType !== "method") return void 0;
1652
1767
  return entry.resolvers;
1653
1768
  }
@@ -1688,17 +1803,20 @@ function collectSurfaceProjection(context, formatterSdk) {
1688
1803
  foldDynamicMembers(entry, surfaceBindings, meta);
1689
1804
  }
1690
1805
  const formatters = {};
1691
- const boundResolvers = {};
1806
+ const resolvers = {};
1692
1807
  const positional = {};
1808
+ const skipInputValidation = {};
1693
1809
  for (const [binding, entry] of Object.entries(entries)) {
1694
1810
  const f = normalizeFormatter(entry, formatterSdk);
1695
1811
  if (f) formatters[binding] = f;
1696
- const r = normalizeBoundResolvers(entry);
1697
- if (r) boundResolvers[binding] = r;
1812
+ const r = normalizeResolvers(entry);
1813
+ if (r) resolvers[binding] = r;
1698
1814
  const p = methodPositional(entry);
1699
1815
  if (p) positional[binding] = p;
1816
+ if (entry.pluginType === "method" && entry.skipInputValidation)
1817
+ skipInputValidation[binding] = true;
1700
1818
  }
1701
- return { meta, formatters, boundResolvers, positional };
1819
+ return { meta, formatters, resolvers, positional, skipInputValidation };
1702
1820
  }
1703
1821
  function buildSurfaceRegistry(context, packageFilter) {
1704
1822
  const surface = {};
@@ -1755,6 +1873,11 @@ function nestedResolvers(resolver) {
1755
1873
  for (const field of Object.values(resolver.properties ?? {})) {
1756
1874
  if (!isResolverRef(field.resolver)) out.push(field.resolver);
1757
1875
  }
1876
+ const ak = resolver.additionalKeys;
1877
+ if (ak) {
1878
+ if (!isResolverRef(ak.values)) out.push(ak.values);
1879
+ if (ak.keys && !isResolverRef(ak.keys)) out.push(ak.keys);
1880
+ }
1758
1881
  out.push(...Object.values(resolver.definitions ?? {}));
1759
1882
  } else if (resolver.type === "array") {
1760
1883
  if (!isResolverRef(resolver.items)) out.push(resolver.items);
@@ -1879,16 +2002,16 @@ function collectPlugins(root, materialized = /* @__PURE__ */ new Set(), configur
1879
2002
  }
1880
2003
  return byId;
1881
2004
  }
1882
- function bindValue(target, key2, entry, callType = "surface") {
2005
+ function bindValue(target, key, entry, callType = "surface", ctx) {
1883
2006
  if (entry.pluginType === "property" && entry.getValue) {
1884
- Object.defineProperty(target, key2, {
2007
+ Object.defineProperty(target, key, {
1885
2008
  get: entry.getValue,
1886
2009
  enumerable: true,
1887
2010
  configurable: true
1888
2011
  });
1889
2012
  } else {
1890
- const value = callType === "internal" && entry.pluginType === "method" ? entry.internalValue ?? entry.value : entry.value;
1891
- Object.defineProperty(target, key2, {
2013
+ const value = callType === "internal" && entry.pluginType === "method" ? entry.bindInternal?.(ctx) ?? entry.internalValue ?? entry.value : entry.value;
2014
+ Object.defineProperty(target, key, {
1892
2015
  value,
1893
2016
  writable: true,
1894
2017
  enumerable: true,
@@ -1905,7 +2028,7 @@ function buildSurface(context, ...maps) {
1905
2028
  sdk[CONTEXT] = context;
1906
2029
  return sdk;
1907
2030
  }
1908
- function buildImports(plugins, importBindings) {
2031
+ function buildImports(plugins, importBindings, ctx) {
1909
2032
  const imports = {};
1910
2033
  for (const { binding, id, optional } of importBindings) {
1911
2034
  const entry = plugins[id];
@@ -1918,7 +2041,7 @@ function buildImports(plugins, importBindings) {
1918
2041
  });
1919
2042
  continue;
1920
2043
  }
1921
- bindValue(imports, binding, entry, "internal");
2044
+ bindValue(imports, binding, entry, "internal", ctx);
1922
2045
  }
1923
2046
  return imports;
1924
2047
  }
@@ -1997,6 +2120,19 @@ function bindResolver(resolver, plugins) {
1997
2120
  const { getProperties } = resolver;
1998
2121
  if (getProperties)
1999
2122
  bound.getProperties = ({ input }) => getProperties({ imports, input });
2123
+ if (resolver.additionalKeys) {
2124
+ const ak = resolver.additionalKeys;
2125
+ const boundAk = {
2126
+ values: isResolverRef(ak.values) ? ak.values : bindResolver(ak.values, plugins),
2127
+ minEntries: ak.minEntries,
2128
+ maxEntries: ak.maxEntries,
2129
+ keyValueType: ak.keyValueType,
2130
+ valueValueType: ak.valueValueType
2131
+ };
2132
+ if (ak.keys)
2133
+ boundAk.keys = isResolverRef(ak.keys) ? ak.keys : bindResolver(ak.keys, plugins);
2134
+ bound.additionalKeys = boundAk;
2135
+ }
2000
2136
  return bound;
2001
2137
  }
2002
2138
  case "array": {
@@ -2052,8 +2188,8 @@ function bindResolver(resolver, plugins) {
2052
2188
  }
2053
2189
  function bindFields(fields, plugins) {
2054
2190
  const out = {};
2055
- for (const [key2, field] of Object.entries(fields)) {
2056
- out[key2] = {
2191
+ for (const [key, field] of Object.entries(fields)) {
2192
+ out[key] = {
2057
2193
  ...field,
2058
2194
  resolver: isResolverRef(field.resolver) ? field.resolver : bindResolver(field.resolver, plugins)
2059
2195
  };
@@ -2062,8 +2198,8 @@ function bindFields(fields, plugins) {
2062
2198
  }
2063
2199
  function bindDefinitions(definitions, plugins) {
2064
2200
  const out = {};
2065
- for (const [key2, def] of Object.entries(definitions)) {
2066
- out[key2] = bindResolver(def, plugins);
2201
+ for (const [key, def] of Object.entries(definitions)) {
2202
+ out[key] = bindResolver(def, plugins);
2067
2203
  }
2068
2204
  return out;
2069
2205
  }
@@ -2148,6 +2284,7 @@ function buildMethodEntries(descriptors, context, states) {
2148
2284
  name: descriptor.name,
2149
2285
  chain: [],
2150
2286
  inputSchema: descriptor.inputSchema,
2287
+ skipInputValidation: descriptor.skipInputValidation,
2151
2288
  // Derive the presentation type from the output mode when the author did
2152
2289
  // not set one; an explicit meta.type (e.g. "create") still wins.
2153
2290
  meta: out.type === "raw" || descriptor.meta?.type ? descriptor.meta : { ...descriptor.meta, type: out.type },
@@ -2155,17 +2292,17 @@ function buildMethodEntries(descriptors, context, states) {
2155
2292
  // Replaced below; never called.
2156
2293
  value: () => void 0
2157
2294
  };
2158
- const callRun = (input) => descriptor.run({
2159
- imports: buildImports(plugins, descriptor.importBindings),
2295
+ const callRun = (input, ctx) => descriptor.run({
2296
+ imports: buildImports(plugins, descriptor.importBindings, ctx),
2160
2297
  state: states.get(id),
2161
2298
  input
2162
2299
  });
2163
- const fold = (coreFn) => (input) => {
2164
- let next = coreFn;
2300
+ const fold = (coreFn) => (input, ctx) => {
2301
+ let next = (i) => coreFn(i, ctx);
2165
2302
  for (const wrap of entry.chain) {
2166
2303
  const inner = next;
2167
2304
  next = (i) => wrap.run({
2168
- imports: buildImports(plugins, wrap.owner.importBindings),
2305
+ imports: buildImports(plugins, wrap.owner.importBindings, ctx),
2169
2306
  next: inner,
2170
2307
  input: i,
2171
2308
  // Overwritten by the chain item's own closure with the owning
@@ -2189,7 +2326,7 @@ function buildMethodEntries(descriptors, context, states) {
2189
2326
  }
2190
2327
  );
2191
2328
  } else if (out.type === "item") {
2192
- const itemCore = async (input) => callRun(input);
2329
+ const itemCore = async (input, ctx) => callRun(input, ctx);
2193
2330
  entry.value = createFunction(
2194
2331
  fold(itemCore),
2195
2332
  {
@@ -2201,7 +2338,7 @@ function buildMethodEntries(descriptors, context, states) {
2201
2338
  );
2202
2339
  } else {
2203
2340
  entry.value = createRawFunction(
2204
- (input) => fold(callRun)(input),
2341
+ (input, ctx) => fold(callRun)(input, ctx),
2205
2342
  {
2206
2343
  sdk,
2207
2344
  name: descriptor.name,
@@ -2224,11 +2361,15 @@ function buildMethodEntries(descriptors, context, states) {
2224
2361
  });
2225
2362
  return packed;
2226
2363
  };
2364
+ const internalValue = (...args) => canonicalValue(pack(args), INTERNAL_CALL);
2227
2365
  entry.value = (...args) => canonicalValue(pack(args));
2228
- entry.internalValue = (...args) => canonicalValue(pack(args), INTERNAL_CALL);
2366
+ entry.internalValue = internalValue;
2367
+ entry.bindInternal = (ctx) => ctx ? (...args) => canonicalValue(pack(args), childCallContext(ctx)) : internalValue;
2229
2368
  entry.positional = names;
2230
2369
  } else {
2231
- entry.internalValue = (input) => canonicalValue(input, INTERNAL_CALL);
2370
+ const internalValue = (input) => canonicalValue(input, INTERNAL_CALL);
2371
+ entry.internalValue = internalValue;
2372
+ entry.bindInternal = (ctx) => ctx ? (input) => canonicalValue(input, childCallContext(ctx)) : internalValue;
2232
2373
  }
2233
2374
  plugins[id] = entry;
2234
2375
  }
@@ -2468,7 +2609,7 @@ function createSdk(root, options) {
2468
2609
  pluginSurface = {};
2469
2610
  bindValue(pluginSurface, plugin.name, plugins2[plugin.id]);
2470
2611
  }
2471
- for (const key2 of Object.keys(legacyExports)) context.surface[key2] = key2;
2612
+ for (const key of Object.keys(legacyExports)) context.surface[key] = key;
2472
2613
  if (plugin.pluginType === "aggregate") {
2473
2614
  recordExportSurface(context, plugin.exports);
2474
2615
  } else {
@@ -2607,6 +2748,7 @@ function valueTypeOf(inner) {
2607
2748
  if (inner instanceof z3.ZodEnum) return "string";
2608
2749
  if (inner instanceof z3.ZodArray) return "array";
2609
2750
  if (inner instanceof z3.ZodObject) return "object";
2751
+ if (inner instanceof z3.ZodRecord) return "object";
2610
2752
  return void 0;
2611
2753
  }
2612
2754
  function staticChoicesOf(inner) {
@@ -2617,7 +2759,8 @@ function staticChoicesOf(inner) {
2617
2759
  return void 0;
2618
2760
  }
2619
2761
  function objectShape(schema) {
2620
- const { inner } = schema ? unwrap(schema) : { inner: void 0 };
2762
+ const canonical = canonicalInputSchema(schema);
2763
+ const { inner } = canonical ? unwrap(canonical) : { inner: void 0 };
2621
2764
  if (inner instanceof z3.ZodObject) {
2622
2765
  return inner.shape;
2623
2766
  }
@@ -2639,7 +2782,7 @@ function topoOrder2(specs) {
2639
2782
  }
2640
2783
  function planParameters(entry) {
2641
2784
  const shape = objectShape(entry.inputSchema);
2642
- const resolvers = entry.boundResolvers ?? {};
2785
+ const resolvers = entry.resolvers ?? {};
2643
2786
  const names = shape ? [
2644
2787
  ...Object.keys(shape),
2645
2788
  ...Object.keys(resolvers).filter(
@@ -2677,24 +2820,48 @@ function getAtPath(root, path) {
2677
2820
  }
2678
2821
  return node;
2679
2822
  }
2823
+ function defineOwn(node, key, value) {
2824
+ Object.defineProperty(node, key, {
2825
+ value,
2826
+ writable: true,
2827
+ enumerable: true,
2828
+ configurable: true
2829
+ });
2830
+ }
2680
2831
  function setAtPath(root, path, value) {
2681
2832
  let node = root;
2682
2833
  for (let i = 0; i < path.length - 1; i++) {
2683
2834
  const seg = path[i];
2684
- if (node[seg] == null || typeof node[seg] !== "object") node[seg] = {};
2685
- node = node[seg];
2835
+ const existing = Object.prototype.hasOwnProperty.call(node, seg) ? node[seg] : void 0;
2836
+ if (existing != null && typeof existing === "object") {
2837
+ node = existing;
2838
+ } else {
2839
+ const child = {};
2840
+ defineOwn(node, seg, child);
2841
+ node = child;
2842
+ }
2686
2843
  }
2687
- node[path[path.length - 1]] = value;
2844
+ defineOwn(node, path[path.length - 1], value);
2688
2845
  }
2689
- var key = (path) => path.join(".");
2846
+ var SAFE_SEGMENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
2847
+ var pathToKey = (path) => {
2848
+ let out = "";
2849
+ for (const segment of path) {
2850
+ if (typeof segment === "number") out += `[${segment}]`;
2851
+ else if (SAFE_SEGMENT.test(segment))
2852
+ out += out === "" ? segment : `.${segment}`;
2853
+ else out += `[${JSON.stringify(segment)}]`;
2854
+ }
2855
+ return out;
2856
+ };
2690
2857
  function isSettled(state, path) {
2691
- return state.settled.includes(key(path));
2858
+ return state.settled.includes(pathToKey(path));
2692
2859
  }
2693
2860
  function remember(state, k) {
2694
2861
  if (!state.settled.includes(k)) state.settled.push(k);
2695
2862
  }
2696
2863
  function settle(state, path) {
2697
- remember(state, key(path));
2864
+ remember(state, pathToKey(path));
2698
2865
  }
2699
2866
  function clone(state) {
2700
2867
  return JSON.parse(JSON.stringify(state));
@@ -2709,6 +2876,28 @@ function coerce(leaf, raw) {
2709
2876
  if (raw === "true") return true;
2710
2877
  if (raw === "false") return false;
2711
2878
  }
2879
+ if (leaf.valueType === "object") {
2880
+ const trimmed = raw.trim();
2881
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
2882
+ try {
2883
+ return JSON.parse(trimmed);
2884
+ } catch {
2885
+ return raw;
2886
+ }
2887
+ }
2888
+ return raw;
2889
+ }
2890
+ if (leaf.valueType === "array") {
2891
+ const trimmed = raw.trim();
2892
+ if (trimmed.startsWith("[")) {
2893
+ try {
2894
+ return JSON.parse(trimmed);
2895
+ } catch {
2896
+ return raw;
2897
+ }
2898
+ }
2899
+ return raw;
2900
+ }
2712
2901
  return raw;
2713
2902
  }
2714
2903
  async function validationError(leaf, value, state) {
@@ -2766,27 +2955,6 @@ async function objectChildren(resolver, input) {
2766
2955
  return toLeaf(name, field, resolver.definitions);
2767
2956
  });
2768
2957
  }
2769
- function arrayItem(resolver) {
2770
- const items = resolver.items;
2771
- const valueType = resolver.itemValueType;
2772
- if (isRef(items)) {
2773
- return {
2774
- name: "",
2775
- required: true,
2776
- resolver: resolver.definitions?.[items.ref],
2777
- extraInput: items.input,
2778
- valueType,
2779
- requires: []
2780
- };
2781
- }
2782
- return {
2783
- name: "",
2784
- required: true,
2785
- resolver: items,
2786
- valueType,
2787
- requires: []
2788
- };
2789
- }
2790
2958
  function autoSettles(resolver) {
2791
2959
  return resolver.type === "constant" || resolver.type === "info";
2792
2960
  }
@@ -2806,10 +2974,28 @@ async function leafAt(ctx, path, resolved) {
2806
2974
  const seg = path[i];
2807
2975
  if (typeof seg === "number") {
2808
2976
  if (leaf?.resolver?.type !== "array") return void 0;
2809
- leaf = arrayItem(leaf.resolver);
2977
+ leaf = boundLeaf(
2978
+ "",
2979
+ leaf.resolver.items,
2980
+ leaf.resolver.definitions,
2981
+ leaf.resolver.itemValueType
2982
+ );
2810
2983
  } else {
2811
- leaf = children.find((c) => c.name === seg);
2812
- if (!leaf) return void 0;
2984
+ const parent = leaf;
2985
+ const found = children.find((c) => c.name === seg);
2986
+ if (found) {
2987
+ leaf = found;
2988
+ } else if (parent?.resolver?.type === "object" && parent.resolver.additionalKeys) {
2989
+ const ak = parent.resolver.additionalKeys;
2990
+ leaf = boundLeaf(
2991
+ String(seg),
2992
+ ak.values,
2993
+ parent.resolver.definitions,
2994
+ ak.valueValueType
2995
+ );
2996
+ } else {
2997
+ return void 0;
2998
+ }
2813
2999
  }
2814
3000
  if (i < path.length - 1 && typeof path[i + 1] === "string") {
2815
3001
  if (leaf?.resolver?.type !== "object") return void 0;
@@ -2821,16 +3007,81 @@ async function leafAt(ctx, path, resolved) {
2821
3007
  }
2822
3008
  return leaf;
2823
3009
  }
3010
+ function boundLeaf(name, resolverOrRef, definitions, valueType) {
3011
+ if (isRef(resolverOrRef)) {
3012
+ return {
3013
+ name,
3014
+ required: true,
3015
+ resolver: definitions?.[resolverOrRef.ref],
3016
+ extraInput: resolverOrRef.input,
3017
+ valueType,
3018
+ requires: []
3019
+ };
3020
+ }
3021
+ return {
3022
+ name,
3023
+ required: true,
3024
+ resolver: resolverOrRef,
3025
+ valueType,
3026
+ requires: []
3027
+ };
3028
+ }
3029
+ async function recordInfoAt(ctx, path, resolved) {
3030
+ const leaf = await leafAt(ctx, path, resolved);
3031
+ const resolver = leaf?.resolver;
3032
+ if (resolver?.type !== "object" || !resolver.additionalKeys) {
3033
+ throw new Error(
3034
+ `expected an object resolver with additionalKeys at "${pathToKey(path)}"`
3035
+ );
3036
+ }
3037
+ if (resolver.getProperties) {
3038
+ throw new Error(
3039
+ `object resolver at "${pathToKey(path)}" cannot combine getProperties with additionalKeys`
3040
+ );
3041
+ }
3042
+ const ak = resolver.additionalKeys;
3043
+ const defs = resolver.definitions;
3044
+ const keyLeaf = ak.keys ? boundLeaf("key", ak.keys, defs, ak.keyValueType ?? "string") : {
3045
+ name: "key",
3046
+ required: true,
3047
+ resolver: { type: "static", inputType: "text" },
3048
+ valueType: "string",
3049
+ requires: []
3050
+ };
3051
+ const valueLeaf = boundLeaf("value", ak.values, defs, ak.valueValueType);
3052
+ if (keyLeaf.resolver && keyLeaf.resolver.type !== "static") {
3053
+ throw new Error(
3054
+ `record key resolver at "${pathToKey(path)}" must be a static free-text prompt, not "${keyLeaf.resolver.type}"`
3055
+ );
3056
+ }
3057
+ if (valueLeaf.resolver?.type === "object" || valueLeaf.resolver?.type === "array") {
3058
+ throw new Error(
3059
+ `record value resolver at "${pathToKey(path)}" must be a single value, not "${valueLeaf.resolver.type}"`
3060
+ );
3061
+ }
3062
+ return {
3063
+ min: ak.minEntries ?? 0,
3064
+ max: ak.maxEntries ?? Infinity,
3065
+ keyLeaf,
3066
+ valueLeaf,
3067
+ fixedKeys: Object.keys(resolver.properties ?? {})
3068
+ };
3069
+ }
2824
3070
  async function arrayInfoAt(ctx, path, resolved) {
2825
3071
  const leaf = await leafAt(ctx, path, resolved);
2826
3072
  const resolver = leaf?.resolver;
2827
3073
  if (resolver?.type !== "array") {
2828
- throw new Error(`expected an array resolver at "${key(path)}"`);
3074
+ throw new Error(`expected an array resolver at "${pathToKey(path)}"`);
2829
3075
  }
2830
3076
  return {
2831
3077
  min: resolver.minItems ?? 0,
2832
3078
  max: resolver.maxItems ?? Infinity,
2833
- item: { ...arrayItem(resolver), name: String(path[path.length - 1]) }
3079
+ item: boundLeaf(
3080
+ String(path[path.length - 1]),
3081
+ resolver.items,
3082
+ resolver.definitions,
3083
+ resolver.itemValueType
3084
+ )
2834
3085
  };
2835
3086
  }
2836
3087
  async function firstPage(result) {
@@ -2911,6 +3162,9 @@ var AFFORDANCE = {
2911
3162
  retry: { action: "retry", description: "Retry loading the options" },
2912
3163
  cancel: { action: "cancel", description: "Cancel resolution" }
2913
3164
  };
3165
+ function affordance(base, description) {
3166
+ return { ...base, description };
3167
+ }
2914
3168
  function selectActions(leaf, page, multiple) {
2915
3169
  const searchMode = leaf.resolver?.type === "dynamic" && leaf.resolver.inputType === "search";
2916
3170
  if (searchMode && page.position.search === void 0 && page.items.length === 0) {
@@ -3025,7 +3279,7 @@ async function buildQuestion(leaf, path, input) {
3025
3279
  }
3026
3280
  };
3027
3281
  }
3028
- function collectionQuestion(t) {
3282
+ function arrayItemsQuestion(t) {
3029
3283
  const actions = [AFFORDANCE.add];
3030
3284
  if (t.count >= t.min) actions.push(AFFORDANCE.done);
3031
3285
  return {
@@ -3041,22 +3295,37 @@ function collectionQuestion(t) {
3041
3295
  actions
3042
3296
  };
3043
3297
  }
3044
- function objectGateQuestion(path) {
3298
+ function recordEntriesQuestion(t) {
3299
+ const actions = [
3300
+ affordance(AFFORDANCE.add, "Add another entry")
3301
+ ];
3302
+ if (t.count >= t.min) {
3303
+ actions.push(affordance(AFFORDANCE.done, "Finish the entries"));
3304
+ }
3305
+ return {
3306
+ type: "collection",
3307
+ path: t.path,
3308
+ message: `Add another ${t.path[t.path.length - 1]} entry? (${t.count} so far)`,
3309
+ container: "record",
3310
+ count: t.count,
3311
+ min: t.min,
3312
+ ...Number.isFinite(t.max) ? { max: t.max } : {},
3313
+ actions
3314
+ };
3315
+ }
3316
+ function objectOptionalQuestion(path) {
3045
3317
  return {
3046
3318
  type: "collection",
3047
3319
  path,
3048
3320
  message: `Add ${path[path.length - 1]}?`,
3049
3321
  container: "object",
3050
3322
  actions: [
3051
- {
3052
- action: "add",
3053
- description: "Provide values for these fields"
3054
- },
3055
- { action: "done", description: "Skip these fields" }
3323
+ affordance(AFFORDANCE.add, "Provide values for these fields"),
3324
+ affordance(AFFORDANCE.done, "Skip these fields")
3056
3325
  ]
3057
3326
  };
3058
3327
  }
3059
- function optionalsGateQuestion(path, pending) {
3328
+ function objectOptionalPropertiesQuestion(path, pending) {
3060
3329
  return {
3061
3330
  type: "collection",
3062
3331
  path,
@@ -3073,8 +3342,8 @@ function optionalsGateQuestion(path, pending) {
3073
3342
  ...leaf.valueType ? { valueType: leaf.valueType } : {}
3074
3343
  })),
3075
3344
  actions: [
3076
- { action: "add", description: "Configure the optional fields" },
3077
- { action: "done", description: "Skip the optional fields" }
3345
+ affordance(AFFORDANCE.add, "Configure the optional fields"),
3346
+ affordance(AFFORDANCE.done, "Skip the optional fields")
3078
3347
  ]
3079
3348
  };
3080
3349
  }
@@ -3092,7 +3361,8 @@ function finalize(ctx, resolved) {
3092
3361
  }));
3093
3362
  return { status: "invalid", issues };
3094
3363
  }
3095
- var optionalsMarker = (path) => `${key(path)}?optionals`;
3364
+ var optionalsMarker = (path) => `${pathToKey(path)}?optionals`;
3365
+ var UNSAFE_RECORD_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
3096
3366
  async function findInArray(ctx, state, path) {
3097
3367
  if (isSettled(state, path)) return null;
3098
3368
  if (getAtPath(state.resolved, path) == null)
@@ -3113,7 +3383,28 @@ async function findInArray(ctx, state, path) {
3113
3383
  }
3114
3384
  }
3115
3385
  if (len < min) return descendItem(ctx, state, path, len, item);
3116
- if (len < max) return { kind: "array", path, count: len, min, max };
3386
+ if (len < max) return { type: "array_items", path, count: len, min, max };
3387
+ settle(state, path);
3388
+ return null;
3389
+ }
3390
+ async function findInRecord(ctx, state, path) {
3391
+ if (isSettled(state, path)) return null;
3392
+ if (getAtPath(state.resolved, path) == null)
3393
+ setAtPath(state.resolved, path, {});
3394
+ if (!state.interactive) {
3395
+ settle(state, path);
3396
+ return null;
3397
+ }
3398
+ const { min, max, keyLeaf, fixedKeys } = await recordInfoAt(
3399
+ ctx,
3400
+ path,
3401
+ state.resolved
3402
+ );
3403
+ const container = getAtPath(state.resolved, path);
3404
+ const fixed = new Set(fixedKeys);
3405
+ const count = Object.keys(container).filter((k) => !fixed.has(k)).length;
3406
+ if (count < min) return { type: "record_key", path, leaf: keyLeaf };
3407
+ if (count < max) return { type: "record_entries", path, count, min, max };
3117
3408
  settle(state, path);
3118
3409
  return null;
3119
3410
  }
@@ -3131,10 +3422,10 @@ function seedItemSlot(state, itemPath, item) {
3131
3422
  }
3132
3423
  async function descendItem(ctx, state, arrayPath, index, item) {
3133
3424
  const itemPath = [...arrayPath, index];
3134
- const kind = seedItemSlot(state, itemPath, item);
3135
- if (kind === "object") return findNext(ctx, state, itemPath);
3136
- if (kind === "array") return findInArray(ctx, state, itemPath);
3137
- return { kind: "leaf", path: itemPath, leaf: item };
3425
+ const slotType = seedItemSlot(state, itemPath, item);
3426
+ if (slotType === "object") return findNext(ctx, state, itemPath);
3427
+ if (slotType === "array") return findInArray(ctx, state, itemPath);
3428
+ return { type: "leaf", path: itemPath, leaf: item };
3138
3429
  }
3139
3430
  async function findNext(ctx, state, path = []) {
3140
3431
  const container = getAtPath(state.resolved, path) ?? {};
@@ -3159,7 +3450,7 @@ async function findNext(ctx, state, path = []) {
3159
3450
  const pending = ordered.filter(
3160
3451
  (c) => !c.required && asksUser(c) && isPendingChild(c)
3161
3452
  );
3162
- return { kind: "optionals", path, pending };
3453
+ return { type: "object_optional_properties", path, pending };
3163
3454
  }
3164
3455
  if (leaf.resolver?.type === "object") {
3165
3456
  if (isSettled(state, childPath)) continue;
@@ -3169,7 +3460,7 @@ async function findNext(ctx, state, path = []) {
3169
3460
  settle(state, childPath);
3170
3461
  continue;
3171
3462
  }
3172
- return { kind: "object", path: childPath, leaf };
3463
+ return { type: "object_optional", path: childPath, leaf };
3173
3464
  }
3174
3465
  setAtPath(state.resolved, childPath, {});
3175
3466
  }
@@ -3201,13 +3492,21 @@ async function findNext(ctx, state, path = []) {
3201
3492
  }
3202
3493
  if (container[leaf.name] !== void 0 || isSettled(state, childPath))
3203
3494
  continue;
3204
- return { kind: "leaf", path: childPath, leaf };
3495
+ return { type: "leaf", path: childPath, leaf };
3496
+ }
3497
+ if (inObject && !isSettled(state, path)) {
3498
+ const self = await leafAt(ctx, path, state.resolved);
3499
+ if (self?.resolver?.type === "object" && self.resolver.additionalKeys) {
3500
+ const rec = await findInRecord(ctx, state, path);
3501
+ if (rec) return rec;
3502
+ }
3205
3503
  }
3206
3504
  return null;
3207
3505
  }
3208
3506
  async function askLeaf(state, path, leaf, opts = {}) {
3209
3507
  state.current = path;
3210
- delete state.gate;
3508
+ if (opts.gate) state.gate = opts.gate;
3509
+ else delete state.gate;
3211
3510
  try {
3212
3511
  const { question, pagination } = await buildQuestion(
3213
3512
  leaf,
@@ -3228,6 +3527,28 @@ async function askLeaf(state, path, leaf, opts = {}) {
3228
3527
  return failedResult(state, leaf.name, error);
3229
3528
  }
3230
3529
  }
3530
+ async function askRecordKey(state, path, keyLeaf, opts = {}) {
3531
+ return askLeaf(state, path, keyLeaf, { gate: "record_key", ...opts });
3532
+ }
3533
+ async function autoResolveLeaf(state, path, leaf) {
3534
+ const resolver = leaf.resolver;
3535
+ if (resolver && autoSettles(resolver)) {
3536
+ if (resolver.type === "constant")
3537
+ setAtPath(state.resolved, path, resolver.value);
3538
+ settle(state, path);
3539
+ return true;
3540
+ }
3541
+ const auto = resolver?.type === "dynamic" ? await resolver.tryResolveWithoutPrompt?.({
3542
+ input: mergeInput(state.resolved, leaf.extraInput)
3543
+ }) : void 0;
3544
+ if (auto) {
3545
+ if (auto.resolvedValue !== void 0)
3546
+ setAtPath(state.resolved, path, auto.resolvedValue);
3547
+ settle(state, path);
3548
+ return true;
3549
+ }
3550
+ return false;
3551
+ }
3231
3552
  async function advance(ctx, state) {
3232
3553
  for (; ; ) {
3233
3554
  const target = await findNext(ctx, state);
@@ -3237,54 +3558,56 @@ async function advance(ctx, state) {
3237
3558
  delete state.pagination;
3238
3559
  return { state, result: finalize(ctx, state.resolved) };
3239
3560
  }
3240
- if (target.kind === "array") {
3561
+ if (target.type === "array_items") {
3241
3562
  state.current = target.path;
3242
- state.gate = "array";
3563
+ state.gate = "array_items";
3243
3564
  delete state.pagination;
3244
3565
  return {
3245
3566
  state,
3246
- result: { status: "ask", question: collectionQuestion(target) }
3567
+ result: { status: "ask", question: arrayItemsQuestion(target) }
3247
3568
  };
3248
3569
  }
3249
- if (target.kind === "object") {
3570
+ if (target.type === "object_optional") {
3250
3571
  state.current = target.path;
3251
- state.gate = "entry";
3572
+ state.gate = "object_optional";
3252
3573
  delete state.pagination;
3253
3574
  return {
3254
3575
  state,
3255
- result: { status: "ask", question: objectGateQuestion(target.path) }
3576
+ result: {
3577
+ status: "ask",
3578
+ question: objectOptionalQuestion(target.path)
3579
+ }
3256
3580
  };
3257
3581
  }
3258
- if (target.kind === "optionals") {
3582
+ if (target.type === "object_optional_properties") {
3259
3583
  state.current = target.path;
3260
- state.gate = "optionals";
3584
+ state.gate = "object_optional_properties";
3261
3585
  delete state.pagination;
3262
3586
  return {
3263
3587
  state,
3264
3588
  result: {
3265
3589
  status: "ask",
3266
- question: optionalsGateQuestion(target.path, target.pending)
3590
+ question: objectOptionalPropertiesQuestion(
3591
+ target.path,
3592
+ target.pending
3593
+ )
3267
3594
  }
3268
3595
  };
3269
3596
  }
3270
- const { path, leaf } = target;
3271
- const resolver = leaf.resolver;
3272
- if (resolver && autoSettles(resolver)) {
3273
- if (resolver.type === "constant") {
3274
- setAtPath(state.resolved, path, resolver.value);
3275
- }
3276
- settle(state, path);
3277
- continue;
3597
+ if (target.type === "record_entries") {
3598
+ state.current = target.path;
3599
+ state.gate = "record_entries";
3600
+ delete state.pagination;
3601
+ return {
3602
+ state,
3603
+ result: { status: "ask", question: recordEntriesQuestion(target) }
3604
+ };
3278
3605
  }
3279
- const auto = resolver?.type === "dynamic" ? await resolver.tryResolveWithoutPrompt?.({
3280
- input: mergeInput(state.resolved, leaf.extraInput)
3281
- }) : void 0;
3282
- if (auto) {
3283
- if (auto.resolvedValue !== void 0)
3284
- setAtPath(state.resolved, path, auto.resolvedValue);
3285
- settle(state, path);
3286
- continue;
3606
+ if (target.type === "record_key") {
3607
+ return askRecordKey(state, target.path, target.leaf);
3287
3608
  }
3609
+ const { path, leaf } = target;
3610
+ if (await autoResolveLeaf(state, path, leaf)) continue;
3288
3611
  if (!state.interactive) {
3289
3612
  if (!leaf.required) {
3290
3613
  settle(state, path);
@@ -3317,10 +3640,18 @@ async function step(ctx, prior, action) {
3317
3640
  delete state.pagination;
3318
3641
  return { state, result: { status: "cancelled" } };
3319
3642
  }
3643
+ if (state.gate === "record_key") {
3644
+ return stepRecordKey(ctx, state, action);
3645
+ }
3320
3646
  const path = state.current;
3321
3647
  if (!path) throw new Error("step called with no outstanding question");
3322
3648
  const leaf = await leafAt(ctx, path, state.resolved);
3323
- if (leaf && (action.type === "search" || action.type === "next_page" || action.type === "previous_page" || action.type === "retry")) {
3649
+ if (!leaf) {
3650
+ throw new Error(
3651
+ `no resolver for the outstanding question at "${pathToKey(path)}"`
3652
+ );
3653
+ }
3654
+ if (action.type === "search" || action.type === "next_page" || action.type === "previous_page" || action.type === "retry") {
3324
3655
  return refine(ctx, state, leaf, path, action);
3325
3656
  }
3326
3657
  if (action.type === "add" || action.type === "done") {
@@ -3337,19 +3668,26 @@ async function step(ctx, prior, action) {
3337
3668
  settle(state, path);
3338
3669
  return advance(ctx, state);
3339
3670
  }
3340
- if (gate === "entry") {
3671
+ if (gate === "object_optional") {
3341
3672
  setAtPath(state.resolved, path, {});
3342
3673
  return advance(ctx, state);
3343
3674
  }
3344
- if (gate === "optionals") {
3675
+ if (gate === "object_optional_properties") {
3345
3676
  remember(state, optionalsMarker(path));
3346
3677
  return advance(ctx, state);
3347
3678
  }
3679
+ if (gate === "record_entries") {
3680
+ const { keyLeaf } = await recordInfoAt(ctx, path, state.resolved);
3681
+ return askRecordKey(state, path, keyLeaf);
3682
+ }
3348
3683
  const items = getAtPath(state.resolved, path) ?? [];
3349
3684
  const { item } = await arrayInfoAt(ctx, path, state.resolved);
3350
3685
  const itemPath = [...path, items.length];
3351
- if (seedItemSlot(state, itemPath, item) === "leaf")
3686
+ if (seedItemSlot(state, itemPath, item) === "leaf") {
3687
+ if (await autoResolveLeaf(state, itemPath, item))
3688
+ return advance(ctx, state);
3352
3689
  return askLeaf(state, itemPath, item);
3690
+ }
3353
3691
  return advance(ctx, state);
3354
3692
  }
3355
3693
  if (state.gate) {
@@ -3360,81 +3698,37 @@ async function step(ctx, prior, action) {
3360
3698
  switch (action.type) {
3361
3699
  case "choose":
3362
3700
  case "custom": {
3363
- if (leaf) {
3364
- let error;
3365
- try {
3366
- error = await validationError(leaf, action.value, state);
3367
- } catch (thrown) {
3368
- return failedResult(state, leaf.name, thrown);
3369
- }
3370
- if (error) {
3371
- if (state.pagination && leaf.resolver?.type === "dynamic") {
3372
- try {
3373
- const context = await resolveContext(leaf, state.resolved);
3374
- const page = await fetchListing(
3375
- leaf,
3376
- state.resolved,
3377
- state.pagination.position,
3378
- context
3379
- );
3380
- state.pagination = toPagination(page);
3381
- return {
3382
- state,
3383
- result: {
3384
- status: "ask",
3385
- question: selectQuestion(
3386
- leaf,
3387
- path,
3388
- state.resolved,
3389
- page,
3390
- context
3391
- ),
3392
- error
3393
- }
3394
- };
3395
- } catch (fetchError) {
3396
- state.pagination = failedPagination(
3397
- state.pagination,
3398
- state.pagination.position
3399
- );
3400
- return failedResult(state, leaf.name, fetchError);
3401
- }
3402
- }
3403
- return askLeaf(state, path, leaf, { error });
3701
+ let error;
3702
+ try {
3703
+ error = await validationError(leaf, action.value, state);
3704
+ } catch (thrown) {
3705
+ return failedResult(state, leaf.name, thrown);
3706
+ }
3707
+ if (error) {
3708
+ if (state.pagination && leaf.resolver?.type === "dynamic") {
3709
+ return renderPageAt(state, leaf, path, state.pagination.position, {
3710
+ error
3711
+ });
3404
3712
  }
3713
+ return askLeaf(state, path, leaf, { error });
3405
3714
  }
3406
- setAtPath(
3407
- state.resolved,
3408
- path,
3409
- leaf ? coerce(leaf, action.value) : action.value
3410
- );
3715
+ setAtPath(state.resolved, path, coerce(leaf, action.value));
3411
3716
  break;
3412
3717
  }
3413
3718
  case "skip":
3414
3719
  settle(state, path);
3415
3720
  break;
3416
3721
  default:
3417
- throw new Error(`action "${action.type}" is not supported here`);
3722
+ throw new Error(
3723
+ `action "${action.type}" is not supported here`
3724
+ );
3418
3725
  }
3419
3726
  delete state.current;
3420
3727
  delete state.pagination;
3421
3728
  return advance(ctx, state);
3422
3729
  }
3423
- async function refine(ctx, state, leaf, path, action) {
3424
- const position = positionAfter(state.pagination, action);
3730
+ async function renderPageAt(state, leaf, path, position, opts = {}) {
3425
3731
  try {
3426
- if (action.type === "search") {
3427
- const exact = leaf.resolver?.type === "dynamic" ? await leaf.resolver.tryResolveFromSearch?.({
3428
- input: mergeInput(state.resolved, leaf.extraInput),
3429
- search: action.term
3430
- }) : void 0;
3431
- if (exact) {
3432
- setAtPath(state.resolved, path, coerce(leaf, exact.resolvedValue));
3433
- delete state.current;
3434
- delete state.pagination;
3435
- return advance(ctx, state);
3436
- }
3437
- }
3438
3732
  const context = await resolveContext(leaf, state.resolved);
3439
3733
  const page = await fetchListing(leaf, state.resolved, position, context);
3440
3734
  state.pagination = toPagination(page);
@@ -3442,7 +3736,8 @@ async function refine(ctx, state, leaf, path, action) {
3442
3736
  state,
3443
3737
  result: {
3444
3738
  status: "ask",
3445
- question: selectQuestion(leaf, path, state.resolved, page, context)
3739
+ question: selectQuestion(leaf, path, state.resolved, page, context),
3740
+ ...opts.error ? { error: opts.error } : {}
3446
3741
  }
3447
3742
  };
3448
3743
  } catch (error) {
@@ -3450,6 +3745,65 @@ async function refine(ctx, state, leaf, path, action) {
3450
3745
  return failedResult(state, leaf.name, error);
3451
3746
  }
3452
3747
  }
3748
+ async function refine(ctx, state, leaf, path, action) {
3749
+ const position = positionAfter(state.pagination, action);
3750
+ if (action.type === "search") {
3751
+ try {
3752
+ const exact = leaf.resolver?.type === "dynamic" ? await leaf.resolver.tryResolveFromSearch?.({
3753
+ input: mergeInput(state.resolved, leaf.extraInput),
3754
+ search: action.term
3755
+ }) : void 0;
3756
+ if (exact) {
3757
+ setAtPath(state.resolved, path, coerce(leaf, exact.resolvedValue));
3758
+ delete state.current;
3759
+ delete state.pagination;
3760
+ return advance(ctx, state);
3761
+ }
3762
+ } catch (error) {
3763
+ state.pagination = failedPagination(state.pagination, position);
3764
+ return failedResult(state, leaf.name, error);
3765
+ }
3766
+ }
3767
+ return renderPageAt(state, leaf, path, position);
3768
+ }
3769
+ async function stepRecordKey(ctx, state, action) {
3770
+ const path = state.current;
3771
+ if (!path)
3772
+ throw new Error("record key step called with no outstanding question");
3773
+ const { keyLeaf, valueLeaf } = await recordInfoAt(ctx, path, state.resolved);
3774
+ if (action.type === "skip") {
3775
+ delete state.gate;
3776
+ delete state.current;
3777
+ delete state.pagination;
3778
+ return advance(ctx, state);
3779
+ }
3780
+ if (action.type !== "custom" && action.type !== "choose") {
3781
+ throw new Error(
3782
+ `action "${action.type}" is not supported while entering a record key`
3783
+ );
3784
+ }
3785
+ const raw = Array.isArray(action.value) ? action.value[0] : action.value;
3786
+ const entryKey = String(coerce(keyLeaf, raw));
3787
+ if (entryKey.trim() === "") {
3788
+ return askRecordKey(state, path, keyLeaf, { error: "A key is required." });
3789
+ }
3790
+ if (UNSAFE_RECORD_KEYS.has(entryKey)) {
3791
+ return askRecordKey(state, path, keyLeaf, {
3792
+ error: `"${entryKey}" is not an allowed key.`
3793
+ });
3794
+ }
3795
+ const container = getAtPath(state.resolved, path);
3796
+ if (Object.prototype.hasOwnProperty.call(container, entryKey)) {
3797
+ return askRecordKey(state, path, keyLeaf, {
3798
+ error: `"${entryKey}" is already set.`
3799
+ });
3800
+ }
3801
+ const valuePath = [...path, entryKey];
3802
+ if (await autoResolveLeaf(state, valuePath, valueLeaf)) {
3803
+ return advance(ctx, state);
3804
+ }
3805
+ return askLeaf(state, valuePath, valueLeaf);
3806
+ }
3453
3807
  function failedPagination(pagination, retryPosition) {
3454
3808
  return {
3455
3809
  position: pagination?.position ?? firstPagePosition(),
@@ -3505,7 +3859,7 @@ function projectSummary(entry) {
3505
3859
  };
3506
3860
  }
3507
3861
  function projectMethod(entry) {
3508
- const inputProperties = toJsonSchema(entry.inputSchema)?.properties;
3862
+ const inputProperties = toJsonSchema(canonicalInputSchema(entry.inputSchema))?.properties;
3509
3863
  const parameters = {};
3510
3864
  for (const spec of planParameters(entry).parameters) {
3511
3865
  const dynamic = spec.resolver?.type === "dynamic" ? spec.resolver : void 0;
@@ -3538,7 +3892,12 @@ function createController(sdk) {
3538
3892
  const entry = entryFor(method);
3539
3893
  return {
3540
3894
  method,
3541
- schema: entry.inputSchema,
3895
+ // A method that owns its input validation (`skipInputValidation`, e.g.
3896
+ // fetch) must not be re-validated by the controller's final `safeParse`;
3897
+ // drop the schema so `finalize` returns the resolved input untouched.
3898
+ // Planning still reads `entry.inputSchema` directly, so parameters are
3899
+ // unaffected.
3900
+ schema: entry.skipInputValidation ? void 0 : entry.inputSchema,
3542
3901
  parameters: planParameters(entry).parameters
3543
3902
  };
3544
3903
  }
@@ -3603,59 +3962,6 @@ function createCorePlugin(options) {
3603
3962
  }
3604
3963
  });
3605
3964
  }
3606
-
3607
- // src/utils/schema-utils.ts
3608
- import { z as z5 } from "zod";
3609
- function getOutputSchema(inputSchema) {
3610
- return inputSchema._zod.def.outputSchema;
3611
- }
3612
- function withOutputSchema(inputSchema, outputSchema) {
3613
- Object.assign(inputSchema._zod.def, {
3614
- outputSchema
3615
- });
3616
- return inputSchema;
3617
- }
3618
- function withResolver(schema, config) {
3619
- schema._zod.def.resolverMeta = config;
3620
- return schema;
3621
- }
3622
- function getSchemaDescription(schema) {
3623
- return schema.description;
3624
- }
3625
- function getFieldDescriptions(schema) {
3626
- const descriptions = {};
3627
- const shape = schema.shape;
3628
- for (const [key2, fieldSchema] of Object.entries(shape)) {
3629
- if (fieldSchema instanceof z5.ZodType && fieldSchema.description) {
3630
- descriptions[key2] = fieldSchema.description;
3631
- }
3632
- }
3633
- return descriptions;
3634
- }
3635
- function withPositional(schema) {
3636
- Object.assign(schema._zod.def, {
3637
- positionalMeta: { positional: true }
3638
- });
3639
- return schema;
3640
- }
3641
- function schemaHasPositionalMeta(schema) {
3642
- return "positionalMeta" in schema._zod.def;
3643
- }
3644
- function isPositional(schema) {
3645
- if (schemaHasPositionalMeta(schema) && schema._zod.def.positionalMeta?.positional) {
3646
- return true;
3647
- }
3648
- if (schema instanceof z5.ZodOptional) {
3649
- return isPositional(schema._zod.def.innerType);
3650
- }
3651
- if (schema instanceof z5.ZodDefault) {
3652
- return isPositional(schema._zod.def.innerType);
3653
- }
3654
- return false;
3655
- }
3656
- function openEnum(values, description) {
3657
- return z5.union([z5.enum(values), z5.string()]).describe(description);
3658
- }
3659
3965
  export {
3660
3966
  CONTEXT,
3661
3967
  CORE_ERROR_SYMBOL,
@@ -3667,7 +3973,9 @@ export {
3667
3973
  CoreErrorCode,
3668
3974
  CoreSignal,
3669
3975
  addPlugin,
3976
+ canonicalInputSchema,
3670
3977
  composePlugins,
3978
+ concatLists,
3671
3979
  concatPaginated,
3672
3980
  coreOptionsPluginRef,
3673
3981
  createAsyncContext,