@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.cjs CHANGED
@@ -22,14 +22,17 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  CONTEXT: () => CONTEXT,
24
24
  CORE_ERROR_SYMBOL: () => CORE_ERROR_SYMBOL,
25
+ CORE_OPTIONS_ID: () => CORE_OPTIONS_ID,
25
26
  CORE_SIGNAL_SYMBOL: () => CORE_SIGNAL_SYMBOL,
26
27
  CoreCancelledSignal: () => CoreCancelledSignal,
28
+ CoreDisposeError: () => CoreDisposeError,
27
29
  CoreError: () => CoreError,
28
30
  CoreErrorCode: () => CoreErrorCode,
29
31
  CoreSignal: () => CoreSignal,
30
32
  addPlugin: () => addPlugin,
31
33
  composePlugins: () => composePlugins,
32
34
  concatPaginated: () => concatPaginated,
35
+ coreOptionsPluginRef: () => coreOptionsPluginRef,
33
36
  createAsyncContext: () => createAsyncContext,
34
37
  createController: () => createController,
35
38
  createCoreError: () => createCoreError,
@@ -45,15 +48,20 @@ __export(index_exports, {
45
48
  createValidator: () => createValidator,
46
49
  dangerousContextPlugin: () => dangerousContextPlugin,
47
50
  declareMethod: () => declareMethod,
51
+ declareOptionalProperty: () => declareOptionalProperty,
48
52
  declarePlugin: () => declarePlugin,
49
53
  declareProperty: () => declareProperty,
50
54
  decodeIncomingCursor: () => decodeIncomingCursor,
55
+ defaultLogDeprecation: () => defaultLogDeprecation,
51
56
  defineFormatter: () => defineFormatter,
57
+ defineHook: () => defineHook,
52
58
  defineLegacyMerge: () => defineLegacyMerge,
53
59
  defineMethod: () => defineMethod,
60
+ defineMethodOverride: () => defineMethodOverride,
54
61
  definePlugin: () => definePlugin,
55
62
  defineProperty: () => defineProperty,
56
63
  defineResolver: () => defineResolver,
64
+ disposeSdk: () => disposeSdk,
57
65
  fromFunctionPlugin: () => fromFunctionPlugin,
58
66
  getContext: () => getContext,
59
67
  getCoreErrorCause: () => getCoreErrorCause,
@@ -69,10 +77,12 @@ __export(index_exports, {
69
77
  isNestedMethodCall: () => isNestedMethodCall,
70
78
  isPositional: () => isPositional,
71
79
  isTelemetryNested: () => isTelemetryNested,
80
+ omitExports: () => omitExports,
72
81
  openEnum: () => openEnum,
73
82
  paginate: () => paginate,
74
83
  paginateBuffered: () => paginateBuffered,
75
84
  paginateMaxItems: () => paginateMaxItems,
85
+ resolvePlugin: () => resolvePlugin,
76
86
  runInMethodScope: () => runInMethodScope,
77
87
  runWithTelemetryContext: () => runWithTelemetryContext,
78
88
  selectExports: () => selectExports,
@@ -640,6 +650,19 @@ function isNestedMethodCall() {
640
650
  const store = scope.get();
641
651
  return store !== void 0 && store.depth > 0;
642
652
  }
653
+ var observerReentrancy = 0;
654
+ function runIsolatedObserver(fn) {
655
+ observerReentrancy++;
656
+ try {
657
+ fn();
658
+ } catch {
659
+ } finally {
660
+ observerReentrancy--;
661
+ }
662
+ }
663
+ function isInsideObserver() {
664
+ return observerReentrancy > 0;
665
+ }
643
666
  function runInMethodScope(fn) {
644
667
  if (!scope.available) return fn();
645
668
  const currentDepth = scope.get()?.depth ?? -1;
@@ -648,7 +671,36 @@ function runInMethodScope(fn) {
648
671
  var runWithTelemetryContext = runInMethodScope;
649
672
  var isTelemetryNested = isNestedMethodCall;
650
673
 
674
+ // src/utils/core-options.ts
675
+ function defaultLogDeprecation({
676
+ methodName,
677
+ deprecation
678
+ }) {
679
+ logDeprecation(`${methodName}() is deprecated. ${deprecation.message}`);
680
+ }
681
+ var CORE_OPTIONS_ID = "kitcore/coreOptions";
682
+
651
683
  // src/utils/function-utils.ts
684
+ function resolveCoreOptions(context) {
685
+ const entry = context.plugins?.[CORE_OPTIONS_ID];
686
+ if (entry) {
687
+ return entry.getValue ? entry.getValue() : entry.value;
688
+ }
689
+ return context.core;
690
+ }
691
+ var INTERNAL_CALL = Symbol("kitcore.internalCall");
692
+ function signalDeprecation(context, methodName, getDeprecation) {
693
+ if (isInsideObserver()) return;
694
+ const deprecation = getDeprecation?.();
695
+ if (!deprecation?.message) return;
696
+ const warning = {
697
+ type: "deprecation",
698
+ methodName,
699
+ deprecation
700
+ };
701
+ const handler = resolveCoreOptions(context)?.logDeprecation ?? defaultLogDeprecation;
702
+ runIsolatedObserver(() => handler(warning));
703
+ }
652
704
  function normalizeError(error, adaptError) {
653
705
  if (error instanceof Error) return error;
654
706
  const message = typeof error === "object" && error !== null && "message" in error && typeof error.message === "string" ? error.message : String(error);
@@ -662,17 +714,20 @@ function normalizeError(error, adaptError) {
662
714
  );
663
715
  }
664
716
  function createFunction(coreFn, options) {
665
- const { sdk, schema, name } = options;
717
+ const { sdk, schema, name, getDeprecation } = options;
666
718
  const functionName = name || coreFn.name;
667
719
  const namedFunctions = {
668
720
  [functionName]: async function(callOptions) {
721
+ if (arguments[1] !== INTERNAL_CALL) {
722
+ signalDeprecation(sdk.context, functionName, getDeprecation);
723
+ }
669
724
  return runInMethodScope(async () => {
670
725
  const startTime = Date.now();
671
726
  const normalizedOptions = callOptions ?? {};
672
727
  const args = [normalizedOptions];
673
728
  const depth = getCurrentDepth();
674
- const hooks = sdk.context.hooks;
675
- const adaptError = sdk.context.core?.adaptError;
729
+ const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
730
+ const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
676
731
  hooks?.onMethodStart?.({
677
732
  methodName: functionName,
678
733
  args,
@@ -721,6 +776,62 @@ function createFunction(coreFn, options) {
721
776
  };
722
777
  return namedFunctions[functionName];
723
778
  }
779
+ function createRawFunction(coreFn, options) {
780
+ const { sdk, name, schema, positional, getDeprecation } = options;
781
+ return function(rawInput) {
782
+ if (arguments[1] !== INTERNAL_CALL) {
783
+ signalDeprecation(sdk.context, name, getDeprecation);
784
+ }
785
+ return runInMethodScope(() => {
786
+ const startTime = Date.now();
787
+ const depth = getCurrentDepth();
788
+ const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
789
+ const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
790
+ const input = schema ? rawInput ?? {} : rawInput;
791
+ const record = input;
792
+ const args = positional ? positional.filter((key2) => record?.[key2] !== void 0).map((key2) => record?.[key2]) : [input];
793
+ hooks?.onMethodStart?.({
794
+ methodName: name,
795
+ args,
796
+ isPaginated: false,
797
+ depth
798
+ });
799
+ const fireEnd = (error) => {
800
+ hooks?.onMethodEnd?.({
801
+ methodName: name,
802
+ args,
803
+ isPaginated: false,
804
+ depth,
805
+ durationMs: Date.now() - startTime,
806
+ ...error ? { error } : {}
807
+ });
808
+ };
809
+ try {
810
+ const parsed = schema ? validateOptions(schema, input, { adaptError }) : input;
811
+ const result = coreFn(parsed);
812
+ if (result !== null && typeof result === "object" && typeof result.then === "function") {
813
+ return result.then(
814
+ (value) => {
815
+ fireEnd();
816
+ return value;
817
+ },
818
+ (error) => {
819
+ fireEnd(
820
+ error instanceof Error ? error : new Error(String(error))
821
+ );
822
+ throw error;
823
+ }
824
+ );
825
+ }
826
+ fireEnd();
827
+ return result;
828
+ } catch (error) {
829
+ fireEnd(error instanceof Error ? error : new Error(String(error)));
830
+ throw error;
831
+ }
832
+ });
833
+ };
834
+ }
724
835
  function isSdkPage(value) {
725
836
  if (typeof value !== "object" || value === null) return false;
726
837
  const page = value;
@@ -749,7 +860,7 @@ function createPageFunction(coreFn, {
749
860
  } catch (error) {
750
861
  throw normalizeError(
751
862
  error,
752
- sdk.context.core?.adaptError
863
+ resolveCoreOptions(sdk.context)?.adaptError
753
864
  );
754
865
  }
755
866
  }
@@ -757,82 +868,98 @@ function createPageFunction(coreFn, {
757
868
  return namedFunctions[functionName];
758
869
  }
759
870
  function createPaginatedFunction(coreFn, options) {
760
- const { sdk, schema, name, defaultPageSize, adaptPage } = options;
871
+ const { sdk, schema, name, defaultPageSize, adaptPage, getDeprecation } = options;
761
872
  const pageFunction = createPageFunction(coreFn, { sdk, adaptPage });
762
873
  const functionName = name || coreFn.name;
763
874
  const namedFunctions = {
764
875
  [functionName]: function(callOptions) {
876
+ if (arguments[1] !== INTERNAL_CALL) {
877
+ signalDeprecation(sdk.context, functionName, getDeprecation);
878
+ }
765
879
  return runInMethodScope(() => {
766
880
  const startTime = Date.now();
767
881
  const normalizedOptions = callOptions ?? {};
768
882
  const args = [normalizedOptions];
769
883
  const depth = getCurrentDepth();
770
- const hooks = sdk.context.hooks;
771
- const adaptError = sdk.context.core?.adaptError;
884
+ const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
885
+ const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
772
886
  hooks?.onMethodStart?.({
773
887
  methodName: functionName,
774
888
  args,
775
889
  isPaginated: true,
776
890
  depth
777
891
  });
778
- const validatedOptions = {
779
- ...normalizedOptions,
780
- ...schema ? createValidator(schema, { adaptError })(normalizedOptions) : normalizedOptions
781
- };
782
- const pageSize = validatedOptions.pageSize ?? defaultPageSize;
783
- const optimizedOptions = {
784
- ...validatedOptions,
785
- pageSize
786
- };
787
- const iterator = paginate(pageFunction, optimizedOptions);
788
- const firstPagePromise = iterator.next().then((result) => {
789
- if (result.done) {
790
- throw new Error("Paginate should always iterate at least once");
791
- }
792
- return result.value;
793
- });
794
- if (hooks?.onMethodEnd) {
795
- firstPagePromise.then(() => {
796
- hooks.onMethodEnd({
797
- methodName: functionName,
798
- args,
799
- isPaginated: true,
800
- depth,
801
- durationMs: Date.now() - startTime
802
- });
803
- }).catch((error) => {
804
- hooks.onMethodEnd({
805
- methodName: functionName,
806
- args,
807
- isPaginated: true,
808
- depth,
809
- durationMs: Date.now() - startTime,
810
- error: error instanceof Error ? error : new Error(String(error))
811
- });
892
+ try {
893
+ const validatedOptions = {
894
+ ...normalizedOptions,
895
+ ...schema ? createValidator(schema, { adaptError })(normalizedOptions) : normalizedOptions
896
+ };
897
+ const pageSize = validatedOptions.pageSize ?? defaultPageSize;
898
+ const optimizedOptions = {
899
+ ...validatedOptions,
900
+ pageSize
901
+ };
902
+ const iterator = paginate(pageFunction, optimizedOptions);
903
+ const firstPagePromise = iterator.next().then((result) => {
904
+ if (result.done) {
905
+ throw new Error("Paginate should always iterate at least once");
906
+ }
907
+ return result.value;
812
908
  });
813
- }
814
- const pageStream = async function* () {
815
- yield await firstPagePromise;
816
- for await (const page of iterator) {
817
- yield page;
909
+ if (hooks?.onMethodEnd) {
910
+ firstPagePromise.then(() => {
911
+ hooks.onMethodEnd({
912
+ methodName: functionName,
913
+ args,
914
+ isPaginated: true,
915
+ depth,
916
+ durationMs: Date.now() - startTime
917
+ });
918
+ }).catch((error) => {
919
+ hooks.onMethodEnd({
920
+ methodName: functionName,
921
+ args,
922
+ isPaginated: true,
923
+ depth,
924
+ durationMs: Date.now() - startTime,
925
+ error: error instanceof Error ? error : new Error(String(error))
926
+ });
927
+ });
818
928
  }
819
- }();
820
- return Object.assign(firstPagePromise, {
821
- [Symbol.asyncIterator]() {
822
- return pageStream;
823
- },
824
- items: function() {
825
- return {
826
- [Symbol.asyncIterator]: async function* () {
827
- for await (const page of pageStream) {
828
- for (const item of page.data) {
829
- yield item;
929
+ const pageStream = async function* () {
930
+ yield await firstPagePromise;
931
+ for await (const page of iterator) {
932
+ yield page;
933
+ }
934
+ }();
935
+ return Object.assign(firstPagePromise, {
936
+ [Symbol.asyncIterator]() {
937
+ return pageStream;
938
+ },
939
+ items: function() {
940
+ return {
941
+ [Symbol.asyncIterator]: async function* () {
942
+ for await (const page of pageStream) {
943
+ for (const item of page.data) {
944
+ yield item;
945
+ }
830
946
  }
831
947
  }
832
- }
833
- };
834
- }
835
- });
948
+ };
949
+ }
950
+ });
951
+ } catch (error) {
952
+ const normalizedError = normalizeError(error, adaptError);
953
+ hooks?.onMethodEnd?.({
954
+ methodName: functionName,
955
+ args,
956
+ isPaginated: true,
957
+ depth,
958
+ durationMs: Date.now() - startTime,
959
+ error: normalizedError
960
+ });
961
+ throw normalizedError;
962
+ }
836
963
  });
837
964
  }
838
965
  };
@@ -841,6 +968,9 @@ function createPaginatedFunction(coreFn, options) {
841
968
 
842
969
  // src/utils/plugin-utils.ts
843
970
  function createPluginMethod(sdk, config) {
971
+ logDeprecation(
972
+ "createPluginMethod() is deprecated. Author methods with defineMethod instead."
973
+ );
844
974
  const { name, inputSchema, handler, ...metaFields } = config;
845
975
  const namedHandlers = {
846
976
  [name]: async function(options) {
@@ -864,6 +994,9 @@ function createPluginMethod(sdk, config) {
864
994
  };
865
995
  }
866
996
  function createPaginatedPluginMethod(sdk, config) {
997
+ logDeprecation(
998
+ 'createPaginatedPluginMethod() is deprecated. Author list methods with defineMethod output "list" instead.'
999
+ );
867
1000
  const {
868
1001
  name,
869
1002
  inputSchema,
@@ -1095,6 +1228,9 @@ function composePlugins(...plugins) {
1095
1228
  return collapseStackEntries(entries, "composePlugins");
1096
1229
  }
1097
1230
  function createPluginStack() {
1231
+ logDeprecation(
1232
+ "createPluginStack() is deprecated. Compose with definePlugin and build with createSdk instead."
1233
+ );
1098
1234
  return buildPluginStack(null, "createPluginStack");
1099
1235
  }
1100
1236
  function buildPluginStack(head, callerLabel) {
@@ -1179,7 +1315,7 @@ function normalizeImports(deps) {
1179
1315
  if (!deps) return { plugins: [], bindings: [] };
1180
1316
  const seen = /* @__PURE__ */ new Map();
1181
1317
  const bindings = [];
1182
- const add = (binding, id) => {
1318
+ const add = (binding, id, optional) => {
1183
1319
  const priorId = seen.get(binding);
1184
1320
  if (priorId !== void 0 && priorId !== id) {
1185
1321
  throw new Error(
@@ -1188,7 +1324,7 @@ function normalizeImports(deps) {
1188
1324
  }
1189
1325
  if (priorId === void 0) {
1190
1326
  seen.set(binding, id);
1191
- bindings.push({ binding, id });
1327
+ bindings.push(optional ? { binding, id, optional } : { binding, id });
1192
1328
  }
1193
1329
  };
1194
1330
  for (const plugin of deps) {
@@ -1196,8 +1332,9 @@ function normalizeImports(deps) {
1196
1332
  for (const [binding, child] of Object.entries(plugin.exports)) {
1197
1333
  add(binding, child.id);
1198
1334
  }
1335
+ } else if (plugin.pluginType === "hook") {
1199
1336
  } else {
1200
- add(plugin.name, plugin.id);
1337
+ add(plugin.name, plugin.id, plugin.optional);
1201
1338
  }
1202
1339
  }
1203
1340
  return { plugins: deps, bindings };
@@ -1209,6 +1346,26 @@ function collectLeafMeta(config) {
1209
1346
  }
1210
1347
  return meta;
1211
1348
  }
1349
+ function formatDynamicMemberName(path) {
1350
+ return path.map((seg) => typeof seg === "string" ? seg : `{${seg.param}}`).join(".");
1351
+ }
1352
+ function collectDynamicMembers(members) {
1353
+ if (!members?.length) return void 0;
1354
+ return members.map((member) => {
1355
+ const root = member.path[0];
1356
+ if (typeof root !== "string") {
1357
+ throw new Error(
1358
+ "defineProperty: a dynamicMember path must start with a literal segment (the owning binding), not a { param }."
1359
+ );
1360
+ }
1361
+ const leaf = collectLeafMeta(member) ?? {};
1362
+ return {
1363
+ name: formatDynamicMemberName(member.path),
1364
+ rootBinding: root,
1365
+ meta: member.inputSchema ? { ...leaf, inputSchema: member.inputSchema } : leaf
1366
+ };
1367
+ });
1368
+ }
1212
1369
  function defineMethod(config) {
1213
1370
  const deps = normalizeImports(config.imports);
1214
1371
  return {
@@ -1219,15 +1376,29 @@ function defineMethod(config) {
1219
1376
  imports: deps.plugins,
1220
1377
  importBindings: deps.bindings,
1221
1378
  inputSchema: config.inputSchema,
1379
+ skipInputValidation: config.skipInputValidation,
1222
1380
  meta: collectLeafMeta(config),
1223
1381
  resolvers: config.resolvers,
1224
1382
  formatter: config.formatter,
1225
1383
  output: config.output,
1226
1384
  positional: config.positional,
1227
1385
  setup: config.setup,
1386
+ dispose: config.dispose,
1228
1387
  run: config.run
1229
1388
  };
1230
1389
  }
1390
+ function defineMethodOverride(config) {
1391
+ const { target, namespace, ...rest } = config;
1392
+ return {
1393
+ pluginType: "method-override",
1394
+ name: `override:${target}`,
1395
+ id: namespace ? `${namespace}/override:${target}` : `override:${target}`,
1396
+ target,
1397
+ imports: [],
1398
+ importBindings: [],
1399
+ meta: collectLeafMeta(rest)
1400
+ };
1401
+ }
1231
1402
  function defineResolver(config) {
1232
1403
  const deps = normalizeImports(config.imports);
1233
1404
  const base = { imports: deps.plugins, importBindings: deps.bindings };
@@ -1319,9 +1490,11 @@ function defineProperty(config) {
1319
1490
  imports: deps.plugins,
1320
1491
  importBindings: deps.bindings,
1321
1492
  setup: config.setup,
1493
+ dispose: config.dispose,
1322
1494
  value: config.value,
1323
1495
  get: config.get,
1324
- meta: collectLeafMeta(config)
1496
+ meta: collectLeafMeta(config),
1497
+ dynamicMembers: collectDynamicMembers(config.dynamicMembers)
1325
1498
  };
1326
1499
  }
1327
1500
  function declareProperty(config) {
@@ -1336,6 +1509,37 @@ function declareProperty(config) {
1336
1509
  importBindings: []
1337
1510
  };
1338
1511
  }
1512
+ function declareOptionalProperty(config) {
1513
+ const { name, namespace } = parseId(config.id);
1514
+ return {
1515
+ pluginType: "property",
1516
+ name,
1517
+ namespace,
1518
+ id: makeId(name, namespace),
1519
+ standIn: true,
1520
+ optional: true,
1521
+ imports: [],
1522
+ importBindings: []
1523
+ // Requires nothing (phantom carrier `<never, never>`): a consumer that
1524
+ // imports it still passes `createSdk`'s completeness check unprovided. The
1525
+ // import binding is still typed `TValue | undefined` from the descriptor.
1526
+ };
1527
+ }
1528
+ function defineHook(config) {
1529
+ const deps = normalizeImports(config.imports);
1530
+ return {
1531
+ pluginType: "hook",
1532
+ name: config.name,
1533
+ namespace: config.namespace,
1534
+ id: makeId(config.name, config.namespace),
1535
+ imports: deps.plugins,
1536
+ importBindings: deps.bindings,
1537
+ setup: config.setup,
1538
+ dispose: config.dispose,
1539
+ wrap: config.wrap,
1540
+ observe: config.observe
1541
+ };
1542
+ }
1339
1543
  function declarePlugin(config) {
1340
1544
  const { name, namespace } = parseId(config.id);
1341
1545
  return {
@@ -1350,7 +1554,12 @@ function declarePlugin(config) {
1350
1554
  };
1351
1555
  }
1352
1556
  function definePlugin(fnOrConfig) {
1353
- if (typeof fnOrConfig === "function") return fnOrConfig;
1557
+ if (typeof fnOrConfig === "function") {
1558
+ logDeprecation(
1559
+ "definePlugin(fn) (the function form) is deprecated. Author plugins with defineMethod/defineProperty/definePlugin({ ... }) instead."
1560
+ );
1561
+ return fnOrConfig;
1562
+ }
1354
1563
  const config = fnOrConfig;
1355
1564
  const deps = normalizeImports(config.imports);
1356
1565
  return {
@@ -1358,12 +1567,24 @@ function definePlugin(fnOrConfig) {
1358
1567
  name: config.name,
1359
1568
  namespace: config.namespace,
1360
1569
  id: makeId(config.name, config.namespace, "aggregate"),
1361
- imports: deps.plugins,
1570
+ // A re-export synthetic (`selectExports` / `omitExports`) is flattened by
1571
+ // `normalizeExports` into bare bindings, which drops its own `imports:
1572
+ // [source]`. That edge is how `omitExports` keeps an omitted (unbound) leaf
1573
+ // materialized + addressable by id, so preserve every exported aggregate's
1574
+ // imports as extra reachability edges here (bindings unaffected).
1575
+ imports: [...deps.plugins, ...exportedAggregateImports(config.exports)],
1362
1576
  importBindings: deps.bindings,
1363
- exports: normalizeExports(config.exports),
1364
- middleware: config.middleware
1577
+ exports: normalizeExports(config.exports)
1365
1578
  };
1366
1579
  }
1580
+ function exportedAggregateImports(exports2) {
1581
+ if (!exports2) return [];
1582
+ const out = [];
1583
+ for (const element of exports2) {
1584
+ if (element.pluginType === "aggregate") out.push(...element.imports);
1585
+ }
1586
+ return out;
1587
+ }
1367
1588
  function normalizeExports(exports2) {
1368
1589
  if (!exports2) return {};
1369
1590
  const out = {};
@@ -1423,9 +1644,33 @@ function selectExports(source, ...specs) {
1423
1644
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1424
1645
  };
1425
1646
  }
1647
+ function omitExports(source, omit) {
1648
+ const omitSet = new Set(omit);
1649
+ for (const name of omit) {
1650
+ if (!(name in source.exports)) {
1651
+ throw new Error(`omitExports: "${source.id}" has no export "${name}".`);
1652
+ }
1653
+ }
1654
+ const kept = {};
1655
+ for (const [binding, child] of Object.entries(source.exports)) {
1656
+ if (!omitSet.has(binding)) kept[binding] = child;
1657
+ }
1658
+ return {
1659
+ pluginType: "aggregate",
1660
+ name: makeId(`omit`, source.name, "aggregate"),
1661
+ id: `${source.id}#omit:${selectSeq++}`,
1662
+ imports: [source],
1663
+ importBindings: [],
1664
+ exports: kept
1665
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1666
+ };
1667
+ }
1426
1668
 
1427
1669
  // src/model/legacy.ts
1428
1670
  function fromFunctionPlugin(fn, config) {
1671
+ logDeprecation(
1672
+ "fromFunctionPlugin() is deprecated. Author plugins with defineMethod/definePlugin instead."
1673
+ );
1429
1674
  return {
1430
1675
  pluginType: "legacy",
1431
1676
  name: config.name,
@@ -1437,6 +1682,9 @@ function fromFunctionPlugin(fn, config) {
1437
1682
  };
1438
1683
  }
1439
1684
  function defineLegacyMerge(args) {
1685
+ logDeprecation(
1686
+ "defineLegacyMerge() is deprecated. Build directly with createSdk(root, { configuration }) instead."
1687
+ );
1440
1688
  return {
1441
1689
  pluginType: "legacy-merge",
1442
1690
  name: args.name,
@@ -1503,40 +1751,60 @@ function pluginEntryMeta(entry) {
1503
1751
  if (entry.pluginType === "property" && entry.meta) return entry.meta;
1504
1752
  return void 0;
1505
1753
  }
1506
- function buildSurfaceRegistry(context, packageFilter) {
1754
+ function foldDynamicMembers(entry, surfaceBindings, meta) {
1755
+ if (entry.pluginType !== "property" || !entry.dynamicMembers) return;
1756
+ for (const member of entry.dynamicMembers) {
1757
+ if (!surfaceBindings.has(member.rootBinding)) {
1758
+ throw new Error(
1759
+ `dynamicMember "${member.name}": its root "${member.rootBinding}" is not a surfaced member. A dynamic member's path must start with a real binding.`
1760
+ );
1761
+ }
1762
+ meta[member.name] = member.meta;
1763
+ }
1764
+ }
1765
+ function collectSurfaceProjection(context, formatterSdk) {
1507
1766
  const meta = {};
1508
- const surface = {};
1509
1767
  const entries = {};
1510
1768
  for (const [binding, id] of Object.entries(context.surface)) {
1511
1769
  const entry = context.plugins[id];
1512
1770
  if (!entry || entry.pluginType === "aggregate") continue;
1513
- surface[binding] = entry.value;
1514
1771
  entries[binding] = entry;
1515
1772
  const m = pluginEntryMeta(entry);
1516
1773
  if (m) meta[binding] = m;
1517
1774
  }
1775
+ const surfaceBindings = new Set(Object.keys(context.surface));
1776
+ for (const entry of Object.values(entries)) {
1777
+ foldDynamicMembers(entry, surfaceBindings, meta);
1778
+ }
1518
1779
  const formatters = {};
1519
1780
  const boundResolvers = {};
1520
1781
  const positional = {};
1521
1782
  for (const [binding, entry] of Object.entries(entries)) {
1522
- const f = normalizeFormatter(entry, surface);
1783
+ const f = normalizeFormatter(entry, formatterSdk);
1523
1784
  if (f) formatters[binding] = f;
1524
1785
  const r = normalizeBoundResolvers(entry);
1525
1786
  if (r) boundResolvers[binding] = r;
1526
1787
  const p = methodPositional(entry);
1527
1788
  if (p) positional[binding] = p;
1528
1789
  }
1790
+ return { meta, formatters, boundResolvers, positional };
1791
+ }
1792
+ function buildSurfaceRegistry(context, packageFilter) {
1793
+ const surface = {};
1794
+ for (const [binding, id] of Object.entries(context.surface)) {
1795
+ const entry = context.plugins[id];
1796
+ if (!entry || entry.pluginType === "aggregate") continue;
1797
+ surface[binding] = entry.pluginType === "property" && entry.getValue ? entry.getValue() : entry.value;
1798
+ }
1529
1799
  return buildRegistry({
1530
1800
  sdk: surface,
1531
- meta,
1532
- formatters,
1533
- boundResolvers,
1534
- positional,
1801
+ ...collectSurfaceProjection(context, surface),
1535
1802
  packageFilter
1536
1803
  });
1537
1804
  }
1538
1805
 
1539
1806
  // src/model/builtins.ts
1807
+ var coreOptionsPluginRef = declareOptionalProperty({ id: CORE_OPTIONS_ID });
1540
1808
  var dangerousContextPlugin = {
1541
1809
  pluginType: "property",
1542
1810
  name: "context",
@@ -1555,12 +1823,15 @@ var getRegistryPlugin = defineMethod({
1555
1823
  });
1556
1824
 
1557
1825
  // src/model/materialize.ts
1826
+ var FRAMEWORK_CONFIGURATION_IDS = /* @__PURE__ */ new Set([
1827
+ CORE_OPTIONS_ID
1828
+ ]);
1558
1829
  function normalizeOutput(output) {
1559
1830
  if (output === void 0) return { type: "raw" };
1560
1831
  if (typeof output === "string") return { type: output };
1561
1832
  return output;
1562
1833
  }
1563
- var CONTEXT = Symbol("kitcore.context");
1834
+ var CONTEXT = Symbol.for("kitcore.context");
1564
1835
  function getContext(sdk) {
1565
1836
  return sdk[CONTEXT];
1566
1837
  }
@@ -1622,7 +1893,7 @@ function topoOrder(descriptors) {
1622
1893
  for (const id of descriptors.keys()) visit(id);
1623
1894
  return order;
1624
1895
  }
1625
- function collectPlugins(root, materialized = /* @__PURE__ */ new Set()) {
1896
+ function collectPlugins(root, materialized = /* @__PURE__ */ new Set(), configuration) {
1626
1897
  const byId = /* @__PURE__ */ new Map();
1627
1898
  const visit = (plugin) => {
1628
1899
  if (materialized.has(plugin.id)) return;
@@ -1645,8 +1916,51 @@ function collectPlugins(root, materialized = /* @__PURE__ */ new Set()) {
1645
1916
  for (const edge of edgesOf(plugin)) visit(edge);
1646
1917
  };
1647
1918
  visit(root);
1919
+ if (configuration) {
1920
+ for (const [id, value] of Object.entries(configuration)) {
1921
+ const existing = byId.get(id);
1922
+ if (!existing) {
1923
+ if (FRAMEWORK_CONFIGURATION_IDS.has(id)) {
1924
+ const { name, namespace } = parseId(id);
1925
+ byId.set(id, {
1926
+ pluginType: "property",
1927
+ name,
1928
+ namespace,
1929
+ id,
1930
+ imports: [],
1931
+ importBindings: [],
1932
+ value
1933
+ });
1934
+ continue;
1935
+ }
1936
+ throw new Error(
1937
+ `createSdk: configuration id "${id}" matches no plugin in the graph. An injected value must satisfy a property stand-in reachable from the root.`
1938
+ );
1939
+ }
1940
+ if (existing.pluginType !== "property") {
1941
+ throw new Error(
1942
+ `createSdk: configuration id "${id}" resolves to a "${existing.pluginType}" plugin; only property values can be injected.`
1943
+ );
1944
+ }
1945
+ if (!isStandIn(existing)) {
1946
+ throw new Error(
1947
+ `createSdk: configuration id "${id}" collides with a registered provider. A property is either injected or provided by a plugin, not both.`
1948
+ );
1949
+ }
1950
+ byId.set(id, {
1951
+ pluginType: "property",
1952
+ name: existing.name,
1953
+ namespace: existing.namespace,
1954
+ id,
1955
+ imports: [],
1956
+ importBindings: [],
1957
+ value
1958
+ });
1959
+ }
1960
+ }
1648
1961
  for (const [id, plugin] of byId) {
1649
1962
  if (isStandIn(plugin)) {
1963
+ if ("optional" in plugin && plugin.optional) continue;
1650
1964
  throw new Error(
1651
1965
  `createSdk: missing dependency "${id}". A plugin depends on it (via a stand-in) but no implementation was registered.`
1652
1966
  );
@@ -1654,7 +1968,7 @@ function collectPlugins(root, materialized = /* @__PURE__ */ new Set()) {
1654
1968
  }
1655
1969
  return byId;
1656
1970
  }
1657
- function bindValue(target, key2, entry) {
1971
+ function bindValue(target, key2, entry, callType = "surface") {
1658
1972
  if (entry.pluginType === "property" && entry.getValue) {
1659
1973
  Object.defineProperty(target, key2, {
1660
1974
  get: entry.getValue,
@@ -1662,8 +1976,9 @@ function bindValue(target, key2, entry) {
1662
1976
  configurable: true
1663
1977
  });
1664
1978
  } else {
1979
+ const value = callType === "internal" && entry.pluginType === "method" ? entry.internalValue ?? entry.value : entry.value;
1665
1980
  Object.defineProperty(target, key2, {
1666
- value: entry.value,
1981
+ value,
1667
1982
  writable: true,
1668
1983
  enumerable: true,
1669
1984
  configurable: true
@@ -1681,8 +1996,18 @@ function buildSurface(context, ...maps) {
1681
1996
  }
1682
1997
  function buildImports(plugins, importBindings) {
1683
1998
  const imports = {};
1684
- for (const { binding, id } of importBindings) {
1685
- bindValue(imports, binding, plugins[id]);
1999
+ for (const { binding, id, optional } of importBindings) {
2000
+ const entry = plugins[id];
2001
+ if (!entry && optional) {
2002
+ Object.defineProperty(imports, binding, {
2003
+ value: void 0,
2004
+ writable: true,
2005
+ enumerable: true,
2006
+ configurable: true
2007
+ });
2008
+ continue;
2009
+ }
2010
+ bindValue(imports, binding, entry, "internal");
1686
2011
  }
1687
2012
  return imports;
1688
2013
  }
@@ -1706,9 +2031,31 @@ function materialize(descriptors, context) {
1706
2031
  buildEagerArtifacts(descriptors, context, states);
1707
2032
  bindAttachments(descriptors, context);
1708
2033
  resolveAggregates(descriptors, context);
1709
- assembleMiddleware(descriptors, context);
2034
+ assembleMiddleware(descriptors, context, states);
2035
+ assembleHooks(descriptors, context, states);
2036
+ applyMethodOverrides(descriptors, context);
1710
2037
  return context.plugins;
1711
2038
  }
2039
+ function applyMethodOverride(context, override) {
2040
+ const entry = context.plugins[override.target];
2041
+ if (!entry) {
2042
+ throw new Error(
2043
+ `defineMethodOverride: no method "${override.target}" to override. Include the target method in the SDK build.`
2044
+ );
2045
+ }
2046
+ if (entry.pluginType !== "method") {
2047
+ throw new Error(
2048
+ `defineMethodOverride: "${override.target}" is a ${entry.pluginType}, not a method; only methods can be overridden.`
2049
+ );
2050
+ }
2051
+ entry.meta = { ...entry.meta, ...override.meta };
2052
+ }
2053
+ function applyMethodOverrides(descriptors, context) {
2054
+ for (const descriptor of descriptors.values()) {
2055
+ if (descriptor.pluginType !== "method-override") continue;
2056
+ applyMethodOverride(context, descriptor);
2057
+ }
2058
+ }
1712
2059
  function bindResolver(resolver, plugins) {
1713
2060
  switch (resolver.type) {
1714
2061
  case "static":
@@ -1754,25 +2101,24 @@ function bindResolver(resolver, plugins) {
1754
2101
  bound.definitions = bindDefinitions(resolver.definitions, plugins);
1755
2102
  return bound;
1756
2103
  }
1757
- default: {
2104
+ case "dynamic": {
1758
2105
  const imports = buildImports(plugins, resolver.importBindings);
1759
- const bound = {
1760
- type: "dynamic",
1761
- requireParameters: resolver.requireParameters,
1762
- inputType: resolver.inputType,
1763
- placeholder: resolver.placeholder,
1764
- prompt: resolver.prompt
1765
- };
1766
2106
  const {
1767
2107
  getContext: getContext2,
1768
2108
  listItems,
1769
2109
  tryResolveWithoutPrompt,
1770
2110
  tryResolveFromSearch
1771
2111
  } = resolver;
2112
+ const bound = {
2113
+ type: "dynamic",
2114
+ requireParameters: resolver.requireParameters,
2115
+ inputType: resolver.inputType,
2116
+ placeholder: resolver.placeholder,
2117
+ prompt: resolver.prompt,
2118
+ listItems: ({ input, context, search, cursor }) => listItems({ imports, input, context, search, cursor })
2119
+ };
1772
2120
  if (getContext2)
1773
2121
  bound.getContext = ({ input }) => getContext2({ imports, input });
1774
- if (listItems)
1775
- bound.listItems = ({ input, context, search, cursor }) => listItems({ imports, input, context, search, cursor });
1776
2122
  if (tryResolveWithoutPrompt) {
1777
2123
  bound.tryResolveWithoutPrompt = ({ input }) => tryResolveWithoutPrompt({ imports, input });
1778
2124
  }
@@ -1781,6 +2127,12 @@ function bindResolver(resolver, plugins) {
1781
2127
  }
1782
2128
  return bound;
1783
2129
  }
2130
+ default: {
2131
+ const unhandled = resolver;
2132
+ throw new Error(
2133
+ `unhandled resolver kind: ${unhandled.type}`
2134
+ );
2135
+ }
1784
2136
  }
1785
2137
  }
1786
2138
  function bindFields(fields, plugins) {
@@ -1851,25 +2203,11 @@ function runLegacyPass(descriptors, context) {
1851
2203
  if (!("getRegistry" in exports2)) {
1852
2204
  let getRegistry2 = function(options) {
1853
2205
  const sdk = this ?? exports2;
1854
- const meta2 = {};
1855
- const formatters = {};
1856
- const boundResolvers = {};
1857
- for (const [binding, id2] of Object.entries(context.surface)) {
1858
- const entry = context.plugins[id2];
1859
- if (!entry || entry.pluginType === "aggregate") continue;
1860
- const m = pluginEntryMeta(entry);
1861
- if (m) meta2[binding] = m;
1862
- const f = normalizeFormatter(entry, sdk);
1863
- if (f) formatters[binding] = f;
1864
- const r = normalizeBoundResolvers(entry);
1865
- if (r) boundResolvers[binding] = r;
1866
- }
1867
- Object.assign(meta2, context.meta);
2206
+ const projection = collectSurfaceProjection(context, sdk);
2207
+ Object.assign(projection.meta, context.meta);
1868
2208
  return buildRegistry({
1869
2209
  sdk,
1870
- meta: meta2,
1871
- formatters,
1872
- boundResolvers,
2210
+ ...projection,
1873
2211
  packageFilter: options?.package
1874
2212
  });
1875
2213
  };
@@ -1914,7 +2252,10 @@ function buildMethodEntries(descriptors, context, states) {
1914
2252
  next = (i) => wrap.run({
1915
2253
  imports: buildImports(plugins, wrap.owner.importBindings),
1916
2254
  next: inner,
1917
- input: i
2255
+ input: i,
2256
+ // Overwritten by the chain item's own closure with the owning
2257
+ // hook's setup state.
2258
+ state: void 0
1918
2259
  });
1919
2260
  }
1920
2261
  return next(input);
@@ -1928,7 +2269,8 @@ function buildMethodEntries(descriptors, context, states) {
1928
2269
  schema: descriptor.inputSchema,
1929
2270
  name: descriptor.name,
1930
2271
  defaultPageSize: out.defaultPageSize,
1931
- adaptPage: out.adaptPage
2272
+ adaptPage: out.adaptPage,
2273
+ getDeprecation: () => entry.meta?.deprecation
1932
2274
  }
1933
2275
  );
1934
2276
  } else if (out.type === "item") {
@@ -1937,25 +2279,43 @@ function buildMethodEntries(descriptors, context, states) {
1937
2279
  });
1938
2280
  entry.value = createFunction(
1939
2281
  fold(itemCore),
1940
- { sdk, schema: descriptor.inputSchema, name: descriptor.name }
2282
+ {
2283
+ sdk,
2284
+ schema: descriptor.inputSchema,
2285
+ name: descriptor.name,
2286
+ getDeprecation: () => entry.meta?.deprecation
2287
+ }
1941
2288
  );
1942
2289
  } else {
1943
- entry.value = (rawInput) => {
1944
- const input = descriptor.inputSchema ? descriptor.inputSchema.parse(rawInput) : rawInput;
1945
- return fold(callRun)(input);
1946
- };
2290
+ entry.value = createRawFunction(
2291
+ (input) => fold(callRun)(input),
2292
+ {
2293
+ sdk,
2294
+ name: descriptor.name,
2295
+ schema: descriptor.skipInputValidation ? void 0 : descriptor.inputSchema,
2296
+ positional: descriptor.positional,
2297
+ // The boundary reads the deprecation LIVE off the entry, so a
2298
+ // deprecation merged after build (defineMethodOverride, addPlugin)
2299
+ // fires too.
2300
+ getDeprecation: () => entry.meta?.deprecation
2301
+ }
2302
+ );
1947
2303
  }
2304
+ const canonicalValue = entry.value;
1948
2305
  if (descriptor.positional) {
1949
2306
  const names = descriptor.positional;
1950
- const canonicalValue = entry.value;
1951
- entry.value = (...args) => {
2307
+ const pack = (args) => {
1952
2308
  const packed = {};
1953
2309
  names.forEach((name, i) => {
1954
2310
  if (i < args.length) packed[name] = args[i];
1955
2311
  });
1956
- return canonicalValue(packed);
2312
+ return packed;
1957
2313
  };
2314
+ entry.value = (...args) => canonicalValue(pack(args));
2315
+ entry.internalValue = (...args) => canonicalValue(pack(args), INTERNAL_CALL);
1958
2316
  entry.positional = names;
2317
+ } else {
2318
+ entry.internalValue = (input) => canonicalValue(input, INTERNAL_CALL);
1959
2319
  }
1960
2320
  plugins[id] = entry;
1961
2321
  }
@@ -1967,7 +2327,7 @@ function buildEagerArtifacts(descriptors, context, states) {
1967
2327
  const ensureBuilt = (id) => {
1968
2328
  if (built.has(id)) return;
1969
2329
  const descriptor = descriptors.get(id);
1970
- if (!descriptor || descriptor.pluginType === "aggregate" || descriptor.pluginType === "legacy") {
2330
+ if (!descriptor || descriptor.pluginType === "aggregate" || descriptor.pluginType === "legacy" || descriptor.pluginType === "method-override" || isStandIn(descriptor)) {
1971
2331
  built.add(id);
1972
2332
  return;
1973
2333
  }
@@ -1976,6 +2336,30 @@ function buildEagerArtifacts(descriptors, context, states) {
1976
2336
  }
1977
2337
  building.add(id);
1978
2338
  for (const { id: depId } of descriptor.importBindings) ensureBuilt(depId);
2339
+ const recordDisposer = () => {
2340
+ const dispose = descriptor.dispose;
2341
+ if (!dispose) return;
2342
+ context.disposers?.push({
2343
+ id,
2344
+ dispose: (input) => dispose({
2345
+ imports: buildImports(plugins, descriptor.importBindings),
2346
+ state: states.get(id),
2347
+ input
2348
+ })
2349
+ });
2350
+ };
2351
+ if (descriptor.pluginType === "hook") {
2352
+ states.set(
2353
+ id,
2354
+ descriptor.setup ? descriptor.setup({
2355
+ imports: buildImports(plugins, descriptor.importBindings)
2356
+ }) : void 0
2357
+ );
2358
+ recordDisposer();
2359
+ building.delete(id);
2360
+ built.add(id);
2361
+ return;
2362
+ }
1979
2363
  if (descriptor.pluginType === "method") {
1980
2364
  states.set(
1981
2365
  id,
@@ -1995,7 +2379,8 @@ function buildEagerArtifacts(descriptors, context, states) {
1995
2379
  pluginType: "property",
1996
2380
  name: descriptor.name,
1997
2381
  value: context,
1998
- meta: descriptor.meta
2382
+ meta: descriptor.meta,
2383
+ dynamicMembers: descriptor.dynamicMembers
1999
2384
  };
2000
2385
  } else if (descriptor.get) {
2001
2386
  const get = descriptor.get;
@@ -2007,22 +2392,68 @@ function buildEagerArtifacts(descriptors, context, states) {
2007
2392
  imports: buildImports(plugins, importBindings),
2008
2393
  state: states.get(id)
2009
2394
  }),
2010
- meta: descriptor.meta
2395
+ meta: descriptor.meta,
2396
+ dynamicMembers: descriptor.dynamicMembers
2011
2397
  };
2012
2398
  } else {
2013
2399
  plugins[id] = {
2014
2400
  pluginType: "property",
2015
2401
  name: descriptor.name,
2016
2402
  value: descriptor.value,
2017
- meta: descriptor.meta
2403
+ meta: descriptor.meta,
2404
+ dynamicMembers: descriptor.dynamicMembers
2018
2405
  };
2019
2406
  }
2020
2407
  }
2408
+ recordDisposer();
2021
2409
  building.delete(id);
2022
2410
  built.add(id);
2023
2411
  };
2024
2412
  for (const id of descriptors.keys()) ensureBuilt(id);
2025
2413
  }
2414
+ function resolvePlugin(sdk, ref) {
2415
+ const entry = getContext(sdk).plugins[ref.id];
2416
+ if (!entry) {
2417
+ if (ref.optional) {
2418
+ return void 0;
2419
+ }
2420
+ throw new Error(
2421
+ `resolvePlugin: plugin "${ref.id}" is not materialized on the SDK.`
2422
+ );
2423
+ }
2424
+ if (entry.pluginType === "property" && entry.getValue) {
2425
+ return entry.getValue();
2426
+ }
2427
+ if (entry.pluginType === "method" && entry.internalValue) {
2428
+ return entry.internalValue;
2429
+ }
2430
+ return entry.value;
2431
+ }
2432
+ var CoreDisposeError = class extends Error {
2433
+ constructor(errors) {
2434
+ super(`disposeSdk: ${errors.length} dispose callback(s) failed.`);
2435
+ this.name = "CoreDisposeError";
2436
+ this.errors = errors;
2437
+ Object.setPrototypeOf(this, new.target.prototype);
2438
+ }
2439
+ };
2440
+ function disposeSdk(sdk, input) {
2441
+ const context = getContext(sdk);
2442
+ if (context.disposed) return context.disposed;
2443
+ const disposers = context.disposers ?? [];
2444
+ context.disposed = (async () => {
2445
+ const errors = [];
2446
+ for (let i = disposers.length - 1; i >= 0; i--) {
2447
+ try {
2448
+ await disposers[i].dispose(input);
2449
+ } catch (error) {
2450
+ errors.push(error);
2451
+ }
2452
+ }
2453
+ if (errors.length > 0) throw new CoreDisposeError(errors);
2454
+ })();
2455
+ return context.disposed;
2456
+ }
2026
2457
  function resolveAggregates(descriptors, context) {
2027
2458
  const plugins = context.plugins;
2028
2459
  for (const [id, descriptor] of descriptors) {
@@ -2034,39 +2465,74 @@ function resolveAggregates(descriptors, context) {
2034
2465
  plugins[id] = { pluginType: "aggregate", name: descriptor.name, exports: exports2 };
2035
2466
  }
2036
2467
  }
2037
- function assembleMiddleware(descriptors, context) {
2468
+ function assembleMiddleware(descriptors, context, states) {
2038
2469
  const plugins = context.plugins;
2039
2470
  for (const id of topoOrder(descriptors)) {
2040
2471
  const descriptor = descriptors.get(id);
2041
- if (!descriptor || descriptor.pluginType !== "aggregate" || !descriptor.middleware) {
2472
+ if (!descriptor || descriptor.pluginType !== "hook" || !descriptor.wrap) {
2042
2473
  continue;
2043
2474
  }
2044
- for (const [targetBinding, fn] of Object.entries(descriptor.middleware)) {
2475
+ for (const [targetBinding, fn] of Object.entries(descriptor.wrap)) {
2045
2476
  const edge = descriptor.importBindings.find(
2046
2477
  (b) => b.binding === targetBinding
2047
2478
  );
2048
2479
  if (!edge) {
2049
2480
  throw new Error(
2050
- `createSdk: middleware target "${targetBinding}" in plugin "${id}" is not a direct dependency. A middleware target must be a declared dependency of the wrapping plugin.`
2481
+ `createSdk: wrap target "${targetBinding}" in hook "${id}" is not a direct dependency. A wrap target must be a declared import of the wrapping hook.`
2051
2482
  );
2052
2483
  }
2053
2484
  const target = plugins[edge.id];
2054
2485
  if (!target || target.pluginType !== "method") {
2055
2486
  throw new Error(
2056
- `createSdk: middleware target "${targetBinding}" in plugin "${id}" does not resolve to a method.`
2487
+ `createSdk: wrap target "${targetBinding}" in hook "${id}" does not resolve to a method.`
2057
2488
  );
2058
2489
  }
2059
2490
  if (target.output?.type === "list") {
2060
2491
  throw new Error(
2061
- `createSdk: middleware target "${targetBinding}" in plugin "${id}" resolves to a list-output method, which does not support middleware yet.`
2492
+ `createSdk: wrap target "${targetBinding}" in hook "${id}" resolves to a list-output method, which does not support wrapping yet.`
2062
2493
  );
2063
2494
  }
2064
- target.chain.push({ run: fn, owner: descriptor });
2495
+ target.chain.push({
2496
+ run: (bag) => fn({ ...bag, state: states.get(id) }),
2497
+ owner: descriptor
2498
+ });
2499
+ }
2500
+ }
2501
+ }
2502
+ function assembleHooks(descriptors, context, states) {
2503
+ const plugins = context.plugins;
2504
+ for (const id of topoOrder(descriptors)) {
2505
+ const descriptor = descriptors.get(id);
2506
+ if (!descriptor || descriptor.pluginType !== "hook" || !descriptor.observe) {
2507
+ continue;
2065
2508
  }
2509
+ const { observe } = descriptor;
2510
+ const imports = buildImports(plugins, descriptor.importBindings);
2511
+ const state = states.get(id);
2512
+ const contributed = {};
2513
+ if (observe.onMethodStart) {
2514
+ const onStart = observe.onMethodStart;
2515
+ contributed.onMethodStart = (input) => {
2516
+ runIsolatedObserver(() => onStart({ imports, input, state }));
2517
+ };
2518
+ }
2519
+ if (observe.onMethodEnd) {
2520
+ const onEnd = observe.onMethodEnd;
2521
+ contributed.onMethodEnd = (input) => {
2522
+ runIsolatedObserver(() => onEnd({ imports, input, state }));
2523
+ };
2524
+ }
2525
+ context.hooks = buildHooks(context.hooks, contributed);
2066
2526
  }
2067
2527
  }
2068
- function createSdk(root) {
2069
- const context = { plugins: {}, meta: {}, hooks: {}, surface: {} };
2528
+ function createSdk(root, options) {
2529
+ const context = {
2530
+ plugins: {},
2531
+ meta: {},
2532
+ hooks: {},
2533
+ surface: {},
2534
+ disposers: []
2535
+ };
2070
2536
  if (root.pluginType === "legacy-merge") {
2071
2537
  const { legacy, plugin } = root;
2072
2538
  const collectRoot = {
@@ -2077,7 +2543,10 @@ function createSdk(root) {
2077
2543
  importBindings: [],
2078
2544
  exports: {}
2079
2545
  };
2080
- const plugins2 = materialize(collectPlugins(collectRoot), context);
2546
+ const plugins2 = materialize(
2547
+ collectPlugins(collectRoot, void 0, options?.configuration),
2548
+ context
2549
+ );
2081
2550
  const legacyExports = plugins2[legacy.id].exports;
2082
2551
  let pluginSurface;
2083
2552
  if (plugin.pluginType === "aggregate") {
@@ -2094,7 +2563,10 @@ function createSdk(root) {
2094
2563
  }
2095
2564
  return buildSurface(context, legacyExports, pluginSurface);
2096
2565
  }
2097
- const plugins = materialize(collectPlugins(root), context);
2566
+ const plugins = materialize(
2567
+ collectPlugins(root, void 0, options?.configuration),
2568
+ context
2569
+ );
2098
2570
  if (root.pluginType === "method" || root.pluginType === "property") {
2099
2571
  context.surface[root.name] = root.id;
2100
2572
  const sdk = buildSurface(context);
@@ -2108,7 +2580,7 @@ function createSdk(root) {
2108
2580
  function addModelPlugin(sdk, plugin, options = {}) {
2109
2581
  const override = options.override === true;
2110
2582
  const context = getContext(sdk);
2111
- const surfaceKeys = plugin.pluginType === "aggregate" ? Object.keys(plugin.exports) : [plugin.name];
2583
+ const surfaceKeys = plugin.pluginType === "aggregate" ? Object.keys(plugin.exports) : plugin.pluginType === "hook" ? [] : [plugin.name];
2112
2584
  checkRootKeyCollisions(sdk, surfaceKeys, override, "addPlugin");
2113
2585
  const materialized = new Set(Object.keys(context.plugins));
2114
2586
  if (override && materialized.has(plugin.id)) {
@@ -2117,6 +2589,7 @@ function addModelPlugin(sdk, plugin, options = {}) {
2117
2589
  );
2118
2590
  }
2119
2591
  materialize(collectPlugins(plugin, materialized), context);
2592
+ if (plugin.pluginType === "hook") return;
2120
2593
  const entry = context.plugins[plugin.id];
2121
2594
  if (entry.pluginType === "aggregate") {
2122
2595
  Object.defineProperties(
@@ -2150,6 +2623,13 @@ function addPlugin(sdk, plugin, options) {
2150
2623
  }
2151
2624
  return;
2152
2625
  }
2626
+ if (plugin.pluginType === "method-override") {
2627
+ applyMethodOverride(
2628
+ getContext(sdk),
2629
+ plugin
2630
+ );
2631
+ return;
2632
+ }
2153
2633
  addModelPlugin(
2154
2634
  sdk,
2155
2635
  plugin,
@@ -2232,20 +2712,14 @@ function objectShape(schema) {
2232
2712
  }
2233
2713
  function topoOrder2(specs) {
2234
2714
  const byName = new Map(specs.map((s) => [s.name, s]));
2235
- const ordered = [];
2236
2715
  const placed = /* @__PURE__ */ new Set();
2237
- let progressed = true;
2238
- while (ordered.length < specs.length && progressed) {
2239
- progressed = false;
2240
- for (const spec of specs) {
2241
- if (placed.has(spec.name)) continue;
2242
- const ready = spec.requires.every((r) => !byName.has(r) || placed.has(r));
2243
- if (ready) {
2244
- ordered.push(spec);
2245
- placed.add(spec.name);
2246
- progressed = true;
2247
- }
2248
- }
2716
+ const ordered = [];
2717
+ const isReady = (spec) => spec.requires.every((r) => !byName.has(r) || placed.has(r));
2718
+ for (; ; ) {
2719
+ const next = specs.find((s) => !placed.has(s.name) && isReady(s));
2720
+ if (!next) break;
2721
+ ordered.push(next);
2722
+ placed.add(next.name);
2249
2723
  }
2250
2724
  for (const spec of specs) if (!placed.has(spec.name)) ordered.push(spec);
2251
2725
  return ordered;
@@ -2253,7 +2727,12 @@ function topoOrder2(specs) {
2253
2727
  function planParameters(entry) {
2254
2728
  const shape = objectShape(entry.inputSchema);
2255
2729
  const resolvers = entry.boundResolvers ?? {};
2256
- const names = shape ? Object.keys(shape) : Object.keys(resolvers);
2730
+ const names = shape ? [
2731
+ ...Object.keys(shape),
2732
+ ...Object.keys(resolvers).filter(
2733
+ (name) => !(name in shape) && resolvers[name].type === "constant"
2734
+ )
2735
+ ] : Object.keys(resolvers);
2257
2736
  const specs = names.map((name) => {
2258
2737
  const field = shape?.[name];
2259
2738
  const { inner, required } = field ? unwrap(field) : { inner: void 0, required: false };
@@ -2267,7 +2746,13 @@ function planParameters(entry) {
2267
2746
  requires: resolver?.requireParameters ?? []
2268
2747
  };
2269
2748
  });
2270
- return { parameters: topoOrder2(specs) };
2749
+ const declared = shape ? specs.filter((s) => s.name in shape) : specs;
2750
+ const scan = [
2751
+ ...shape ? specs.filter((s) => !(s.name in shape)) : [],
2752
+ ...declared.filter((s) => s.required),
2753
+ ...declared.filter((s) => !s.required)
2754
+ ];
2755
+ return { parameters: topoOrder2(scan) };
2271
2756
  }
2272
2757
 
2273
2758
  // src/model/resolution/engine.ts
@@ -2292,10 +2777,12 @@ var key = (path) => path.join(".");
2292
2777
  function isSettled(state, path) {
2293
2778
  return state.settled.includes(key(path));
2294
2779
  }
2295
- function settle(state, path) {
2296
- const k = key(path);
2780
+ function remember(state, k) {
2297
2781
  if (!state.settled.includes(k)) state.settled.push(k);
2298
2782
  }
2783
+ function settle(state, path) {
2784
+ remember(state, key(path));
2785
+ }
2299
2786
  function clone(state) {
2300
2787
  return JSON.parse(JSON.stringify(state));
2301
2788
  }
@@ -2312,8 +2799,9 @@ function coerce(leaf, raw) {
2312
2799
  return raw;
2313
2800
  }
2314
2801
  async function validationError(leaf, value, state) {
2802
+ if (leaf.resolver?.type !== "dynamic") return null;
2315
2803
  const context = await resolveContext(leaf, state.resolved);
2316
- const config = leaf.resolver?.prompt?.({
2804
+ const config = leaf.resolver.prompt?.({
2317
2805
  items: state.listing?.items ?? [],
2318
2806
  input: mergeInput(state.resolved, leaf.extraInput),
2319
2807
  context
@@ -2323,11 +2811,6 @@ async function validationError(leaf, value, state) {
2323
2811
  if (verdict === true) return null;
2324
2812
  return typeof verdict === "string" ? verdict : `${leaf.name}: invalid value`;
2325
2813
  }
2326
- function toChoice(c) {
2327
- const label = "label" in c ? c.label : c.name;
2328
- const hint = Array.isArray(c.hint) ? c.hint.join(", ") : c.hint;
2329
- return { label, value: String(c.value), hint };
2330
- }
2331
2814
  function isRef(r) {
2332
2815
  return typeof r === "object" && r !== null && "ref" in r;
2333
2816
  }
@@ -2343,6 +2826,7 @@ function toLeaf(name, field, definitions) {
2343
2826
  return {
2344
2827
  name,
2345
2828
  required: field.required ?? false,
2829
+ label: field.label,
2346
2830
  resolver,
2347
2831
  extraInput,
2348
2832
  // Carry the field's value type so nested answers coerce (number/boolean)
@@ -2390,6 +2874,9 @@ function arrayItem(resolver) {
2390
2874
  requires: []
2391
2875
  };
2392
2876
  }
2877
+ function autoSettles(resolver) {
2878
+ return resolver.type === "constant" || resolver.type === "info";
2879
+ }
2393
2880
  function mergeInput(input, extra) {
2394
2881
  return extra ? { ...input, ...extra } : input;
2395
2882
  }
@@ -2433,25 +2920,6 @@ async function arrayInfoAt(ctx, path, resolved) {
2433
2920
  item: { ...arrayItem(resolver), name: String(path[path.length - 1]) }
2434
2921
  };
2435
2922
  }
2436
- var AFFORDANCE = {
2437
- choose: { action: "choose", description: "Pick one of the listed options" },
2438
- custom: {
2439
- action: "custom",
2440
- description: "Provide a value directly",
2441
- supply: "value"
2442
- },
2443
- search: {
2444
- action: "search",
2445
- description: "Filter the options by a search term",
2446
- supply: "term"
2447
- },
2448
- more: { action: "more", description: "Load more options" },
2449
- skip: { action: "skip", description: "Omit this optional parameter" },
2450
- add: { action: "add", description: "Add another item" },
2451
- done: { action: "done", description: "Finish the list" },
2452
- retry: { action: "retry", description: "Retry loading the options" },
2453
- cancel: { action: "cancel", description: "Cancel resolution" }
2454
- };
2455
2923
  async function firstPage(result) {
2456
2924
  const page = await result;
2457
2925
  return {
@@ -2460,18 +2928,19 @@ async function firstPage(result) {
2460
2928
  };
2461
2929
  }
2462
2930
  async function resolveContext(leaf, input) {
2463
- return leaf.resolver?.getContext?.({
2931
+ if (leaf.resolver?.type !== "dynamic") return void 0;
2932
+ return leaf.resolver.getContext?.({
2464
2933
  input: mergeInput(input, leaf.extraInput)
2465
2934
  });
2466
2935
  }
2467
2936
  async function fetchListing(leaf, input, opts = {}) {
2468
2937
  const page = await firstPage(
2469
- leaf.resolver?.listItems?.({
2938
+ leaf.resolver?.type === "dynamic" ? leaf.resolver.listItems({
2470
2939
  input: mergeInput(input, leaf.extraInput),
2471
2940
  context: opts.context,
2472
2941
  search: opts.search,
2473
2942
  cursor: opts.cursor
2474
- })
2943
+ }) : void 0
2475
2944
  );
2476
2945
  return {
2477
2946
  items: [...opts.priorItems ?? [], ...page.data],
@@ -2480,15 +2949,52 @@ async function fetchListing(leaf, input, opts = {}) {
2480
2949
  exhausted: page.nextCursor == null
2481
2950
  };
2482
2951
  }
2952
+
2953
+ // src/model/resolution/questions.ts
2954
+ function toChoice(c) {
2955
+ const label = "label" in c ? c.label : c.name;
2956
+ const hint = Array.isArray(c.hint) ? c.hint.join(", ") : c.hint;
2957
+ return { label, value: String(c.value), hint };
2958
+ }
2959
+ var AFFORDANCE = {
2960
+ choose: { action: "choose", description: "Pick one of the listed options" },
2961
+ custom: {
2962
+ action: "custom",
2963
+ description: "Provide a value directly",
2964
+ supply: "value"
2965
+ },
2966
+ search: {
2967
+ action: "search",
2968
+ description: "Filter the options by a search term",
2969
+ supply: "term"
2970
+ },
2971
+ more: { action: "more", description: "Load more options" },
2972
+ skip: { action: "skip", description: "Omit this optional parameter" },
2973
+ add: { action: "add", description: "Add another item" },
2974
+ done: { action: "done", description: "Finish the list" },
2975
+ retry: { action: "retry", description: "Retry loading the options" },
2976
+ cancel: { action: "cancel", description: "Cancel resolution" }
2977
+ };
2483
2978
  function selectActions(leaf, listing, multiple) {
2979
+ const searchMode = leaf.resolver?.type === "dynamic" && leaf.resolver.inputType === "search";
2980
+ if (searchMode && listing.search === void 0 && listing.items.length === 0) {
2981
+ const actions2 = multiple ? [AFFORDANCE.search] : [AFFORDANCE.search, AFFORDANCE.custom];
2982
+ if (!leaf.required) actions2.push(AFFORDANCE.skip);
2983
+ return actions2;
2984
+ }
2484
2985
  const actions = multiple ? [AFFORDANCE.choose] : [AFFORDANCE.choose, AFFORDANCE.custom];
2485
- if (leaf.resolver?.inputType === "search") actions.push(AFFORDANCE.search);
2986
+ if (searchMode) actions.push(AFFORDANCE.search);
2486
2987
  if (listing.cursor) actions.push(AFFORDANCE.more);
2487
2988
  if (!leaf.required) actions.push(AFFORDANCE.skip);
2488
2989
  return actions;
2489
2990
  }
2991
+ function labeledMessage(leaf) {
2992
+ if (!leaf.label) return void 0;
2993
+ return `${leaf.label} (${leaf.required ? "required" : "optional"}):`;
2994
+ }
2490
2995
  function selectQuestion(leaf, input, listing, context) {
2491
- const config = leaf.resolver?.prompt?.({
2996
+ const resolver = leaf.resolver?.type === "dynamic" ? leaf.resolver : void 0;
2997
+ const config = resolver?.prompt?.({
2492
2998
  items: listing.items,
2493
2999
  input: mergeInput(input, leaf.extraInput),
2494
3000
  context
@@ -2496,10 +3002,15 @@ function selectQuestion(leaf, input, listing, context) {
2496
3002
  const multiple = config?.type === "checkbox";
2497
3003
  return {
2498
3004
  type: "select",
2499
- message: config?.message ?? `Select ${leaf.name}:`,
3005
+ // A labeled field's title beats the resolver's message: per-field
3006
+ // resolvers are shared across fields (one choices-fetcher for every
3007
+ // field), so only the leaf knows which field is being asked.
3008
+ message: labeledMessage(leaf) ?? config?.message ?? `Select ${leaf.name}:`,
2500
3009
  choices: (config?.choices ?? []).map(toChoice),
2501
3010
  ...multiple ? { multiple: true } : {},
2502
3011
  ...config?.notes?.length ? { notes: config.notes } : {},
3012
+ ...listing.search !== void 0 ? { search: listing.search } : {},
3013
+ ...resolver?.placeholder ? { placeholder: resolver.placeholder } : {},
2503
3014
  actions: selectActions(leaf, listing, multiple)
2504
3015
  };
2505
3016
  }
@@ -2532,8 +3043,20 @@ function failedResult(state, name, error) {
2532
3043
  async function buildQuestion(leaf, input) {
2533
3044
  const optional = !leaf.required;
2534
3045
  const resolver = leaf.resolver;
2535
- if (resolver?.listItems) {
3046
+ if (resolver?.type === "dynamic") {
2536
3047
  const context = await resolveContext(leaf, input);
3048
+ if (resolver.inputType === "search") {
3049
+ const listing2 = {
3050
+ items: [],
3051
+ cursor: void 0,
3052
+ search: void 0,
3053
+ exhausted: true
3054
+ };
3055
+ return {
3056
+ question: selectQuestion(leaf, input, listing2, context),
3057
+ listing: listing2
3058
+ };
3059
+ }
2537
3060
  const listing = await fetchListing(leaf, input, { context });
2538
3061
  return {
2539
3062
  question: selectQuestion(leaf, input, listing, context),
@@ -2552,37 +3075,29 @@ async function buildQuestion(leaf, input) {
2552
3075
  }
2553
3076
  };
2554
3077
  }
2555
- const inputType = resolver?.inputType && resolver.inputType !== "search" ? resolver.inputType : "text";
3078
+ const textSource = resolver?.type === "static" ? resolver : void 0;
3079
+ const inputType = textSource?.inputType && textSource.inputType !== "search" ? textSource.inputType : "text";
2556
3080
  const actions = [AFFORDANCE.custom];
2557
3081
  if (optional) actions.push(AFFORDANCE.skip);
2558
3082
  return {
2559
3083
  question: {
2560
3084
  type: "input",
2561
- message: `Enter ${leaf.name}:`,
3085
+ // The optional marker makes Enter-to-pass discoverable on a bare
3086
+ // parameter; a labeled field carries its marker via labeledMessage.
3087
+ message: labeledMessage(leaf) ?? `Enter ${leaf.name}${optional ? " (optional)" : ""}:`,
2562
3088
  inputType,
2563
- placeholder: resolver?.placeholder,
3089
+ placeholder: textSource?.placeholder,
2564
3090
  actions
2565
3091
  }
2566
3092
  };
2567
3093
  }
2568
- function finalize(ctx, resolved) {
2569
- if (!ctx.schema) return { status: "done", value: resolved };
2570
- const parsed = ctx.schema.safeParse(resolved);
2571
- if (parsed.success) {
2572
- return { status: "done", value: parsed.data };
2573
- }
2574
- const issues = parsed.error.issues.map((i) => ({
2575
- parameter: i.path.map(String).join(".") || void 0,
2576
- message: i.message
2577
- }));
2578
- return { status: "invalid", issues };
2579
- }
2580
3094
  function collectionQuestion(t) {
2581
3095
  const actions = [AFFORDANCE.add];
2582
3096
  if (t.count >= t.min) actions.push(AFFORDANCE.done);
2583
3097
  return {
2584
3098
  type: "collection",
2585
- message: `Add another ${t.path[t.path.length - 1]}? (${t.count} so far)`,
3099
+ message: `Add ${t.path[t.path.length - 1]}[${t.count}]?`,
3100
+ container: "array",
2586
3101
  count: t.count,
2587
3102
  min: t.min,
2588
3103
  // An unbounded array's max is Infinity, which JSON.stringify turns to null;
@@ -2591,6 +3106,56 @@ function collectionQuestion(t) {
2591
3106
  actions
2592
3107
  };
2593
3108
  }
3109
+ function objectGateQuestion(path) {
3110
+ return {
3111
+ type: "collection",
3112
+ message: `Add ${path[path.length - 1]}?`,
3113
+ container: "object",
3114
+ actions: [
3115
+ {
3116
+ action: "add",
3117
+ description: "Provide values for these fields"
3118
+ },
3119
+ { action: "done", description: "Skip these fields" }
3120
+ ]
3121
+ };
3122
+ }
3123
+ function optionalsGateQuestion(pending) {
3124
+ return {
3125
+ type: "collection",
3126
+ // The prompt and its context ride separately so a host renders the info
3127
+ // line above the confirm without composing any text of its own.
3128
+ message: "Would you like to configure optional fields?",
3129
+ description: `There are ${pending.length} optional field(s) available.`,
3130
+ container: "object",
3131
+ // The gated fields' projection, so a smart host can show WHAT `add` would
3132
+ // walk (or render a form section) instead of a blind yes/no.
3133
+ fields: pending.map((leaf) => ({
3134
+ key: leaf.name,
3135
+ ...leaf.label ? { label: leaf.label } : {},
3136
+ ...leaf.valueType ? { valueType: leaf.valueType } : {}
3137
+ })),
3138
+ actions: [
3139
+ { action: "add", description: "Configure the optional fields" },
3140
+ { action: "done", description: "Skip the optional fields" }
3141
+ ]
3142
+ };
3143
+ }
3144
+
3145
+ // src/model/resolution/walk.ts
3146
+ function finalize(ctx, resolved) {
3147
+ if (!ctx.schema) return { status: "done", value: resolved };
3148
+ const parsed = ctx.schema.safeParse(resolved);
3149
+ if (parsed.success) {
3150
+ return { status: "done", value: parsed.data };
3151
+ }
3152
+ const issues = parsed.error.issues.map((i) => ({
3153
+ parameter: i.path.map(String).join(".") || void 0,
3154
+ message: i.message
3155
+ }));
3156
+ return { status: "invalid", issues };
3157
+ }
3158
+ var optionalsMarker = (path) => `${key(path)}?optionals`;
2594
3159
  async function findInArray(ctx, state, path) {
2595
3160
  if (isSettled(state, path)) return null;
2596
3161
  if (getAtPath(state.resolved, path) == null)
@@ -2604,8 +3169,11 @@ async function findInArray(ctx, state, path) {
2604
3169
  const len = items.length;
2605
3170
  const itemType = item.resolver?.type;
2606
3171
  if (len > 0 && (itemType === "object" || itemType === "array")) {
2607
- const inner = await findNext(ctx, state, [...path, len - 1]);
2608
- if (inner) return inner;
3172
+ const itemPath = [...path, len - 1];
3173
+ if (!isSettled(state, itemPath)) {
3174
+ const inner = await findNext(ctx, state, itemPath);
3175
+ if (inner) return inner;
3176
+ }
2609
3177
  }
2610
3178
  if (len < min) return descendItem(ctx, state, path, len, item);
2611
3179
  if (len < max) return { kind: "array", path, count: len, min, max };
@@ -2633,14 +3201,60 @@ async function descendItem(ctx, state, arrayPath, index, item) {
2633
3201
  }
2634
3202
  async function findNext(ctx, state, path = []) {
2635
3203
  const container = getAtPath(state.resolved, path) ?? {};
2636
- for (const leaf of await childrenAt(ctx, path, state.resolved)) {
3204
+ const children = await childrenAt(ctx, path, state.resolved);
3205
+ const inObject = path.length > 0;
3206
+ const ordered = inObject ? [
3207
+ ...children.filter((c) => c.required),
3208
+ ...children.filter((c) => !c.required)
3209
+ ] : children;
3210
+ const asksUser = (c) => c.resolver ? !autoSettles(c.resolver) : c.required;
3211
+ const isPendingChild = (c) => container[c.name] === void 0 && !isSettled(state, [...path, c.name]);
3212
+ const hasAskableRequired = children.some((c) => c.required && asksUser(c));
3213
+ for (const leaf of ordered) {
2637
3214
  const childPath = [...path, leaf.name];
2638
- if (!leaf.requires.every((r) => container[r] !== void 0)) continue;
3215
+ if (!leaf.requires.every(
3216
+ (r) => container[r] !== void 0 || getAtPath(state.resolved, [r]) !== void 0 || isSettled(state, [...path, r]) || isSettled(state, [r])
3217
+ )) {
3218
+ continue;
3219
+ }
3220
+ const inArrayItem = path.some((segment) => typeof segment === "number");
3221
+ 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))) {
3222
+ const pending = ordered.filter(
3223
+ (c) => !c.required && asksUser(c) && isPendingChild(c)
3224
+ );
3225
+ return { kind: "optionals", path, pending };
3226
+ }
2639
3227
  if (leaf.resolver?.type === "object") {
2640
- if (getAtPath(state.resolved, childPath) == null)
3228
+ if (isSettled(state, childPath)) continue;
3229
+ if (getAtPath(state.resolved, childPath) == null) {
3230
+ if (!leaf.required && !inArrayItem) {
3231
+ if (!state.interactive) {
3232
+ settle(state, childPath);
3233
+ continue;
3234
+ }
3235
+ return { kind: "object", path: childPath, leaf };
3236
+ }
2641
3237
  setAtPath(state.resolved, childPath, {});
3238
+ }
2642
3239
  const inner = await findNext(ctx, state, childPath);
2643
3240
  if (inner) return inner;
3241
+ if (!leaf.required) {
3242
+ const value = getAtPath(state.resolved, childPath);
3243
+ if (value !== null && typeof value === "object" && Object.keys(value).length === 0) {
3244
+ const grandchildren = await childrenAt(
3245
+ ctx,
3246
+ childPath,
3247
+ state.resolved
3248
+ );
3249
+ const pending = grandchildren.some(
3250
+ (c) => value[c.name] === void 0 && !isSettled(state, [...childPath, c.name])
3251
+ );
3252
+ if (!pending) {
3253
+ delete container[leaf.name];
3254
+ settle(state, childPath);
3255
+ }
3256
+ }
3257
+ }
2644
3258
  continue;
2645
3259
  }
2646
3260
  if (leaf.resolver?.type === "array") {
@@ -2656,6 +3270,7 @@ async function findNext(ctx, state, path = []) {
2656
3270
  }
2657
3271
  async function askLeaf(state, path, leaf, opts = {}) {
2658
3272
  state.current = path;
3273
+ delete state.gate;
2659
3274
  try {
2660
3275
  const { question, listing } = await buildQuestion(leaf, state.resolved);
2661
3276
  state.listing = listing;
@@ -2677,21 +3292,52 @@ async function advance(ctx, state) {
2677
3292
  const target = await findNext(ctx, state);
2678
3293
  if (!target) {
2679
3294
  delete state.current;
3295
+ delete state.gate;
2680
3296
  delete state.listing;
2681
3297
  return { state, result: finalize(ctx, state.resolved) };
2682
3298
  }
2683
3299
  if (target.kind === "array") {
2684
3300
  state.current = target.path;
3301
+ state.gate = "array";
2685
3302
  delete state.listing;
2686
3303
  return {
2687
3304
  state,
2688
3305
  result: { status: "ask", question: collectionQuestion(target) }
2689
3306
  };
2690
3307
  }
3308
+ if (target.kind === "object") {
3309
+ state.current = target.path;
3310
+ state.gate = "entry";
3311
+ delete state.listing;
3312
+ return {
3313
+ state,
3314
+ result: { status: "ask", question: objectGateQuestion(target.path) }
3315
+ };
3316
+ }
3317
+ if (target.kind === "optionals") {
3318
+ state.current = target.path;
3319
+ state.gate = "optionals";
3320
+ delete state.listing;
3321
+ return {
3322
+ state,
3323
+ result: {
3324
+ status: "ask",
3325
+ question: optionalsGateQuestion(target.pending)
3326
+ }
3327
+ };
3328
+ }
2691
3329
  const { path, leaf } = target;
2692
- const auto = await leaf.resolver?.tryResolveWithoutPrompt?.({
3330
+ const resolver = leaf.resolver;
3331
+ if (resolver && autoSettles(resolver)) {
3332
+ if (resolver.type === "constant") {
3333
+ setAtPath(state.resolved, path, resolver.value);
3334
+ }
3335
+ settle(state, path);
3336
+ continue;
3337
+ }
3338
+ const auto = resolver?.type === "dynamic" ? await resolver.tryResolveWithoutPrompt?.({
2693
3339
  input: mergeInput(state.resolved, leaf.extraInput)
2694
- });
3340
+ }) : void 0;
2695
3341
  if (auto) {
2696
3342
  if (auto.resolvedValue !== void 0)
2697
3343
  setAtPath(state.resolved, path, auto.resolvedValue);
@@ -2711,23 +3357,22 @@ async function advance(ctx, state) {
2711
3357
  }
2712
3358
  }
2713
3359
  async function start(ctx, input = {}, interactive = true) {
2714
- const resolved = {};
2715
- for (const spec of ctx.parameters) {
2716
- if (spec.resolver?.type === "constant")
2717
- resolved[spec.name] = spec.resolver.value;
2718
- }
2719
- Object.assign(resolved, input);
2720
- return advance(ctx, {
3360
+ const state = {
2721
3361
  method: ctx.method,
2722
- resolved,
3362
+ resolved: { ...input },
2723
3363
  settled: [],
2724
3364
  interactive
2725
- });
3365
+ };
3366
+ for (const [name, value] of Object.entries(input)) {
3367
+ if (value !== void 0) settle(state, [name]);
3368
+ }
3369
+ return advance(ctx, state);
2726
3370
  }
2727
3371
  async function step(ctx, prior, action) {
2728
3372
  const state = clone(prior);
2729
3373
  if (action.type === "cancel") {
2730
3374
  delete state.current;
3375
+ delete state.gate;
2731
3376
  delete state.listing;
2732
3377
  return { state, result: { status: "cancelled" } };
2733
3378
  }
@@ -2738,12 +3383,27 @@ async function step(ctx, prior, action) {
2738
3383
  return refine(ctx, state, leaf, path, action);
2739
3384
  }
2740
3385
  if (action.type === "add" || action.type === "done") {
3386
+ const gate = state.gate;
3387
+ if (!gate) {
3388
+ throw new Error(
3389
+ `action "${action.type}" is not supported here: no container decision is outstanding`
3390
+ );
3391
+ }
2741
3392
  delete state.current;
3393
+ delete state.gate;
2742
3394
  delete state.listing;
2743
3395
  if (action.type === "done") {
2744
3396
  settle(state, path);
2745
3397
  return advance(ctx, state);
2746
3398
  }
3399
+ if (gate === "entry") {
3400
+ setAtPath(state.resolved, path, {});
3401
+ return advance(ctx, state);
3402
+ }
3403
+ if (gate === "optionals") {
3404
+ remember(state, optionalsMarker(path));
3405
+ return advance(ctx, state);
3406
+ }
2747
3407
  const items = getAtPath(state.resolved, path) ?? [];
2748
3408
  const { item } = await arrayInfoAt(ctx, path, state.resolved);
2749
3409
  const itemPath = [...path, items.length];
@@ -2751,12 +3411,35 @@ async function step(ctx, prior, action) {
2751
3411
  return askLeaf(state, itemPath, item);
2752
3412
  return advance(ctx, state);
2753
3413
  }
3414
+ if (state.gate) {
3415
+ throw new Error(
3416
+ `action "${action.type}" is not supported here: a container decision (${state.gate}) is outstanding`
3417
+ );
3418
+ }
2754
3419
  switch (action.type) {
2755
3420
  case "choose":
2756
3421
  case "custom": {
2757
3422
  if (leaf) {
2758
3423
  const error = await validationError(leaf, action.value, state);
2759
- if (error) return askLeaf(state, path, leaf, { error });
3424
+ if (error) {
3425
+ if (state.listing && leaf.resolver?.type === "dynamic") {
3426
+ const context = await resolveContext(leaf, state.resolved);
3427
+ return {
3428
+ state,
3429
+ result: {
3430
+ status: "ask",
3431
+ question: selectQuestion(
3432
+ leaf,
3433
+ state.resolved,
3434
+ state.listing,
3435
+ context
3436
+ ),
3437
+ error
3438
+ }
3439
+ };
3440
+ }
3441
+ return askLeaf(state, path, leaf, { error });
3442
+ }
2760
3443
  }
2761
3444
  setAtPath(
2762
3445
  state.resolved,
@@ -2783,10 +3466,10 @@ async function refine(ctx, state, leaf, path, action) {
2783
3466
  };
2784
3467
  try {
2785
3468
  if (action.type === "search") {
2786
- const exact = await leaf.resolver?.tryResolveFromSearch?.({
3469
+ const exact = leaf.resolver?.type === "dynamic" ? await leaf.resolver.tryResolveFromSearch?.({
2787
3470
  input: mergeInput(state.resolved, leaf.extraInput),
2788
3471
  search: action.term
2789
- });
3472
+ }) : void 0;
2790
3473
  if (exact) {
2791
3474
  setAtPath(state.resolved, path, coerce(leaf, exact.resolvedValue));
2792
3475
  delete state.current;
@@ -2837,10 +3520,11 @@ function projectMethod(entry) {
2837
3520
  const inputProperties = toJsonSchema(entry.inputSchema)?.properties;
2838
3521
  const parameters = {};
2839
3522
  for (const spec of planParameters(entry).parameters) {
3523
+ const dynamic = spec.resolver?.type === "dynamic" ? spec.resolver : void 0;
2840
3524
  parameters[spec.name] = {
2841
3525
  required: spec.required,
2842
- dynamic: Boolean(spec.resolver?.listItems),
2843
- ...spec.resolver?.inputType === "search" ? { searchable: true } : {},
3526
+ dynamic: Boolean(dynamic),
3527
+ ...dynamic?.inputType === "search" ? { searchable: true } : {},
2844
3528
  ...inputProperties?.[spec.name] ? { schema: inputProperties[spec.name] } : {},
2845
3529
  ...spec.staticChoices ? { choices: spec.staticChoices } : {},
2846
3530
  ...spec.requires.length ? { requireParameters: spec.requires } : {}
@@ -2907,12 +3591,13 @@ function createController(sdk) {
2907
3591
  const spec = contextFor(method).parameters.find(
2908
3592
  (p) => p.name === parameter
2909
3593
  );
2910
- if (!spec?.resolver?.listItems) return { data: [] };
2911
- const context = await spec.resolver.getContext?.({ input });
3594
+ const dynamic = spec?.resolver?.type === "dynamic" ? spec.resolver : void 0;
3595
+ if (!dynamic) return { data: [] };
3596
+ const context = await dynamic.getContext?.({ input });
2912
3597
  const page = await firstPage(
2913
- spec.resolver.listItems({ input, context, search, cursor })
3598
+ dynamic.listItems({ input, context, search, cursor })
2914
3599
  );
2915
- const config = spec.resolver.prompt?.({ items: page.data, input, context });
3600
+ const config = dynamic.prompt?.({ items: page.data, input, context });
2916
3601
  const data = (config?.choices ?? []).map(toChoice);
2917
3602
  return { data, nextCursor: page.nextCursor };
2918
3603
  };
@@ -2921,6 +3606,9 @@ function createController(sdk) {
2921
3606
 
2922
3607
  // src/utils/core-plugin.ts
2923
3608
  function createCorePlugin(options) {
3609
+ logDeprecation(
3610
+ "createCorePlugin() is deprecated. Inject the options under CORE_OPTIONS_ID via createSdk's configuration instead."
3611
+ );
2924
3612
  return () => ({
2925
3613
  context: {
2926
3614
  core: options
@@ -2984,14 +3672,17 @@ function openEnum(values, description) {
2984
3672
  0 && (module.exports = {
2985
3673
  CONTEXT,
2986
3674
  CORE_ERROR_SYMBOL,
3675
+ CORE_OPTIONS_ID,
2987
3676
  CORE_SIGNAL_SYMBOL,
2988
3677
  CoreCancelledSignal,
3678
+ CoreDisposeError,
2989
3679
  CoreError,
2990
3680
  CoreErrorCode,
2991
3681
  CoreSignal,
2992
3682
  addPlugin,
2993
3683
  composePlugins,
2994
3684
  concatPaginated,
3685
+ coreOptionsPluginRef,
2995
3686
  createAsyncContext,
2996
3687
  createController,
2997
3688
  createCoreError,
@@ -3007,15 +3698,20 @@ function openEnum(values, description) {
3007
3698
  createValidator,
3008
3699
  dangerousContextPlugin,
3009
3700
  declareMethod,
3701
+ declareOptionalProperty,
3010
3702
  declarePlugin,
3011
3703
  declareProperty,
3012
3704
  decodeIncomingCursor,
3705
+ defaultLogDeprecation,
3013
3706
  defineFormatter,
3707
+ defineHook,
3014
3708
  defineLegacyMerge,
3015
3709
  defineMethod,
3710
+ defineMethodOverride,
3016
3711
  definePlugin,
3017
3712
  defineProperty,
3018
3713
  defineResolver,
3714
+ disposeSdk,
3019
3715
  fromFunctionPlugin,
3020
3716
  getContext,
3021
3717
  getCoreErrorCause,
@@ -3031,10 +3727,12 @@ function openEnum(values, description) {
3031
3727
  isNestedMethodCall,
3032
3728
  isPositional,
3033
3729
  isTelemetryNested,
3730
+ omitExports,
3034
3731
  openEnum,
3035
3732
  paginate,
3036
3733
  paginateBuffered,
3037
3734
  paginateMaxItems,
3735
+ resolvePlugin,
3038
3736
  runInMethodScope,
3039
3737
  runWithTelemetryContext,
3040
3738
  selectExports,