@zapier/kitcore 0.4.0 → 0.5.1

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
@@ -553,6 +553,19 @@ function isNestedMethodCall() {
553
553
  const store = scope.get();
554
554
  return store !== void 0 && store.depth > 0;
555
555
  }
556
+ var observerReentrancy = 0;
557
+ function runIsolatedObserver(fn) {
558
+ observerReentrancy++;
559
+ try {
560
+ fn();
561
+ } catch {
562
+ } finally {
563
+ observerReentrancy--;
564
+ }
565
+ }
566
+ function isInsideObserver() {
567
+ return observerReentrancy > 0;
568
+ }
556
569
  function runInMethodScope(fn) {
557
570
  if (!scope.available) return fn();
558
571
  const currentDepth = scope.get()?.depth ?? -1;
@@ -561,7 +574,36 @@ function runInMethodScope(fn) {
561
574
  var runWithTelemetryContext = runInMethodScope;
562
575
  var isTelemetryNested = isNestedMethodCall;
563
576
 
577
+ // src/utils/core-options.ts
578
+ function defaultLogDeprecation({
579
+ methodName,
580
+ deprecation
581
+ }) {
582
+ logDeprecation(`${methodName}() is deprecated. ${deprecation.message}`);
583
+ }
584
+ var CORE_OPTIONS_ID = "kitcore/coreOptions";
585
+
564
586
  // src/utils/function-utils.ts
587
+ function resolveCoreOptions(context) {
588
+ const entry = context.plugins?.[CORE_OPTIONS_ID];
589
+ if (entry) {
590
+ return entry.getValue ? entry.getValue() : entry.value;
591
+ }
592
+ return context.core;
593
+ }
594
+ var INTERNAL_CALL = Symbol("kitcore.internalCall");
595
+ function signalDeprecation(context, methodName, getDeprecation) {
596
+ if (isInsideObserver()) return;
597
+ const deprecation = getDeprecation?.();
598
+ if (!deprecation?.message) return;
599
+ const warning = {
600
+ type: "deprecation",
601
+ methodName,
602
+ deprecation
603
+ };
604
+ const handler = resolveCoreOptions(context)?.logDeprecation ?? defaultLogDeprecation;
605
+ runIsolatedObserver(() => handler(warning));
606
+ }
565
607
  function normalizeError(error, adaptError) {
566
608
  if (error instanceof Error) return error;
567
609
  const message = typeof error === "object" && error !== null && "message" in error && typeof error.message === "string" ? error.message : String(error);
@@ -575,17 +617,20 @@ function normalizeError(error, adaptError) {
575
617
  );
576
618
  }
577
619
  function createFunction(coreFn, options) {
578
- const { sdk, schema, name } = options;
620
+ const { sdk, schema, name, getDeprecation } = options;
579
621
  const functionName = name || coreFn.name;
580
622
  const namedFunctions = {
581
623
  [functionName]: async function(callOptions) {
624
+ if (arguments[1] !== INTERNAL_CALL) {
625
+ signalDeprecation(sdk.context, functionName, getDeprecation);
626
+ }
582
627
  return runInMethodScope(async () => {
583
628
  const startTime = Date.now();
584
629
  const normalizedOptions = callOptions ?? {};
585
630
  const args = [normalizedOptions];
586
631
  const depth = getCurrentDepth();
587
- const hooks = sdk.context.hooks;
588
- const adaptError = sdk.context.core?.adaptError;
632
+ const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
633
+ const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
589
634
  hooks?.onMethodStart?.({
590
635
  methodName: functionName,
591
636
  args,
@@ -634,6 +679,62 @@ function createFunction(coreFn, options) {
634
679
  };
635
680
  return namedFunctions[functionName];
636
681
  }
682
+ function createRawFunction(coreFn, options) {
683
+ const { sdk, name, schema, positional, getDeprecation } = options;
684
+ return function(rawInput) {
685
+ if (arguments[1] !== INTERNAL_CALL) {
686
+ signalDeprecation(sdk.context, name, getDeprecation);
687
+ }
688
+ return runInMethodScope(() => {
689
+ const startTime = Date.now();
690
+ const depth = getCurrentDepth();
691
+ const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
692
+ const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
693
+ const input = schema ? rawInput ?? {} : rawInput;
694
+ const record = input;
695
+ const args = positional ? positional.filter((key2) => record?.[key2] !== void 0).map((key2) => record?.[key2]) : [input];
696
+ hooks?.onMethodStart?.({
697
+ methodName: name,
698
+ args,
699
+ isPaginated: false,
700
+ depth
701
+ });
702
+ const fireEnd = (error) => {
703
+ hooks?.onMethodEnd?.({
704
+ methodName: name,
705
+ args,
706
+ isPaginated: false,
707
+ depth,
708
+ durationMs: Date.now() - startTime,
709
+ ...error ? { error } : {}
710
+ });
711
+ };
712
+ try {
713
+ const parsed = schema ? validateOptions(schema, input, { adaptError }) : input;
714
+ const result = coreFn(parsed);
715
+ if (result !== null && typeof result === "object" && typeof result.then === "function") {
716
+ return result.then(
717
+ (value) => {
718
+ fireEnd();
719
+ return value;
720
+ },
721
+ (error) => {
722
+ fireEnd(
723
+ error instanceof Error ? error : new Error(String(error))
724
+ );
725
+ throw error;
726
+ }
727
+ );
728
+ }
729
+ fireEnd();
730
+ return result;
731
+ } catch (error) {
732
+ fireEnd(error instanceof Error ? error : new Error(String(error)));
733
+ throw error;
734
+ }
735
+ });
736
+ };
737
+ }
637
738
  function isSdkPage(value) {
638
739
  if (typeof value !== "object" || value === null) return false;
639
740
  const page = value;
@@ -662,7 +763,7 @@ function createPageFunction(coreFn, {
662
763
  } catch (error) {
663
764
  throw normalizeError(
664
765
  error,
665
- sdk.context.core?.adaptError
766
+ resolveCoreOptions(sdk.context)?.adaptError
666
767
  );
667
768
  }
668
769
  }
@@ -670,82 +771,98 @@ function createPageFunction(coreFn, {
670
771
  return namedFunctions[functionName];
671
772
  }
672
773
  function createPaginatedFunction(coreFn, options) {
673
- const { sdk, schema, name, defaultPageSize, adaptPage } = options;
774
+ const { sdk, schema, name, defaultPageSize, adaptPage, getDeprecation } = options;
674
775
  const pageFunction = createPageFunction(coreFn, { sdk, adaptPage });
675
776
  const functionName = name || coreFn.name;
676
777
  const namedFunctions = {
677
778
  [functionName]: function(callOptions) {
779
+ if (arguments[1] !== INTERNAL_CALL) {
780
+ signalDeprecation(sdk.context, functionName, getDeprecation);
781
+ }
678
782
  return runInMethodScope(() => {
679
783
  const startTime = Date.now();
680
784
  const normalizedOptions = callOptions ?? {};
681
785
  const args = [normalizedOptions];
682
786
  const depth = getCurrentDepth();
683
- const hooks = sdk.context.hooks;
684
- const adaptError = sdk.context.core?.adaptError;
787
+ const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
788
+ const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
685
789
  hooks?.onMethodStart?.({
686
790
  methodName: functionName,
687
791
  args,
688
792
  isPaginated: true,
689
793
  depth
690
794
  });
691
- const validatedOptions = {
692
- ...normalizedOptions,
693
- ...schema ? createValidator(schema, { adaptError })(normalizedOptions) : normalizedOptions
694
- };
695
- const pageSize = validatedOptions.pageSize ?? defaultPageSize;
696
- const optimizedOptions = {
697
- ...validatedOptions,
698
- pageSize
699
- };
700
- const iterator = paginate(pageFunction, optimizedOptions);
701
- const firstPagePromise = iterator.next().then((result) => {
702
- if (result.done) {
703
- throw new Error("Paginate should always iterate at least once");
704
- }
705
- return result.value;
706
- });
707
- if (hooks?.onMethodEnd) {
708
- firstPagePromise.then(() => {
709
- hooks.onMethodEnd({
710
- methodName: functionName,
711
- args,
712
- isPaginated: true,
713
- depth,
714
- durationMs: Date.now() - startTime
715
- });
716
- }).catch((error) => {
717
- hooks.onMethodEnd({
718
- methodName: functionName,
719
- args,
720
- isPaginated: true,
721
- depth,
722
- durationMs: Date.now() - startTime,
723
- error: error instanceof Error ? error : new Error(String(error))
724
- });
795
+ try {
796
+ const validatedOptions = {
797
+ ...normalizedOptions,
798
+ ...schema ? createValidator(schema, { adaptError })(normalizedOptions) : normalizedOptions
799
+ };
800
+ const pageSize = validatedOptions.pageSize ?? defaultPageSize;
801
+ const optimizedOptions = {
802
+ ...validatedOptions,
803
+ pageSize
804
+ };
805
+ const iterator = paginate(pageFunction, optimizedOptions);
806
+ const firstPagePromise = iterator.next().then((result) => {
807
+ if (result.done) {
808
+ throw new Error("Paginate should always iterate at least once");
809
+ }
810
+ return result.value;
725
811
  });
726
- }
727
- const pageStream = async function* () {
728
- yield await firstPagePromise;
729
- for await (const page of iterator) {
730
- yield page;
812
+ if (hooks?.onMethodEnd) {
813
+ firstPagePromise.then(() => {
814
+ hooks.onMethodEnd({
815
+ methodName: functionName,
816
+ args,
817
+ isPaginated: true,
818
+ depth,
819
+ durationMs: Date.now() - startTime
820
+ });
821
+ }).catch((error) => {
822
+ hooks.onMethodEnd({
823
+ methodName: functionName,
824
+ args,
825
+ isPaginated: true,
826
+ depth,
827
+ durationMs: Date.now() - startTime,
828
+ error: error instanceof Error ? error : new Error(String(error))
829
+ });
830
+ });
731
831
  }
732
- }();
733
- return Object.assign(firstPagePromise, {
734
- [Symbol.asyncIterator]() {
735
- return pageStream;
736
- },
737
- items: function() {
738
- return {
739
- [Symbol.asyncIterator]: async function* () {
740
- for await (const page of pageStream) {
741
- for (const item of page.data) {
742
- yield item;
832
+ const pageStream = async function* () {
833
+ yield await firstPagePromise;
834
+ for await (const page of iterator) {
835
+ yield page;
836
+ }
837
+ }();
838
+ return Object.assign(firstPagePromise, {
839
+ [Symbol.asyncIterator]() {
840
+ return pageStream;
841
+ },
842
+ items: function() {
843
+ return {
844
+ [Symbol.asyncIterator]: async function* () {
845
+ for await (const page of pageStream) {
846
+ for (const item of page.data) {
847
+ yield item;
848
+ }
743
849
  }
744
850
  }
745
- }
746
- };
747
- }
748
- });
851
+ };
852
+ }
853
+ });
854
+ } catch (error) {
855
+ const normalizedError = normalizeError(error, adaptError);
856
+ hooks?.onMethodEnd?.({
857
+ methodName: functionName,
858
+ args,
859
+ isPaginated: true,
860
+ depth,
861
+ durationMs: Date.now() - startTime,
862
+ error: normalizedError
863
+ });
864
+ throw normalizedError;
865
+ }
749
866
  });
750
867
  }
751
868
  };
@@ -754,6 +871,9 @@ function createPaginatedFunction(coreFn, options) {
754
871
 
755
872
  // src/utils/plugin-utils.ts
756
873
  function createPluginMethod(sdk, config) {
874
+ logDeprecation(
875
+ "createPluginMethod() is deprecated. Author methods with defineMethod instead."
876
+ );
757
877
  const { name, inputSchema, handler, ...metaFields } = config;
758
878
  const namedHandlers = {
759
879
  [name]: async function(options) {
@@ -777,6 +897,9 @@ function createPluginMethod(sdk, config) {
777
897
  };
778
898
  }
779
899
  function createPaginatedPluginMethod(sdk, config) {
900
+ logDeprecation(
901
+ 'createPaginatedPluginMethod() is deprecated. Author list methods with defineMethod output "list" instead.'
902
+ );
780
903
  const {
781
904
  name,
782
905
  inputSchema,
@@ -1008,6 +1131,9 @@ function composePlugins(...plugins) {
1008
1131
  return collapseStackEntries(entries, "composePlugins");
1009
1132
  }
1010
1133
  function createPluginStack() {
1134
+ logDeprecation(
1135
+ "createPluginStack() is deprecated. Compose with definePlugin and build with createSdk instead."
1136
+ );
1011
1137
  return buildPluginStack(null, "createPluginStack");
1012
1138
  }
1013
1139
  function buildPluginStack(head, callerLabel) {
@@ -1092,7 +1218,7 @@ function normalizeImports(deps) {
1092
1218
  if (!deps) return { plugins: [], bindings: [] };
1093
1219
  const seen = /* @__PURE__ */ new Map();
1094
1220
  const bindings = [];
1095
- const add = (binding, id) => {
1221
+ const add = (binding, id, optional) => {
1096
1222
  const priorId = seen.get(binding);
1097
1223
  if (priorId !== void 0 && priorId !== id) {
1098
1224
  throw new Error(
@@ -1101,7 +1227,7 @@ function normalizeImports(deps) {
1101
1227
  }
1102
1228
  if (priorId === void 0) {
1103
1229
  seen.set(binding, id);
1104
- bindings.push({ binding, id });
1230
+ bindings.push(optional ? { binding, id, optional } : { binding, id });
1105
1231
  }
1106
1232
  };
1107
1233
  for (const plugin of deps) {
@@ -1109,8 +1235,9 @@ function normalizeImports(deps) {
1109
1235
  for (const [binding, child] of Object.entries(plugin.exports)) {
1110
1236
  add(binding, child.id);
1111
1237
  }
1238
+ } else if (plugin.pluginType === "hook") {
1112
1239
  } else {
1113
- add(plugin.name, plugin.id);
1240
+ add(plugin.name, plugin.id, plugin.optional);
1114
1241
  }
1115
1242
  }
1116
1243
  return { plugins: deps, bindings };
@@ -1122,6 +1249,26 @@ function collectLeafMeta(config) {
1122
1249
  }
1123
1250
  return meta;
1124
1251
  }
1252
+ function formatDynamicMemberName(path) {
1253
+ return path.map((seg) => typeof seg === "string" ? seg : `{${seg.param}}`).join(".");
1254
+ }
1255
+ function collectDynamicMembers(members) {
1256
+ if (!members?.length) return void 0;
1257
+ return members.map((member) => {
1258
+ const root = member.path[0];
1259
+ if (typeof root !== "string") {
1260
+ throw new Error(
1261
+ "defineProperty: a dynamicMember path must start with a literal segment (the owning binding), not a { param }."
1262
+ );
1263
+ }
1264
+ const leaf = collectLeafMeta(member) ?? {};
1265
+ return {
1266
+ name: formatDynamicMemberName(member.path),
1267
+ rootBinding: root,
1268
+ meta: member.inputSchema ? { ...leaf, inputSchema: member.inputSchema } : leaf
1269
+ };
1270
+ });
1271
+ }
1125
1272
  function defineMethod(config) {
1126
1273
  const deps = normalizeImports(config.imports);
1127
1274
  return {
@@ -1132,15 +1279,29 @@ function defineMethod(config) {
1132
1279
  imports: deps.plugins,
1133
1280
  importBindings: deps.bindings,
1134
1281
  inputSchema: config.inputSchema,
1282
+ skipInputValidation: config.skipInputValidation,
1135
1283
  meta: collectLeafMeta(config),
1136
1284
  resolvers: config.resolvers,
1137
1285
  formatter: config.formatter,
1138
1286
  output: config.output,
1139
1287
  positional: config.positional,
1140
1288
  setup: config.setup,
1289
+ dispose: config.dispose,
1141
1290
  run: config.run
1142
1291
  };
1143
1292
  }
1293
+ function defineMethodOverride(config) {
1294
+ const { target, namespace, ...rest } = config;
1295
+ return {
1296
+ pluginType: "method-override",
1297
+ name: `override:${target}`,
1298
+ id: namespace ? `${namespace}/override:${target}` : `override:${target}`,
1299
+ target,
1300
+ imports: [],
1301
+ importBindings: [],
1302
+ meta: collectLeafMeta(rest)
1303
+ };
1304
+ }
1144
1305
  function defineResolver(config) {
1145
1306
  const deps = normalizeImports(config.imports);
1146
1307
  const base = { imports: deps.plugins, importBindings: deps.bindings };
@@ -1232,9 +1393,11 @@ function defineProperty(config) {
1232
1393
  imports: deps.plugins,
1233
1394
  importBindings: deps.bindings,
1234
1395
  setup: config.setup,
1396
+ dispose: config.dispose,
1235
1397
  value: config.value,
1236
1398
  get: config.get,
1237
- meta: collectLeafMeta(config)
1399
+ meta: collectLeafMeta(config),
1400
+ dynamicMembers: collectDynamicMembers(config.dynamicMembers)
1238
1401
  };
1239
1402
  }
1240
1403
  function declareProperty(config) {
@@ -1249,6 +1412,37 @@ function declareProperty(config) {
1249
1412
  importBindings: []
1250
1413
  };
1251
1414
  }
1415
+ function declareOptionalProperty(config) {
1416
+ const { name, namespace } = parseId(config.id);
1417
+ return {
1418
+ pluginType: "property",
1419
+ name,
1420
+ namespace,
1421
+ id: makeId(name, namespace),
1422
+ standIn: true,
1423
+ optional: true,
1424
+ imports: [],
1425
+ importBindings: []
1426
+ // Requires nothing (phantom carrier `<never, never>`): a consumer that
1427
+ // imports it still passes `createSdk`'s completeness check unprovided. The
1428
+ // import binding is still typed `TValue | undefined` from the descriptor.
1429
+ };
1430
+ }
1431
+ function defineHook(config) {
1432
+ const deps = normalizeImports(config.imports);
1433
+ return {
1434
+ pluginType: "hook",
1435
+ name: config.name,
1436
+ namespace: config.namespace,
1437
+ id: makeId(config.name, config.namespace),
1438
+ imports: deps.plugins,
1439
+ importBindings: deps.bindings,
1440
+ setup: config.setup,
1441
+ dispose: config.dispose,
1442
+ wrap: config.wrap,
1443
+ observe: config.observe
1444
+ };
1445
+ }
1252
1446
  function declarePlugin(config) {
1253
1447
  const { name, namespace } = parseId(config.id);
1254
1448
  return {
@@ -1263,7 +1457,12 @@ function declarePlugin(config) {
1263
1457
  };
1264
1458
  }
1265
1459
  function definePlugin(fnOrConfig) {
1266
- if (typeof fnOrConfig === "function") return fnOrConfig;
1460
+ if (typeof fnOrConfig === "function") {
1461
+ logDeprecation(
1462
+ "definePlugin(fn) (the function form) is deprecated. Author plugins with defineMethod/defineProperty/definePlugin({ ... }) instead."
1463
+ );
1464
+ return fnOrConfig;
1465
+ }
1267
1466
  const config = fnOrConfig;
1268
1467
  const deps = normalizeImports(config.imports);
1269
1468
  return {
@@ -1271,12 +1470,24 @@ function definePlugin(fnOrConfig) {
1271
1470
  name: config.name,
1272
1471
  namespace: config.namespace,
1273
1472
  id: makeId(config.name, config.namespace, "aggregate"),
1274
- imports: deps.plugins,
1473
+ // A re-export synthetic (`selectExports` / `omitExports`) is flattened by
1474
+ // `normalizeExports` into bare bindings, which drops its own `imports:
1475
+ // [source]`. That edge is how `omitExports` keeps an omitted (unbound) leaf
1476
+ // materialized + addressable by id, so preserve every exported aggregate's
1477
+ // imports as extra reachability edges here (bindings unaffected).
1478
+ imports: [...deps.plugins, ...exportedAggregateImports(config.exports)],
1275
1479
  importBindings: deps.bindings,
1276
- exports: normalizeExports(config.exports),
1277
- middleware: config.middleware
1480
+ exports: normalizeExports(config.exports)
1278
1481
  };
1279
1482
  }
1483
+ function exportedAggregateImports(exports) {
1484
+ if (!exports) return [];
1485
+ const out = [];
1486
+ for (const element of exports) {
1487
+ if (element.pluginType === "aggregate") out.push(...element.imports);
1488
+ }
1489
+ return out;
1490
+ }
1280
1491
  function normalizeExports(exports) {
1281
1492
  if (!exports) return {};
1282
1493
  const out = {};
@@ -1336,9 +1547,33 @@ function selectExports(source, ...specs) {
1336
1547
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1337
1548
  };
1338
1549
  }
1550
+ function omitExports(source, omit) {
1551
+ const omitSet = new Set(omit);
1552
+ for (const name of omit) {
1553
+ if (!(name in source.exports)) {
1554
+ throw new Error(`omitExports: "${source.id}" has no export "${name}".`);
1555
+ }
1556
+ }
1557
+ const kept = {};
1558
+ for (const [binding, child] of Object.entries(source.exports)) {
1559
+ if (!omitSet.has(binding)) kept[binding] = child;
1560
+ }
1561
+ return {
1562
+ pluginType: "aggregate",
1563
+ name: makeId(`omit`, source.name, "aggregate"),
1564
+ id: `${source.id}#omit:${selectSeq++}`,
1565
+ imports: [source],
1566
+ importBindings: [],
1567
+ exports: kept
1568
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1569
+ };
1570
+ }
1339
1571
 
1340
1572
  // src/model/legacy.ts
1341
1573
  function fromFunctionPlugin(fn, config) {
1574
+ logDeprecation(
1575
+ "fromFunctionPlugin() is deprecated. Author plugins with defineMethod/definePlugin instead."
1576
+ );
1342
1577
  return {
1343
1578
  pluginType: "legacy",
1344
1579
  name: config.name,
@@ -1350,6 +1585,9 @@ function fromFunctionPlugin(fn, config) {
1350
1585
  };
1351
1586
  }
1352
1587
  function defineLegacyMerge(args) {
1588
+ logDeprecation(
1589
+ "defineLegacyMerge() is deprecated. Build directly with createSdk(root, { configuration }) instead."
1590
+ );
1353
1591
  return {
1354
1592
  pluginType: "legacy-merge",
1355
1593
  name: args.name,
@@ -1416,40 +1654,60 @@ function pluginEntryMeta(entry) {
1416
1654
  if (entry.pluginType === "property" && entry.meta) return entry.meta;
1417
1655
  return void 0;
1418
1656
  }
1419
- function buildSurfaceRegistry(context, packageFilter) {
1657
+ function foldDynamicMembers(entry, surfaceBindings, meta) {
1658
+ if (entry.pluginType !== "property" || !entry.dynamicMembers) return;
1659
+ for (const member of entry.dynamicMembers) {
1660
+ if (!surfaceBindings.has(member.rootBinding)) {
1661
+ throw new Error(
1662
+ `dynamicMember "${member.name}": its root "${member.rootBinding}" is not a surfaced member. A dynamic member's path must start with a real binding.`
1663
+ );
1664
+ }
1665
+ meta[member.name] = member.meta;
1666
+ }
1667
+ }
1668
+ function collectSurfaceProjection(context, formatterSdk) {
1420
1669
  const meta = {};
1421
- const surface = {};
1422
1670
  const entries = {};
1423
1671
  for (const [binding, id] of Object.entries(context.surface)) {
1424
1672
  const entry = context.plugins[id];
1425
1673
  if (!entry || entry.pluginType === "aggregate") continue;
1426
- surface[binding] = entry.value;
1427
1674
  entries[binding] = entry;
1428
1675
  const m = pluginEntryMeta(entry);
1429
1676
  if (m) meta[binding] = m;
1430
1677
  }
1678
+ const surfaceBindings = new Set(Object.keys(context.surface));
1679
+ for (const entry of Object.values(entries)) {
1680
+ foldDynamicMembers(entry, surfaceBindings, meta);
1681
+ }
1431
1682
  const formatters = {};
1432
1683
  const boundResolvers = {};
1433
1684
  const positional = {};
1434
1685
  for (const [binding, entry] of Object.entries(entries)) {
1435
- const f = normalizeFormatter(entry, surface);
1686
+ const f = normalizeFormatter(entry, formatterSdk);
1436
1687
  if (f) formatters[binding] = f;
1437
1688
  const r = normalizeBoundResolvers(entry);
1438
1689
  if (r) boundResolvers[binding] = r;
1439
1690
  const p = methodPositional(entry);
1440
1691
  if (p) positional[binding] = p;
1441
1692
  }
1693
+ return { meta, formatters, boundResolvers, positional };
1694
+ }
1695
+ function buildSurfaceRegistry(context, packageFilter) {
1696
+ const surface = {};
1697
+ for (const [binding, id] of Object.entries(context.surface)) {
1698
+ const entry = context.plugins[id];
1699
+ if (!entry || entry.pluginType === "aggregate") continue;
1700
+ surface[binding] = entry.pluginType === "property" && entry.getValue ? entry.getValue() : entry.value;
1701
+ }
1442
1702
  return buildRegistry({
1443
1703
  sdk: surface,
1444
- meta,
1445
- formatters,
1446
- boundResolvers,
1447
- positional,
1704
+ ...collectSurfaceProjection(context, surface),
1448
1705
  packageFilter
1449
1706
  });
1450
1707
  }
1451
1708
 
1452
1709
  // src/model/builtins.ts
1710
+ var coreOptionsPluginRef = declareOptionalProperty({ id: CORE_OPTIONS_ID });
1453
1711
  var dangerousContextPlugin = {
1454
1712
  pluginType: "property",
1455
1713
  name: "context",
@@ -1468,12 +1726,15 @@ var getRegistryPlugin = defineMethod({
1468
1726
  });
1469
1727
 
1470
1728
  // src/model/materialize.ts
1729
+ var FRAMEWORK_CONFIGURATION_IDS = /* @__PURE__ */ new Set([
1730
+ CORE_OPTIONS_ID
1731
+ ]);
1471
1732
  function normalizeOutput(output) {
1472
1733
  if (output === void 0) return { type: "raw" };
1473
1734
  if (typeof output === "string") return { type: output };
1474
1735
  return output;
1475
1736
  }
1476
- var CONTEXT = Symbol("kitcore.context");
1737
+ var CONTEXT = Symbol.for("kitcore.context");
1477
1738
  function getContext(sdk) {
1478
1739
  return sdk[CONTEXT];
1479
1740
  }
@@ -1535,7 +1796,7 @@ function topoOrder(descriptors) {
1535
1796
  for (const id of descriptors.keys()) visit(id);
1536
1797
  return order;
1537
1798
  }
1538
- function collectPlugins(root, materialized = /* @__PURE__ */ new Set()) {
1799
+ function collectPlugins(root, materialized = /* @__PURE__ */ new Set(), configuration) {
1539
1800
  const byId = /* @__PURE__ */ new Map();
1540
1801
  const visit = (plugin) => {
1541
1802
  if (materialized.has(plugin.id)) return;
@@ -1558,8 +1819,51 @@ function collectPlugins(root, materialized = /* @__PURE__ */ new Set()) {
1558
1819
  for (const edge of edgesOf(plugin)) visit(edge);
1559
1820
  };
1560
1821
  visit(root);
1822
+ if (configuration) {
1823
+ for (const [id, value] of Object.entries(configuration)) {
1824
+ const existing = byId.get(id);
1825
+ if (!existing) {
1826
+ if (FRAMEWORK_CONFIGURATION_IDS.has(id)) {
1827
+ const { name, namespace } = parseId(id);
1828
+ byId.set(id, {
1829
+ pluginType: "property",
1830
+ name,
1831
+ namespace,
1832
+ id,
1833
+ imports: [],
1834
+ importBindings: [],
1835
+ value
1836
+ });
1837
+ continue;
1838
+ }
1839
+ throw new Error(
1840
+ `createSdk: configuration id "${id}" matches no plugin in the graph. An injected value must satisfy a property stand-in reachable from the root.`
1841
+ );
1842
+ }
1843
+ if (existing.pluginType !== "property") {
1844
+ throw new Error(
1845
+ `createSdk: configuration id "${id}" resolves to a "${existing.pluginType}" plugin; only property values can be injected.`
1846
+ );
1847
+ }
1848
+ if (!isStandIn(existing)) {
1849
+ throw new Error(
1850
+ `createSdk: configuration id "${id}" collides with a registered provider. A property is either injected or provided by a plugin, not both.`
1851
+ );
1852
+ }
1853
+ byId.set(id, {
1854
+ pluginType: "property",
1855
+ name: existing.name,
1856
+ namespace: existing.namespace,
1857
+ id,
1858
+ imports: [],
1859
+ importBindings: [],
1860
+ value
1861
+ });
1862
+ }
1863
+ }
1561
1864
  for (const [id, plugin] of byId) {
1562
1865
  if (isStandIn(plugin)) {
1866
+ if ("optional" in plugin && plugin.optional) continue;
1563
1867
  throw new Error(
1564
1868
  `createSdk: missing dependency "${id}". A plugin depends on it (via a stand-in) but no implementation was registered.`
1565
1869
  );
@@ -1567,7 +1871,7 @@ function collectPlugins(root, materialized = /* @__PURE__ */ new Set()) {
1567
1871
  }
1568
1872
  return byId;
1569
1873
  }
1570
- function bindValue(target, key2, entry) {
1874
+ function bindValue(target, key2, entry, callType = "surface") {
1571
1875
  if (entry.pluginType === "property" && entry.getValue) {
1572
1876
  Object.defineProperty(target, key2, {
1573
1877
  get: entry.getValue,
@@ -1575,8 +1879,9 @@ function bindValue(target, key2, entry) {
1575
1879
  configurable: true
1576
1880
  });
1577
1881
  } else {
1882
+ const value = callType === "internal" && entry.pluginType === "method" ? entry.internalValue ?? entry.value : entry.value;
1578
1883
  Object.defineProperty(target, key2, {
1579
- value: entry.value,
1884
+ value,
1580
1885
  writable: true,
1581
1886
  enumerable: true,
1582
1887
  configurable: true
@@ -1594,8 +1899,18 @@ function buildSurface(context, ...maps) {
1594
1899
  }
1595
1900
  function buildImports(plugins, importBindings) {
1596
1901
  const imports = {};
1597
- for (const { binding, id } of importBindings) {
1598
- bindValue(imports, binding, plugins[id]);
1902
+ for (const { binding, id, optional } of importBindings) {
1903
+ const entry = plugins[id];
1904
+ if (!entry && optional) {
1905
+ Object.defineProperty(imports, binding, {
1906
+ value: void 0,
1907
+ writable: true,
1908
+ enumerable: true,
1909
+ configurable: true
1910
+ });
1911
+ continue;
1912
+ }
1913
+ bindValue(imports, binding, entry, "internal");
1599
1914
  }
1600
1915
  return imports;
1601
1916
  }
@@ -1619,9 +1934,31 @@ function materialize(descriptors, context) {
1619
1934
  buildEagerArtifacts(descriptors, context, states);
1620
1935
  bindAttachments(descriptors, context);
1621
1936
  resolveAggregates(descriptors, context);
1622
- assembleMiddleware(descriptors, context);
1937
+ assembleMiddleware(descriptors, context, states);
1938
+ assembleHooks(descriptors, context, states);
1939
+ applyMethodOverrides(descriptors, context);
1623
1940
  return context.plugins;
1624
1941
  }
1942
+ function applyMethodOverride(context, override) {
1943
+ const entry = context.plugins[override.target];
1944
+ if (!entry) {
1945
+ throw new Error(
1946
+ `defineMethodOverride: no method "${override.target}" to override. Include the target method in the SDK build.`
1947
+ );
1948
+ }
1949
+ if (entry.pluginType !== "method") {
1950
+ throw new Error(
1951
+ `defineMethodOverride: "${override.target}" is a ${entry.pluginType}, not a method; only methods can be overridden.`
1952
+ );
1953
+ }
1954
+ entry.meta = { ...entry.meta, ...override.meta };
1955
+ }
1956
+ function applyMethodOverrides(descriptors, context) {
1957
+ for (const descriptor of descriptors.values()) {
1958
+ if (descriptor.pluginType !== "method-override") continue;
1959
+ applyMethodOverride(context, descriptor);
1960
+ }
1961
+ }
1625
1962
  function bindResolver(resolver, plugins) {
1626
1963
  switch (resolver.type) {
1627
1964
  case "static":
@@ -1667,25 +2004,24 @@ function bindResolver(resolver, plugins) {
1667
2004
  bound.definitions = bindDefinitions(resolver.definitions, plugins);
1668
2005
  return bound;
1669
2006
  }
1670
- default: {
2007
+ case "dynamic": {
1671
2008
  const imports = buildImports(plugins, resolver.importBindings);
1672
- const bound = {
1673
- type: "dynamic",
1674
- requireParameters: resolver.requireParameters,
1675
- inputType: resolver.inputType,
1676
- placeholder: resolver.placeholder,
1677
- prompt: resolver.prompt
1678
- };
1679
2009
  const {
1680
2010
  getContext: getContext2,
1681
2011
  listItems,
1682
2012
  tryResolveWithoutPrompt,
1683
2013
  tryResolveFromSearch
1684
2014
  } = resolver;
2015
+ const bound = {
2016
+ type: "dynamic",
2017
+ requireParameters: resolver.requireParameters,
2018
+ inputType: resolver.inputType,
2019
+ placeholder: resolver.placeholder,
2020
+ prompt: resolver.prompt,
2021
+ listItems: ({ input, context, search, cursor }) => listItems({ imports, input, context, search, cursor })
2022
+ };
1685
2023
  if (getContext2)
1686
2024
  bound.getContext = ({ input }) => getContext2({ imports, input });
1687
- if (listItems)
1688
- bound.listItems = ({ input, context, search, cursor }) => listItems({ imports, input, context, search, cursor });
1689
2025
  if (tryResolveWithoutPrompt) {
1690
2026
  bound.tryResolveWithoutPrompt = ({ input }) => tryResolveWithoutPrompt({ imports, input });
1691
2027
  }
@@ -1694,6 +2030,12 @@ function bindResolver(resolver, plugins) {
1694
2030
  }
1695
2031
  return bound;
1696
2032
  }
2033
+ default: {
2034
+ const unhandled = resolver;
2035
+ throw new Error(
2036
+ `unhandled resolver kind: ${unhandled.type}`
2037
+ );
2038
+ }
1697
2039
  }
1698
2040
  }
1699
2041
  function bindFields(fields, plugins) {
@@ -1764,25 +2106,11 @@ function runLegacyPass(descriptors, context) {
1764
2106
  if (!("getRegistry" in exports)) {
1765
2107
  let getRegistry2 = function(options) {
1766
2108
  const sdk = this ?? exports;
1767
- const meta2 = {};
1768
- const formatters = {};
1769
- const boundResolvers = {};
1770
- for (const [binding, id2] of Object.entries(context.surface)) {
1771
- const entry = context.plugins[id2];
1772
- if (!entry || entry.pluginType === "aggregate") continue;
1773
- const m = pluginEntryMeta(entry);
1774
- if (m) meta2[binding] = m;
1775
- const f = normalizeFormatter(entry, sdk);
1776
- if (f) formatters[binding] = f;
1777
- const r = normalizeBoundResolvers(entry);
1778
- if (r) boundResolvers[binding] = r;
1779
- }
1780
- Object.assign(meta2, context.meta);
2109
+ const projection = collectSurfaceProjection(context, sdk);
2110
+ Object.assign(projection.meta, context.meta);
1781
2111
  return buildRegistry({
1782
2112
  sdk,
1783
- meta: meta2,
1784
- formatters,
1785
- boundResolvers,
2113
+ ...projection,
1786
2114
  packageFilter: options?.package
1787
2115
  });
1788
2116
  };
@@ -1827,7 +2155,10 @@ function buildMethodEntries(descriptors, context, states) {
1827
2155
  next = (i) => wrap.run({
1828
2156
  imports: buildImports(plugins, wrap.owner.importBindings),
1829
2157
  next: inner,
1830
- input: i
2158
+ input: i,
2159
+ // Overwritten by the chain item's own closure with the owning
2160
+ // hook's setup state.
2161
+ state: void 0
1831
2162
  });
1832
2163
  }
1833
2164
  return next(input);
@@ -1841,7 +2172,8 @@ function buildMethodEntries(descriptors, context, states) {
1841
2172
  schema: descriptor.inputSchema,
1842
2173
  name: descriptor.name,
1843
2174
  defaultPageSize: out.defaultPageSize,
1844
- adaptPage: out.adaptPage
2175
+ adaptPage: out.adaptPage,
2176
+ getDeprecation: () => entry.meta?.deprecation
1845
2177
  }
1846
2178
  );
1847
2179
  } else if (out.type === "item") {
@@ -1850,25 +2182,43 @@ function buildMethodEntries(descriptors, context, states) {
1850
2182
  });
1851
2183
  entry.value = createFunction(
1852
2184
  fold(itemCore),
1853
- { sdk, schema: descriptor.inputSchema, name: descriptor.name }
2185
+ {
2186
+ sdk,
2187
+ schema: descriptor.inputSchema,
2188
+ name: descriptor.name,
2189
+ getDeprecation: () => entry.meta?.deprecation
2190
+ }
1854
2191
  );
1855
2192
  } else {
1856
- entry.value = (rawInput) => {
1857
- const input = descriptor.inputSchema ? descriptor.inputSchema.parse(rawInput) : rawInput;
1858
- return fold(callRun)(input);
1859
- };
2193
+ entry.value = createRawFunction(
2194
+ (input) => fold(callRun)(input),
2195
+ {
2196
+ sdk,
2197
+ name: descriptor.name,
2198
+ schema: descriptor.skipInputValidation ? void 0 : descriptor.inputSchema,
2199
+ positional: descriptor.positional,
2200
+ // The boundary reads the deprecation LIVE off the entry, so a
2201
+ // deprecation merged after build (defineMethodOverride, addPlugin)
2202
+ // fires too.
2203
+ getDeprecation: () => entry.meta?.deprecation
2204
+ }
2205
+ );
1860
2206
  }
2207
+ const canonicalValue = entry.value;
1861
2208
  if (descriptor.positional) {
1862
2209
  const names = descriptor.positional;
1863
- const canonicalValue = entry.value;
1864
- entry.value = (...args) => {
2210
+ const pack = (args) => {
1865
2211
  const packed = {};
1866
2212
  names.forEach((name, i) => {
1867
2213
  if (i < args.length) packed[name] = args[i];
1868
2214
  });
1869
- return canonicalValue(packed);
2215
+ return packed;
1870
2216
  };
2217
+ entry.value = (...args) => canonicalValue(pack(args));
2218
+ entry.internalValue = (...args) => canonicalValue(pack(args), INTERNAL_CALL);
1871
2219
  entry.positional = names;
2220
+ } else {
2221
+ entry.internalValue = (input) => canonicalValue(input, INTERNAL_CALL);
1872
2222
  }
1873
2223
  plugins[id] = entry;
1874
2224
  }
@@ -1880,7 +2230,7 @@ function buildEagerArtifacts(descriptors, context, states) {
1880
2230
  const ensureBuilt = (id) => {
1881
2231
  if (built.has(id)) return;
1882
2232
  const descriptor = descriptors.get(id);
1883
- if (!descriptor || descriptor.pluginType === "aggregate" || descriptor.pluginType === "legacy") {
2233
+ if (!descriptor || descriptor.pluginType === "aggregate" || descriptor.pluginType === "legacy" || descriptor.pluginType === "method-override" || isStandIn(descriptor)) {
1884
2234
  built.add(id);
1885
2235
  return;
1886
2236
  }
@@ -1889,6 +2239,30 @@ function buildEagerArtifacts(descriptors, context, states) {
1889
2239
  }
1890
2240
  building.add(id);
1891
2241
  for (const { id: depId } of descriptor.importBindings) ensureBuilt(depId);
2242
+ const recordDisposer = () => {
2243
+ const dispose = descriptor.dispose;
2244
+ if (!dispose) return;
2245
+ context.disposers?.push({
2246
+ id,
2247
+ dispose: (input) => dispose({
2248
+ imports: buildImports(plugins, descriptor.importBindings),
2249
+ state: states.get(id),
2250
+ input
2251
+ })
2252
+ });
2253
+ };
2254
+ if (descriptor.pluginType === "hook") {
2255
+ states.set(
2256
+ id,
2257
+ descriptor.setup ? descriptor.setup({
2258
+ imports: buildImports(plugins, descriptor.importBindings)
2259
+ }) : void 0
2260
+ );
2261
+ recordDisposer();
2262
+ building.delete(id);
2263
+ built.add(id);
2264
+ return;
2265
+ }
1892
2266
  if (descriptor.pluginType === "method") {
1893
2267
  states.set(
1894
2268
  id,
@@ -1908,7 +2282,8 @@ function buildEagerArtifacts(descriptors, context, states) {
1908
2282
  pluginType: "property",
1909
2283
  name: descriptor.name,
1910
2284
  value: context,
1911
- meta: descriptor.meta
2285
+ meta: descriptor.meta,
2286
+ dynamicMembers: descriptor.dynamicMembers
1912
2287
  };
1913
2288
  } else if (descriptor.get) {
1914
2289
  const get = descriptor.get;
@@ -1920,22 +2295,68 @@ function buildEagerArtifacts(descriptors, context, states) {
1920
2295
  imports: buildImports(plugins, importBindings),
1921
2296
  state: states.get(id)
1922
2297
  }),
1923
- meta: descriptor.meta
2298
+ meta: descriptor.meta,
2299
+ dynamicMembers: descriptor.dynamicMembers
1924
2300
  };
1925
2301
  } else {
1926
2302
  plugins[id] = {
1927
2303
  pluginType: "property",
1928
2304
  name: descriptor.name,
1929
2305
  value: descriptor.value,
1930
- meta: descriptor.meta
2306
+ meta: descriptor.meta,
2307
+ dynamicMembers: descriptor.dynamicMembers
1931
2308
  };
1932
2309
  }
1933
2310
  }
2311
+ recordDisposer();
1934
2312
  building.delete(id);
1935
2313
  built.add(id);
1936
2314
  };
1937
2315
  for (const id of descriptors.keys()) ensureBuilt(id);
1938
2316
  }
2317
+ function resolvePlugin(sdk, ref) {
2318
+ const entry = getContext(sdk).plugins[ref.id];
2319
+ if (!entry) {
2320
+ if (ref.optional) {
2321
+ return void 0;
2322
+ }
2323
+ throw new Error(
2324
+ `resolvePlugin: plugin "${ref.id}" is not materialized on the SDK.`
2325
+ );
2326
+ }
2327
+ if (entry.pluginType === "property" && entry.getValue) {
2328
+ return entry.getValue();
2329
+ }
2330
+ if (entry.pluginType === "method" && entry.internalValue) {
2331
+ return entry.internalValue;
2332
+ }
2333
+ return entry.value;
2334
+ }
2335
+ var CoreDisposeError = class extends Error {
2336
+ constructor(errors) {
2337
+ super(`disposeSdk: ${errors.length} dispose callback(s) failed.`);
2338
+ this.name = "CoreDisposeError";
2339
+ this.errors = errors;
2340
+ Object.setPrototypeOf(this, new.target.prototype);
2341
+ }
2342
+ };
2343
+ function disposeSdk(sdk, input) {
2344
+ const context = getContext(sdk);
2345
+ if (context.disposed) return context.disposed;
2346
+ const disposers = context.disposers ?? [];
2347
+ context.disposed = (async () => {
2348
+ const errors = [];
2349
+ for (let i = disposers.length - 1; i >= 0; i--) {
2350
+ try {
2351
+ await disposers[i].dispose(input);
2352
+ } catch (error) {
2353
+ errors.push(error);
2354
+ }
2355
+ }
2356
+ if (errors.length > 0) throw new CoreDisposeError(errors);
2357
+ })();
2358
+ return context.disposed;
2359
+ }
1939
2360
  function resolveAggregates(descriptors, context) {
1940
2361
  const plugins = context.plugins;
1941
2362
  for (const [id, descriptor] of descriptors) {
@@ -1947,39 +2368,74 @@ function resolveAggregates(descriptors, context) {
1947
2368
  plugins[id] = { pluginType: "aggregate", name: descriptor.name, exports };
1948
2369
  }
1949
2370
  }
1950
- function assembleMiddleware(descriptors, context) {
2371
+ function assembleMiddleware(descriptors, context, states) {
1951
2372
  const plugins = context.plugins;
1952
2373
  for (const id of topoOrder(descriptors)) {
1953
2374
  const descriptor = descriptors.get(id);
1954
- if (!descriptor || descriptor.pluginType !== "aggregate" || !descriptor.middleware) {
2375
+ if (!descriptor || descriptor.pluginType !== "hook" || !descriptor.wrap) {
1955
2376
  continue;
1956
2377
  }
1957
- for (const [targetBinding, fn] of Object.entries(descriptor.middleware)) {
2378
+ for (const [targetBinding, fn] of Object.entries(descriptor.wrap)) {
1958
2379
  const edge = descriptor.importBindings.find(
1959
2380
  (b) => b.binding === targetBinding
1960
2381
  );
1961
2382
  if (!edge) {
1962
2383
  throw new Error(
1963
- `createSdk: middleware target "${targetBinding}" in plugin "${id}" is not a direct dependency. A middleware target must be a declared dependency of the wrapping plugin.`
2384
+ `createSdk: wrap target "${targetBinding}" in hook "${id}" is not a direct dependency. A wrap target must be a declared import of the wrapping hook.`
1964
2385
  );
1965
2386
  }
1966
2387
  const target = plugins[edge.id];
1967
2388
  if (!target || target.pluginType !== "method") {
1968
2389
  throw new Error(
1969
- `createSdk: middleware target "${targetBinding}" in plugin "${id}" does not resolve to a method.`
2390
+ `createSdk: wrap target "${targetBinding}" in hook "${id}" does not resolve to a method.`
1970
2391
  );
1971
2392
  }
1972
2393
  if (target.output?.type === "list") {
1973
2394
  throw new Error(
1974
- `createSdk: middleware target "${targetBinding}" in plugin "${id}" resolves to a list-output method, which does not support middleware yet.`
2395
+ `createSdk: wrap target "${targetBinding}" in hook "${id}" resolves to a list-output method, which does not support wrapping yet.`
1975
2396
  );
1976
2397
  }
1977
- target.chain.push({ run: fn, owner: descriptor });
2398
+ target.chain.push({
2399
+ run: (bag) => fn({ ...bag, state: states.get(id) }),
2400
+ owner: descriptor
2401
+ });
2402
+ }
2403
+ }
2404
+ }
2405
+ function assembleHooks(descriptors, context, states) {
2406
+ const plugins = context.plugins;
2407
+ for (const id of topoOrder(descriptors)) {
2408
+ const descriptor = descriptors.get(id);
2409
+ if (!descriptor || descriptor.pluginType !== "hook" || !descriptor.observe) {
2410
+ continue;
1978
2411
  }
2412
+ const { observe } = descriptor;
2413
+ const imports = buildImports(plugins, descriptor.importBindings);
2414
+ const state = states.get(id);
2415
+ const contributed = {};
2416
+ if (observe.onMethodStart) {
2417
+ const onStart = observe.onMethodStart;
2418
+ contributed.onMethodStart = (input) => {
2419
+ runIsolatedObserver(() => onStart({ imports, input, state }));
2420
+ };
2421
+ }
2422
+ if (observe.onMethodEnd) {
2423
+ const onEnd = observe.onMethodEnd;
2424
+ contributed.onMethodEnd = (input) => {
2425
+ runIsolatedObserver(() => onEnd({ imports, input, state }));
2426
+ };
2427
+ }
2428
+ context.hooks = buildHooks(context.hooks, contributed);
1979
2429
  }
1980
2430
  }
1981
- function createSdk(root) {
1982
- const context = { plugins: {}, meta: {}, hooks: {}, surface: {} };
2431
+ function createSdk(root, options) {
2432
+ const context = {
2433
+ plugins: {},
2434
+ meta: {},
2435
+ hooks: {},
2436
+ surface: {},
2437
+ disposers: []
2438
+ };
1983
2439
  if (root.pluginType === "legacy-merge") {
1984
2440
  const { legacy, plugin } = root;
1985
2441
  const collectRoot = {
@@ -1990,7 +2446,10 @@ function createSdk(root) {
1990
2446
  importBindings: [],
1991
2447
  exports: {}
1992
2448
  };
1993
- const plugins2 = materialize(collectPlugins(collectRoot), context);
2449
+ const plugins2 = materialize(
2450
+ collectPlugins(collectRoot, void 0, options?.configuration),
2451
+ context
2452
+ );
1994
2453
  const legacyExports = plugins2[legacy.id].exports;
1995
2454
  let pluginSurface;
1996
2455
  if (plugin.pluginType === "aggregate") {
@@ -2007,7 +2466,10 @@ function createSdk(root) {
2007
2466
  }
2008
2467
  return buildSurface(context, legacyExports, pluginSurface);
2009
2468
  }
2010
- const plugins = materialize(collectPlugins(root), context);
2469
+ const plugins = materialize(
2470
+ collectPlugins(root, void 0, options?.configuration),
2471
+ context
2472
+ );
2011
2473
  if (root.pluginType === "method" || root.pluginType === "property") {
2012
2474
  context.surface[root.name] = root.id;
2013
2475
  const sdk = buildSurface(context);
@@ -2021,7 +2483,7 @@ function createSdk(root) {
2021
2483
  function addModelPlugin(sdk, plugin, options = {}) {
2022
2484
  const override = options.override === true;
2023
2485
  const context = getContext(sdk);
2024
- const surfaceKeys = plugin.pluginType === "aggregate" ? Object.keys(plugin.exports) : [plugin.name];
2486
+ const surfaceKeys = plugin.pluginType === "aggregate" ? Object.keys(plugin.exports) : plugin.pluginType === "hook" ? [] : [plugin.name];
2025
2487
  checkRootKeyCollisions(sdk, surfaceKeys, override, "addPlugin");
2026
2488
  const materialized = new Set(Object.keys(context.plugins));
2027
2489
  if (override && materialized.has(plugin.id)) {
@@ -2030,6 +2492,7 @@ function addModelPlugin(sdk, plugin, options = {}) {
2030
2492
  );
2031
2493
  }
2032
2494
  materialize(collectPlugins(plugin, materialized), context);
2495
+ if (plugin.pluginType === "hook") return;
2033
2496
  const entry = context.plugins[plugin.id];
2034
2497
  if (entry.pluginType === "aggregate") {
2035
2498
  Object.defineProperties(
@@ -2063,6 +2526,13 @@ function addPlugin(sdk, plugin, options) {
2063
2526
  }
2064
2527
  return;
2065
2528
  }
2529
+ if (plugin.pluginType === "method-override") {
2530
+ applyMethodOverride(
2531
+ getContext(sdk),
2532
+ plugin
2533
+ );
2534
+ return;
2535
+ }
2066
2536
  addModelPlugin(
2067
2537
  sdk,
2068
2538
  plugin,
@@ -2145,20 +2615,14 @@ function objectShape(schema) {
2145
2615
  }
2146
2616
  function topoOrder2(specs) {
2147
2617
  const byName = new Map(specs.map((s) => [s.name, s]));
2148
- const ordered = [];
2149
2618
  const placed = /* @__PURE__ */ new Set();
2150
- let progressed = true;
2151
- while (ordered.length < specs.length && progressed) {
2152
- progressed = false;
2153
- for (const spec of specs) {
2154
- if (placed.has(spec.name)) continue;
2155
- const ready = spec.requires.every((r) => !byName.has(r) || placed.has(r));
2156
- if (ready) {
2157
- ordered.push(spec);
2158
- placed.add(spec.name);
2159
- progressed = true;
2160
- }
2161
- }
2619
+ const ordered = [];
2620
+ const isReady = (spec) => spec.requires.every((r) => !byName.has(r) || placed.has(r));
2621
+ for (; ; ) {
2622
+ const next = specs.find((s) => !placed.has(s.name) && isReady(s));
2623
+ if (!next) break;
2624
+ ordered.push(next);
2625
+ placed.add(next.name);
2162
2626
  }
2163
2627
  for (const spec of specs) if (!placed.has(spec.name)) ordered.push(spec);
2164
2628
  return ordered;
@@ -2166,7 +2630,12 @@ function topoOrder2(specs) {
2166
2630
  function planParameters(entry) {
2167
2631
  const shape = objectShape(entry.inputSchema);
2168
2632
  const resolvers = entry.boundResolvers ?? {};
2169
- const names = shape ? Object.keys(shape) : Object.keys(resolvers);
2633
+ const names = shape ? [
2634
+ ...Object.keys(shape),
2635
+ ...Object.keys(resolvers).filter(
2636
+ (name) => !(name in shape) && resolvers[name].type === "constant"
2637
+ )
2638
+ ] : Object.keys(resolvers);
2170
2639
  const specs = names.map((name) => {
2171
2640
  const field = shape?.[name];
2172
2641
  const { inner, required } = field ? unwrap(field) : { inner: void 0, required: false };
@@ -2180,7 +2649,13 @@ function planParameters(entry) {
2180
2649
  requires: resolver?.requireParameters ?? []
2181
2650
  };
2182
2651
  });
2183
- return { parameters: topoOrder2(specs) };
2652
+ const declared = shape ? specs.filter((s) => s.name in shape) : specs;
2653
+ const scan = [
2654
+ ...shape ? specs.filter((s) => !(s.name in shape)) : [],
2655
+ ...declared.filter((s) => s.required),
2656
+ ...declared.filter((s) => !s.required)
2657
+ ];
2658
+ return { parameters: topoOrder2(scan) };
2184
2659
  }
2185
2660
 
2186
2661
  // src/model/resolution/engine.ts
@@ -2205,10 +2680,12 @@ var key = (path) => path.join(".");
2205
2680
  function isSettled(state, path) {
2206
2681
  return state.settled.includes(key(path));
2207
2682
  }
2208
- function settle(state, path) {
2209
- const k = key(path);
2683
+ function remember(state, k) {
2210
2684
  if (!state.settled.includes(k)) state.settled.push(k);
2211
2685
  }
2686
+ function settle(state, path) {
2687
+ remember(state, key(path));
2688
+ }
2212
2689
  function clone(state) {
2213
2690
  return JSON.parse(JSON.stringify(state));
2214
2691
  }
@@ -2225,8 +2702,9 @@ function coerce(leaf, raw) {
2225
2702
  return raw;
2226
2703
  }
2227
2704
  async function validationError(leaf, value, state) {
2705
+ if (leaf.resolver?.type !== "dynamic") return null;
2228
2706
  const context = await resolveContext(leaf, state.resolved);
2229
- const config = leaf.resolver?.prompt?.({
2707
+ const config = leaf.resolver.prompt?.({
2230
2708
  items: state.listing?.items ?? [],
2231
2709
  input: mergeInput(state.resolved, leaf.extraInput),
2232
2710
  context
@@ -2236,11 +2714,6 @@ async function validationError(leaf, value, state) {
2236
2714
  if (verdict === true) return null;
2237
2715
  return typeof verdict === "string" ? verdict : `${leaf.name}: invalid value`;
2238
2716
  }
2239
- function toChoice(c) {
2240
- const label = "label" in c ? c.label : c.name;
2241
- const hint = Array.isArray(c.hint) ? c.hint.join(", ") : c.hint;
2242
- return { label, value: String(c.value), hint };
2243
- }
2244
2717
  function isRef(r) {
2245
2718
  return typeof r === "object" && r !== null && "ref" in r;
2246
2719
  }
@@ -2256,6 +2729,7 @@ function toLeaf(name, field, definitions) {
2256
2729
  return {
2257
2730
  name,
2258
2731
  required: field.required ?? false,
2732
+ label: field.label,
2259
2733
  resolver,
2260
2734
  extraInput,
2261
2735
  // Carry the field's value type so nested answers coerce (number/boolean)
@@ -2303,6 +2777,9 @@ function arrayItem(resolver) {
2303
2777
  requires: []
2304
2778
  };
2305
2779
  }
2780
+ function autoSettles(resolver) {
2781
+ return resolver.type === "constant" || resolver.type === "info";
2782
+ }
2306
2783
  function mergeInput(input, extra) {
2307
2784
  return extra ? { ...input, ...extra } : input;
2308
2785
  }
@@ -2346,25 +2823,6 @@ async function arrayInfoAt(ctx, path, resolved) {
2346
2823
  item: { ...arrayItem(resolver), name: String(path[path.length - 1]) }
2347
2824
  };
2348
2825
  }
2349
- var AFFORDANCE = {
2350
- choose: { action: "choose", description: "Pick one of the listed options" },
2351
- custom: {
2352
- action: "custom",
2353
- description: "Provide a value directly",
2354
- supply: "value"
2355
- },
2356
- search: {
2357
- action: "search",
2358
- description: "Filter the options by a search term",
2359
- supply: "term"
2360
- },
2361
- more: { action: "more", description: "Load more options" },
2362
- skip: { action: "skip", description: "Omit this optional parameter" },
2363
- add: { action: "add", description: "Add another item" },
2364
- done: { action: "done", description: "Finish the list" },
2365
- retry: { action: "retry", description: "Retry loading the options" },
2366
- cancel: { action: "cancel", description: "Cancel resolution" }
2367
- };
2368
2826
  async function firstPage(result) {
2369
2827
  const page = await result;
2370
2828
  return {
@@ -2373,18 +2831,19 @@ async function firstPage(result) {
2373
2831
  };
2374
2832
  }
2375
2833
  async function resolveContext(leaf, input) {
2376
- return leaf.resolver?.getContext?.({
2834
+ if (leaf.resolver?.type !== "dynamic") return void 0;
2835
+ return leaf.resolver.getContext?.({
2377
2836
  input: mergeInput(input, leaf.extraInput)
2378
2837
  });
2379
2838
  }
2380
2839
  async function fetchListing(leaf, input, opts = {}) {
2381
2840
  const page = await firstPage(
2382
- leaf.resolver?.listItems?.({
2841
+ leaf.resolver?.type === "dynamic" ? leaf.resolver.listItems({
2383
2842
  input: mergeInput(input, leaf.extraInput),
2384
2843
  context: opts.context,
2385
2844
  search: opts.search,
2386
2845
  cursor: opts.cursor
2387
- })
2846
+ }) : void 0
2388
2847
  );
2389
2848
  return {
2390
2849
  items: [...opts.priorItems ?? [], ...page.data],
@@ -2393,15 +2852,52 @@ async function fetchListing(leaf, input, opts = {}) {
2393
2852
  exhausted: page.nextCursor == null
2394
2853
  };
2395
2854
  }
2855
+
2856
+ // src/model/resolution/questions.ts
2857
+ function toChoice(c) {
2858
+ const label = "label" in c ? c.label : c.name;
2859
+ const hint = Array.isArray(c.hint) ? c.hint.join(", ") : c.hint;
2860
+ return { label, value: String(c.value), hint };
2861
+ }
2862
+ var AFFORDANCE = {
2863
+ choose: { action: "choose", description: "Pick one of the listed options" },
2864
+ custom: {
2865
+ action: "custom",
2866
+ description: "Provide a value directly",
2867
+ supply: "value"
2868
+ },
2869
+ search: {
2870
+ action: "search",
2871
+ description: "Filter the options by a search term",
2872
+ supply: "term"
2873
+ },
2874
+ more: { action: "more", description: "Load more options" },
2875
+ skip: { action: "skip", description: "Omit this optional parameter" },
2876
+ add: { action: "add", description: "Add another item" },
2877
+ done: { action: "done", description: "Finish the list" },
2878
+ retry: { action: "retry", description: "Retry loading the options" },
2879
+ cancel: { action: "cancel", description: "Cancel resolution" }
2880
+ };
2396
2881
  function selectActions(leaf, listing, multiple) {
2882
+ const searchMode = leaf.resolver?.type === "dynamic" && leaf.resolver.inputType === "search";
2883
+ if (searchMode && listing.search === void 0 && listing.items.length === 0) {
2884
+ const actions2 = multiple ? [AFFORDANCE.search] : [AFFORDANCE.search, AFFORDANCE.custom];
2885
+ if (!leaf.required) actions2.push(AFFORDANCE.skip);
2886
+ return actions2;
2887
+ }
2397
2888
  const actions = multiple ? [AFFORDANCE.choose] : [AFFORDANCE.choose, AFFORDANCE.custom];
2398
- if (leaf.resolver?.inputType === "search") actions.push(AFFORDANCE.search);
2889
+ if (searchMode) actions.push(AFFORDANCE.search);
2399
2890
  if (listing.cursor) actions.push(AFFORDANCE.more);
2400
2891
  if (!leaf.required) actions.push(AFFORDANCE.skip);
2401
2892
  return actions;
2402
2893
  }
2894
+ function labeledMessage(leaf) {
2895
+ if (!leaf.label) return void 0;
2896
+ return `${leaf.label} (${leaf.required ? "required" : "optional"}):`;
2897
+ }
2403
2898
  function selectQuestion(leaf, input, listing, context) {
2404
- const config = leaf.resolver?.prompt?.({
2899
+ const resolver = leaf.resolver?.type === "dynamic" ? leaf.resolver : void 0;
2900
+ const config = resolver?.prompt?.({
2405
2901
  items: listing.items,
2406
2902
  input: mergeInput(input, leaf.extraInput),
2407
2903
  context
@@ -2409,10 +2905,15 @@ function selectQuestion(leaf, input, listing, context) {
2409
2905
  const multiple = config?.type === "checkbox";
2410
2906
  return {
2411
2907
  type: "select",
2412
- message: config?.message ?? `Select ${leaf.name}:`,
2908
+ // A labeled field's title beats the resolver's message: per-field
2909
+ // resolvers are shared across fields (one choices-fetcher for every
2910
+ // field), so only the leaf knows which field is being asked.
2911
+ message: labeledMessage(leaf) ?? config?.message ?? `Select ${leaf.name}:`,
2413
2912
  choices: (config?.choices ?? []).map(toChoice),
2414
2913
  ...multiple ? { multiple: true } : {},
2415
2914
  ...config?.notes?.length ? { notes: config.notes } : {},
2915
+ ...listing.search !== void 0 ? { search: listing.search } : {},
2916
+ ...resolver?.placeholder ? { placeholder: resolver.placeholder } : {},
2416
2917
  actions: selectActions(leaf, listing, multiple)
2417
2918
  };
2418
2919
  }
@@ -2445,8 +2946,20 @@ function failedResult(state, name, error) {
2445
2946
  async function buildQuestion(leaf, input) {
2446
2947
  const optional = !leaf.required;
2447
2948
  const resolver = leaf.resolver;
2448
- if (resolver?.listItems) {
2949
+ if (resolver?.type === "dynamic") {
2449
2950
  const context = await resolveContext(leaf, input);
2951
+ if (resolver.inputType === "search") {
2952
+ const listing2 = {
2953
+ items: [],
2954
+ cursor: void 0,
2955
+ search: void 0,
2956
+ exhausted: true
2957
+ };
2958
+ return {
2959
+ question: selectQuestion(leaf, input, listing2, context),
2960
+ listing: listing2
2961
+ };
2962
+ }
2450
2963
  const listing = await fetchListing(leaf, input, { context });
2451
2964
  return {
2452
2965
  question: selectQuestion(leaf, input, listing, context),
@@ -2465,37 +2978,29 @@ async function buildQuestion(leaf, input) {
2465
2978
  }
2466
2979
  };
2467
2980
  }
2468
- const inputType = resolver?.inputType && resolver.inputType !== "search" ? resolver.inputType : "text";
2981
+ const textSource = resolver?.type === "static" ? resolver : void 0;
2982
+ const inputType = textSource?.inputType && textSource.inputType !== "search" ? textSource.inputType : "text";
2469
2983
  const actions = [AFFORDANCE.custom];
2470
2984
  if (optional) actions.push(AFFORDANCE.skip);
2471
2985
  return {
2472
2986
  question: {
2473
2987
  type: "input",
2474
- message: `Enter ${leaf.name}:`,
2988
+ // The optional marker makes Enter-to-pass discoverable on a bare
2989
+ // parameter; a labeled field carries its marker via labeledMessage.
2990
+ message: labeledMessage(leaf) ?? `Enter ${leaf.name}${optional ? " (optional)" : ""}:`,
2475
2991
  inputType,
2476
- placeholder: resolver?.placeholder,
2992
+ placeholder: textSource?.placeholder,
2477
2993
  actions
2478
2994
  }
2479
2995
  };
2480
2996
  }
2481
- function finalize(ctx, resolved) {
2482
- if (!ctx.schema) return { status: "done", value: resolved };
2483
- const parsed = ctx.schema.safeParse(resolved);
2484
- if (parsed.success) {
2485
- return { status: "done", value: parsed.data };
2486
- }
2487
- const issues = parsed.error.issues.map((i) => ({
2488
- parameter: i.path.map(String).join(".") || void 0,
2489
- message: i.message
2490
- }));
2491
- return { status: "invalid", issues };
2492
- }
2493
2997
  function collectionQuestion(t) {
2494
2998
  const actions = [AFFORDANCE.add];
2495
2999
  if (t.count >= t.min) actions.push(AFFORDANCE.done);
2496
3000
  return {
2497
3001
  type: "collection",
2498
- message: `Add another ${t.path[t.path.length - 1]}? (${t.count} so far)`,
3002
+ message: `Add ${t.path[t.path.length - 1]}[${t.count}]?`,
3003
+ container: "array",
2499
3004
  count: t.count,
2500
3005
  min: t.min,
2501
3006
  // An unbounded array's max is Infinity, which JSON.stringify turns to null;
@@ -2504,6 +3009,56 @@ function collectionQuestion(t) {
2504
3009
  actions
2505
3010
  };
2506
3011
  }
3012
+ function objectGateQuestion(path) {
3013
+ return {
3014
+ type: "collection",
3015
+ message: `Add ${path[path.length - 1]}?`,
3016
+ container: "object",
3017
+ actions: [
3018
+ {
3019
+ action: "add",
3020
+ description: "Provide values for these fields"
3021
+ },
3022
+ { action: "done", description: "Skip these fields" }
3023
+ ]
3024
+ };
3025
+ }
3026
+ function optionalsGateQuestion(pending) {
3027
+ return {
3028
+ type: "collection",
3029
+ // The prompt and its context ride separately so a host renders the info
3030
+ // line above the confirm without composing any text of its own.
3031
+ message: "Would you like to configure optional fields?",
3032
+ description: `There are ${pending.length} optional field(s) available.`,
3033
+ container: "object",
3034
+ // The gated fields' projection, so a smart host can show WHAT `add` would
3035
+ // walk (or render a form section) instead of a blind yes/no.
3036
+ fields: pending.map((leaf) => ({
3037
+ key: leaf.name,
3038
+ ...leaf.label ? { label: leaf.label } : {},
3039
+ ...leaf.valueType ? { valueType: leaf.valueType } : {}
3040
+ })),
3041
+ actions: [
3042
+ { action: "add", description: "Configure the optional fields" },
3043
+ { action: "done", description: "Skip the optional fields" }
3044
+ ]
3045
+ };
3046
+ }
3047
+
3048
+ // src/model/resolution/walk.ts
3049
+ function finalize(ctx, resolved) {
3050
+ if (!ctx.schema) return { status: "done", value: resolved };
3051
+ const parsed = ctx.schema.safeParse(resolved);
3052
+ if (parsed.success) {
3053
+ return { status: "done", value: parsed.data };
3054
+ }
3055
+ const issues = parsed.error.issues.map((i) => ({
3056
+ parameter: i.path.map(String).join(".") || void 0,
3057
+ message: i.message
3058
+ }));
3059
+ return { status: "invalid", issues };
3060
+ }
3061
+ var optionalsMarker = (path) => `${key(path)}?optionals`;
2507
3062
  async function findInArray(ctx, state, path) {
2508
3063
  if (isSettled(state, path)) return null;
2509
3064
  if (getAtPath(state.resolved, path) == null)
@@ -2517,8 +3072,11 @@ async function findInArray(ctx, state, path) {
2517
3072
  const len = items.length;
2518
3073
  const itemType = item.resolver?.type;
2519
3074
  if (len > 0 && (itemType === "object" || itemType === "array")) {
2520
- const inner = await findNext(ctx, state, [...path, len - 1]);
2521
- if (inner) return inner;
3075
+ const itemPath = [...path, len - 1];
3076
+ if (!isSettled(state, itemPath)) {
3077
+ const inner = await findNext(ctx, state, itemPath);
3078
+ if (inner) return inner;
3079
+ }
2522
3080
  }
2523
3081
  if (len < min) return descendItem(ctx, state, path, len, item);
2524
3082
  if (len < max) return { kind: "array", path, count: len, min, max };
@@ -2546,14 +3104,60 @@ async function descendItem(ctx, state, arrayPath, index, item) {
2546
3104
  }
2547
3105
  async function findNext(ctx, state, path = []) {
2548
3106
  const container = getAtPath(state.resolved, path) ?? {};
2549
- for (const leaf of await childrenAt(ctx, path, state.resolved)) {
3107
+ const children = await childrenAt(ctx, path, state.resolved);
3108
+ const inObject = path.length > 0;
3109
+ const ordered = inObject ? [
3110
+ ...children.filter((c) => c.required),
3111
+ ...children.filter((c) => !c.required)
3112
+ ] : children;
3113
+ const asksUser = (c) => c.resolver ? !autoSettles(c.resolver) : c.required;
3114
+ const isPendingChild = (c) => container[c.name] === void 0 && !isSettled(state, [...path, c.name]);
3115
+ const hasAskableRequired = children.some((c) => c.required && asksUser(c));
3116
+ for (const leaf of ordered) {
2550
3117
  const childPath = [...path, leaf.name];
2551
- if (!leaf.requires.every((r) => container[r] !== void 0)) continue;
3118
+ if (!leaf.requires.every(
3119
+ (r) => container[r] !== void 0 || getAtPath(state.resolved, [r]) !== void 0 || isSettled(state, [...path, r]) || isSettled(state, [r])
3120
+ )) {
3121
+ continue;
3122
+ }
3123
+ const inArrayItem = path.some((segment) => typeof segment === "number");
3124
+ if (inObject && !inArrayItem && state.interactive && hasAskableRequired && !leaf.required && asksUser(leaf) && isPendingChild(leaf) && !state.settled.includes(optionalsMarker(path)) && !children.some((c) => c.required && asksUser(c) && isPendingChild(c))) {
3125
+ const pending = ordered.filter(
3126
+ (c) => !c.required && asksUser(c) && isPendingChild(c)
3127
+ );
3128
+ return { kind: "optionals", path, pending };
3129
+ }
2552
3130
  if (leaf.resolver?.type === "object") {
2553
- if (getAtPath(state.resolved, childPath) == null)
3131
+ if (isSettled(state, childPath)) continue;
3132
+ if (getAtPath(state.resolved, childPath) == null) {
3133
+ if (!leaf.required && !inArrayItem) {
3134
+ if (!state.interactive) {
3135
+ settle(state, childPath);
3136
+ continue;
3137
+ }
3138
+ return { kind: "object", path: childPath, leaf };
3139
+ }
2554
3140
  setAtPath(state.resolved, childPath, {});
3141
+ }
2555
3142
  const inner = await findNext(ctx, state, childPath);
2556
3143
  if (inner) return inner;
3144
+ if (!leaf.required) {
3145
+ const value = getAtPath(state.resolved, childPath);
3146
+ if (value !== null && typeof value === "object" && Object.keys(value).length === 0) {
3147
+ const grandchildren = await childrenAt(
3148
+ ctx,
3149
+ childPath,
3150
+ state.resolved
3151
+ );
3152
+ const pending = grandchildren.some(
3153
+ (c) => value[c.name] === void 0 && !isSettled(state, [...childPath, c.name])
3154
+ );
3155
+ if (!pending) {
3156
+ delete container[leaf.name];
3157
+ settle(state, childPath);
3158
+ }
3159
+ }
3160
+ }
2557
3161
  continue;
2558
3162
  }
2559
3163
  if (leaf.resolver?.type === "array") {
@@ -2569,6 +3173,7 @@ async function findNext(ctx, state, path = []) {
2569
3173
  }
2570
3174
  async function askLeaf(state, path, leaf, opts = {}) {
2571
3175
  state.current = path;
3176
+ delete state.gate;
2572
3177
  try {
2573
3178
  const { question, listing } = await buildQuestion(leaf, state.resolved);
2574
3179
  state.listing = listing;
@@ -2590,21 +3195,52 @@ async function advance(ctx, state) {
2590
3195
  const target = await findNext(ctx, state);
2591
3196
  if (!target) {
2592
3197
  delete state.current;
3198
+ delete state.gate;
2593
3199
  delete state.listing;
2594
3200
  return { state, result: finalize(ctx, state.resolved) };
2595
3201
  }
2596
3202
  if (target.kind === "array") {
2597
3203
  state.current = target.path;
3204
+ state.gate = "array";
2598
3205
  delete state.listing;
2599
3206
  return {
2600
3207
  state,
2601
3208
  result: { status: "ask", question: collectionQuestion(target) }
2602
3209
  };
2603
3210
  }
3211
+ if (target.kind === "object") {
3212
+ state.current = target.path;
3213
+ state.gate = "entry";
3214
+ delete state.listing;
3215
+ return {
3216
+ state,
3217
+ result: { status: "ask", question: objectGateQuestion(target.path) }
3218
+ };
3219
+ }
3220
+ if (target.kind === "optionals") {
3221
+ state.current = target.path;
3222
+ state.gate = "optionals";
3223
+ delete state.listing;
3224
+ return {
3225
+ state,
3226
+ result: {
3227
+ status: "ask",
3228
+ question: optionalsGateQuestion(target.pending)
3229
+ }
3230
+ };
3231
+ }
2604
3232
  const { path, leaf } = target;
2605
- const auto = await leaf.resolver?.tryResolveWithoutPrompt?.({
3233
+ const resolver = leaf.resolver;
3234
+ if (resolver && autoSettles(resolver)) {
3235
+ if (resolver.type === "constant") {
3236
+ setAtPath(state.resolved, path, resolver.value);
3237
+ }
3238
+ settle(state, path);
3239
+ continue;
3240
+ }
3241
+ const auto = resolver?.type === "dynamic" ? await resolver.tryResolveWithoutPrompt?.({
2606
3242
  input: mergeInput(state.resolved, leaf.extraInput)
2607
- });
3243
+ }) : void 0;
2608
3244
  if (auto) {
2609
3245
  if (auto.resolvedValue !== void 0)
2610
3246
  setAtPath(state.resolved, path, auto.resolvedValue);
@@ -2624,23 +3260,22 @@ async function advance(ctx, state) {
2624
3260
  }
2625
3261
  }
2626
3262
  async function start(ctx, input = {}, interactive = true) {
2627
- const resolved = {};
2628
- for (const spec of ctx.parameters) {
2629
- if (spec.resolver?.type === "constant")
2630
- resolved[spec.name] = spec.resolver.value;
2631
- }
2632
- Object.assign(resolved, input);
2633
- return advance(ctx, {
3263
+ const state = {
2634
3264
  method: ctx.method,
2635
- resolved,
3265
+ resolved: { ...input },
2636
3266
  settled: [],
2637
3267
  interactive
2638
- });
3268
+ };
3269
+ for (const [name, value] of Object.entries(input)) {
3270
+ if (value !== void 0) settle(state, [name]);
3271
+ }
3272
+ return advance(ctx, state);
2639
3273
  }
2640
3274
  async function step(ctx, prior, action) {
2641
3275
  const state = clone(prior);
2642
3276
  if (action.type === "cancel") {
2643
3277
  delete state.current;
3278
+ delete state.gate;
2644
3279
  delete state.listing;
2645
3280
  return { state, result: { status: "cancelled" } };
2646
3281
  }
@@ -2651,12 +3286,27 @@ async function step(ctx, prior, action) {
2651
3286
  return refine(ctx, state, leaf, path, action);
2652
3287
  }
2653
3288
  if (action.type === "add" || action.type === "done") {
3289
+ const gate = state.gate;
3290
+ if (!gate) {
3291
+ throw new Error(
3292
+ `action "${action.type}" is not supported here: no container decision is outstanding`
3293
+ );
3294
+ }
2654
3295
  delete state.current;
3296
+ delete state.gate;
2655
3297
  delete state.listing;
2656
3298
  if (action.type === "done") {
2657
3299
  settle(state, path);
2658
3300
  return advance(ctx, state);
2659
3301
  }
3302
+ if (gate === "entry") {
3303
+ setAtPath(state.resolved, path, {});
3304
+ return advance(ctx, state);
3305
+ }
3306
+ if (gate === "optionals") {
3307
+ remember(state, optionalsMarker(path));
3308
+ return advance(ctx, state);
3309
+ }
2660
3310
  const items = getAtPath(state.resolved, path) ?? [];
2661
3311
  const { item } = await arrayInfoAt(ctx, path, state.resolved);
2662
3312
  const itemPath = [...path, items.length];
@@ -2664,12 +3314,35 @@ async function step(ctx, prior, action) {
2664
3314
  return askLeaf(state, itemPath, item);
2665
3315
  return advance(ctx, state);
2666
3316
  }
3317
+ if (state.gate) {
3318
+ throw new Error(
3319
+ `action "${action.type}" is not supported here: a container decision (${state.gate}) is outstanding`
3320
+ );
3321
+ }
2667
3322
  switch (action.type) {
2668
3323
  case "choose":
2669
3324
  case "custom": {
2670
3325
  if (leaf) {
2671
3326
  const error = await validationError(leaf, action.value, state);
2672
- if (error) return askLeaf(state, path, leaf, { error });
3327
+ if (error) {
3328
+ if (state.listing && leaf.resolver?.type === "dynamic") {
3329
+ const context = await resolveContext(leaf, state.resolved);
3330
+ return {
3331
+ state,
3332
+ result: {
3333
+ status: "ask",
3334
+ question: selectQuestion(
3335
+ leaf,
3336
+ state.resolved,
3337
+ state.listing,
3338
+ context
3339
+ ),
3340
+ error
3341
+ }
3342
+ };
3343
+ }
3344
+ return askLeaf(state, path, leaf, { error });
3345
+ }
2673
3346
  }
2674
3347
  setAtPath(
2675
3348
  state.resolved,
@@ -2696,10 +3369,10 @@ async function refine(ctx, state, leaf, path, action) {
2696
3369
  };
2697
3370
  try {
2698
3371
  if (action.type === "search") {
2699
- const exact = await leaf.resolver?.tryResolveFromSearch?.({
3372
+ const exact = leaf.resolver?.type === "dynamic" ? await leaf.resolver.tryResolveFromSearch?.({
2700
3373
  input: mergeInput(state.resolved, leaf.extraInput),
2701
3374
  search: action.term
2702
- });
3375
+ }) : void 0;
2703
3376
  if (exact) {
2704
3377
  setAtPath(state.resolved, path, coerce(leaf, exact.resolvedValue));
2705
3378
  delete state.current;
@@ -2750,10 +3423,11 @@ function projectMethod(entry) {
2750
3423
  const inputProperties = toJsonSchema(entry.inputSchema)?.properties;
2751
3424
  const parameters = {};
2752
3425
  for (const spec of planParameters(entry).parameters) {
3426
+ const dynamic = spec.resolver?.type === "dynamic" ? spec.resolver : void 0;
2753
3427
  parameters[spec.name] = {
2754
3428
  required: spec.required,
2755
- dynamic: Boolean(spec.resolver?.listItems),
2756
- ...spec.resolver?.inputType === "search" ? { searchable: true } : {},
3429
+ dynamic: Boolean(dynamic),
3430
+ ...dynamic?.inputType === "search" ? { searchable: true } : {},
2757
3431
  ...inputProperties?.[spec.name] ? { schema: inputProperties[spec.name] } : {},
2758
3432
  ...spec.staticChoices ? { choices: spec.staticChoices } : {},
2759
3433
  ...spec.requires.length ? { requireParameters: spec.requires } : {}
@@ -2820,12 +3494,13 @@ function createController(sdk) {
2820
3494
  const spec = contextFor(method).parameters.find(
2821
3495
  (p) => p.name === parameter
2822
3496
  );
2823
- if (!spec?.resolver?.listItems) return { data: [] };
2824
- const context = await spec.resolver.getContext?.({ input });
3497
+ const dynamic = spec?.resolver?.type === "dynamic" ? spec.resolver : void 0;
3498
+ if (!dynamic) return { data: [] };
3499
+ const context = await dynamic.getContext?.({ input });
2825
3500
  const page = await firstPage(
2826
- spec.resolver.listItems({ input, context, search, cursor })
3501
+ dynamic.listItems({ input, context, search, cursor })
2827
3502
  );
2828
- const config = spec.resolver.prompt?.({ items: page.data, input, context });
3503
+ const config = dynamic.prompt?.({ items: page.data, input, context });
2829
3504
  const data = (config?.choices ?? []).map(toChoice);
2830
3505
  return { data, nextCursor: page.nextCursor };
2831
3506
  };
@@ -2834,6 +3509,9 @@ function createController(sdk) {
2834
3509
 
2835
3510
  // src/utils/core-plugin.ts
2836
3511
  function createCorePlugin(options) {
3512
+ logDeprecation(
3513
+ "createCorePlugin() is deprecated. Inject the options under CORE_OPTIONS_ID via createSdk's configuration instead."
3514
+ );
2837
3515
  return () => ({
2838
3516
  context: {
2839
3517
  core: options
@@ -2896,14 +3574,17 @@ function openEnum(values, description) {
2896
3574
  export {
2897
3575
  CONTEXT,
2898
3576
  CORE_ERROR_SYMBOL,
3577
+ CORE_OPTIONS_ID,
2899
3578
  CORE_SIGNAL_SYMBOL,
2900
3579
  CoreCancelledSignal,
3580
+ CoreDisposeError,
2901
3581
  CoreError,
2902
3582
  CoreErrorCode,
2903
3583
  CoreSignal,
2904
3584
  addPlugin,
2905
3585
  composePlugins,
2906
3586
  concatPaginated,
3587
+ coreOptionsPluginRef,
2907
3588
  createAsyncContext,
2908
3589
  createController,
2909
3590
  createCoreError,
@@ -2919,15 +3600,20 @@ export {
2919
3600
  createValidator,
2920
3601
  dangerousContextPlugin,
2921
3602
  declareMethod,
3603
+ declareOptionalProperty,
2922
3604
  declarePlugin,
2923
3605
  declareProperty,
2924
3606
  decodeIncomingCursor,
3607
+ defaultLogDeprecation,
2925
3608
  defineFormatter,
3609
+ defineHook,
2926
3610
  defineLegacyMerge,
2927
3611
  defineMethod,
3612
+ defineMethodOverride,
2928
3613
  definePlugin,
2929
3614
  defineProperty,
2930
3615
  defineResolver,
3616
+ disposeSdk,
2931
3617
  fromFunctionPlugin,
2932
3618
  getContext,
2933
3619
  getCoreErrorCause,
@@ -2943,10 +3629,12 @@ export {
2943
3629
  isNestedMethodCall,
2944
3630
  isPositional,
2945
3631
  isTelemetryNested,
3632
+ omitExports,
2946
3633
  openEnum,
2947
3634
  paginate,
2948
3635
  paginateBuffered,
2949
3636
  paginateMaxItems,
3637
+ resolvePlugin,
2950
3638
  runInMethodScope,
2951
3639
  runWithTelemetryContext,
2952
3640
  selectExports,