@zapier/kitcore 0.10.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -49,7 +49,9 @@ __export(index_exports, {
49
49
  createSdk: () => createSdk,
50
50
  createValidator: () => createValidator,
51
51
  dangerousContextPlugin: () => dangerousContextPlugin,
52
+ declareDefault: () => declareDefault,
52
53
  declareMethod: () => declareMethod,
54
+ declareOptionalMethod: () => declareOptionalMethod,
53
55
  declareOptionalProperty: () => declareOptionalProperty,
54
56
  declarePlugin: () => declarePlugin,
55
57
  declareProperty: () => declareProperty,
@@ -299,12 +301,19 @@ function composeVoid(existing, added) {
299
301
  isolated.add(composed);
300
302
  return composed;
301
303
  }
304
+ function composeAnnotators(existing, added) {
305
+ if (!existing) return added;
306
+ if (!added) return existing;
307
+ return (ctx) => ({ ...existing(ctx), ...added(ctx) });
308
+ }
302
309
  function buildHooks(existing, added) {
303
310
  const result = {};
304
311
  const start2 = composeVoid(existing.onMethodStart, added.onMethodStart);
305
312
  if (start2) result.onMethodStart = start2;
306
313
  const end = composeVoid(existing.onMethodEnd, added.onMethodEnd);
307
314
  if (end) result.onMethodEnd = end;
315
+ const annotator = composeAnnotators(existing.annotator, added.annotator);
316
+ if (annotator) result.annotator = annotator;
308
317
  return result;
309
318
  }
310
319
 
@@ -777,11 +786,14 @@ function generateCallId() {
777
786
  }
778
787
  return null;
779
788
  }
780
- function rootCallContext() {
789
+ function rootCallContext({
790
+ callOrigin = "surface"
791
+ } = {}) {
781
792
  return {
782
793
  callId: generateCallId(),
783
794
  depth: 0,
784
795
  annotations: {},
796
+ callOrigin,
785
797
  [CALL_CONTEXT_BRAND]: true
786
798
  };
787
799
  }
@@ -790,6 +802,7 @@ function childCallContext(parent) {
790
802
  callId: parent.callId,
791
803
  depth: parent.depth + 1,
792
804
  annotations: {},
805
+ callOrigin: parent.callOrigin,
793
806
  [CALL_CONTEXT_BRAND]: true
794
807
  };
795
808
  }
@@ -815,6 +828,28 @@ var INTERNAL_CALL = Symbol("kitcore.internalCall");
815
828
  function resolveCallContext(secondArg) {
816
829
  return isCallContext(secondArg) ? secondArg : rootCallContext();
817
830
  }
831
+ var hookAnnotatorReentrancy = 0;
832
+ function applyAnnotations({
833
+ context,
834
+ methodName,
835
+ input,
836
+ hookAnnotator,
837
+ methodAnnotator
838
+ }) {
839
+ if (hookAnnotator && !isInsideObserver() && context.depth === 0 && context.callOrigin !== "internal" && hookAnnotatorReentrancy === 0) {
840
+ hookAnnotatorReentrancy++;
841
+ try {
842
+ Object.assign(context.annotations, hookAnnotator({ methodName, input }));
843
+ } catch {
844
+ } finally {
845
+ hookAnnotatorReentrancy--;
846
+ }
847
+ }
848
+ try {
849
+ Object.assign(context.annotations, methodAnnotator?.(input));
850
+ } catch {
851
+ }
852
+ }
818
853
  function signalDeprecation(context, methodName, getDeprecation) {
819
854
  if (isInsideObserver()) return;
820
855
  const deprecation = getDeprecation?.();
@@ -840,7 +875,7 @@ function normalizeError(error, adaptError) {
840
875
  );
841
876
  }
842
877
  function createFunction(coreFn, options) {
843
- const { sdk, schema, name, getDeprecation } = options;
878
+ const { sdk, schema, name, annotator, getDeprecation } = options;
844
879
  const functionName = name || coreFn.name;
845
880
  const namedFunctions = {
846
881
  [functionName]: async function(callOptions) {
@@ -854,14 +889,26 @@ function createFunction(coreFn, options) {
854
889
  const normalizedOptions = callOptions ?? {};
855
890
  const args = [normalizedOptions];
856
891
  const depth = Math.max(context.depth, getCurrentDepth());
857
- const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
892
+ const insideObserver = isInsideObserver();
893
+ const hooks = insideObserver ? void 0 : sdk.context.hooks;
858
894
  const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
859
- hooks?.onMethodStart?.({
895
+ applyAnnotations({
896
+ context,
897
+ methodName: functionName,
898
+ input: normalizedOptions,
899
+ hookAnnotator: hooks?.annotator,
900
+ methodAnnotator: annotator
901
+ });
902
+ const hookBase = {
860
903
  methodName: functionName,
861
904
  args,
862
905
  isPaginated: false,
863
- depth
864
- });
906
+ depth,
907
+ callId: context.callId,
908
+ callOrigin: context.callOrigin,
909
+ annotations: context.annotations
910
+ };
911
+ hooks?.onMethodStart?.({ ...hookBase });
865
912
  try {
866
913
  let result;
867
914
  if (schema) {
@@ -883,20 +930,14 @@ function createFunction(coreFn, options) {
883
930
  result = await coreFn(normalizedOptions, context);
884
931
  }
885
932
  hooks?.onMethodEnd?.({
886
- methodName: functionName,
887
- args,
888
- isPaginated: false,
889
- depth,
933
+ ...hookBase,
890
934
  durationMs: Date.now() - startTime
891
935
  });
892
936
  return result;
893
937
  } catch (error) {
894
938
  const normalizedError = normalizeError(error, adaptError);
895
939
  hooks?.onMethodEnd?.({
896
- methodName: functionName,
897
- args,
898
- isPaginated: false,
899
- depth,
940
+ ...hookBase,
900
941
  durationMs: Date.now() - startTime,
901
942
  error: normalizedError
902
943
  });
@@ -908,7 +949,7 @@ function createFunction(coreFn, options) {
908
949
  return namedFunctions[functionName];
909
950
  }
910
951
  function createRawFunction(coreFn, options) {
911
- const { sdk, name, schema, positional, getDeprecation } = options;
952
+ const { sdk, name, schema, positional, annotator, getDeprecation } = options;
912
953
  return function(rawInput) {
913
954
  const internal = arguments[1];
914
955
  const context = resolveCallContext(internal);
@@ -918,23 +959,32 @@ function createRawFunction(coreFn, options) {
918
959
  return runInMethodScope(() => {
919
960
  const startTime = Date.now();
920
961
  const depth = Math.max(context.depth, getCurrentDepth());
921
- const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
962
+ const insideObserver = isInsideObserver();
963
+ const hooks = insideObserver ? void 0 : sdk.context.hooks;
922
964
  const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
923
965
  const input = schema ? rawInput ?? {} : rawInput;
966
+ applyAnnotations({
967
+ context,
968
+ methodName: name,
969
+ input,
970
+ hookAnnotator: hooks?.annotator,
971
+ methodAnnotator: annotator
972
+ });
924
973
  const record = input;
925
974
  const args = positional ? positional.filter((key) => record?.[key] !== void 0).map((key) => record?.[key]) : [input];
926
- hooks?.onMethodStart?.({
975
+ const hookBase = {
927
976
  methodName: name,
928
977
  args,
929
978
  isPaginated: false,
930
- depth
931
- });
979
+ depth,
980
+ callId: context.callId,
981
+ callOrigin: context.callOrigin,
982
+ annotations: context.annotations
983
+ };
984
+ hooks?.onMethodStart?.({ ...hookBase });
932
985
  const fireEnd = (error) => {
933
986
  hooks?.onMethodEnd?.({
934
- methodName: name,
935
- args,
936
- isPaginated: false,
937
- depth,
987
+ ...hookBase,
938
988
  durationMs: Date.now() - startTime,
939
989
  ...error ? { error } : {}
940
990
  });
@@ -976,7 +1026,8 @@ function isSdkPage(value) {
976
1026
  }
977
1027
  function createPageFunction(coreFn, {
978
1028
  sdk,
979
- adaptPage
1029
+ adaptPage,
1030
+ finalizePage
980
1031
  }) {
981
1032
  const functionName = coreFn.name + "Page";
982
1033
  const namedFunctions = {
@@ -989,7 +1040,7 @@ function createPageFunction(coreFn, {
989
1040
  `${functionName}: paginated result must be exactly { data: TItem[], nextCursor? } (produced by the handler or its \`adaptPage\`); got keys [${page && typeof page === "object" ? Object.keys(page).join(", ") : typeof page}]. If the handler returns a raw shape, set \`adaptPage\` to translate it; if \`adaptPage\` already runs, it must return only \`data\`/\`nextCursor\`.`
990
1041
  );
991
1042
  }
992
- return page;
1043
+ return finalizePage ? finalizePage(page) : page;
993
1044
  } catch (error) {
994
1045
  throw normalizeError(
995
1046
  error,
@@ -1001,8 +1052,21 @@ function createPageFunction(coreFn, {
1001
1052
  return namedFunctions[functionName];
1002
1053
  }
1003
1054
  function createPaginatedFunction(coreFn, options) {
1004
- const { sdk, schema, name, defaultPageSize, adaptPage, getDeprecation } = options;
1005
- const pageFunction = createPageFunction(coreFn, { sdk, adaptPage });
1055
+ const {
1056
+ sdk,
1057
+ schema,
1058
+ name,
1059
+ defaultPageSize,
1060
+ adaptPage,
1061
+ annotator,
1062
+ finalizePage,
1063
+ getDeprecation
1064
+ } = options;
1065
+ const pageFunction = createPageFunction(coreFn, {
1066
+ sdk,
1067
+ adaptPage,
1068
+ finalizePage
1069
+ });
1006
1070
  const functionName = name || coreFn.name;
1007
1071
  const namedFunctions = {
1008
1072
  [functionName]: function(callOptions) {
@@ -1016,14 +1080,26 @@ function createPaginatedFunction(coreFn, options) {
1016
1080
  const normalizedOptions = callOptions ?? {};
1017
1081
  const args = [normalizedOptions];
1018
1082
  const depth = Math.max(context.depth, getCurrentDepth());
1019
- const hooks = isInsideObserver() ? void 0 : sdk.context.hooks;
1083
+ const insideObserver = isInsideObserver();
1084
+ const hooks = insideObserver ? void 0 : sdk.context.hooks;
1020
1085
  const adaptError = resolveCoreOptions(sdk.context)?.adaptError;
1021
- hooks?.onMethodStart?.({
1086
+ applyAnnotations({
1087
+ context,
1088
+ methodName: functionName,
1089
+ input: normalizedOptions,
1090
+ hookAnnotator: hooks?.annotator,
1091
+ methodAnnotator: annotator
1092
+ });
1093
+ const hookBase = {
1022
1094
  methodName: functionName,
1023
1095
  args,
1024
1096
  isPaginated: true,
1025
- depth
1026
- });
1097
+ depth,
1098
+ callId: context.callId,
1099
+ callOrigin: context.callOrigin,
1100
+ annotations: context.annotations
1101
+ };
1102
+ hooks?.onMethodStart?.({ ...hookBase });
1027
1103
  try {
1028
1104
  const validatedOptions = {
1029
1105
  ...normalizedOptions,
@@ -1049,19 +1125,13 @@ function createPaginatedFunction(coreFn, options) {
1049
1125
  firstPagePromise.then(
1050
1126
  () => {
1051
1127
  hooks.onMethodEnd({
1052
- methodName: functionName,
1053
- args,
1054
- isPaginated: true,
1055
- depth,
1128
+ ...hookBase,
1056
1129
  durationMs: Date.now() - startTime
1057
1130
  });
1058
1131
  },
1059
1132
  (error) => {
1060
1133
  hooks.onMethodEnd({
1061
- methodName: functionName,
1062
- args,
1063
- isPaginated: true,
1064
- depth,
1134
+ ...hookBase,
1065
1135
  durationMs: Date.now() - startTime,
1066
1136
  error: error instanceof Error ? error : new Error(String(error))
1067
1137
  });
@@ -1100,10 +1170,7 @@ function createPaginatedFunction(coreFn, options) {
1100
1170
  } catch (error) {
1101
1171
  const normalizedError = normalizeError(error, adaptError);
1102
1172
  hooks?.onMethodEnd?.({
1103
- methodName: functionName,
1104
- args,
1105
- isPaginated: true,
1106
- depth,
1173
+ ...hookBase,
1107
1174
  durationMs: Date.now() - startTime,
1108
1175
  error: normalizedError
1109
1176
  });
@@ -1525,9 +1592,11 @@ function defineMethod(config) {
1525
1592
  importBindings: deps.bindings,
1526
1593
  inputSchema: config.inputSchema,
1527
1594
  skipInputValidation: config.skipInputValidation,
1595
+ skipOutputValidation: config.skipOutputValidation,
1528
1596
  meta: collectLeafMeta(config),
1529
1597
  resolvers: config.resolvers,
1530
1598
  formatter: config.formatter,
1599
+ annotator: config.annotator,
1531
1600
  output: config.output,
1532
1601
  positional: config.positional,
1533
1602
  setup: config.setup,
@@ -1630,6 +1699,29 @@ function declareMethod(config) {
1630
1699
  }
1631
1700
  };
1632
1701
  }
1702
+ function declareOptionalMethod(config) {
1703
+ const { name, namespace } = parseId(config.id);
1704
+ const id = makeId(name, namespace);
1705
+ return {
1706
+ pluginType: "method",
1707
+ name,
1708
+ namespace,
1709
+ id,
1710
+ standIn: true,
1711
+ optional: true,
1712
+ imports: [],
1713
+ importBindings: [],
1714
+ run: () => {
1715
+ throw new Error(
1716
+ `Plugin "${id}" is an optional stand-in (declareOptionalMethod) with no implementation. Its binding is \`undefined\` unless a real plugin is registered under this id.`
1717
+ );
1718
+ }
1719
+ // Requires nothing (phantom carrier `<never, never>`): a consumer that
1720
+ // imports it still passes `createSdk`'s completeness check unprovided. The
1721
+ // `optional: true` literal drives `PluginSurface` to type the binding
1722
+ // `| undefined`.
1723
+ };
1724
+ }
1633
1725
  function defineProperty(config) {
1634
1726
  const deps = normalizeImports(config.imports);
1635
1727
  return {
@@ -1675,6 +1767,11 @@ function declareOptionalProperty(config) {
1675
1767
  // import binding is still typed `TValue | undefined` from the descriptor.
1676
1768
  };
1677
1769
  }
1770
+ function declareDefault({
1771
+ plugin
1772
+ }) {
1773
+ return { ...plugin, defaultSource: plugin };
1774
+ }
1678
1775
  function defineHook(config) {
1679
1776
  const deps = normalizeImports(config.imports);
1680
1777
  return {
@@ -1687,7 +1784,8 @@ function defineHook(config) {
1687
1784
  setup: config.setup,
1688
1785
  dispose: config.dispose,
1689
1786
  wrap: config.wrap,
1690
- observe: config.observe
1787
+ observe: config.observe,
1788
+ annotator: config.annotator
1691
1789
  };
1692
1790
  }
1693
1791
  function declarePlugin(config) {
@@ -1975,6 +2073,97 @@ var getRegistryPlugin = defineMethod({
1975
2073
  run: ({ imports, input }) => buildSurfaceRegistry(imports.context, input?.package)
1976
2074
  });
1977
2075
 
2076
+ // src/utils/output-policy.ts
2077
+ function isRecord(value) {
2078
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2079
+ }
2080
+ function diffDroppedPaths(raw, parsed, prefix = "") {
2081
+ const paths = [];
2082
+ walkDroppedPaths(raw, parsed, prefix, paths);
2083
+ return paths;
2084
+ }
2085
+ function walkDroppedPaths(raw, parsed, prefix, out) {
2086
+ if (Array.isArray(raw) && Array.isArray(parsed)) {
2087
+ const seen = /* @__PURE__ */ new Set();
2088
+ const length = Math.min(raw.length, parsed.length);
2089
+ for (let index = 0; index < length; index++) {
2090
+ const elementPaths = [];
2091
+ walkDroppedPaths(raw[index], parsed[index], `${prefix}[]`, elementPaths);
2092
+ for (const path of elementPaths) {
2093
+ if (seen.has(path)) continue;
2094
+ seen.add(path);
2095
+ out.push(path);
2096
+ }
2097
+ }
2098
+ return;
2099
+ }
2100
+ if (isRecord(raw) && isRecord(parsed)) {
2101
+ for (const key of Object.keys(raw)) {
2102
+ const path = prefix ? `${prefix}.${key}` : key;
2103
+ if (!(key in parsed)) {
2104
+ out.push(path);
2105
+ continue;
2106
+ }
2107
+ walkDroppedPaths(raw[key], parsed[key], path, out);
2108
+ }
2109
+ return;
2110
+ }
2111
+ }
2112
+ function parseOutput(schema, value, policy, locator) {
2113
+ const result = schema.safeParse(value);
2114
+ if (result.success) return result.data;
2115
+ const issues = result.error.issues.map((issue) => {
2116
+ const path = issue.path.length > 0 ? issue.path.join(".") : "data";
2117
+ return `${path}: ${issue.message}`;
2118
+ });
2119
+ const subject = policy.methodName ? ` for "${policy.methodName}"` : "";
2120
+ const at = locator ? ` at ${locator}` : "";
2121
+ throw createCoreError(
2122
+ {
2123
+ code: CoreErrorCode.Validation,
2124
+ message: `Output validation failed${subject}${at}:
2125
+ ${issues.join("\n ")}
2126
+
2127
+ The response does not match the method's \`outputSchema\`. Correct the schema, or set \`skipOutputValidation: true\` on the method to pass the response through unvalidated.`,
2128
+ details: { zodErrors: result.error.issues, output: value }
2129
+ },
2130
+ policy.adaptError
2131
+ );
2132
+ }
2133
+ function applyItemOutputPolicy(result, policy) {
2134
+ const schema = policy.outputSchema;
2135
+ if (!schema || policy.skipOutputValidation) return result;
2136
+ if (!isRecord(result) || !("data" in result)) return result;
2137
+ const data = parseOutput(schema, result.data, policy);
2138
+ const next = { ...result, data };
2139
+ if (policy.includeOutputValidationDroppedPaths) {
2140
+ const droppedPaths = diffDroppedPaths(result.data, data);
2141
+ if (droppedPaths.length > 0) {
2142
+ next.meta = withOutputValidation(result.meta, droppedPaths);
2143
+ }
2144
+ }
2145
+ return next;
2146
+ }
2147
+ function withOutputValidation(existing, droppedPaths) {
2148
+ const base = isRecord(existing) ? existing : {};
2149
+ return { ...base, outputValidation: { droppedPaths } };
2150
+ }
2151
+ function applyListOutputPolicy(page, policy) {
2152
+ const schema = policy.outputSchema;
2153
+ if (!schema || policy.skipOutputValidation) return page;
2154
+ const data = page.data.map(
2155
+ (item, index) => parseOutput(schema, item, policy, `data[${index}]`)
2156
+ );
2157
+ const next = { ...page, data };
2158
+ if (policy.includeOutputValidationDroppedPaths) {
2159
+ const droppedPaths = diffDroppedPaths(page.data, data);
2160
+ if (droppedPaths.length > 0) {
2161
+ next.meta = { ...page.meta, outputValidation: { droppedPaths } };
2162
+ }
2163
+ }
2164
+ return next;
2165
+ }
2166
+
1978
2167
  // src/model/materialize.ts
1979
2168
  var FRAMEWORK_CONFIGURATION_IDS = /* @__PURE__ */ new Set([
1980
2169
  CORE_OPTIONS_ID
@@ -2038,6 +2227,9 @@ function edgesOf(plugin) {
2038
2227
  function isStandIn(plugin) {
2039
2228
  return (plugin.pluginType === "method" || plugin.pluginType === "property" || plugin.pluginType === "aggregate") && plugin.standIn === true;
2040
2229
  }
2230
+ function isDefault(plugin) {
2231
+ return (plugin.pluginType === "method" || plugin.pluginType === "property") && plugin.defaultSource !== void 0;
2232
+ }
2041
2233
  function topoOrder(descriptors) {
2042
2234
  const order = [];
2043
2235
  const visited = /* @__PURE__ */ new Set();
@@ -2052,28 +2244,89 @@ function topoOrder(descriptors) {
2052
2244
  return order;
2053
2245
  }
2054
2246
  function collectPlugins(root, materialized = /* @__PURE__ */ new Set(), configuration) {
2247
+ const rank = (plugin) => isStandIn(plugin) ? 0 : isDefault(plugin) ? 1 : 2;
2248
+ const allNodes = [];
2249
+ const seen = /* @__PURE__ */ new Set();
2250
+ const collect = (plugin) => {
2251
+ if (materialized.has(plugin.id) || seen.has(plugin)) return;
2252
+ seen.add(plugin);
2253
+ allNodes.push(plugin);
2254
+ for (const edge of edgesOf(plugin)) collect(edge);
2255
+ };
2256
+ collect(root);
2257
+ const childrenOf = /* @__PURE__ */ new Map();
2258
+ const candidatesById = /* @__PURE__ */ new Map();
2259
+ for (const node of allNodes) {
2260
+ childrenOf.set(
2261
+ node,
2262
+ edgesOf(node).filter((edge) => seen.has(edge))
2263
+ );
2264
+ const candidates = candidatesById.get(node.id);
2265
+ if (candidates) candidates.push(node);
2266
+ else candidatesById.set(node.id, [node]);
2267
+ }
2268
+ const live = new Set(allNodes);
2269
+ for (; ; ) {
2270
+ const reachable = /* @__PURE__ */ new Set();
2271
+ if (live.has(root)) reachable.add(root);
2272
+ const queue = reachable.has(root) ? [root] : [];
2273
+ while (queue.length) {
2274
+ const node = queue.pop();
2275
+ for (const child of childrenOf.get(node) ?? []) {
2276
+ if (reachable.has(child)) continue;
2277
+ reachable.add(child);
2278
+ if (live.has(child)) queue.push(child);
2279
+ }
2280
+ }
2281
+ let changed = false;
2282
+ for (const node of live) {
2283
+ if (!reachable.has(node)) {
2284
+ live.delete(node);
2285
+ changed = true;
2286
+ }
2287
+ }
2288
+ for (const candidates of candidatesById.values()) {
2289
+ let maxRank = -1;
2290
+ for (const candidate of candidates) {
2291
+ if (live.has(candidate)) maxRank = Math.max(maxRank, rank(candidate));
2292
+ }
2293
+ if (maxRank < 0) continue;
2294
+ for (const candidate of candidates) {
2295
+ if (live.has(candidate) && rank(candidate) < maxRank) {
2296
+ live.delete(candidate);
2297
+ changed = true;
2298
+ }
2299
+ }
2300
+ }
2301
+ if (!changed) break;
2302
+ }
2055
2303
  const byId = /* @__PURE__ */ new Map();
2056
- const visit = (plugin) => {
2057
- if (materialized.has(plugin.id)) return;
2058
- const existing = byId.get(plugin.id);
2059
- if (existing === plugin) return;
2060
- if (existing) {
2061
- const bothReal = !isStandIn(existing) && !isStandIn(plugin);
2062
- if (bothReal) {
2304
+ const conflictedDefaults = /* @__PURE__ */ new Set();
2305
+ const isOptional = (plugin) => "optional" in plugin && plugin.optional === true;
2306
+ for (const [id, candidates] of candidatesById) {
2307
+ const liveCandidates = candidates.filter(
2308
+ (candidate) => live.has(candidate)
2309
+ );
2310
+ const winner = liveCandidates.find((candidate) => !isOptional(candidate)) ?? liveCandidates[0];
2311
+ if (!winner) continue;
2312
+ if (liveCandidates.length > 1) {
2313
+ const winnerRank = rank(winner);
2314
+ if (winnerRank === 2) {
2063
2315
  throw new Error(
2064
- `createSdk: duplicate plugin id "${plugin.id}". Two different plugins registered under the same id.`
2316
+ `createSdk: duplicate plugin id "${id}". Two different plugins registered under the same id.`
2065
2317
  );
2066
2318
  }
2067
- if (isStandIn(existing) && !isStandIn(plugin)) {
2068
- byId.set(plugin.id, plugin);
2069
- for (const edge of edgesOf(plugin)) visit(edge);
2319
+ if (winnerRank === 1) {
2320
+ const sources = new Set(
2321
+ liveCandidates.map(
2322
+ (candidate) => candidate.defaultSource
2323
+ )
2324
+ );
2325
+ if (sources.size > 1) conflictedDefaults.add(id);
2070
2326
  }
2071
- return;
2072
2327
  }
2073
- byId.set(plugin.id, plugin);
2074
- for (const edge of edgesOf(plugin)) visit(edge);
2075
- };
2076
- visit(root);
2328
+ byId.set(id, winner);
2329
+ }
2077
2330
  if (configuration) {
2078
2331
  for (const [id, value] of Object.entries(configuration)) {
2079
2332
  const existing = byId.get(id);
@@ -2124,9 +2377,24 @@ function collectPlugins(root, materialized = /* @__PURE__ */ new Set(), configur
2124
2377
  );
2125
2378
  }
2126
2379
  }
2380
+ for (const id of conflictedDefaults) {
2381
+ const winner = byId.get(id);
2382
+ if (winner && isDefault(winner)) {
2383
+ throw new Error(
2384
+ `createSdk: conflicting defaults for "${id}". Two different plugins were declared as defaults for the same id and nothing else provides it. Register an explicit (non-default) plugin for this id to choose the winner, or give the implementations distinct ids if they are meant to coexist.`
2385
+ );
2386
+ }
2387
+ }
2127
2388
  return byId;
2128
2389
  }
2129
- function bindValue(target, key, entry, callType = "surface", ctx) {
2390
+ function bindValue({
2391
+ target,
2392
+ key,
2393
+ entry,
2394
+ bindMode = "surface",
2395
+ ctx,
2396
+ frameworkOrigin = false
2397
+ }) {
2130
2398
  if (entry.pluginType === "property" && entry.getValue) {
2131
2399
  Object.defineProperty(target, key, {
2132
2400
  get: entry.getValue,
@@ -2134,7 +2402,7 @@ function bindValue(target, key, entry, callType = "surface", ctx) {
2134
2402
  configurable: true
2135
2403
  });
2136
2404
  } else {
2137
- const value = callType === "internal" && entry.pluginType === "method" ? entry.bindInternal?.(ctx) ?? entry.internalValue ?? entry.value : entry.value;
2405
+ const value = bindMode === "internal" && entry.pluginType === "method" ? entry.bindInternal?.({ ctx, frameworkOrigin }) ?? entry.internalValue ?? entry.value : entry.value;
2138
2406
  Object.defineProperty(target, key, {
2139
2407
  value,
2140
2408
  writable: true,
@@ -2152,7 +2420,12 @@ function buildSurface(context, ...maps) {
2152
2420
  sdk[CONTEXT] = context;
2153
2421
  return sdk;
2154
2422
  }
2155
- function buildImports(plugins, importBindings, ctx) {
2423
+ function buildImports({
2424
+ plugins,
2425
+ importBindings,
2426
+ ctx,
2427
+ frameworkOrigin = false
2428
+ }) {
2156
2429
  const imports = {};
2157
2430
  for (const { binding, id, optional } of importBindings) {
2158
2431
  const entry = plugins[id];
@@ -2165,10 +2438,31 @@ function buildImports(plugins, importBindings, ctx) {
2165
2438
  });
2166
2439
  continue;
2167
2440
  }
2168
- bindValue(imports, binding, entry, "internal", ctx);
2441
+ bindValue({
2442
+ target: imports,
2443
+ key: binding,
2444
+ entry,
2445
+ bindMode: "internal",
2446
+ ctx,
2447
+ frameworkOrigin
2448
+ });
2169
2449
  }
2170
2450
  return imports;
2171
2451
  }
2452
+ function bindInternalTwin({
2453
+ ctx,
2454
+ frameworkOrigin,
2455
+ withContext,
2456
+ internalValue
2457
+ }) {
2458
+ if (ctx) {
2459
+ return (...args) => withContext(childCallContext(ctx))(...args);
2460
+ }
2461
+ if (frameworkOrigin) {
2462
+ return (...args) => withContext(rootCallContext({ callOrigin: "internal" }))(...args);
2463
+ }
2464
+ return internalValue;
2465
+ }
2172
2466
  function mirrorLegacyRootKeys(context, rootKeys, meta) {
2173
2467
  const exports2 = {};
2174
2468
  for (const [name, value] of Object.entries(rootKeys)) {
@@ -2232,7 +2526,11 @@ function bindResolver(resolver, plugins) {
2232
2526
  case "info":
2233
2527
  return { type: "info", text: resolver.text };
2234
2528
  case "object": {
2235
- const imports = buildImports(plugins, resolver.importBindings);
2529
+ const imports = buildImports({
2530
+ plugins,
2531
+ importBindings: resolver.importBindings,
2532
+ frameworkOrigin: true
2533
+ });
2236
2534
  const bound = {
2237
2535
  type: "object",
2238
2536
  requireParameters: resolver.requireParameters
@@ -2273,7 +2571,11 @@ function bindResolver(resolver, plugins) {
2273
2571
  return bound;
2274
2572
  }
2275
2573
  case "dynamic": {
2276
- const imports = buildImports(plugins, resolver.importBindings);
2574
+ const imports = buildImports({
2575
+ plugins,
2576
+ importBindings: resolver.importBindings,
2577
+ frameworkOrigin: true
2578
+ });
2277
2579
  const {
2278
2580
  getContext: getContext2,
2279
2581
  listItems,
@@ -2328,7 +2630,11 @@ function bindDefinitions(definitions, plugins) {
2328
2630
  return out;
2329
2631
  }
2330
2632
  function bindFormatter(formatter, plugins) {
2331
- const imports = buildImports(plugins, formatter.importBindings);
2633
+ const imports = buildImports({
2634
+ plugins,
2635
+ importBindings: formatter.importBindings,
2636
+ frameworkOrigin: true
2637
+ });
2332
2638
  const bound = { format: formatter.format };
2333
2639
  const { getContext: getContext2 } = formatter;
2334
2640
  if (getContext2)
@@ -2402,6 +2708,7 @@ function buildMethodEntries(descriptors, context, states) {
2402
2708
  const plugins = context.plugins;
2403
2709
  for (const [id, descriptor] of descriptors) {
2404
2710
  if (descriptor.pluginType !== "method") continue;
2711
+ if (isStandIn(descriptor)) continue;
2405
2712
  const out = normalizeOutput(descriptor.output);
2406
2713
  const entry = {
2407
2714
  pluginType: "method",
@@ -2416,17 +2723,32 @@ function buildMethodEntries(descriptors, context, states) {
2416
2723
  // Replaced below; never called.
2417
2724
  value: () => void 0
2418
2725
  };
2419
- const callRun = (input, ctx) => descriptor.run({
2420
- imports: buildImports(plugins, descriptor.importBindings, ctx),
2421
- state: states.get(id),
2422
- input
2423
- });
2726
+ const callRun = (input, ctx) => {
2727
+ const callContext = ctx ?? rootCallContext();
2728
+ return descriptor.run({
2729
+ imports: buildImports({
2730
+ plugins,
2731
+ importBindings: descriptor.importBindings,
2732
+ ctx: callContext
2733
+ }),
2734
+ state: states.get(id),
2735
+ input,
2736
+ callContext,
2737
+ annotate: (metadata) => {
2738
+ Object.assign(callContext.annotations, metadata);
2739
+ }
2740
+ });
2741
+ };
2424
2742
  const fold = (coreFn) => (input, ctx) => {
2425
2743
  let next = (i) => coreFn(i, ctx);
2426
2744
  for (const wrap of entry.chain) {
2427
2745
  const inner = next;
2428
2746
  next = (i) => wrap.run({
2429
- imports: buildImports(plugins, wrap.owner.importBindings, ctx),
2747
+ imports: buildImports({
2748
+ plugins,
2749
+ importBindings: wrap.owner.importBindings,
2750
+ ctx
2751
+ }),
2430
2752
  next: inner,
2431
2753
  input: i,
2432
2754
  // Overwritten by the chain item's own closure with the owning
@@ -2437,6 +2759,18 @@ function buildMethodEntries(descriptors, context, states) {
2437
2759
  return next(input);
2438
2760
  };
2439
2761
  const sdk = { context };
2762
+ const methodAnnotator = descriptor.annotator;
2763
+ const boundAnnotator = methodAnnotator ? (input) => methodAnnotator({ input }) : void 0;
2764
+ const outputPolicy = () => {
2765
+ const core = resolveCoreOptions(context);
2766
+ return {
2767
+ outputSchema: descriptor.meta?.outputSchema,
2768
+ skipOutputValidation: descriptor.skipOutputValidation,
2769
+ includeOutputValidationDroppedPaths: core?.includeOutputValidationDroppedPaths,
2770
+ methodName: descriptor.name,
2771
+ adaptError: core?.adaptError
2772
+ };
2773
+ };
2440
2774
  if (out.type === "list") {
2441
2775
  entry.value = createPaginatedFunction(
2442
2776
  fold(callRun),
@@ -2446,17 +2780,23 @@ function buildMethodEntries(descriptors, context, states) {
2446
2780
  name: descriptor.name,
2447
2781
  defaultPageSize: out.defaultPageSize,
2448
2782
  adaptPage: out.adaptPage,
2783
+ annotator: boundAnnotator,
2784
+ // Validate + strip each item against the item `outputSchema`
2785
+ // (item mode's sibling); dropped paths surface as `[].x` in the page's
2786
+ // `meta`, unioned across items.
2787
+ finalizePage: (page) => applyListOutputPolicy(page, outputPolicy()),
2449
2788
  getDeprecation: () => entry.meta?.deprecation
2450
2789
  }
2451
2790
  );
2452
2791
  } else if (out.type === "item") {
2453
- const itemCore = async (input, ctx) => callRun(input, ctx);
2792
+ const itemCore = async (input, ctx) => applyItemOutputPolicy(await callRun(input, ctx), outputPolicy());
2454
2793
  entry.value = createFunction(
2455
2794
  fold(itemCore),
2456
2795
  {
2457
2796
  sdk,
2458
2797
  schema: descriptor.inputSchema,
2459
2798
  name: descriptor.name,
2799
+ annotator: boundAnnotator,
2460
2800
  getDeprecation: () => entry.meta?.deprecation
2461
2801
  }
2462
2802
  );
@@ -2468,6 +2808,7 @@ function buildMethodEntries(descriptors, context, states) {
2468
2808
  name: descriptor.name,
2469
2809
  schema: descriptor.skipInputValidation ? void 0 : descriptor.inputSchema,
2470
2810
  positional: descriptor.positional,
2811
+ annotator: boundAnnotator,
2471
2812
  // The boundary reads the deprecation LIVE off the entry, so a
2472
2813
  // deprecation merged after build (defineMethodOverride, addPlugin)
2473
2814
  // fires too.
@@ -2488,12 +2829,22 @@ function buildMethodEntries(descriptors, context, states) {
2488
2829
  const internalValue = (...args) => canonicalValue(pack(args), INTERNAL_CALL);
2489
2830
  entry.value = (...args) => canonicalValue(pack(args));
2490
2831
  entry.internalValue = internalValue;
2491
- entry.bindInternal = (ctx) => ctx ? (...args) => canonicalValue(pack(args), childCallContext(ctx)) : internalValue;
2832
+ entry.bindInternal = (opts) => bindInternalTwin({
2833
+ ...opts,
2834
+ withContext: (context2) => {
2835
+ return (...args) => canonicalValue(pack(args), context2);
2836
+ },
2837
+ internalValue
2838
+ });
2492
2839
  entry.positional = names;
2493
2840
  } else {
2494
2841
  const internalValue = (input) => canonicalValue(input, INTERNAL_CALL);
2495
2842
  entry.internalValue = internalValue;
2496
- entry.bindInternal = (ctx) => ctx ? (input) => canonicalValue(input, childCallContext(ctx)) : internalValue;
2843
+ entry.bindInternal = (opts) => bindInternalTwin({
2844
+ ...opts,
2845
+ withContext: (context2) => (input) => canonicalValue(input, context2),
2846
+ internalValue
2847
+ });
2497
2848
  }
2498
2849
  plugins[id] = entry;
2499
2850
  }
@@ -2519,8 +2870,14 @@ function buildEagerArtifacts(descriptors, context, states) {
2519
2870
  if (!dispose) return;
2520
2871
  context.disposers?.push({
2521
2872
  id,
2873
+ // Teardown is framework-internal: an SDK method a `dispose` calls runs
2874
+ // on an internal-origin root (dropped from telemetry).
2522
2875
  dispose: (input) => dispose({
2523
- imports: buildImports(plugins, descriptor.importBindings),
2876
+ imports: buildImports({
2877
+ plugins,
2878
+ importBindings: descriptor.importBindings,
2879
+ frameworkOrigin: true
2880
+ }),
2524
2881
  state: states.get(id),
2525
2882
  input
2526
2883
  })
@@ -2530,7 +2887,10 @@ function buildEagerArtifacts(descriptors, context, states) {
2530
2887
  states.set(
2531
2888
  id,
2532
2889
  descriptor.setup ? descriptor.setup({
2533
- imports: buildImports(plugins, descriptor.importBindings)
2890
+ imports: buildImports({
2891
+ plugins,
2892
+ importBindings: descriptor.importBindings
2893
+ })
2534
2894
  }) : void 0
2535
2895
  );
2536
2896
  recordDisposer();
@@ -2542,14 +2902,20 @@ function buildEagerArtifacts(descriptors, context, states) {
2542
2902
  states.set(
2543
2903
  id,
2544
2904
  descriptor.setup ? descriptor.setup({
2545
- imports: buildImports(plugins, descriptor.importBindings)
2905
+ imports: buildImports({
2906
+ plugins,
2907
+ importBindings: descriptor.importBindings
2908
+ })
2546
2909
  }) : void 0
2547
2910
  );
2548
2911
  } else {
2549
2912
  states.set(
2550
2913
  id,
2551
2914
  descriptor.setup ? descriptor.setup({
2552
- imports: buildImports(plugins, descriptor.importBindings)
2915
+ imports: buildImports({
2916
+ plugins,
2917
+ importBindings: descriptor.importBindings
2918
+ })
2553
2919
  }) : void 0
2554
2920
  );
2555
2921
  if (descriptor.privileged) {
@@ -2567,7 +2933,7 @@ function buildEagerArtifacts(descriptors, context, states) {
2567
2933
  pluginType: "property",
2568
2934
  name: descriptor.name,
2569
2935
  getValue: () => get({
2570
- imports: buildImports(plugins, importBindings),
2936
+ imports: buildImports({ plugins, importBindings }),
2571
2937
  state: states.get(id)
2572
2938
  }),
2573
2939
  meta: descriptor.meta,
@@ -2603,7 +2969,7 @@ function resolvePlugin(sdk, ref) {
2603
2969
  return entry.getValue();
2604
2970
  }
2605
2971
  if (entry.pluginType === "method" && entry.internalValue) {
2606
- return entry.internalValue;
2972
+ return entry.bindInternal?.({ frameworkOrigin: true }) ?? entry.internalValue;
2607
2973
  }
2608
2974
  return entry.value;
2609
2975
  }
@@ -2638,7 +3004,7 @@ function resolveAggregates(descriptors, context) {
2638
3004
  if (descriptor.pluginType !== "aggregate") continue;
2639
3005
  const exports2 = {};
2640
3006
  for (const [binding, child] of Object.entries(descriptor.exports)) {
2641
- bindValue(exports2, binding, plugins[child.id]);
3007
+ bindValue({ target: exports2, key: binding, entry: plugins[child.id] });
2642
3008
  }
2643
3009
  plugins[id] = { pluginType: "aggregate", name: descriptor.name, exports: exports2 };
2644
3010
  }
@@ -2681,23 +3047,39 @@ function assembleHooks(descriptors, context, states) {
2681
3047
  const plugins = context.plugins;
2682
3048
  for (const id of topoOrder(descriptors)) {
2683
3049
  const descriptor = descriptors.get(id);
2684
- if (!descriptor || descriptor.pluginType !== "hook" || !descriptor.observe) {
3050
+ if (!descriptor || descriptor.pluginType !== "hook" || !descriptor.observe && !descriptor.annotator) {
2685
3051
  continue;
2686
3052
  }
2687
- const { observe } = descriptor;
2688
- const imports = buildImports(plugins, descriptor.importBindings);
3053
+ const { observe, annotator } = descriptor;
2689
3054
  const state = states.get(id);
2690
3055
  const contributed = {};
2691
- if (observe.onMethodStart) {
2692
- const onStart = observe.onMethodStart;
2693
- contributed.onMethodStart = (input) => {
2694
- runIsolatedObserver(() => onStart({ imports, input, state }));
2695
- };
3056
+ if (observe?.onMethodStart || observe?.onMethodEnd) {
3057
+ const imports = buildImports({
3058
+ plugins,
3059
+ importBindings: descriptor.importBindings,
3060
+ frameworkOrigin: true
3061
+ });
3062
+ if (observe.onMethodStart) {
3063
+ const onStart = observe.onMethodStart;
3064
+ contributed.onMethodStart = (input) => {
3065
+ runIsolatedObserver(() => onStart({ imports, input, state }));
3066
+ };
3067
+ }
3068
+ if (observe.onMethodEnd) {
3069
+ const onEnd = observe.onMethodEnd;
3070
+ contributed.onMethodEnd = (input) => {
3071
+ runIsolatedObserver(() => onEnd({ imports, input, state }));
3072
+ };
3073
+ }
2696
3074
  }
2697
- if (observe.onMethodEnd) {
2698
- const onEnd = observe.onMethodEnd;
2699
- contributed.onMethodEnd = (input) => {
2700
- runIsolatedObserver(() => onEnd({ imports, input, state }));
3075
+ if (annotator) {
3076
+ const annotatorFn = annotator;
3077
+ contributed.annotator = ({ methodName, input }) => {
3078
+ try {
3079
+ return annotatorFn({ methodName, input, state });
3080
+ } catch {
3081
+ return {};
3082
+ }
2701
3083
  };
2702
3084
  }
2703
3085
  context.hooks = buildHooks(context.hooks, contributed);
@@ -2731,7 +3113,11 @@ function createSdk(root, options) {
2731
3113
  pluginSurface = plugins2[plugin.id].exports;
2732
3114
  } else {
2733
3115
  pluginSurface = {};
2734
- bindValue(pluginSurface, plugin.name, plugins2[plugin.id]);
3116
+ bindValue({
3117
+ target: pluginSurface,
3118
+ key: plugin.name,
3119
+ entry: plugins2[plugin.id]
3120
+ });
2735
3121
  }
2736
3122
  for (const key of Object.keys(legacyExports)) context.surface[key] = key;
2737
3123
  if (plugin.pluginType === "aggregate") {
@@ -2748,7 +3134,7 @@ function createSdk(root, options) {
2748
3134
  if (root.pluginType === "method" || root.pluginType === "property") {
2749
3135
  context.surface[root.name] = root.id;
2750
3136
  const sdk = buildSurface(context);
2751
- bindValue(sdk, root.name, plugins[root.id]);
3137
+ bindValue({ target: sdk, key: root.name, entry: plugins[root.id] });
2752
3138
  return sdk;
2753
3139
  }
2754
3140
  if (root.pluginType === "aggregate")
@@ -2780,7 +3166,7 @@ function addModelPlugin(sdk, plugin, options = {}) {
2780
3166
  context.surface[binding] = child.id;
2781
3167
  }
2782
3168
  } else {
2783
- bindValue(sdk, plugin.name, entry);
3169
+ bindValue({ target: sdk, key: plugin.name, entry });
2784
3170
  context.surface[plugin.name] = plugin.id;
2785
3171
  }
2786
3172
  }
@@ -4120,7 +4506,9 @@ function createCorePlugin(options) {
4120
4506
  createSdk,
4121
4507
  createValidator,
4122
4508
  dangerousContextPlugin,
4509
+ declareDefault,
4123
4510
  declareMethod,
4511
+ declareOptionalMethod,
4124
4512
  declareOptionalProperty,
4125
4513
  declarePlugin,
4126
4514
  declareProperty,