@zapier/kitcore 0.8.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.cjs CHANGED
@@ -9,9 +9,9 @@ var __export = (target, all) => {
9
9
  };
10
10
  var __copyProps = (to, from, except, desc) => {
11
11
  if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key2 of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key2) && key2 !== except)
14
- __defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
15
  }
16
16
  return to;
17
17
  };
@@ -30,6 +30,7 @@ __export(index_exports, {
30
30
  CoreErrorCode: () => CoreErrorCode,
31
31
  CoreSignal: () => CoreSignal,
32
32
  addPlugin: () => addPlugin,
33
+ canonicalInputSchema: () => canonicalInputSchema,
33
34
  composePlugins: () => composePlugins,
34
35
  concatLists: () => concatLists,
35
36
  concatPaginated: () => concatPaginated,
@@ -98,9 +99,6 @@ __export(index_exports, {
98
99
  });
99
100
  module.exports = __toCommonJS(index_exports);
100
101
 
101
- // src/registry.ts
102
- var import_zod = require("zod");
103
-
104
102
  // src/utils/string-utils.ts
105
103
  function toTitleCase(input) {
106
104
  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(" ");
@@ -124,6 +122,65 @@ function pluralizeLastWord(title) {
124
122
  return [...words.slice(0, -1), pluralize(words[words.length - 1])].join(" ");
125
123
  }
126
124
 
125
+ // src/utils/schema-utils.ts
126
+ var import_zod = require("zod");
127
+ function canonicalInputSchema(schema) {
128
+ if (schema instanceof import_zod.z.ZodUnion) {
129
+ return schema.options[0];
130
+ }
131
+ return schema;
132
+ }
133
+ function getOutputSchema(inputSchema) {
134
+ return inputSchema._zod.def.outputSchema;
135
+ }
136
+ function withOutputSchema(inputSchema, outputSchema) {
137
+ Object.assign(inputSchema._zod.def, {
138
+ outputSchema
139
+ });
140
+ return inputSchema;
141
+ }
142
+ function withResolver(schema, config) {
143
+ schema._zod.def.resolverMeta = config;
144
+ return schema;
145
+ }
146
+ function getSchemaDescription(schema) {
147
+ return schema.description;
148
+ }
149
+ function getFieldDescriptions(schema) {
150
+ const descriptions = {};
151
+ const shape = schema.shape;
152
+ for (const [key, fieldSchema] of Object.entries(shape)) {
153
+ if (fieldSchema instanceof import_zod.z.ZodType && fieldSchema.description) {
154
+ descriptions[key] = fieldSchema.description;
155
+ }
156
+ }
157
+ return descriptions;
158
+ }
159
+ function withPositional(schema) {
160
+ Object.assign(schema._zod.def, {
161
+ positionalMeta: { positional: true }
162
+ });
163
+ return schema;
164
+ }
165
+ function schemaHasPositionalMeta(schema) {
166
+ return "positionalMeta" in schema._zod.def;
167
+ }
168
+ function isPositional(schema) {
169
+ if (schemaHasPositionalMeta(schema) && schema._zod.def.positionalMeta?.positional) {
170
+ return true;
171
+ }
172
+ if (schema instanceof import_zod.z.ZodOptional) {
173
+ return isPositional(schema._zod.def.innerType);
174
+ }
175
+ if (schema instanceof import_zod.z.ZodDefault) {
176
+ return isPositional(schema._zod.def.innerType);
177
+ }
178
+ return false;
179
+ }
180
+ function openEnum(values, description) {
181
+ return import_zod.z.union([import_zod.z.enum(values), import_zod.z.string()]).describe(description);
182
+ }
183
+
127
184
  // src/registry.ts
128
185
  function resolveCategoryDefinition(ref) {
129
186
  const def = typeof ref === "string" ? { key: ref } : ref;
@@ -134,30 +191,25 @@ function resolveCategoryDefinition(ref) {
134
191
  titlePlural: def.titlePlural ?? pluralizeLastWord(title)
135
192
  };
136
193
  }
137
- function canonicalInputSchema(schema) {
138
- if (schema instanceof import_zod.z.ZodUnion) {
139
- return schema.options[0];
140
- }
141
- return schema;
142
- }
143
194
  function buildRegistry({
144
195
  sdk,
145
196
  meta,
146
197
  formatters,
147
- boundResolvers,
198
+ resolvers,
148
199
  positional,
200
+ skipInputValidation,
149
201
  packageFilter
150
202
  }) {
151
203
  const definitionsByKey = /* @__PURE__ */ new Map();
152
204
  const objectDeclaredKeys = /* @__PURE__ */ new Set();
153
205
  for (const m of Object.values(meta)) {
154
206
  for (const ref of m.categories ?? []) {
155
- const key2 = typeof ref === "string" ? ref : ref.key;
207
+ const key = typeof ref === "string" ? ref : ref.key;
156
208
  if (typeof ref === "object") {
157
- objectDeclaredKeys.add(key2);
158
- definitionsByKey.set(key2, resolveCategoryDefinition(ref));
159
- } else if (!objectDeclaredKeys.has(key2)) {
160
- definitionsByKey.set(key2, resolveCategoryDefinition(ref));
209
+ objectDeclaredKeys.add(key);
210
+ definitionsByKey.set(key, resolveCategoryDefinition(ref));
211
+ } else if (!objectDeclaredKeys.has(key)) {
212
+ definitionsByKey.set(key, resolveCategoryDefinition(ref));
161
213
  }
162
214
  }
163
215
  }
@@ -165,30 +217,29 @@ function buildRegistry({
165
217
  definitionsByKey.set("other", resolveCategoryDefinition("other"));
166
218
  }
167
219
  const knownCategories = Array.from(definitionsByKey.keys());
168
- const functions = Object.keys(meta).filter((key2) => {
169
- const property = sdk[key2];
220
+ const functions = Object.keys(meta).filter((key) => {
221
+ const property = sdk[key];
170
222
  if (typeof property === "function") return true;
171
- const [rootKey] = key2.split(".");
223
+ const [rootKey] = key.split(".");
172
224
  const rootProperty = sdk[rootKey];
173
225
  return typeof rootProperty === "object" && rootProperty !== null;
174
- }).map((key2) => {
175
- const m = meta[key2];
226
+ }).map((key) => {
227
+ const m = meta[key];
176
228
  return {
177
- name: key2,
229
+ name: key,
178
230
  description: m.description,
179
231
  type: m.type,
180
232
  itemType: m.itemType,
181
233
  returnType: m.returnType,
182
234
  inputSchema: canonicalInputSchema(m.inputSchema),
183
- inputParameters: m.inputParameters,
184
235
  outputSchema: m.outputSchema,
185
- positional: positional?.[key2],
236
+ positional: positional?.[key],
237
+ skipInputValidation: skipInputValidation?.[key],
186
238
  categories: (m.categories ?? []).map(
187
239
  (c) => typeof c === "string" ? c : c.key
188
240
  ),
189
- resolvers: m.resolvers,
190
- boundResolvers: boundResolvers?.[key2],
191
- formatter: formatters?.[key2],
241
+ resolvers: resolvers?.[key],
242
+ formatter: formatters?.[key],
192
243
  experimental: m.experimental,
193
244
  packages: m.packages,
194
245
  confirm: m.confirm ?? (m.type === "delete" ? "delete" : void 0),
@@ -675,6 +726,52 @@ function runInMethodScope(fn) {
675
726
  var runWithTelemetryContext = runInMethodScope;
676
727
  var isTelemetryNested = isNestedMethodCall;
677
728
 
729
+ // src/utils/call-context.ts
730
+ var CALL_CONTEXT_BRAND = Symbol("kitcore.callContext");
731
+ function isCallContext(value) {
732
+ return typeof value === "object" && value !== null && value[CALL_CONTEXT_BRAND] === true;
733
+ }
734
+ function generateCallId() {
735
+ try {
736
+ const webCrypto = globalThis.crypto;
737
+ if (webCrypto?.randomUUID) {
738
+ return webCrypto.randomUUID();
739
+ }
740
+ if (webCrypto?.getRandomValues) {
741
+ const bytes = webCrypto.getRandomValues(new Uint8Array(16));
742
+ const hex = Array.from(bytes, (byte, i) => {
743
+ const value = i === 6 ? byte & 15 | 64 : i === 8 ? byte & 63 | 128 : byte;
744
+ return value.toString(16).padStart(2, "0");
745
+ });
746
+ return [
747
+ hex.slice(0, 4).join(""),
748
+ hex.slice(4, 6).join(""),
749
+ hex.slice(6, 8).join(""),
750
+ hex.slice(8, 10).join(""),
751
+ hex.slice(10, 16).join("")
752
+ ].join("-");
753
+ }
754
+ } catch {
755
+ }
756
+ return null;
757
+ }
758
+ function rootCallContext() {
759
+ return {
760
+ callId: generateCallId(),
761
+ depth: 0,
762
+ annotations: {},
763
+ [CALL_CONTEXT_BRAND]: true
764
+ };
765
+ }
766
+ function childCallContext(parent) {
767
+ return {
768
+ callId: parent.callId,
769
+ depth: parent.depth + 1,
770
+ annotations: {},
771
+ [CALL_CONTEXT_BRAND]: true
772
+ };
773
+ }
774
+
678
775
  // src/utils/core-options.ts
679
776
  function defaultLogDeprecation({
680
777
  methodName,
@@ -693,6 +790,9 @@ function resolveCoreOptions(context) {
693
790
  return context.core;
694
791
  }
695
792
  var INTERNAL_CALL = Symbol("kitcore.internalCall");
793
+ function resolveCallContext(secondArg) {
794
+ return isCallContext(secondArg) ? secondArg : rootCallContext();
795
+ }
696
796
  function signalDeprecation(context, methodName, getDeprecation) {
697
797
  if (isInsideObserver()) return;
698
798
  const deprecation = getDeprecation?.();
@@ -722,14 +822,16 @@ function createFunction(coreFn, options) {
722
822
  const functionName = name || coreFn.name;
723
823
  const namedFunctions = {
724
824
  [functionName]: async function(callOptions) {
725
- if (arguments[1] !== INTERNAL_CALL) {
825
+ const internal = arguments[1];
826
+ const context = resolveCallContext(internal);
827
+ if (!isCallContext(internal) && internal !== INTERNAL_CALL) {
726
828
  signalDeprecation(sdk.context, functionName, getDeprecation);
727
829
  }
728
830
  return runInMethodScope(async () => {
729
831
  const startTime = Date.now();
730
832
  const normalizedOptions = callOptions ?? {};
731
833
  const args = [normalizedOptions];
732
- const depth = getCurrentDepth();
834
+ const depth = Math.max(context.depth, getCurrentDepth());
733
835
  const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
734
836
  const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
735
837
  hooks?.onMethodStart?.({
@@ -748,12 +850,15 @@ function createFunction(coreFn, options) {
748
850
  adaptError
749
851
  }
750
852
  );
751
- result = await coreFn({
752
- ...normalizedOptions,
753
- ...validatedOptions
754
- });
853
+ result = await coreFn(
854
+ {
855
+ ...normalizedOptions,
856
+ ...validatedOptions
857
+ },
858
+ context
859
+ );
755
860
  } else {
756
- result = await coreFn(normalizedOptions);
861
+ result = await coreFn(normalizedOptions, context);
757
862
  }
758
863
  hooks?.onMethodEnd?.({
759
864
  methodName: functionName,
@@ -783,17 +888,19 @@ function createFunction(coreFn, options) {
783
888
  function createRawFunction(coreFn, options) {
784
889
  const { sdk, name, schema, positional, getDeprecation } = options;
785
890
  return function(rawInput) {
786
- if (arguments[1] !== INTERNAL_CALL) {
891
+ const internal = arguments[1];
892
+ const context = resolveCallContext(internal);
893
+ if (!isCallContext(internal) && internal !== INTERNAL_CALL) {
787
894
  signalDeprecation(sdk.context, name, getDeprecation);
788
895
  }
789
896
  return runInMethodScope(() => {
790
897
  const startTime = Date.now();
791
- const depth = getCurrentDepth();
898
+ const depth = Math.max(context.depth, getCurrentDepth());
792
899
  const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
793
900
  const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
794
901
  const input = schema ? rawInput ?? {} : rawInput;
795
902
  const record = input;
796
- const args = positional ? positional.filter((key2) => record?.[key2] !== void 0).map((key2) => record?.[key2]) : [input];
903
+ const args = positional ? positional.filter((key) => record?.[key] !== void 0).map((key) => record?.[key]) : [input];
797
904
  hooks?.onMethodStart?.({
798
905
  methodName: name,
799
906
  args,
@@ -812,7 +919,7 @@ function createRawFunction(coreFn, options) {
812
919
  };
813
920
  try {
814
921
  const parsed = schema ? validateOptions(schema, input, { adaptError }) : input;
815
- const result = coreFn(parsed);
922
+ const result = coreFn(parsed, context);
816
923
  if (result !== null && typeof result === "object" && typeof result.then === "function") {
817
924
  return result.then(
818
925
  (value) => {
@@ -851,9 +958,9 @@ function createPageFunction(coreFn, {
851
958
  }) {
852
959
  const functionName = coreFn.name + "Page";
853
960
  const namedFunctions = {
854
- [functionName]: async function(options) {
961
+ [functionName]: async function(options, callContext) {
855
962
  try {
856
- const response = await coreFn(options);
963
+ const response = await coreFn(options, callContext);
857
964
  const page = adaptPage ? adaptPage(response) : response;
858
965
  if (!isSdkPage(page)) {
859
966
  throw new Error(
@@ -877,14 +984,16 @@ function createPaginatedFunction(coreFn, options) {
877
984
  const functionName = name || coreFn.name;
878
985
  const namedFunctions = {
879
986
  [functionName]: function(callOptions) {
880
- if (arguments[1] !== INTERNAL_CALL) {
987
+ const internal = arguments[1];
988
+ const context = resolveCallContext(internal);
989
+ if (!isCallContext(internal) && internal !== INTERNAL_CALL) {
881
990
  signalDeprecation(sdk.context, functionName, getDeprecation);
882
991
  }
883
992
  return runInMethodScope(() => {
884
993
  const startTime = Date.now();
885
994
  const normalizedOptions = callOptions ?? {};
886
995
  const args = [normalizedOptions];
887
- const depth = getCurrentDepth();
996
+ const depth = Math.max(context.depth, getCurrentDepth());
888
997
  const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
889
998
  const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
890
999
  hooks?.onMethodStart?.({
@@ -903,7 +1012,11 @@ function createPaginatedFunction(coreFn, options) {
903
1012
  ...validatedOptions,
904
1013
  pageSize
905
1014
  };
906
- const iterator = paginate(pageFunction, optimizedOptions);
1015
+ const iterator = paginate(
1016
+ (pageOptions) => pageFunction(pageOptions, context),
1017
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1018
+ optimizedOptions
1019
+ );
907
1020
  const firstPagePromise = iterator.next().then((result) => {
908
1021
  if (result.done) {
909
1022
  throw new Error("Paginate should always iterate at least once");
@@ -1054,11 +1167,11 @@ var RESERVED_ROOT_KEYS = /* @__PURE__ */ new Set([
1054
1167
  "context",
1055
1168
  "getRegistry"
1056
1169
  ]);
1057
- function hasOwn(obj, key2) {
1058
- return Object.prototype.hasOwnProperty.call(obj, key2);
1170
+ function hasOwn(obj, key) {
1171
+ return Object.prototype.hasOwnProperty.call(obj, key);
1059
1172
  }
1060
- function setOwn(target, key2, value) {
1061
- Object.defineProperty(target, key2, {
1173
+ function setOwn(target, key, value) {
1174
+ Object.defineProperty(target, key, {
1062
1175
  value,
1063
1176
  enumerable: true,
1064
1177
  configurable: true,
@@ -1070,31 +1183,31 @@ function checkCollisions(target, source, kind, callerLabel, override) {
1070
1183
  checkRootKeyCollisions(target, Object.keys(source), override, callerLabel);
1071
1184
  return;
1072
1185
  }
1073
- for (const key2 of Object.keys(source)) {
1074
- if (!override && hasOwn(target, key2)) {
1186
+ for (const key of Object.keys(source)) {
1187
+ if (!override && hasOwn(target, key)) {
1075
1188
  throw new Error(
1076
- `${callerLabel}: duplicate ${kind} "${key2}". If the override is intentional, pass { override: true } in the options.`
1189
+ `${callerLabel}: duplicate ${kind} "${key}". If the override is intentional, pass { override: true } in the options.`
1077
1190
  );
1078
1191
  }
1079
1192
  }
1080
1193
  }
1081
1194
  function checkRootKeyCollisions(target, keys, override, callerLabel) {
1082
- for (const key2 of keys) {
1083
- if (RESERVED_ROOT_KEYS.has(key2)) {
1195
+ for (const key of keys) {
1196
+ if (RESERVED_ROOT_KEYS.has(key)) {
1084
1197
  throw new Error(
1085
- `${callerLabel}: plugin attempted to register reserved root key "${key2}". The SDK uses this key for its own accessor; rename the plugin's method.`
1198
+ `${callerLabel}: plugin attempted to register reserved root key "${key}". The SDK uses this key for its own accessor; rename the plugin's method.`
1086
1199
  );
1087
1200
  }
1088
- if (!override && hasOwn(target, key2)) {
1201
+ if (!override && hasOwn(target, key)) {
1089
1202
  throw new Error(
1090
- `${callerLabel}: duplicate root key "${key2}". If the override is intentional, pass { override: true } in the options.`
1203
+ `${callerLabel}: duplicate root key "${key}". If the override is intentional, pass { override: true } in the options.`
1091
1204
  );
1092
1205
  }
1093
1206
  }
1094
1207
  }
1095
1208
  function applyOwnProperties(target, source) {
1096
- for (const key2 of Object.keys(source)) {
1097
- setOwn(target, key2, source[key2]);
1209
+ for (const key of Object.keys(source)) {
1210
+ setOwn(target, key, source[key]);
1098
1211
  }
1099
1212
  }
1100
1213
  function createPluginAccumulator(initialProperties = {}, initialContext = {}) {
@@ -1312,7 +1425,6 @@ var LEAF_META_KEYS = [
1312
1425
  "itemType",
1313
1426
  "returnType",
1314
1427
  "outputSchema",
1315
- "inputParameters",
1316
1428
  "packages",
1317
1429
  "experimental",
1318
1430
  "confirm",
@@ -1352,8 +1464,8 @@ function normalizeImports(deps) {
1352
1464
  }
1353
1465
  function collectLeafMeta(config) {
1354
1466
  let meta;
1355
- for (const key2 of LEAF_META_KEYS) {
1356
- if (config[key2] !== void 0) (meta ?? (meta = {}))[key2] = config[key2];
1467
+ for (const key of LEAF_META_KEYS) {
1468
+ if (config[key] !== void 0) (meta ?? (meta = {}))[key] = config[key];
1357
1469
  }
1358
1470
  return meta;
1359
1471
  }
@@ -1436,7 +1548,8 @@ function defineResolver(config) {
1436
1548
  type: "object",
1437
1549
  properties: config.properties,
1438
1550
  definitions: config.definitions,
1439
- getProperties: config.getProperties
1551
+ getProperties: config.getProperties,
1552
+ additionalKeys: config.additionalKeys
1440
1553
  };
1441
1554
  case "array":
1442
1555
  return {
@@ -1748,7 +1861,7 @@ function normalizeFormatter(entry, sdk) {
1748
1861
  const legacy = entry.meta?.formatter;
1749
1862
  return legacy ? adaptLegacyFormatter(legacy, sdk) : void 0;
1750
1863
  }
1751
- function normalizeBoundResolvers(entry) {
1864
+ function normalizeResolvers(entry) {
1752
1865
  if (entry.pluginType !== "method") return void 0;
1753
1866
  return entry.resolvers;
1754
1867
  }
@@ -1789,17 +1902,20 @@ function collectSurfaceProjection(context, formatterSdk) {
1789
1902
  foldDynamicMembers(entry, surfaceBindings, meta);
1790
1903
  }
1791
1904
  const formatters = {};
1792
- const boundResolvers = {};
1905
+ const resolvers = {};
1793
1906
  const positional = {};
1907
+ const skipInputValidation = {};
1794
1908
  for (const [binding, entry] of Object.entries(entries)) {
1795
1909
  const f = normalizeFormatter(entry, formatterSdk);
1796
1910
  if (f) formatters[binding] = f;
1797
- const r = normalizeBoundResolvers(entry);
1798
- if (r) boundResolvers[binding] = r;
1911
+ const r = normalizeResolvers(entry);
1912
+ if (r) resolvers[binding] = r;
1799
1913
  const p = methodPositional(entry);
1800
1914
  if (p) positional[binding] = p;
1915
+ if (entry.pluginType === "method" && entry.skipInputValidation)
1916
+ skipInputValidation[binding] = true;
1801
1917
  }
1802
- return { meta, formatters, boundResolvers, positional };
1918
+ return { meta, formatters, resolvers, positional, skipInputValidation };
1803
1919
  }
1804
1920
  function buildSurfaceRegistry(context, packageFilter) {
1805
1921
  const surface = {};
@@ -1856,6 +1972,11 @@ function nestedResolvers(resolver) {
1856
1972
  for (const field of Object.values(resolver.properties ?? {})) {
1857
1973
  if (!isResolverRef(field.resolver)) out.push(field.resolver);
1858
1974
  }
1975
+ const ak = resolver.additionalKeys;
1976
+ if (ak) {
1977
+ if (!isResolverRef(ak.values)) out.push(ak.values);
1978
+ if (ak.keys && !isResolverRef(ak.keys)) out.push(ak.keys);
1979
+ }
1859
1980
  out.push(...Object.values(resolver.definitions ?? {}));
1860
1981
  } else if (resolver.type === "array") {
1861
1982
  if (!isResolverRef(resolver.items)) out.push(resolver.items);
@@ -1980,16 +2101,16 @@ function collectPlugins(root, materialized = /* @__PURE__ */ new Set(), configur
1980
2101
  }
1981
2102
  return byId;
1982
2103
  }
1983
- function bindValue(target, key2, entry, callType = "surface") {
2104
+ function bindValue(target, key, entry, callType = "surface", ctx) {
1984
2105
  if (entry.pluginType === "property" && entry.getValue) {
1985
- Object.defineProperty(target, key2, {
2106
+ Object.defineProperty(target, key, {
1986
2107
  get: entry.getValue,
1987
2108
  enumerable: true,
1988
2109
  configurable: true
1989
2110
  });
1990
2111
  } else {
1991
- const value = callType === "internal" && entry.pluginType === "method" ? entry.internalValue ?? entry.value : entry.value;
1992
- Object.defineProperty(target, key2, {
2112
+ const value = callType === "internal" && entry.pluginType === "method" ? entry.bindInternal?.(ctx) ?? entry.internalValue ?? entry.value : entry.value;
2113
+ Object.defineProperty(target, key, {
1993
2114
  value,
1994
2115
  writable: true,
1995
2116
  enumerable: true,
@@ -2006,7 +2127,7 @@ function buildSurface(context, ...maps) {
2006
2127
  sdk[CONTEXT] = context;
2007
2128
  return sdk;
2008
2129
  }
2009
- function buildImports(plugins, importBindings) {
2130
+ function buildImports(plugins, importBindings, ctx) {
2010
2131
  const imports = {};
2011
2132
  for (const { binding, id, optional } of importBindings) {
2012
2133
  const entry = plugins[id];
@@ -2019,7 +2140,7 @@ function buildImports(plugins, importBindings) {
2019
2140
  });
2020
2141
  continue;
2021
2142
  }
2022
- bindValue(imports, binding, entry, "internal");
2143
+ bindValue(imports, binding, entry, "internal", ctx);
2023
2144
  }
2024
2145
  return imports;
2025
2146
  }
@@ -2098,6 +2219,19 @@ function bindResolver(resolver, plugins) {
2098
2219
  const { getProperties } = resolver;
2099
2220
  if (getProperties)
2100
2221
  bound.getProperties = ({ input }) => getProperties({ imports, input });
2222
+ if (resolver.additionalKeys) {
2223
+ const ak = resolver.additionalKeys;
2224
+ const boundAk = {
2225
+ values: isResolverRef(ak.values) ? ak.values : bindResolver(ak.values, plugins),
2226
+ minEntries: ak.minEntries,
2227
+ maxEntries: ak.maxEntries,
2228
+ keyValueType: ak.keyValueType,
2229
+ valueValueType: ak.valueValueType
2230
+ };
2231
+ if (ak.keys)
2232
+ boundAk.keys = isResolverRef(ak.keys) ? ak.keys : bindResolver(ak.keys, plugins);
2233
+ bound.additionalKeys = boundAk;
2234
+ }
2101
2235
  return bound;
2102
2236
  }
2103
2237
  case "array": {
@@ -2153,8 +2287,8 @@ function bindResolver(resolver, plugins) {
2153
2287
  }
2154
2288
  function bindFields(fields, plugins) {
2155
2289
  const out = {};
2156
- for (const [key2, field] of Object.entries(fields)) {
2157
- out[key2] = {
2290
+ for (const [key, field] of Object.entries(fields)) {
2291
+ out[key] = {
2158
2292
  ...field,
2159
2293
  resolver: isResolverRef(field.resolver) ? field.resolver : bindResolver(field.resolver, plugins)
2160
2294
  };
@@ -2163,8 +2297,8 @@ function bindFields(fields, plugins) {
2163
2297
  }
2164
2298
  function bindDefinitions(definitions, plugins) {
2165
2299
  const out = {};
2166
- for (const [key2, def] of Object.entries(definitions)) {
2167
- out[key2] = bindResolver(def, plugins);
2300
+ for (const [key, def] of Object.entries(definitions)) {
2301
+ out[key] = bindResolver(def, plugins);
2168
2302
  }
2169
2303
  return out;
2170
2304
  }
@@ -2249,6 +2383,7 @@ function buildMethodEntries(descriptors, context, states) {
2249
2383
  name: descriptor.name,
2250
2384
  chain: [],
2251
2385
  inputSchema: descriptor.inputSchema,
2386
+ skipInputValidation: descriptor.skipInputValidation,
2252
2387
  // Derive the presentation type from the output mode when the author did
2253
2388
  // not set one; an explicit meta.type (e.g. "create") still wins.
2254
2389
  meta: out.type === "raw" || descriptor.meta?.type ? descriptor.meta : { ...descriptor.meta, type: out.type },
@@ -2256,17 +2391,17 @@ function buildMethodEntries(descriptors, context, states) {
2256
2391
  // Replaced below; never called.
2257
2392
  value: () => void 0
2258
2393
  };
2259
- const callRun = (input) => descriptor.run({
2260
- imports: buildImports(plugins, descriptor.importBindings),
2394
+ const callRun = (input, ctx) => descriptor.run({
2395
+ imports: buildImports(plugins, descriptor.importBindings, ctx),
2261
2396
  state: states.get(id),
2262
2397
  input
2263
2398
  });
2264
- const fold = (coreFn) => (input) => {
2265
- let next = coreFn;
2399
+ const fold = (coreFn) => (input, ctx) => {
2400
+ let next = (i) => coreFn(i, ctx);
2266
2401
  for (const wrap of entry.chain) {
2267
2402
  const inner = next;
2268
2403
  next = (i) => wrap.run({
2269
- imports: buildImports(plugins, wrap.owner.importBindings),
2404
+ imports: buildImports(plugins, wrap.owner.importBindings, ctx),
2270
2405
  next: inner,
2271
2406
  input: i,
2272
2407
  // Overwritten by the chain item's own closure with the owning
@@ -2290,7 +2425,7 @@ function buildMethodEntries(descriptors, context, states) {
2290
2425
  }
2291
2426
  );
2292
2427
  } else if (out.type === "item") {
2293
- const itemCore = async (input) => callRun(input);
2428
+ const itemCore = async (input, ctx) => callRun(input, ctx);
2294
2429
  entry.value = createFunction(
2295
2430
  fold(itemCore),
2296
2431
  {
@@ -2302,7 +2437,7 @@ function buildMethodEntries(descriptors, context, states) {
2302
2437
  );
2303
2438
  } else {
2304
2439
  entry.value = createRawFunction(
2305
- (input) => fold(callRun)(input),
2440
+ (input, ctx) => fold(callRun)(input, ctx),
2306
2441
  {
2307
2442
  sdk,
2308
2443
  name: descriptor.name,
@@ -2325,11 +2460,15 @@ function buildMethodEntries(descriptors, context, states) {
2325
2460
  });
2326
2461
  return packed;
2327
2462
  };
2463
+ const internalValue = (...args) => canonicalValue(pack(args), INTERNAL_CALL);
2328
2464
  entry.value = (...args) => canonicalValue(pack(args));
2329
- entry.internalValue = (...args) => canonicalValue(pack(args), INTERNAL_CALL);
2465
+ entry.internalValue = internalValue;
2466
+ entry.bindInternal = (ctx) => ctx ? (...args) => canonicalValue(pack(args), childCallContext(ctx)) : internalValue;
2330
2467
  entry.positional = names;
2331
2468
  } else {
2332
- entry.internalValue = (input) => canonicalValue(input, INTERNAL_CALL);
2469
+ const internalValue = (input) => canonicalValue(input, INTERNAL_CALL);
2470
+ entry.internalValue = internalValue;
2471
+ entry.bindInternal = (ctx) => ctx ? (input) => canonicalValue(input, childCallContext(ctx)) : internalValue;
2333
2472
  }
2334
2473
  plugins[id] = entry;
2335
2474
  }
@@ -2569,7 +2708,7 @@ function createSdk(root, options) {
2569
2708
  pluginSurface = {};
2570
2709
  bindValue(pluginSurface, plugin.name, plugins2[plugin.id]);
2571
2710
  }
2572
- for (const key2 of Object.keys(legacyExports)) context.surface[key2] = key2;
2711
+ for (const key of Object.keys(legacyExports)) context.surface[key] = key;
2573
2712
  if (plugin.pluginType === "aggregate") {
2574
2713
  recordExportSurface(context, plugin.exports);
2575
2714
  } else {
@@ -2708,6 +2847,7 @@ function valueTypeOf(inner) {
2708
2847
  if (inner instanceof import_zod3.z.ZodEnum) return "string";
2709
2848
  if (inner instanceof import_zod3.z.ZodArray) return "array";
2710
2849
  if (inner instanceof import_zod3.z.ZodObject) return "object";
2850
+ if (inner instanceof import_zod3.z.ZodRecord) return "object";
2711
2851
  return void 0;
2712
2852
  }
2713
2853
  function staticChoicesOf(inner) {
@@ -2718,7 +2858,8 @@ function staticChoicesOf(inner) {
2718
2858
  return void 0;
2719
2859
  }
2720
2860
  function objectShape(schema) {
2721
- const { inner } = schema ? unwrap(schema) : { inner: void 0 };
2861
+ const canonical = canonicalInputSchema(schema);
2862
+ const { inner } = canonical ? unwrap(canonical) : { inner: void 0 };
2722
2863
  if (inner instanceof import_zod3.z.ZodObject) {
2723
2864
  return inner.shape;
2724
2865
  }
@@ -2740,7 +2881,7 @@ function topoOrder2(specs) {
2740
2881
  }
2741
2882
  function planParameters(entry) {
2742
2883
  const shape = objectShape(entry.inputSchema);
2743
- const resolvers = entry.boundResolvers ?? {};
2884
+ const resolvers = entry.resolvers ?? {};
2744
2885
  const names = shape ? [
2745
2886
  ...Object.keys(shape),
2746
2887
  ...Object.keys(resolvers).filter(
@@ -2778,24 +2919,48 @@ function getAtPath(root, path) {
2778
2919
  }
2779
2920
  return node;
2780
2921
  }
2922
+ function defineOwn(node, key, value) {
2923
+ Object.defineProperty(node, key, {
2924
+ value,
2925
+ writable: true,
2926
+ enumerable: true,
2927
+ configurable: true
2928
+ });
2929
+ }
2781
2930
  function setAtPath(root, path, value) {
2782
2931
  let node = root;
2783
2932
  for (let i = 0; i < path.length - 1; i++) {
2784
2933
  const seg = path[i];
2785
- if (node[seg] == null || typeof node[seg] !== "object") node[seg] = {};
2786
- node = node[seg];
2934
+ const existing = Object.prototype.hasOwnProperty.call(node, seg) ? node[seg] : void 0;
2935
+ if (existing != null && typeof existing === "object") {
2936
+ node = existing;
2937
+ } else {
2938
+ const child = {};
2939
+ defineOwn(node, seg, child);
2940
+ node = child;
2941
+ }
2787
2942
  }
2788
- node[path[path.length - 1]] = value;
2943
+ defineOwn(node, path[path.length - 1], value);
2789
2944
  }
2790
- var key = (path) => path.join(".");
2945
+ var SAFE_SEGMENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
2946
+ var pathToKey = (path) => {
2947
+ let out = "";
2948
+ for (const segment of path) {
2949
+ if (typeof segment === "number") out += `[${segment}]`;
2950
+ else if (SAFE_SEGMENT.test(segment))
2951
+ out += out === "" ? segment : `.${segment}`;
2952
+ else out += `[${JSON.stringify(segment)}]`;
2953
+ }
2954
+ return out;
2955
+ };
2791
2956
  function isSettled(state, path) {
2792
- return state.settled.includes(key(path));
2957
+ return state.settled.includes(pathToKey(path));
2793
2958
  }
2794
2959
  function remember(state, k) {
2795
2960
  if (!state.settled.includes(k)) state.settled.push(k);
2796
2961
  }
2797
2962
  function settle(state, path) {
2798
- remember(state, key(path));
2963
+ remember(state, pathToKey(path));
2799
2964
  }
2800
2965
  function clone(state) {
2801
2966
  return JSON.parse(JSON.stringify(state));
@@ -2810,6 +2975,28 @@ function coerce(leaf, raw) {
2810
2975
  if (raw === "true") return true;
2811
2976
  if (raw === "false") return false;
2812
2977
  }
2978
+ if (leaf.valueType === "object") {
2979
+ const trimmed = raw.trim();
2980
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
2981
+ try {
2982
+ return JSON.parse(trimmed);
2983
+ } catch {
2984
+ return raw;
2985
+ }
2986
+ }
2987
+ return raw;
2988
+ }
2989
+ if (leaf.valueType === "array") {
2990
+ const trimmed = raw.trim();
2991
+ if (trimmed.startsWith("[")) {
2992
+ try {
2993
+ return JSON.parse(trimmed);
2994
+ } catch {
2995
+ return raw;
2996
+ }
2997
+ }
2998
+ return raw;
2999
+ }
2813
3000
  return raw;
2814
3001
  }
2815
3002
  async function validationError(leaf, value, state) {
@@ -2867,27 +3054,6 @@ async function objectChildren(resolver, input) {
2867
3054
  return toLeaf(name, field, resolver.definitions);
2868
3055
  });
2869
3056
  }
2870
- function arrayItem(resolver) {
2871
- const items = resolver.items;
2872
- const valueType = resolver.itemValueType;
2873
- if (isRef(items)) {
2874
- return {
2875
- name: "",
2876
- required: true,
2877
- resolver: resolver.definitions?.[items.ref],
2878
- extraInput: items.input,
2879
- valueType,
2880
- requires: []
2881
- };
2882
- }
2883
- return {
2884
- name: "",
2885
- required: true,
2886
- resolver: items,
2887
- valueType,
2888
- requires: []
2889
- };
2890
- }
2891
3057
  function autoSettles(resolver) {
2892
3058
  return resolver.type === "constant" || resolver.type === "info";
2893
3059
  }
@@ -2907,10 +3073,28 @@ async function leafAt(ctx, path, resolved) {
2907
3073
  const seg = path[i];
2908
3074
  if (typeof seg === "number") {
2909
3075
  if (leaf?.resolver?.type !== "array") return void 0;
2910
- leaf = arrayItem(leaf.resolver);
3076
+ leaf = boundLeaf(
3077
+ "",
3078
+ leaf.resolver.items,
3079
+ leaf.resolver.definitions,
3080
+ leaf.resolver.itemValueType
3081
+ );
2911
3082
  } else {
2912
- leaf = children.find((c) => c.name === seg);
2913
- if (!leaf) return void 0;
3083
+ const parent = leaf;
3084
+ const found = children.find((c) => c.name === seg);
3085
+ if (found) {
3086
+ leaf = found;
3087
+ } else if (parent?.resolver?.type === "object" && parent.resolver.additionalKeys) {
3088
+ const ak = parent.resolver.additionalKeys;
3089
+ leaf = boundLeaf(
3090
+ String(seg),
3091
+ ak.values,
3092
+ parent.resolver.definitions,
3093
+ ak.valueValueType
3094
+ );
3095
+ } else {
3096
+ return void 0;
3097
+ }
2914
3098
  }
2915
3099
  if (i < path.length - 1 && typeof path[i + 1] === "string") {
2916
3100
  if (leaf?.resolver?.type !== "object") return void 0;
@@ -2922,16 +3106,81 @@ async function leafAt(ctx, path, resolved) {
2922
3106
  }
2923
3107
  return leaf;
2924
3108
  }
3109
+ function boundLeaf(name, resolverOrRef, definitions, valueType) {
3110
+ if (isRef(resolverOrRef)) {
3111
+ return {
3112
+ name,
3113
+ required: true,
3114
+ resolver: definitions?.[resolverOrRef.ref],
3115
+ extraInput: resolverOrRef.input,
3116
+ valueType,
3117
+ requires: []
3118
+ };
3119
+ }
3120
+ return {
3121
+ name,
3122
+ required: true,
3123
+ resolver: resolverOrRef,
3124
+ valueType,
3125
+ requires: []
3126
+ };
3127
+ }
3128
+ async function recordInfoAt(ctx, path, resolved) {
3129
+ const leaf = await leafAt(ctx, path, resolved);
3130
+ const resolver = leaf?.resolver;
3131
+ if (resolver?.type !== "object" || !resolver.additionalKeys) {
3132
+ throw new Error(
3133
+ `expected an object resolver with additionalKeys at "${pathToKey(path)}"`
3134
+ );
3135
+ }
3136
+ if (resolver.getProperties) {
3137
+ throw new Error(
3138
+ `object resolver at "${pathToKey(path)}" cannot combine getProperties with additionalKeys`
3139
+ );
3140
+ }
3141
+ const ak = resolver.additionalKeys;
3142
+ const defs = resolver.definitions;
3143
+ const keyLeaf = ak.keys ? boundLeaf("key", ak.keys, defs, ak.keyValueType ?? "string") : {
3144
+ name: "key",
3145
+ required: true,
3146
+ resolver: { type: "static", inputType: "text" },
3147
+ valueType: "string",
3148
+ requires: []
3149
+ };
3150
+ const valueLeaf = boundLeaf("value", ak.values, defs, ak.valueValueType);
3151
+ if (keyLeaf.resolver && keyLeaf.resolver.type !== "static") {
3152
+ throw new Error(
3153
+ `record key resolver at "${pathToKey(path)}" must be a static free-text prompt, not "${keyLeaf.resolver.type}"`
3154
+ );
3155
+ }
3156
+ if (valueLeaf.resolver?.type === "object" || valueLeaf.resolver?.type === "array") {
3157
+ throw new Error(
3158
+ `record value resolver at "${pathToKey(path)}" must be a single value, not "${valueLeaf.resolver.type}"`
3159
+ );
3160
+ }
3161
+ return {
3162
+ min: ak.minEntries ?? 0,
3163
+ max: ak.maxEntries ?? Infinity,
3164
+ keyLeaf,
3165
+ valueLeaf,
3166
+ fixedKeys: Object.keys(resolver.properties ?? {})
3167
+ };
3168
+ }
2925
3169
  async function arrayInfoAt(ctx, path, resolved) {
2926
3170
  const leaf = await leafAt(ctx, path, resolved);
2927
3171
  const resolver = leaf?.resolver;
2928
3172
  if (resolver?.type !== "array") {
2929
- throw new Error(`expected an array resolver at "${key(path)}"`);
3173
+ throw new Error(`expected an array resolver at "${pathToKey(path)}"`);
2930
3174
  }
2931
3175
  return {
2932
3176
  min: resolver.minItems ?? 0,
2933
3177
  max: resolver.maxItems ?? Infinity,
2934
- item: { ...arrayItem(resolver), name: String(path[path.length - 1]) }
3178
+ item: boundLeaf(
3179
+ String(path[path.length - 1]),
3180
+ resolver.items,
3181
+ resolver.definitions,
3182
+ resolver.itemValueType
3183
+ )
2935
3184
  };
2936
3185
  }
2937
3186
  async function firstPage(result) {
@@ -3012,6 +3261,9 @@ var AFFORDANCE = {
3012
3261
  retry: { action: "retry", description: "Retry loading the options" },
3013
3262
  cancel: { action: "cancel", description: "Cancel resolution" }
3014
3263
  };
3264
+ function affordance(base, description) {
3265
+ return { ...base, description };
3266
+ }
3015
3267
  function selectActions(leaf, page, multiple) {
3016
3268
  const searchMode = leaf.resolver?.type === "dynamic" && leaf.resolver.inputType === "search";
3017
3269
  if (searchMode && page.position.search === void 0 && page.items.length === 0) {
@@ -3126,7 +3378,7 @@ async function buildQuestion(leaf, path, input) {
3126
3378
  }
3127
3379
  };
3128
3380
  }
3129
- function collectionQuestion(t) {
3381
+ function arrayItemsQuestion(t) {
3130
3382
  const actions = [AFFORDANCE.add];
3131
3383
  if (t.count >= t.min) actions.push(AFFORDANCE.done);
3132
3384
  return {
@@ -3142,22 +3394,37 @@ function collectionQuestion(t) {
3142
3394
  actions
3143
3395
  };
3144
3396
  }
3145
- function objectGateQuestion(path) {
3397
+ function recordEntriesQuestion(t) {
3398
+ const actions = [
3399
+ affordance(AFFORDANCE.add, "Add another entry")
3400
+ ];
3401
+ if (t.count >= t.min) {
3402
+ actions.push(affordance(AFFORDANCE.done, "Finish the entries"));
3403
+ }
3404
+ return {
3405
+ type: "collection",
3406
+ path: t.path,
3407
+ message: `Add another ${t.path[t.path.length - 1]} entry? (${t.count} so far)`,
3408
+ container: "record",
3409
+ count: t.count,
3410
+ min: t.min,
3411
+ ...Number.isFinite(t.max) ? { max: t.max } : {},
3412
+ actions
3413
+ };
3414
+ }
3415
+ function objectOptionalQuestion(path) {
3146
3416
  return {
3147
3417
  type: "collection",
3148
3418
  path,
3149
3419
  message: `Add ${path[path.length - 1]}?`,
3150
3420
  container: "object",
3151
3421
  actions: [
3152
- {
3153
- action: "add",
3154
- description: "Provide values for these fields"
3155
- },
3156
- { action: "done", description: "Skip these fields" }
3422
+ affordance(AFFORDANCE.add, "Provide values for these fields"),
3423
+ affordance(AFFORDANCE.done, "Skip these fields")
3157
3424
  ]
3158
3425
  };
3159
3426
  }
3160
- function optionalsGateQuestion(path, pending) {
3427
+ function objectOptionalPropertiesQuestion(path, pending) {
3161
3428
  return {
3162
3429
  type: "collection",
3163
3430
  path,
@@ -3174,8 +3441,8 @@ function optionalsGateQuestion(path, pending) {
3174
3441
  ...leaf.valueType ? { valueType: leaf.valueType } : {}
3175
3442
  })),
3176
3443
  actions: [
3177
- { action: "add", description: "Configure the optional fields" },
3178
- { action: "done", description: "Skip the optional fields" }
3444
+ affordance(AFFORDANCE.add, "Configure the optional fields"),
3445
+ affordance(AFFORDANCE.done, "Skip the optional fields")
3179
3446
  ]
3180
3447
  };
3181
3448
  }
@@ -3193,7 +3460,8 @@ function finalize(ctx, resolved) {
3193
3460
  }));
3194
3461
  return { status: "invalid", issues };
3195
3462
  }
3196
- var optionalsMarker = (path) => `${key(path)}?optionals`;
3463
+ var optionalsMarker = (path) => `${pathToKey(path)}?optionals`;
3464
+ var UNSAFE_RECORD_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
3197
3465
  async function findInArray(ctx, state, path) {
3198
3466
  if (isSettled(state, path)) return null;
3199
3467
  if (getAtPath(state.resolved, path) == null)
@@ -3214,7 +3482,28 @@ async function findInArray(ctx, state, path) {
3214
3482
  }
3215
3483
  }
3216
3484
  if (len < min) return descendItem(ctx, state, path, len, item);
3217
- if (len < max) return { kind: "array", path, count: len, min, max };
3485
+ if (len < max) return { type: "array_items", path, count: len, min, max };
3486
+ settle(state, path);
3487
+ return null;
3488
+ }
3489
+ async function findInRecord(ctx, state, path) {
3490
+ if (isSettled(state, path)) return null;
3491
+ if (getAtPath(state.resolved, path) == null)
3492
+ setAtPath(state.resolved, path, {});
3493
+ if (!state.interactive) {
3494
+ settle(state, path);
3495
+ return null;
3496
+ }
3497
+ const { min, max, keyLeaf, fixedKeys } = await recordInfoAt(
3498
+ ctx,
3499
+ path,
3500
+ state.resolved
3501
+ );
3502
+ const container = getAtPath(state.resolved, path);
3503
+ const fixed = new Set(fixedKeys);
3504
+ const count = Object.keys(container).filter((k) => !fixed.has(k)).length;
3505
+ if (count < min) return { type: "record_key", path, leaf: keyLeaf };
3506
+ if (count < max) return { type: "record_entries", path, count, min, max };
3218
3507
  settle(state, path);
3219
3508
  return null;
3220
3509
  }
@@ -3232,10 +3521,10 @@ function seedItemSlot(state, itemPath, item) {
3232
3521
  }
3233
3522
  async function descendItem(ctx, state, arrayPath, index, item) {
3234
3523
  const itemPath = [...arrayPath, index];
3235
- const kind = seedItemSlot(state, itemPath, item);
3236
- if (kind === "object") return findNext(ctx, state, itemPath);
3237
- if (kind === "array") return findInArray(ctx, state, itemPath);
3238
- return { kind: "leaf", path: itemPath, leaf: item };
3524
+ const slotType = seedItemSlot(state, itemPath, item);
3525
+ if (slotType === "object") return findNext(ctx, state, itemPath);
3526
+ if (slotType === "array") return findInArray(ctx, state, itemPath);
3527
+ return { type: "leaf", path: itemPath, leaf: item };
3239
3528
  }
3240
3529
  async function findNext(ctx, state, path = []) {
3241
3530
  const container = getAtPath(state.resolved, path) ?? {};
@@ -3260,7 +3549,7 @@ async function findNext(ctx, state, path = []) {
3260
3549
  const pending = ordered.filter(
3261
3550
  (c) => !c.required && asksUser(c) && isPendingChild(c)
3262
3551
  );
3263
- return { kind: "optionals", path, pending };
3552
+ return { type: "object_optional_properties", path, pending };
3264
3553
  }
3265
3554
  if (leaf.resolver?.type === "object") {
3266
3555
  if (isSettled(state, childPath)) continue;
@@ -3270,7 +3559,7 @@ async function findNext(ctx, state, path = []) {
3270
3559
  settle(state, childPath);
3271
3560
  continue;
3272
3561
  }
3273
- return { kind: "object", path: childPath, leaf };
3562
+ return { type: "object_optional", path: childPath, leaf };
3274
3563
  }
3275
3564
  setAtPath(state.resolved, childPath, {});
3276
3565
  }
@@ -3302,13 +3591,21 @@ async function findNext(ctx, state, path = []) {
3302
3591
  }
3303
3592
  if (container[leaf.name] !== void 0 || isSettled(state, childPath))
3304
3593
  continue;
3305
- return { kind: "leaf", path: childPath, leaf };
3594
+ return { type: "leaf", path: childPath, leaf };
3595
+ }
3596
+ if (inObject && !isSettled(state, path)) {
3597
+ const self = await leafAt(ctx, path, state.resolved);
3598
+ if (self?.resolver?.type === "object" && self.resolver.additionalKeys) {
3599
+ const rec = await findInRecord(ctx, state, path);
3600
+ if (rec) return rec;
3601
+ }
3306
3602
  }
3307
3603
  return null;
3308
3604
  }
3309
3605
  async function askLeaf(state, path, leaf, opts = {}) {
3310
3606
  state.current = path;
3311
- delete state.gate;
3607
+ if (opts.gate) state.gate = opts.gate;
3608
+ else delete state.gate;
3312
3609
  try {
3313
3610
  const { question, pagination } = await buildQuestion(
3314
3611
  leaf,
@@ -3329,6 +3626,28 @@ async function askLeaf(state, path, leaf, opts = {}) {
3329
3626
  return failedResult(state, leaf.name, error);
3330
3627
  }
3331
3628
  }
3629
+ async function askRecordKey(state, path, keyLeaf, opts = {}) {
3630
+ return askLeaf(state, path, keyLeaf, { gate: "record_key", ...opts });
3631
+ }
3632
+ async function autoResolveLeaf(state, path, leaf) {
3633
+ const resolver = leaf.resolver;
3634
+ if (resolver && autoSettles(resolver)) {
3635
+ if (resolver.type === "constant")
3636
+ setAtPath(state.resolved, path, resolver.value);
3637
+ settle(state, path);
3638
+ return true;
3639
+ }
3640
+ const auto = resolver?.type === "dynamic" ? await resolver.tryResolveWithoutPrompt?.({
3641
+ input: mergeInput(state.resolved, leaf.extraInput)
3642
+ }) : void 0;
3643
+ if (auto) {
3644
+ if (auto.resolvedValue !== void 0)
3645
+ setAtPath(state.resolved, path, auto.resolvedValue);
3646
+ settle(state, path);
3647
+ return true;
3648
+ }
3649
+ return false;
3650
+ }
3332
3651
  async function advance(ctx, state) {
3333
3652
  for (; ; ) {
3334
3653
  const target = await findNext(ctx, state);
@@ -3338,54 +3657,56 @@ async function advance(ctx, state) {
3338
3657
  delete state.pagination;
3339
3658
  return { state, result: finalize(ctx, state.resolved) };
3340
3659
  }
3341
- if (target.kind === "array") {
3660
+ if (target.type === "array_items") {
3342
3661
  state.current = target.path;
3343
- state.gate = "array";
3662
+ state.gate = "array_items";
3344
3663
  delete state.pagination;
3345
3664
  return {
3346
3665
  state,
3347
- result: { status: "ask", question: collectionQuestion(target) }
3666
+ result: { status: "ask", question: arrayItemsQuestion(target) }
3348
3667
  };
3349
3668
  }
3350
- if (target.kind === "object") {
3669
+ if (target.type === "object_optional") {
3351
3670
  state.current = target.path;
3352
- state.gate = "entry";
3671
+ state.gate = "object_optional";
3353
3672
  delete state.pagination;
3354
3673
  return {
3355
3674
  state,
3356
- result: { status: "ask", question: objectGateQuestion(target.path) }
3675
+ result: {
3676
+ status: "ask",
3677
+ question: objectOptionalQuestion(target.path)
3678
+ }
3357
3679
  };
3358
3680
  }
3359
- if (target.kind === "optionals") {
3681
+ if (target.type === "object_optional_properties") {
3360
3682
  state.current = target.path;
3361
- state.gate = "optionals";
3683
+ state.gate = "object_optional_properties";
3362
3684
  delete state.pagination;
3363
3685
  return {
3364
3686
  state,
3365
3687
  result: {
3366
3688
  status: "ask",
3367
- question: optionalsGateQuestion(target.path, target.pending)
3689
+ question: objectOptionalPropertiesQuestion(
3690
+ target.path,
3691
+ target.pending
3692
+ )
3368
3693
  }
3369
3694
  };
3370
3695
  }
3371
- const { path, leaf } = target;
3372
- const resolver = leaf.resolver;
3373
- if (resolver && autoSettles(resolver)) {
3374
- if (resolver.type === "constant") {
3375
- setAtPath(state.resolved, path, resolver.value);
3376
- }
3377
- settle(state, path);
3378
- continue;
3696
+ if (target.type === "record_entries") {
3697
+ state.current = target.path;
3698
+ state.gate = "record_entries";
3699
+ delete state.pagination;
3700
+ return {
3701
+ state,
3702
+ result: { status: "ask", question: recordEntriesQuestion(target) }
3703
+ };
3379
3704
  }
3380
- const auto = resolver?.type === "dynamic" ? await resolver.tryResolveWithoutPrompt?.({
3381
- input: mergeInput(state.resolved, leaf.extraInput)
3382
- }) : void 0;
3383
- if (auto) {
3384
- if (auto.resolvedValue !== void 0)
3385
- setAtPath(state.resolved, path, auto.resolvedValue);
3386
- settle(state, path);
3387
- continue;
3705
+ if (target.type === "record_key") {
3706
+ return askRecordKey(state, target.path, target.leaf);
3388
3707
  }
3708
+ const { path, leaf } = target;
3709
+ if (await autoResolveLeaf(state, path, leaf)) continue;
3389
3710
  if (!state.interactive) {
3390
3711
  if (!leaf.required) {
3391
3712
  settle(state, path);
@@ -3418,10 +3739,18 @@ async function step(ctx, prior, action) {
3418
3739
  delete state.pagination;
3419
3740
  return { state, result: { status: "cancelled" } };
3420
3741
  }
3742
+ if (state.gate === "record_key") {
3743
+ return stepRecordKey(ctx, state, action);
3744
+ }
3421
3745
  const path = state.current;
3422
3746
  if (!path) throw new Error("step called with no outstanding question");
3423
3747
  const leaf = await leafAt(ctx, path, state.resolved);
3424
- if (leaf && (action.type === "search" || action.type === "next_page" || action.type === "previous_page" || action.type === "retry")) {
3748
+ if (!leaf) {
3749
+ throw new Error(
3750
+ `no resolver for the outstanding question at "${pathToKey(path)}"`
3751
+ );
3752
+ }
3753
+ if (action.type === "search" || action.type === "next_page" || action.type === "previous_page" || action.type === "retry") {
3425
3754
  return refine(ctx, state, leaf, path, action);
3426
3755
  }
3427
3756
  if (action.type === "add" || action.type === "done") {
@@ -3438,19 +3767,26 @@ async function step(ctx, prior, action) {
3438
3767
  settle(state, path);
3439
3768
  return advance(ctx, state);
3440
3769
  }
3441
- if (gate === "entry") {
3770
+ if (gate === "object_optional") {
3442
3771
  setAtPath(state.resolved, path, {});
3443
3772
  return advance(ctx, state);
3444
3773
  }
3445
- if (gate === "optionals") {
3774
+ if (gate === "object_optional_properties") {
3446
3775
  remember(state, optionalsMarker(path));
3447
3776
  return advance(ctx, state);
3448
3777
  }
3778
+ if (gate === "record_entries") {
3779
+ const { keyLeaf } = await recordInfoAt(ctx, path, state.resolved);
3780
+ return askRecordKey(state, path, keyLeaf);
3781
+ }
3449
3782
  const items = getAtPath(state.resolved, path) ?? [];
3450
3783
  const { item } = await arrayInfoAt(ctx, path, state.resolved);
3451
3784
  const itemPath = [...path, items.length];
3452
- if (seedItemSlot(state, itemPath, item) === "leaf")
3785
+ if (seedItemSlot(state, itemPath, item) === "leaf") {
3786
+ if (await autoResolveLeaf(state, itemPath, item))
3787
+ return advance(ctx, state);
3453
3788
  return askLeaf(state, itemPath, item);
3789
+ }
3454
3790
  return advance(ctx, state);
3455
3791
  }
3456
3792
  if (state.gate) {
@@ -3461,81 +3797,37 @@ async function step(ctx, prior, action) {
3461
3797
  switch (action.type) {
3462
3798
  case "choose":
3463
3799
  case "custom": {
3464
- if (leaf) {
3465
- let error;
3466
- try {
3467
- error = await validationError(leaf, action.value, state);
3468
- } catch (thrown) {
3469
- return failedResult(state, leaf.name, thrown);
3470
- }
3471
- if (error) {
3472
- if (state.pagination && leaf.resolver?.type === "dynamic") {
3473
- try {
3474
- const context = await resolveContext(leaf, state.resolved);
3475
- const page = await fetchListing(
3476
- leaf,
3477
- state.resolved,
3478
- state.pagination.position,
3479
- context
3480
- );
3481
- state.pagination = toPagination(page);
3482
- return {
3483
- state,
3484
- result: {
3485
- status: "ask",
3486
- question: selectQuestion(
3487
- leaf,
3488
- path,
3489
- state.resolved,
3490
- page,
3491
- context
3492
- ),
3493
- error
3494
- }
3495
- };
3496
- } catch (fetchError) {
3497
- state.pagination = failedPagination(
3498
- state.pagination,
3499
- state.pagination.position
3500
- );
3501
- return failedResult(state, leaf.name, fetchError);
3502
- }
3503
- }
3504
- return askLeaf(state, path, leaf, { error });
3800
+ let error;
3801
+ try {
3802
+ error = await validationError(leaf, action.value, state);
3803
+ } catch (thrown) {
3804
+ return failedResult(state, leaf.name, thrown);
3805
+ }
3806
+ if (error) {
3807
+ if (state.pagination && leaf.resolver?.type === "dynamic") {
3808
+ return renderPageAt(state, leaf, path, state.pagination.position, {
3809
+ error
3810
+ });
3505
3811
  }
3812
+ return askLeaf(state, path, leaf, { error });
3506
3813
  }
3507
- setAtPath(
3508
- state.resolved,
3509
- path,
3510
- leaf ? coerce(leaf, action.value) : action.value
3511
- );
3814
+ setAtPath(state.resolved, path, coerce(leaf, action.value));
3512
3815
  break;
3513
3816
  }
3514
3817
  case "skip":
3515
3818
  settle(state, path);
3516
3819
  break;
3517
3820
  default:
3518
- throw new Error(`action "${action.type}" is not supported here`);
3821
+ throw new Error(
3822
+ `action "${action.type}" is not supported here`
3823
+ );
3519
3824
  }
3520
3825
  delete state.current;
3521
3826
  delete state.pagination;
3522
3827
  return advance(ctx, state);
3523
3828
  }
3524
- async function refine(ctx, state, leaf, path, action) {
3525
- const position = positionAfter(state.pagination, action);
3829
+ async function renderPageAt(state, leaf, path, position, opts = {}) {
3526
3830
  try {
3527
- if (action.type === "search") {
3528
- const exact = leaf.resolver?.type === "dynamic" ? await leaf.resolver.tryResolveFromSearch?.({
3529
- input: mergeInput(state.resolved, leaf.extraInput),
3530
- search: action.term
3531
- }) : void 0;
3532
- if (exact) {
3533
- setAtPath(state.resolved, path, coerce(leaf, exact.resolvedValue));
3534
- delete state.current;
3535
- delete state.pagination;
3536
- return advance(ctx, state);
3537
- }
3538
- }
3539
3831
  const context = await resolveContext(leaf, state.resolved);
3540
3832
  const page = await fetchListing(leaf, state.resolved, position, context);
3541
3833
  state.pagination = toPagination(page);
@@ -3543,7 +3835,8 @@ async function refine(ctx, state, leaf, path, action) {
3543
3835
  state,
3544
3836
  result: {
3545
3837
  status: "ask",
3546
- question: selectQuestion(leaf, path, state.resolved, page, context)
3838
+ question: selectQuestion(leaf, path, state.resolved, page, context),
3839
+ ...opts.error ? { error: opts.error } : {}
3547
3840
  }
3548
3841
  };
3549
3842
  } catch (error) {
@@ -3551,6 +3844,65 @@ async function refine(ctx, state, leaf, path, action) {
3551
3844
  return failedResult(state, leaf.name, error);
3552
3845
  }
3553
3846
  }
3847
+ async function refine(ctx, state, leaf, path, action) {
3848
+ const position = positionAfter(state.pagination, action);
3849
+ if (action.type === "search") {
3850
+ try {
3851
+ const exact = leaf.resolver?.type === "dynamic" ? await leaf.resolver.tryResolveFromSearch?.({
3852
+ input: mergeInput(state.resolved, leaf.extraInput),
3853
+ search: action.term
3854
+ }) : void 0;
3855
+ if (exact) {
3856
+ setAtPath(state.resolved, path, coerce(leaf, exact.resolvedValue));
3857
+ delete state.current;
3858
+ delete state.pagination;
3859
+ return advance(ctx, state);
3860
+ }
3861
+ } catch (error) {
3862
+ state.pagination = failedPagination(state.pagination, position);
3863
+ return failedResult(state, leaf.name, error);
3864
+ }
3865
+ }
3866
+ return renderPageAt(state, leaf, path, position);
3867
+ }
3868
+ async function stepRecordKey(ctx, state, action) {
3869
+ const path = state.current;
3870
+ if (!path)
3871
+ throw new Error("record key step called with no outstanding question");
3872
+ const { keyLeaf, valueLeaf } = await recordInfoAt(ctx, path, state.resolved);
3873
+ if (action.type === "skip") {
3874
+ delete state.gate;
3875
+ delete state.current;
3876
+ delete state.pagination;
3877
+ return advance(ctx, state);
3878
+ }
3879
+ if (action.type !== "custom" && action.type !== "choose") {
3880
+ throw new Error(
3881
+ `action "${action.type}" is not supported while entering a record key`
3882
+ );
3883
+ }
3884
+ const raw = Array.isArray(action.value) ? action.value[0] : action.value;
3885
+ const entryKey = String(coerce(keyLeaf, raw));
3886
+ if (entryKey.trim() === "") {
3887
+ return askRecordKey(state, path, keyLeaf, { error: "A key is required." });
3888
+ }
3889
+ if (UNSAFE_RECORD_KEYS.has(entryKey)) {
3890
+ return askRecordKey(state, path, keyLeaf, {
3891
+ error: `"${entryKey}" is not an allowed key.`
3892
+ });
3893
+ }
3894
+ const container = getAtPath(state.resolved, path);
3895
+ if (Object.prototype.hasOwnProperty.call(container, entryKey)) {
3896
+ return askRecordKey(state, path, keyLeaf, {
3897
+ error: `"${entryKey}" is already set.`
3898
+ });
3899
+ }
3900
+ const valuePath = [...path, entryKey];
3901
+ if (await autoResolveLeaf(state, valuePath, valueLeaf)) {
3902
+ return advance(ctx, state);
3903
+ }
3904
+ return askLeaf(state, valuePath, valueLeaf);
3905
+ }
3554
3906
  function failedPagination(pagination, retryPosition) {
3555
3907
  return {
3556
3908
  position: pagination?.position ?? firstPagePosition(),
@@ -3606,7 +3958,7 @@ function projectSummary(entry) {
3606
3958
  };
3607
3959
  }
3608
3960
  function projectMethod(entry) {
3609
- const inputProperties = toJsonSchema(entry.inputSchema)?.properties;
3961
+ const inputProperties = toJsonSchema(canonicalInputSchema(entry.inputSchema))?.properties;
3610
3962
  const parameters = {};
3611
3963
  for (const spec of planParameters(entry).parameters) {
3612
3964
  const dynamic = spec.resolver?.type === "dynamic" ? spec.resolver : void 0;
@@ -3639,7 +3991,12 @@ function createController(sdk) {
3639
3991
  const entry = entryFor(method);
3640
3992
  return {
3641
3993
  method,
3642
- schema: entry.inputSchema,
3994
+ // A method that owns its input validation (`skipInputValidation`, e.g.
3995
+ // fetch) must not be re-validated by the controller's final `safeParse`;
3996
+ // drop the schema so `finalize` returns the resolved input untouched.
3997
+ // Planning still reads `entry.inputSchema` directly, so parameters are
3998
+ // unaffected.
3999
+ schema: entry.skipInputValidation ? void 0 : entry.inputSchema,
3643
4000
  parameters: planParameters(entry).parameters
3644
4001
  };
3645
4002
  }
@@ -3704,59 +4061,6 @@ function createCorePlugin(options) {
3704
4061
  }
3705
4062
  });
3706
4063
  }
3707
-
3708
- // src/utils/schema-utils.ts
3709
- var import_zod5 = require("zod");
3710
- function getOutputSchema(inputSchema) {
3711
- return inputSchema._zod.def.outputSchema;
3712
- }
3713
- function withOutputSchema(inputSchema, outputSchema) {
3714
- Object.assign(inputSchema._zod.def, {
3715
- outputSchema
3716
- });
3717
- return inputSchema;
3718
- }
3719
- function withResolver(schema, config) {
3720
- schema._zod.def.resolverMeta = config;
3721
- return schema;
3722
- }
3723
- function getSchemaDescription(schema) {
3724
- return schema.description;
3725
- }
3726
- function getFieldDescriptions(schema) {
3727
- const descriptions = {};
3728
- const shape = schema.shape;
3729
- for (const [key2, fieldSchema] of Object.entries(shape)) {
3730
- if (fieldSchema instanceof import_zod5.z.ZodType && fieldSchema.description) {
3731
- descriptions[key2] = fieldSchema.description;
3732
- }
3733
- }
3734
- return descriptions;
3735
- }
3736
- function withPositional(schema) {
3737
- Object.assign(schema._zod.def, {
3738
- positionalMeta: { positional: true }
3739
- });
3740
- return schema;
3741
- }
3742
- function schemaHasPositionalMeta(schema) {
3743
- return "positionalMeta" in schema._zod.def;
3744
- }
3745
- function isPositional(schema) {
3746
- if (schemaHasPositionalMeta(schema) && schema._zod.def.positionalMeta?.positional) {
3747
- return true;
3748
- }
3749
- if (schema instanceof import_zod5.z.ZodOptional) {
3750
- return isPositional(schema._zod.def.innerType);
3751
- }
3752
- if (schema instanceof import_zod5.z.ZodDefault) {
3753
- return isPositional(schema._zod.def.innerType);
3754
- }
3755
- return false;
3756
- }
3757
- function openEnum(values, description) {
3758
- return import_zod5.z.union([import_zod5.z.enum(values), import_zod5.z.string()]).describe(description);
3759
- }
3760
4064
  // Annotate the CommonJS export names for ESM import in node:
3761
4065
  0 && (module.exports = {
3762
4066
  CONTEXT,
@@ -3769,6 +4073,7 @@ function openEnum(values, description) {
3769
4073
  CoreErrorCode,
3770
4074
  CoreSignal,
3771
4075
  addPlugin,
4076
+ canonicalInputSchema,
3772
4077
  composePlugins,
3773
4078
  concatLists,
3774
4079
  concatPaginated,