@adcp/sdk 15.3.0 → 15.5.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.
Files changed (130) hide show
  1. package/dist/lib/advanced.d.mts +2 -2
  2. package/dist/lib/advanced.d.ts +2 -2
  3. package/dist/lib/advanced.js +8 -2
  4. package/dist/lib/advanced.mjs +8 -2
  5. package/dist/lib/core/AgentClient.d.mts +4 -0
  6. package/dist/lib/core/AgentClient.d.ts +4 -0
  7. package/dist/lib/core/AgentClient.js +8 -0
  8. package/dist/lib/core/AgentClient.mjs +8 -0
  9. package/dist/lib/core/SingleAgentClient.d.mts +154 -0
  10. package/dist/lib/core/SingleAgentClient.d.ts +154 -0
  11. package/dist/lib/core/SingleAgentClient.js +661 -23
  12. package/dist/lib/core/SingleAgentClient.mjs +666 -23
  13. package/dist/lib/core/agent-discovery-cache.d.mts +103 -0
  14. package/dist/lib/core/agent-discovery-cache.d.ts +103 -0
  15. package/dist/lib/core/agent-discovery-cache.js +121 -0
  16. package/dist/lib/core/agent-discovery-cache.mjs +92 -0
  17. package/dist/lib/index.d.mts +5 -3
  18. package/dist/lib/index.d.ts +5 -3
  19. package/dist/lib/index.js +7 -0
  20. package/dist/lib/index.mjs +6 -0
  21. package/dist/lib/protocols/index.d.mts +2 -2
  22. package/dist/lib/protocols/index.d.ts +2 -2
  23. package/dist/lib/protocols/index.js +1 -1
  24. package/dist/lib/protocols/index.mjs +1 -1
  25. package/dist/lib/protocols/mcp-call-context.d.mts +109 -0
  26. package/dist/lib/protocols/mcp-call-context.d.ts +109 -0
  27. package/dist/lib/protocols/mcp-call-context.js +221 -0
  28. package/dist/lib/protocols/mcp-call-context.mjs +186 -0
  29. package/dist/lib/protocols/mcp-modern.d.mts +9 -1
  30. package/dist/lib/protocols/mcp-modern.d.ts +9 -1
  31. package/dist/lib/protocols/mcp-modern.js +180 -104
  32. package/dist/lib/protocols/mcp-modern.mjs +193 -104
  33. package/dist/lib/protocols/mcp-scope.d.mts +15 -0
  34. package/dist/lib/protocols/mcp-scope.d.ts +15 -0
  35. package/dist/lib/protocols/mcp.d.mts +4 -2
  36. package/dist/lib/protocols/mcp.d.ts +4 -2
  37. package/dist/lib/protocols/mcp.js +178 -154
  38. package/dist/lib/protocols/mcp.mjs +189 -153
  39. package/dist/lib/protocols/transportDiagnostics.d.mts +33 -1
  40. package/dist/lib/protocols/transportDiagnostics.d.ts +33 -1
  41. package/dist/lib/protocols/transportDiagnostics.js +128 -12
  42. package/dist/lib/protocols/transportDiagnostics.mjs +128 -12
  43. package/dist/lib/reporting/gcs/index.d.mts +2 -0
  44. package/dist/lib/reporting/gcs/index.d.ts +2 -0
  45. package/dist/lib/reporting/gcs/index.js +3 -0
  46. package/dist/lib/reporting/gcs/index.mjs +2 -0
  47. package/dist/lib/reporting/gcs/row-provider.d.mts +32 -0
  48. package/dist/lib/reporting/gcs/row-provider.d.ts +32 -0
  49. package/dist/lib/reporting/gcs/row-provider.js +205 -0
  50. package/dist/lib/reporting/gcs/row-provider.mjs +188 -0
  51. package/dist/lib/reporting/ledger/change-feed.d.mts +41 -0
  52. package/dist/lib/reporting/ledger/change-feed.d.ts +41 -0
  53. package/dist/lib/reporting/ledger/change-feed.js +101 -0
  54. package/dist/lib/reporting/ledger/change-feed.mjs +74 -0
  55. package/dist/lib/reporting/ledger/handler.d.mts +6 -0
  56. package/dist/lib/reporting/ledger/handler.d.ts +6 -0
  57. package/dist/lib/reporting/ledger/handler.js +51 -8
  58. package/dist/lib/reporting/ledger/handler.mjs +50 -8
  59. package/dist/lib/reporting/ledger/index.d.mts +5 -0
  60. package/dist/lib/reporting/ledger/index.d.ts +5 -0
  61. package/dist/lib/reporting/ledger/index.js +11 -1
  62. package/dist/lib/reporting/ledger/index.mjs +5 -0
  63. package/dist/lib/reporting/ledger/lifecycle.js +3 -2
  64. package/dist/lib/reporting/ledger/lifecycle.mjs +3 -2
  65. package/dist/lib/reporting/ledger/managed-postgres.d.mts +2 -0
  66. package/dist/lib/reporting/ledger/managed-postgres.d.ts +2 -0
  67. package/dist/lib/reporting/ledger/managed-postgres.js +26 -6
  68. package/dist/lib/reporting/ledger/managed-postgres.mjs +26 -6
  69. package/dist/lib/reporting/ledger/managed.d.mts +9 -0
  70. package/dist/lib/reporting/ledger/managed.d.ts +9 -0
  71. package/dist/lib/reporting/ledger/managed.js +18 -0
  72. package/dist/lib/reporting/ledger/managed.mjs +18 -0
  73. package/dist/lib/reporting/ledger/postgres.d.mts +179 -1
  74. package/dist/lib/reporting/ledger/postgres.d.ts +179 -1
  75. package/dist/lib/reporting/ledger/postgres.js +882 -19
  76. package/dist/lib/reporting/ledger/postgres.mjs +890 -19
  77. package/dist/lib/reporting/ledger/producer.js +13 -3
  78. package/dist/lib/reporting/ledger/producer.mjs +13 -3
  79. package/dist/lib/reporting/ledger/revision-metadata.d.mts +17 -0
  80. package/dist/lib/reporting/ledger/revision-metadata.d.ts +17 -0
  81. package/dist/lib/reporting/ledger/revision-metadata.js +71 -0
  82. package/dist/lib/reporting/ledger/revision-metadata.mjs +44 -0
  83. package/dist/lib/reporting/ledger/row-encoding.d.mts +128 -0
  84. package/dist/lib/reporting/ledger/row-encoding.d.ts +128 -0
  85. package/dist/lib/reporting/ledger/row-encoding.js +355 -0
  86. package/dist/lib/reporting/ledger/row-encoding.mjs +315 -0
  87. package/dist/lib/reporting/ledger/row-storage-errors.d.mts +7 -0
  88. package/dist/lib/reporting/ledger/row-storage-errors.d.ts +7 -0
  89. package/dist/lib/reporting/ledger/row-storage-errors.js +41 -0
  90. package/dist/lib/reporting/ledger/row-storage-errors.mjs +16 -0
  91. package/dist/lib/reporting/ledger/row-storage-migration.d.mts +41 -0
  92. package/dist/lib/reporting/ledger/row-storage-migration.d.ts +41 -0
  93. package/dist/lib/reporting/ledger/row-storage-migration.js +234 -0
  94. package/dist/lib/reporting/ledger/row-storage-migration.mjs +208 -0
  95. package/dist/lib/reporting/ledger/row-storage-object.d.mts +112 -0
  96. package/dist/lib/reporting/ledger/row-storage-object.d.ts +112 -0
  97. package/dist/lib/reporting/ledger/row-storage-object.js +281 -0
  98. package/dist/lib/reporting/ledger/row-storage-object.mjs +239 -0
  99. package/dist/lib/reporting/ledger/row-storage-provider-io.d.mts +51 -0
  100. package/dist/lib/reporting/ledger/row-storage-provider-io.d.ts +51 -0
  101. package/dist/lib/reporting/ledger/row-storage-provider-io.js +152 -0
  102. package/dist/lib/reporting/ledger/row-storage-provider-io.mjs +119 -0
  103. package/dist/lib/reporting/ledger/row-storage.d.mts +224 -0
  104. package/dist/lib/reporting/ledger/row-storage.d.ts +224 -0
  105. package/dist/lib/reporting/ledger/row-storage.js +692 -0
  106. package/dist/lib/reporting/ledger/row-storage.mjs +680 -0
  107. package/dist/lib/reporting/ledger/types.d.mts +45 -0
  108. package/dist/lib/reporting/ledger/types.d.ts +45 -0
  109. package/dist/lib/reporting/service/index.d.mts +41 -1
  110. package/dist/lib/reporting/service/index.d.ts +41 -1
  111. package/dist/lib/reporting/service/index.js +68 -1
  112. package/dist/lib/reporting/service/index.mjs +70 -1
  113. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  114. package/dist/lib/signing/agent-resolver/fetch-helpers.js +1 -1
  115. package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +1 -1
  116. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  117. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  118. package/dist/lib/version.d.mts +3 -3
  119. package/dist/lib/version.d.ts +3 -3
  120. package/dist/lib/version.js +3 -3
  121. package/dist/lib/version.mjs +3 -3
  122. package/docs/README.md +1 -1
  123. package/docs/TYPE-SUMMARY.md +35 -2
  124. package/docs/guides/DISCOVERY-CACHE-AND-SESSION-REUSE.md +142 -0
  125. package/docs/guides/EXISTING-PLATFORM.md +43 -12
  126. package/docs/guides/REPORTING-LEDGER.md +209 -0
  127. package/docs/llms.txt +4 -2
  128. package/docs/migration-14.x-rc-worksheet.md +4 -4
  129. package/docs/recipes/verifying-inbound-webhooks.md +8 -0
  130. package/package.json +1 -1
@@ -51,6 +51,7 @@ var import_task_deadline = require('./task-deadline.js');
51
51
  var import_auth = require('../auth/index.js');
52
52
  var import_abort = require('../protocols/abort.js');
53
53
  var import_signedRequestRejection = require('../protocols/signedRequestRejection.js');
54
+ var import_mcp_call_context = require('../protocols/mcp-call-context.js');
54
55
  var import_protocols = require('../protocols/index.js');
55
56
  var import_errors = require('../errors/index.js');
56
57
  var import_net = require('../net/index.js');
@@ -68,6 +69,7 @@ var import_ProtocolResponseParser = require('./ProtocolResponseParser.js');
68
69
  var import_a2a_discovery = require('../utils/a2a-discovery.js');
69
70
  var crypto = __toESM(require("crypto"));
70
71
  var import_abort2 = require('../protocols/abort.js');
72
+ var import_agent_discovery_cache = require('./agent-discovery-cache.js');
71
73
  var import_capabilities = require('../utils/capabilities.js');
72
74
  var import_errors3 = require('../errors/index.js');
73
75
  var import_buyer_account_registry = require('./buyer-account-registry.js');
@@ -656,6 +658,30 @@ const DURABLE_SETTLEMENT_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
656
658
  "aborted"
657
659
  ]);
658
660
  const MCP_WEBHOOK_REQUIRED_FIELDS = ["idempotency_key", "task_id", "task_type", "status", "timestamp"];
661
+ const sharedInvalidations = /* @__PURE__ */ new WeakMap();
662
+ const sharedInvalidationFloors = /* @__PURE__ */ new WeakMap();
663
+ let sharedInvalidationCounter = 0;
664
+ const MAX_SHARED_INVALIDATION_RECORDS = 1024;
665
+ const sharedMutationChains = /* @__PURE__ */ new WeakMap();
666
+ const MAX_PENDING_SHARED_MUTATIONS_PER_KEY = 16;
667
+ const MAX_SHARED_MUTATION_KEYS = 1024;
668
+ const sharedDiscoveryFlights = /* @__PURE__ */ new WeakMap();
669
+ const MAX_SHARED_DISCOVERY_WAITS = 3;
670
+ const SHARED_DISCOVERY_CACHE_TIMEOUT_MS = 2e3;
671
+ const SHARED_DISCOVERY_CLOCK_SKEW_MS = 5e3;
672
+ const CAPABILITY_STALE_ERROR_CODES = /* @__PURE__ */ new Set([
673
+ "VERSION_UNSUPPORTED",
674
+ "UNSUPPORTED_FEATURE",
675
+ "FEATURE_UNSUPPORTED",
676
+ "TOOL_NOT_FOUND",
677
+ "METHOD_NOT_FOUND",
678
+ "UNKNOWN_TOOL"
679
+ ]);
680
+ function isToolSchemaRecord(value) {
681
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every(
682
+ (properties) => typeof properties === "object" && properties !== null && !Array.isArray(properties)
683
+ );
684
+ }
659
685
  function isCapabilityEvidence(value) {
660
686
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
661
687
  const candidate = value;
@@ -753,6 +779,7 @@ class SingleAgentClient {
753
779
  ...configuredWebhookVerification !== void 0 && { webhookVerification: configuredWebhookVerification }
754
780
  };
755
781
  config = this.config;
782
+ (0, import_agent_discovery_cache.validateAgentDiscoveryCacheConfig)(config.discoveryCache);
756
783
  this.resolvedAdcpVersion = (0, import_adcp_version_config.resolveAdcpVersion)(config.adcpVersion);
757
784
  this.webhookRegistrationStore = config.webhookRegistrationStore ?? new import_webhook_registration.InMemoryWebhookRegistrationStore();
758
785
  this.webhookReplayStore = config.webhookVerification?.replayStore ?? new import_server.InMemoryReplayStore();
@@ -1031,6 +1058,20 @@ class SingleAgentClient {
1031
1058
  // Freshness bound for application-owned evidence only
1032
1059
  capabilityEvidenceScopeKey = (0, import_node_crypto.randomUUID)();
1033
1060
  capabilityEvidenceAuthMaterial;
1061
+ capabilitiesFromSharedCache = false;
1062
+ // Installed from config.discoveryCache rather than discovered live
1063
+ discoveryEpoch = 0;
1064
+ // bumped by invalidation so an older in-flight discovery cannot re-install what was dropped
1065
+ discoveredEndpointKey;
1066
+ // discoveryCache key the local endpoint was obtained under
1067
+ discoveredEndpointEpoch;
1068
+ // shared invalidation epoch of that key when the endpoint was obtained
1069
+ discoveredEndpointExpiresAt;
1070
+ // freshness bound for that endpoint (shared-cache mode only)
1071
+ sharedEvidenceEpoch;
1072
+ // shared invalidation epoch of that key when the evidence was obtained
1073
+ sharedEvidenceKey;
1074
+ // discoveryCache key the local capability cache was obtained under
1034
1075
  _v2WarningFired = false;
1035
1076
  // Gate: emit the v2-sunset warning once per client instance
1036
1077
  _syntheticV3WarningFired = false;
@@ -1702,19 +1743,34 @@ class SingleAgentClient {
1702
1743
  if (!needsDiscovery) {
1703
1744
  return this.normalizedAgent;
1704
1745
  }
1746
+ const effectiveEndpointKey = this.config.discoveryCache && !usesScopedFetch && this.discoveredEndpoint ? this.sharedDiscoveryKey(options) : void 0;
1747
+ if (this.config.discoveryCache && !usesScopedFetch && this.discoveredEndpoint && (this.discoveredEndpointKey !== effectiveEndpointKey || effectiveEndpointKey !== void 0 && this.discoveredEndpointEpoch !== this.sharedInvalidationEpoch(effectiveEndpointKey) || (this.discoveredEndpointExpiresAt ?? 0) <= Date.now())) {
1748
+ this.discoveredEndpoint = void 0;
1749
+ this.discoveredAgent = void 0;
1750
+ this.discoveredMcpEra = void 0;
1751
+ }
1705
1752
  if (!usesScopedFetch && this.discoveredAgent) {
1706
1753
  return this.discoveredAgent;
1707
1754
  }
1755
+ if (!usesScopedFetch && !this.discoveredEndpoint && this.config.discoveryCache) {
1756
+ await this.adoptSharedEndpoint(options);
1757
+ if (this.discoveredEndpoint && (this.discoveredEndpointEpoch !== this.sharedInvalidationEpoch(this.sharedDiscoveryKey(options)) || (this.discoveredEndpointExpiresAt ?? 0) <= Date.now())) {
1758
+ this.discoveredEndpoint = void 0;
1759
+ this.discoveredAgent = void 0;
1760
+ this.discoveredMcpEra = void 0;
1761
+ }
1762
+ }
1708
1763
  if (!usesScopedFetch && this.discoveredEndpoint) {
1709
- this.discoveredAgent = {
1764
+ const adoptedAgent = {
1710
1765
  ...this.normalizedAgent,
1711
1766
  agent_uri: this.discoveredEndpoint
1712
1767
  };
1768
+ this.discoveredAgent = adoptedAgent;
1713
1769
  if (this.normalizedAgent.oauth_tokens && !this.normalizedAgent.oauth_client_credentials) {
1714
1770
  const { shareNonInteractiveOAuthProvider } = await import("../auth/oauth/provider-cache.js");
1715
- shareNonInteractiveOAuthProvider(this.normalizedAgent, this.discoveredAgent);
1771
+ shareNonInteractiveOAuthProvider(this.normalizedAgent, adoptedAgent);
1716
1772
  }
1717
- return this.discoveredAgent;
1773
+ return adoptedAgent;
1718
1774
  }
1719
1775
  if (this.normalizedAgent.oauth_client_credentials) {
1720
1776
  const { ensureClientCredentialsTokens, getAgentStorage } = await import("../auth/oauth/index.js");
@@ -1725,26 +1781,59 @@ class SingleAgentClient {
1725
1781
  signal: options?.signal
1726
1782
  });
1727
1783
  }
1784
+ const endpointEpoch = this.discoveryEpochSnapshot(options);
1728
1785
  const discoveredEndpoint = await this.discoverMCPEndpoint(this.normalizedAgent.agent_uri, options);
1729
1786
  if (usesScopedFetch) {
1730
- const discoveredAgent = { ...this.normalizedAgent, agent_uri: discoveredEndpoint };
1787
+ const discoveredAgent2 = { ...this.normalizedAgent, agent_uri: discoveredEndpoint };
1731
1788
  if (this.normalizedAgent.oauth_tokens && !this.normalizedAgent.oauth_client_credentials) {
1732
1789
  const { shareNonInteractiveOAuthProvider } = await import("../auth/oauth/provider-cache.js");
1733
- shareNonInteractiveOAuthProvider(this.normalizedAgent, discoveredAgent);
1790
+ shareNonInteractiveOAuthProvider(this.normalizedAgent, discoveredAgent2);
1734
1791
  }
1735
- return discoveredAgent;
1792
+ return discoveredAgent2;
1793
+ }
1794
+ if (endpointEpoch && !this.discoveryEpochIs(endpointEpoch)) {
1795
+ return { ...this.normalizedAgent, agent_uri: discoveredEndpoint };
1736
1796
  }
1737
1797
  this.discoveredEndpoint = discoveredEndpoint;
1738
- this.canonicalBaseUrl = this.computeBaseUrl(this.discoveredEndpoint);
1739
- this.discoveredAgent = {
1798
+ if (this.config.discoveryCache) {
1799
+ this.discoveredEndpointKey = this.sharedDiscoveryKey(options);
1800
+ this.discoveredEndpointEpoch = endpointEpoch?.shared;
1801
+ this.discoveredEndpointExpiresAt = Date.now() + (0, import_agent_discovery_cache.resolveAgentDiscoveryTtlMs)(this.config.discoveryCache.ttlMs);
1802
+ const stored = await this.writeSharedDiscovery(
1803
+ options,
1804
+ {
1805
+ endpoint: {
1806
+ agentUri: discoveredEndpoint,
1807
+ ...this.discoveredMcpEra && { mcpEra: this.discoveredMcpEra }
1808
+ }
1809
+ },
1810
+ false,
1811
+ endpointEpoch
1812
+ );
1813
+ if (endpointEpoch && !this.discoveryEpochIs(endpointEpoch)) {
1814
+ if (this.discoveredEndpoint === discoveredEndpoint && this.discoveredEndpointEpoch === endpointEpoch.shared) {
1815
+ this.discoveredEndpoint = void 0;
1816
+ this.discoveredEndpointKey = void 0;
1817
+ this.discoveredEndpointEpoch = void 0;
1818
+ this.discoveredEndpointExpiresAt = void 0;
1819
+ if (this.discoveredAgent?.agent_uri === discoveredEndpoint) this.discoveredAgent = void 0;
1820
+ this.discoveredMcpEra = void 0;
1821
+ }
1822
+ return { ...this.normalizedAgent, agent_uri: discoveredEndpoint };
1823
+ }
1824
+ if (stored && this.discoveredEndpoint === discoveredEndpoint) this.discoveredEndpointExpiresAt = stored.expiresAt;
1825
+ }
1826
+ this.canonicalBaseUrl = this.computeBaseUrl(discoveredEndpoint);
1827
+ const discoveredAgent = {
1740
1828
  ...this.normalizedAgent,
1741
- agent_uri: this.discoveredEndpoint
1829
+ agent_uri: discoveredEndpoint
1742
1830
  };
1831
+ this.discoveredAgent = discoveredAgent;
1743
1832
  if (this.normalizedAgent.oauth_tokens && !this.normalizedAgent.oauth_client_credentials) {
1744
1833
  const { shareNonInteractiveOAuthProvider } = await import("../auth/oauth/provider-cache.js");
1745
- shareNonInteractiveOAuthProvider(this.normalizedAgent, this.discoveredAgent);
1834
+ shareNonInteractiveOAuthProvider(this.normalizedAgent, discoveredAgent);
1746
1835
  }
1747
- return this.discoveredAgent;
1836
+ return discoveredAgent;
1748
1837
  }
1749
1838
  /**
1750
1839
  * Ensure A2A canonical URL is resolved (lazy initialization)
@@ -3214,6 +3303,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
3214
3303
  )
3215
3304
  );
3216
3305
  (0, import_abort2.throwIfAborted)(effectiveOptions?.signal);
3306
+ if (!result.success) await this.invalidateDiscoveryOnCapabilityError(result.adcpError?.code, effectiveOptions);
3217
3307
  const postAdapterLogs = [...inputSchemaStripLogs, ...v25DriftLogs];
3218
3308
  if (postAdapterLogs.length > 0) {
3219
3309
  result.debug_logs = [...result.debug_logs ?? [], ...postAdapterLogs];
@@ -5722,6 +5812,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
5722
5812
  beforeDispatch,
5723
5813
  deferredClientContext
5724
5814
  );
5815
+ if (!result.success) await this.invalidateDiscoveryOnCapabilityError(result.adcpError?.code, effectiveOptions);
5725
5816
  const postAdapterLogs = [...inputSchemaStripLogs, ...v25DriftLogs];
5726
5817
  if (postAdapterLogs.length > 0) {
5727
5818
  result.debug_logs = [...result.debug_logs ?? [], ...postAdapterLogs];
@@ -6287,6 +6378,26 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6287
6378
  tools
6288
6379
  };
6289
6380
  }
6381
+ const { withScopedLegacyConnection } = await import("../protocols/mcp.js");
6382
+ const scopedList = await withScopedLegacyConnection(
6383
+ connectOptions,
6384
+ "tools/list",
6385
+ (client) => withResponseSizeLimit(maxResponseBytes, () => client.listTools(mcpListParams, mcpRequestOptions))
6386
+ );
6387
+ if (scopedList) {
6388
+ return {
6389
+ name: this.normalizedAgent.name,
6390
+ description: void 0,
6391
+ protocol: this.normalizedAgent.protocol,
6392
+ url: agent.agent_uri,
6393
+ tools: scopedList.value.tools.map((tool) => ({
6394
+ name: tool.name,
6395
+ description: tool.description,
6396
+ inputSchema: tool.inputSchema,
6397
+ parameters: tool.inputSchema?.properties ? Object.keys(tool.inputSchema.properties) : []
6398
+ }))
6399
+ };
6400
+ }
6290
6401
  const { client: mcpClient } = await connectMCP(connectOptions);
6291
6402
  try {
6292
6403
  const toolsList = await withResponseSizeLimit(
@@ -6400,12 +6511,98 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6400
6511
  this.cachedCapabilities = void 0;
6401
6512
  this.cachedToolSchemas = void 0;
6402
6513
  this.primedCapabilitiesExpiresAt = void 0;
6514
+ this.capabilitiesFromSharedCache = false;
6515
+ }
6516
+ const effectiveSharedKey = this.config.discoveryCache && !usesScopedFetch && this.cachedCapabilities ? this.sharedDiscoveryKey(options) : void 0;
6517
+ if (this.config.discoveryCache && !usesScopedFetch && this.cachedCapabilities && (this.sharedEvidenceKey !== effectiveSharedKey || effectiveSharedKey !== void 0 && this.sharedEvidenceEpoch !== void 0 && this.sharedEvidenceEpoch !== this.sharedInvalidationEpoch(effectiveSharedKey))) {
6518
+ this.cachedCapabilities = void 0;
6519
+ this.cachedToolSchemas = void 0;
6520
+ this.primedCapabilitiesExpiresAt = void 0;
6521
+ this.capabilitiesFromSharedCache = false;
6403
6522
  }
6404
6523
  if (!usesScopedFetch && this.cachedCapabilities) {
6405
6524
  if (discoveryContext) discoveryContext.toolSchemas = this.cachedToolSchemas;
6406
6525
  this.maybeWarnV2Sunset(this.cachedCapabilities);
6407
6526
  return this.cachedCapabilities;
6408
6527
  }
6528
+ if (!usesScopedFetch && this.config.discoveryCache) {
6529
+ return this.discoverCapabilitiesThroughSharedCache(options, usesScopedFetch, discoveryContext);
6530
+ }
6531
+ return this.discoverCapabilitiesLive(options, usesScopedFetch, discoveryContext);
6532
+ }
6533
+ /**
6534
+ * Serve capabilities from the shared `discoveryCache`, or discover them live
6535
+ * as the single in-process "leader" for this cache key. Concurrent clients
6536
+ * for the same key wait for the leader under their *own* signal and deadline;
6537
+ * a leader that fails or is cancelled never fails its followers, which then
6538
+ * discover for themselves.
6539
+ */
6540
+ async discoverCapabilitiesThroughSharedCache(options, usesScopedFetch, discoveryContext) {
6541
+ const config = this.config.discoveryCache;
6542
+ const key = this.sharedDiscoveryKey(options);
6543
+ if (!key) return this.discoverCapabilitiesLive(options, usesScopedFetch, discoveryContext);
6544
+ let flights = sharedDiscoveryFlights.get(config.cache);
6545
+ if (!flights) {
6546
+ flights = /* @__PURE__ */ new Map();
6547
+ sharedDiscoveryFlights.set(config.cache, flights);
6548
+ }
6549
+ for (let attempt = 0; ; attempt++) {
6550
+ const readEpoch = this.discoveryEpochSnapshot(options);
6551
+ const entry = await this.readSharedDiscovery(options);
6552
+ const installed = entry && (!readEpoch || this.discoveryEpochIs(readEpoch)) && this.installSharedCapabilities(entry, key, discoveryContext);
6553
+ if (installed) return installed;
6554
+ const flight = flights.get(key);
6555
+ if (flight && attempt < MAX_SHARED_DISCOVERY_WAITS) {
6556
+ const requestTimeoutMs = (0, import_abort2.resolveRequestTimeoutMs)(
6557
+ options?.transport?.requestTimeoutMs ?? this.config.transport?.requestTimeoutMs
6558
+ );
6559
+ await (0, import_abort2.withAbortSignal)([options?.signal], requestTimeoutMs, () => flight);
6560
+ continue;
6561
+ }
6562
+ let finish;
6563
+ const mine = new Promise((resolve) => finish = resolve);
6564
+ const ownsFlight = !flights.has(key);
6565
+ if (ownsFlight) flights.set(key, mine);
6566
+ try {
6567
+ return await this.discoverCapabilitiesLive(options, usesScopedFetch, discoveryContext);
6568
+ } finally {
6569
+ if (ownsFlight) {
6570
+ flights.delete(key);
6571
+ }
6572
+ finish();
6573
+ }
6574
+ }
6575
+ }
6576
+ /** Install capabilities from a shared entry; `undefined` when the entry is not usable evidence. */
6577
+ installSharedCapabilities(entry, key, discoveryContext) {
6578
+ const capabilities = entry.capabilities;
6579
+ if (!isCapabilityEvidence(capabilities) || capabilities._synthetic) return void 0;
6580
+ const toolSchemas = entry.toolSchemas;
6581
+ const hasToolEvidence = toolSchemas !== void 0 || Array.isArray(capabilities.discoveredTools);
6582
+ if (!hasToolEvidence) return void 0;
6583
+ if (toolSchemas !== void 0 && !isToolSchemaRecord(toolSchemas)) {
6584
+ return void 0;
6585
+ }
6586
+ let installed;
6587
+ let schemas;
6588
+ try {
6589
+ installed = structuredClone(capabilities);
6590
+ schemas = toolSchemas ? new Map(Object.entries(toolSchemas).map(([tool, properties]) => [tool, structuredClone(properties)])) : void 0;
6591
+ } catch {
6592
+ return void 0;
6593
+ }
6594
+ this.cachedCapabilities = installed;
6595
+ this.cachedToolSchemas = schemas;
6596
+ this.primedCapabilitiesExpiresAt = entry.expiresAt;
6597
+ this.capabilitiesFromSharedCache = true;
6598
+ this.recordSharedEvidence(key);
6599
+ if (discoveryContext) discoveryContext.toolSchemas = this.cachedToolSchemas;
6600
+ this.maybeWarnV2Sunset(installed);
6601
+ return installed;
6602
+ }
6603
+ async discoverCapabilitiesLive(options, usesScopedFetch, discoveryContext) {
6604
+ const startedAtEpoch = this.discoveryEpochSnapshot(options);
6605
+ const superseded = () => startedAtEpoch !== void 0 && !this.discoveryEpochIs(startedAtEpoch);
6409
6606
  const agentInfo = await this.getAgentInfo(options);
6410
6607
  const tools = agentInfo.tools.map((t) => ({
6411
6608
  name: t.name,
@@ -6415,7 +6612,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6415
6612
  agentInfo.tools.filter((t) => t.inputSchema?.properties).map((t) => [t.name, t.inputSchema.properties])
6416
6613
  );
6417
6614
  if (discoveryContext) discoveryContext.toolSchemas = discoveredToolSchemas;
6418
- if (!usesScopedFetch) this.cachedToolSchemas = discoveredToolSchemas;
6615
+ if (!usesScopedFetch && !superseded()) this.cachedToolSchemas = discoveredToolSchemas;
6419
6616
  const advertisesCapabilitiesTool = tools.some((t) => t.name === "get_adcp_capabilities");
6420
6617
  const officialA2ALifecycle = this.normalizedAgent.protocol === "a2a" && tools.some((tool) => ["tasks/get", "get_task_status", "list_tasks"].includes(tool.name));
6421
6618
  const hasCapabilitiesTool = advertisesCapabilitiesTool || officialA2ALifecycle;
@@ -6447,7 +6644,31 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6447
6644
  }
6448
6645
  if (result.success && result.data) {
6449
6646
  const capabilities2 = (0, import_capabilities.augmentCapabilitiesFromTools)((0, import_capabilities.parseCapabilitiesResponse)(result.data), tools);
6450
- if (!usesScopedFetch) this.cachedCapabilities = capabilities2;
6647
+ if (!usesScopedFetch && !superseded()) {
6648
+ this.cachedCapabilities = capabilities2;
6649
+ this.capabilitiesFromSharedCache = false;
6650
+ if (!capabilities2._synthetic && this.config.discoveryCache) {
6651
+ this.recordSharedEvidence(this.sharedDiscoveryKey(options));
6652
+ const stored = await this.writeSharedDiscovery(
6653
+ options,
6654
+ {
6655
+ capabilities: structuredClone(capabilities2),
6656
+ toolSchemas: Object.fromEntries(discoveredToolSchemas)
6657
+ },
6658
+ false,
6659
+ startedAtEpoch
6660
+ );
6661
+ if (superseded()) {
6662
+ if (this.cachedCapabilities === capabilities2) {
6663
+ this.cachedCapabilities = void 0;
6664
+ this.cachedToolSchemas = void 0;
6665
+ this.primedCapabilitiesExpiresAt = void 0;
6666
+ }
6667
+ } else {
6668
+ this.primedCapabilitiesExpiresAt = stored?.expiresAt ?? Date.now() + (0, import_agent_discovery_cache.resolveAgentDiscoveryTtlMs)(this.config.discoveryCache.ttlMs);
6669
+ }
6670
+ }
6671
+ }
6451
6672
  this.maybeWarnV2Sunset(capabilities2);
6452
6673
  return capabilities2;
6453
6674
  }
@@ -6466,7 +6687,11 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6466
6687
  majorVersions: parsed.majorVersions.includes(3) ? parsed.majorVersions : [3]
6467
6688
  };
6468
6689
  const capabilities2 = (0, import_capabilities.augmentCapabilitiesFromTools)(v3Capabilities, tools);
6469
- if (!usesScopedFetch) this.cachedCapabilities = capabilities2;
6690
+ if (!usesScopedFetch && !superseded()) {
6691
+ this.cachedCapabilities = capabilities2;
6692
+ this.capabilitiesFromSharedCache = false;
6693
+ this.recordSharedEvidence(this.sharedDiscoveryKey(options));
6694
+ }
6470
6695
  this.maybeWarnV2Sunset(capabilities2);
6471
6696
  return capabilities2;
6472
6697
  }
@@ -6490,7 +6715,11 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6490
6715
  }
6491
6716
  if (advertisesCapabilitiesTool) {
6492
6717
  const capabilities2 = (0, import_capabilities.augmentCapabilitiesFromTools)((0, import_capabilities.buildSyntheticV3Capabilities)(tools), tools);
6493
- if (!usesScopedFetch) this.cachedCapabilities = capabilities2;
6718
+ if (!usesScopedFetch && !superseded()) {
6719
+ this.cachedCapabilities = capabilities2;
6720
+ this.capabilitiesFromSharedCache = false;
6721
+ this.recordSharedEvidence(this.sharedDiscoveryKey(options));
6722
+ }
6494
6723
  this.maybeWarnV2Sunset(capabilities2);
6495
6724
  return capabilities2;
6496
6725
  }
@@ -6499,7 +6728,11 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6499
6728
  `[AdCP] Agent "${this.agent.id}" detected as v2 (no get_adcp_capabilities tool). Tools: [${tools.map((t) => t.name).join(", ")}]`
6500
6729
  );
6501
6730
  const capabilities = (0, import_capabilities.buildSyntheticCapabilities)(tools);
6502
- if (!usesScopedFetch) this.cachedCapabilities = capabilities;
6731
+ if (!usesScopedFetch && !superseded()) {
6732
+ this.cachedCapabilities = capabilities;
6733
+ this.capabilitiesFromSharedCache = false;
6734
+ this.recordSharedEvidence(this.sharedDiscoveryKey(options));
6735
+ }
6503
6736
  return capabilities;
6504
6737
  }
6505
6738
  /**
@@ -6570,6 +6803,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6570
6803
  this.cachedCapabilities = void 0;
6571
6804
  this.cachedToolSchemas = void 0;
6572
6805
  this.primedCapabilitiesExpiresAt = void 0;
6806
+ this.capabilitiesFromSharedCache = false;
6573
6807
  return false;
6574
6808
  }
6575
6809
  try {
@@ -6578,6 +6812,8 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6578
6812
  ].map((name) => ({ name }));
6579
6813
  this.cachedCapabilities = (0, import_capabilities.augmentCapabilitiesFromTools)(structuredClone(capabilities), observedTools);
6580
6814
  this.primedCapabilitiesExpiresAt = expiresAt;
6815
+ this.capabilitiesFromSharedCache = false;
6816
+ this.recordSharedEvidence(this.sharedDiscoveryKey(), true);
6581
6817
  this.cachedToolSchemas = snapshot.toolSchemas ? new Map(
6582
6818
  Object.entries(snapshot.toolSchemas).map(([tool, properties]) => [
6583
6819
  tool,
@@ -6592,6 +6828,396 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6592
6828
  return false;
6593
6829
  }
6594
6830
  }
6831
+ /** Which kind of credential this client presents (never its value), so an unauthenticated client cannot adopt an authenticated one's evidence. */
6832
+ discoveryCredentialKind() {
6833
+ const agent = this.normalizedAgent;
6834
+ if (agent.oauth_client_credentials) return "oauth-client-credentials";
6835
+ if (agent.oauth_tokens) return "oauth";
6836
+ if (agent.auth_token) return "bearer";
6837
+ const headerNames = Object.keys(agent.headers ?? {}).map((name) => name.toLowerCase());
6838
+ return headerNames.includes("authorization") || headerNames.includes("x-adcp-auth") ? "header" : "none";
6839
+ }
6840
+ /** Epoch of this client's evidence for the call's effective shared key; `undefined` without a shared cache. */
6841
+ discoveryEpochSnapshot(options) {
6842
+ const key = this.sharedDiscoveryKey(options);
6843
+ if (!key || !this.config.discoveryCache) return void 0;
6844
+ return { local: this.discoveryEpoch, shared: this.sharedInvalidationEpoch(key), key };
6845
+ }
6846
+ discoveryEpochIs(epoch) {
6847
+ return epoch.local === this.discoveryEpoch && epoch.shared === this.sharedInvalidationEpoch(epoch.key);
6848
+ }
6849
+ /** The latest invalidation that applies to `key`: its own, combined with the cache's floor of forgotten ones. */
6850
+ sharedInvalidationRecord(key) {
6851
+ const cache = this.config.discoveryCache.cache;
6852
+ const record = sharedInvalidations.get(cache)?.get(key);
6853
+ const floor = sharedInvalidationFloors.get(cache);
6854
+ if (!floor) return record;
6855
+ if (!record) return floor;
6856
+ return { epoch: Math.max(record.epoch, floor.epoch), at: Math.max(record.at, floor.at) };
6857
+ }
6858
+ sharedInvalidationEpoch(key) {
6859
+ return this.sharedInvalidationRecord(key)?.epoch ?? 0;
6860
+ }
6861
+ bumpSharedInvalidationEpoch(key) {
6862
+ const cache = this.config.discoveryCache.cache;
6863
+ let records = sharedInvalidations.get(cache);
6864
+ if (!records) {
6865
+ records = /* @__PURE__ */ new Map();
6866
+ sharedInvalidations.set(cache, records);
6867
+ }
6868
+ const now = Date.now();
6869
+ records.delete(key);
6870
+ records.set(key, { epoch: ++sharedInvalidationCounter, at: now });
6871
+ for (const [oldKey, record] of records) {
6872
+ if (records.size <= MAX_SHARED_INVALIDATION_RECORDS && record.at + import_agent_discovery_cache.MAX_AGENT_DISCOVERY_TTL_MS > now) break;
6873
+ const floor = sharedInvalidationFloors.get(cache);
6874
+ sharedInvalidationFloors.set(cache, {
6875
+ epoch: Math.max(record.epoch, floor?.epoch ?? 0),
6876
+ at: Math.max(record.at, floor?.at ?? 0)
6877
+ });
6878
+ records.delete(oldKey);
6879
+ }
6880
+ }
6881
+ /**
6882
+ * Remember which key the local capability evidence belongs to and, unless it
6883
+ * is application-owned (`primeCapabilities`), the shared invalidation epoch it
6884
+ * was obtained under. Application-owned evidence keeps its caller-declared
6885
+ * freshness and is not discarded by another instance's invalidation.
6886
+ */
6887
+ recordSharedEvidence(key, applicationOwned = false) {
6888
+ this.sharedEvidenceKey = key;
6889
+ this.sharedEvidenceEpoch = key && this.config.discoveryCache && !applicationOwned ? this.sharedInvalidationEpoch(key) : void 0;
6890
+ }
6891
+ /** Non-secret fingerprint of the request-signing identity, so keys never mix signing identities. */
6892
+ discoverySigningFingerprint() {
6893
+ const signing = this.normalizedAgent.request_signing;
6894
+ if (!signing) return void 0;
6895
+ const provider = signing.kind === "provider" ? signing.provider : void 0;
6896
+ return {
6897
+ kind: signing.kind ?? "inline",
6898
+ agentUrl: signing.agent_url ?? null,
6899
+ kid: (provider ? provider.keyid : signing.kid) ?? null,
6900
+ alg: (provider ? provider.algorithm : signing.alg) ?? null,
6901
+ alwaysSign: signing.always_sign ?? null,
6902
+ signSupported: signing.sign_supported ?? null
6903
+ };
6904
+ }
6905
+ /**
6906
+ * Shared-discovery key for the effective transport policy of this call, or
6907
+ * `undefined` when sharing is off or the call uses a scoped `trustedFetchFn`
6908
+ * (whose network boundary is per-call, so its evidence is never shared).
6909
+ */
6910
+ sharedDiscoveryKey(options) {
6911
+ const config = this.config.discoveryCache;
6912
+ if (!config) return void 0;
6913
+ const transport = (0, import_protocols.normalizeTransportOptions)(options?.transport ?? this.config.transport);
6914
+ if (transport?.trustedFetchFn !== void 0) return void 0;
6915
+ return (0, import_agent_discovery_cache.agentDiscoveryCacheKey)({
6916
+ protocol: this.normalizedAgent.protocol,
6917
+ agentUri: this.normalizedAgent.agent_uri,
6918
+ adcpVersion: this.resolvedAdcpVersion,
6919
+ wireAdcpVersion: this.config.wireAdcpVersion,
6920
+ authIdentity: config.authIdentity,
6921
+ credentialKind: this.discoveryCredentialKind(),
6922
+ headers: (0, import_mcp_call_context.withAmbientIdentityHeaders)(this.normalizedAgent.headers),
6923
+ signing: this.discoverySigningFingerprint(),
6924
+ allowPrivateIp: transport?.allowPrivateIp,
6925
+ maxResponseBytes: transport?.maxResponseBytes
6926
+ });
6927
+ }
6928
+ /**
6929
+ * Run a shared-cache operation under the caller's signal and a fixed backend
6930
+ * deadline, so an unresponsive store can neither make a call uncancellable nor
6931
+ * stall it beyond {@link SHARED_DISCOVERY_CACHE_TIMEOUT_MS}. Rejects on the
6932
+ * caller's abort or when the backend fails or times out; callers decide which
6933
+ * of those is a miss.
6934
+ */
6935
+ boundedCacheOperation(operation, signal) {
6936
+ return (0, import_abort2.withAbortSignal)([signal], SHARED_DISCOVERY_CACHE_TIMEOUT_MS, () => Promise.resolve().then(operation));
6937
+ }
6938
+ /**
6939
+ * Run one backend mutation (or read-modify-write) for a shared key strictly
6940
+ * after every earlier mutation of that key in this process has actually
6941
+ * settled. The caller's wait is still bounded: an unresponsive backend makes
6942
+ * the caller see a miss, but later mutations stay queued behind the hung
6943
+ * operation, so an old callback can never land after a newer one.
6944
+ *
6945
+ * The queue is bounded. A `droppable` mutation (a write or cleanup) is
6946
+ * abandoned when its caller stopped waiting before it started, and is refused
6947
+ * when the key's queue is full; refusal is a failed write, never a call
6948
+ * failure. A `delete` is never refused for a known key: while one is queued
6949
+ * and not yet started, a newer invalidation joins it. Only for a new key beyond
6950
+ * the key cap is a delete refused — the invalidation tombstone still hides the
6951
+ * entry from this process's readers.
6952
+ */
6953
+ serializedCacheMutation(key, operation, signal, kind = "droppable") {
6954
+ const cache = this.config.discoveryCache.cache;
6955
+ let chains = sharedMutationChains.get(cache);
6956
+ if (!chains) {
6957
+ chains = /* @__PURE__ */ new Map();
6958
+ sharedMutationChains.set(cache, chains);
6959
+ }
6960
+ let queue = chains.get(key);
6961
+ if (!queue && chains.size >= MAX_SHARED_MUTATION_KEYS) {
6962
+ return Promise.reject(new Error("Shared discovery cache mutation queue is full."));
6963
+ }
6964
+ if (kind === "delete" && queue?.waitingDelete && !queue.waitingDelete.started) {
6965
+ const joined = queue.waitingDelete.promise;
6966
+ return this.boundedCacheOperation(() => joined, signal);
6967
+ }
6968
+ if (kind === "droppable" && queue && queue.pending >= MAX_PENDING_SHARED_MUTATIONS_PER_KEY) {
6969
+ return Promise.reject(new Error("Shared discovery cache mutation queue is full."));
6970
+ }
6971
+ if (!queue) {
6972
+ queue = { tail: Promise.resolve(), pending: 0 };
6973
+ chains.set(key, queue);
6974
+ }
6975
+ const current = queue;
6976
+ const state = { abandoned: false, started: false };
6977
+ const run = current.tail.then(() => {
6978
+ if (kind === "droppable" && state.abandoned) throw new Error("Shared discovery cache mutation abandoned.");
6979
+ state.started = true;
6980
+ if (current.waitingDelete?.promise === run) current.waitingDelete.started = true;
6981
+ return operation();
6982
+ });
6983
+ current.pending++;
6984
+ if (kind === "delete") current.waitingDelete = { promise: run, started: false };
6985
+ const tail = run.then(
6986
+ () => void 0,
6987
+ () => void 0
6988
+ );
6989
+ current.tail = tail;
6990
+ void tail.then(() => {
6991
+ current.pending--;
6992
+ if (current.waitingDelete?.promise === run) current.waitingDelete = void 0;
6993
+ if (current.pending === 0) {
6994
+ if (chains.get(key) === current) chains.delete(key);
6995
+ if (chains.size === 0 && sharedMutationChains.get(cache) === chains) sharedMutationChains.delete(cache);
6996
+ }
6997
+ });
6998
+ return this.boundedCacheOperation(() => run, signal).catch((error) => {
6999
+ state.abandoned = true;
7000
+ throw error;
7001
+ });
7002
+ }
7003
+ /** Is this stored entry structurally sound and not expired (before any caller-TTL cap)? */
7004
+ isLiveSharedEntry(entry, now) {
7005
+ return Number.isFinite(entry.expiresAt) && Number.isFinite(entry.observedAt) && entry.expiresAt >= entry.observedAt && entry.expiresAt > now;
7006
+ }
7007
+ /**
7008
+ * Read a fresh, structurally valid shared entry. A backend failure or
7009
+ * timeout is a miss, never a call failure; the caller's own abort propagates.
7010
+ */
7011
+ async readSharedDiscovery(options) {
7012
+ const key = this.sharedDiscoveryKey(options);
7013
+ if (!key) return void 0;
7014
+ const cache = this.config.discoveryCache.cache;
7015
+ const epoch = this.discoveryEpochSnapshot(options);
7016
+ try {
7017
+ const entry = await this.boundedCacheOperation(() => cache.get(key), options?.signal);
7018
+ if (epoch && !this.discoveryEpochIs(epoch)) return void 0;
7019
+ if (!entry) return void 0;
7020
+ const invalidation = this.sharedInvalidationRecord(key);
7021
+ if (invalidation && entry.observedAt <= invalidation.at) return void 0;
7022
+ const now = Date.now();
7023
+ if (!this.isLiveSharedEntry(entry, now)) {
7024
+ await this.serializedCacheMutation(
7025
+ key,
7026
+ async () => {
7027
+ const current = await cache.get(key);
7028
+ if (current && !this.isLiveSharedEntry(current, Date.now())) await cache.delete(key);
7029
+ },
7030
+ options?.signal
7031
+ );
7032
+ return void 0;
7033
+ }
7034
+ if (entry.observedAt > now + SHARED_DISCOVERY_CLOCK_SKEW_MS) return void 0;
7035
+ const callerExpiresAt = entry.observedAt + (0, import_agent_discovery_cache.resolveAgentDiscoveryTtlMs)(this.config.discoveryCache.ttlMs);
7036
+ if (!(callerExpiresAt > now)) return void 0;
7037
+ return callerExpiresAt < entry.expiresAt ? { ...entry, expiresAt: callerExpiresAt } : entry;
7038
+ } catch {
7039
+ (0, import_abort2.throwIfAborted)(options?.signal);
7040
+ return void 0;
7041
+ }
7042
+ }
7043
+ /**
7044
+ * Merge fresh evidence into the shared entry; the merged entry never outlives
7045
+ * its oldest part. Returns the stored entry, or `undefined` when it was not
7046
+ * stored: automatic writes
7047
+ * ignore a `false` (a failed write only costs a later rediscovery), while
7048
+ * {@link primeDiscoveryCache} reports it.
7049
+ */
7050
+ async writeSharedDiscovery(options, patch, replace = false, epoch = this.discoveryEpochSnapshot(options)) {
7051
+ const key = this.sharedDiscoveryKey(options);
7052
+ if (!key) return void 0;
7053
+ if (patch.endpoint && !this.isDiscoverableEndpoint(patch.endpoint.agentUri)) {
7054
+ patch = { capabilities: patch.capabilities, toolSchemas: patch.toolSchemas };
7055
+ if (!patch.capabilities && !patch.toolSchemas) return void 0;
7056
+ }
7057
+ const config = this.config.discoveryCache;
7058
+ const stale = () => epoch !== void 0 && !this.discoveryEpochIs(epoch);
7059
+ try {
7060
+ return await this.serializedCacheMutation(
7061
+ key,
7062
+ async () => {
7063
+ if (stale()) return void 0;
7064
+ const invalidation = this.sharedInvalidationRecord(key);
7065
+ const now = Math.max(Date.now(), (invalidation?.at ?? 0) + 1);
7066
+ let existing;
7067
+ if (!replace) {
7068
+ const raw = await config.cache.get(key);
7069
+ if (raw && this.isLiveSharedEntry(raw, now) && raw.observedAt <= now + SHARED_DISCOVERY_CLOCK_SKEW_MS && !(invalidation && raw.observedAt <= invalidation.at)) {
7070
+ const endpointObservedAt = raw.endpoint?.observedAt ?? raw.observedAt;
7071
+ existing = { ...raw };
7072
+ if (existing.endpoint && (!this.isDiscoverableEndpoint(existing.endpoint.agentUri) || invalidation && endpointObservedAt <= invalidation.at))
7073
+ delete existing.endpoint;
7074
+ }
7075
+ if (stale()) return void 0;
7076
+ }
7077
+ const restartsClock = !existing || patch.capabilities !== void 0;
7078
+ const ttl = (0, import_agent_discovery_cache.resolveAgentDiscoveryTtlMs)(config.ttlMs);
7079
+ const entry = {
7080
+ ...existing?.capabilities && { capabilities: existing.capabilities },
7081
+ ...existing?.toolSchemas && { toolSchemas: existing.toolSchemas },
7082
+ ...existing?.endpoint && { endpoint: existing.endpoint },
7083
+ ...patch.endpoint && { endpoint: { ...patch.endpoint, observedAt: now } },
7084
+ ...patch.capabilities && { capabilities: patch.capabilities },
7085
+ ...patch.toolSchemas && { toolSchemas: patch.toolSchemas },
7086
+ observedAt: restartsClock || !existing ? now : Math.min(existing.observedAt, now),
7087
+ expiresAt: restartsClock || !existing ? now + ttl : Math.min(existing.expiresAt, now + ttl)
7088
+ };
7089
+ await config.cache.set(key, structuredClone(entry));
7090
+ return stale() ? void 0 : entry;
7091
+ },
7092
+ options?.signal
7093
+ );
7094
+ } catch {
7095
+ return void 0;
7096
+ }
7097
+ }
7098
+ /** Is `agentUri` a credential-free URL on the configured agent's origin (all discovery ever tries)? */
7099
+ isDiscoverableEndpoint(agentUri) {
7100
+ if (typeof agentUri !== "string") return false;
7101
+ try {
7102
+ const candidate = new URL(agentUri);
7103
+ return candidate.username === "" && candidate.password === "" && candidate.search === "" && candidate.hash === "" && candidate.origin === new URL(this.normalizedAgent.agent_uri).origin;
7104
+ } catch {
7105
+ return false;
7106
+ }
7107
+ }
7108
+ /** Adopt a previously discovered MCP endpoint instead of re-probing the agent. */
7109
+ async adoptSharedEndpoint(options) {
7110
+ const readEpoch = this.discoveryEpochSnapshot(options);
7111
+ const entry = await this.readSharedDiscovery(options);
7112
+ if (readEpoch && !this.discoveryEpochIs(readEpoch)) return;
7113
+ const endpoint = entry?.endpoint;
7114
+ if (!entry || !endpoint || !this.isDiscoverableEndpoint(endpoint.agentUri)) return;
7115
+ const observedAt = Number.isFinite(endpoint.observedAt) ? endpoint.observedAt : entry.observedAt;
7116
+ const sharedKey = this.sharedDiscoveryKey(options);
7117
+ const tombstone = sharedKey ? this.sharedInvalidationRecord(sharedKey) : void 0;
7118
+ if (tombstone && observedAt <= tombstone.at) return;
7119
+ const expiresAt = Math.min(
7120
+ entry.expiresAt,
7121
+ observedAt + (0, import_agent_discovery_cache.resolveAgentDiscoveryTtlMs)(this.config.discoveryCache.ttlMs)
7122
+ );
7123
+ if (!(expiresAt > Date.now()) || observedAt > Date.now() + SHARED_DISCOVERY_CLOCK_SKEW_MS) return;
7124
+ this.discoveredEndpoint = endpoint.agentUri;
7125
+ this.discoveredEndpointKey = this.sharedDiscoveryKey(options);
7126
+ this.discoveredEndpointEpoch = this.discoveredEndpointKey ? this.sharedInvalidationEpoch(this.discoveredEndpointKey) : void 0;
7127
+ this.discoveredEndpointExpiresAt = expiresAt;
7128
+ this.canonicalBaseUrl = this.computeBaseUrl(endpoint.agentUri);
7129
+ if (endpoint.mcpEra === "legacy" || endpoint.mcpEra === "modern") {
7130
+ this.discoveredMcpEra = endpoint.mcpEra;
7131
+ this.discoveredMcpEraAt = observedAt;
7132
+ }
7133
+ }
7134
+ /**
7135
+ * Drop discovery evidence for this client: its in-memory capability and
7136
+ * tool-schema caches, the discovered endpoint, and the shared
7137
+ * `discoveryCache` entry (when configured). The next call rediscovers.
7138
+ *
7139
+ * Call this when you learn the seller changed (a deploy, a version bump, a
7140
+ * capability-dependent error you observed). The SDK also invalidates by
7141
+ * itself when a feature check fails against shared evidence, and when a
7142
+ * seller answers with a version or capability error.
7143
+ */
7144
+ async invalidateDiscoveryCache(options) {
7145
+ this.cachedCapabilities = void 0;
7146
+ this.cachedToolSchemas = void 0;
7147
+ this.primedCapabilitiesExpiresAt = void 0;
7148
+ this.capabilitiesFromSharedCache = false;
7149
+ this.capabilityEvidenceScopeKey = (0, import_node_crypto.randomUUID)();
7150
+ this.discoveryEpoch++;
7151
+ if (this.normalizedAgent._needsDiscovery) {
7152
+ this.discoveredEndpoint = void 0;
7153
+ this.discoveredAgent = void 0;
7154
+ this.discoveredMcpEra = void 0;
7155
+ this.discoveredEndpointKey = void 0;
7156
+ this.discoveredEndpointEpoch = void 0;
7157
+ this.discoveredEndpointExpiresAt = void 0;
7158
+ }
7159
+ const key = this.sharedDiscoveryKey(options);
7160
+ if (!key) return;
7161
+ this.bumpSharedInvalidationEpoch(key);
7162
+ try {
7163
+ const cache = this.config.discoveryCache.cache;
7164
+ await this.serializedCacheMutation(key, () => cache.delete(key), void 0, "delete");
7165
+ } catch {
7166
+ }
7167
+ }
7168
+ /**
7169
+ * Pre-seed the shared `discoveryCache` with capability evidence the caller
7170
+ * already holds (for example from a registry sync), so no client sharing
7171
+ * this identity probes the seller within the TTL. Unlike
7172
+ * {@link primeCapabilities} the seeded evidence outlives this instance.
7173
+ *
7174
+ * Returns `true` only once the backing cache has accepted the entry. Returns
7175
+ * `false` — having seeded nothing — when sharing is not configured, the
7176
+ * client uses a scoped `trustedFetchFn`, or the evidence is synthetic,
7177
+ * malformed or not cloneable. When the cache rejects the write or does not
7178
+ * answer within the backend deadline, the result is also `false`, but the
7179
+ * outcome is indeterminate: a write already handed to the backend may still
7180
+ * land, whereas one still queued behind earlier operations is dropped. Treat
7181
+ * `false` as "not confirmed", and seed again (or invalidate) rather than
7182
+ * assuming the cache is empty. Seed `endpoint` as well
7183
+ * to skip the endpoint probe on the first call; without it that probe still
7184
+ * runs once per cold client.
7185
+ */
7186
+ async primeDiscoveryCache(seed) {
7187
+ const key = this.sharedDiscoveryKey();
7188
+ const capabilities = seed?.capabilities;
7189
+ if (!key || !isCapabilityEvidence(capabilities) || capabilities._synthetic) return false;
7190
+ const toolSchemas = seed.toolSchemas;
7191
+ if (toolSchemas !== void 0 && !isToolSchemaRecord(toolSchemas)) {
7192
+ return false;
7193
+ }
7194
+ if (toolSchemas === void 0 && !Array.isArray(capabilities.discoveredTools)) return false;
7195
+ if (seed.endpoint !== void 0 && !this.isDiscoverableEndpoint(seed.endpoint.agentUri)) return false;
7196
+ try {
7197
+ const stored = await this.writeSharedDiscovery(
7198
+ void 0,
7199
+ {
7200
+ capabilities: structuredClone(capabilities),
7201
+ ...toolSchemas && { toolSchemas: structuredClone(toolSchemas) },
7202
+ ...seed.endpoint && {
7203
+ endpoint: {
7204
+ agentUri: seed.endpoint.agentUri,
7205
+ ...seed.endpoint.mcpEra && { mcpEra: seed.endpoint.mcpEra }
7206
+ }
7207
+ }
7208
+ },
7209
+ true
7210
+ );
7211
+ return stored !== void 0;
7212
+ } catch {
7213
+ return false;
7214
+ }
7215
+ }
7216
+ /** Drop shared evidence when a seller answers with an error that says our cached view of it is stale. */
7217
+ async invalidateDiscoveryOnCapabilityError(code, options) {
7218
+ if (!this.config.discoveryCache || !code || !CAPABILITY_STALE_ERROR_CODES.has(code)) return;
7219
+ await this.invalidateDiscoveryCache(options);
7220
+ }
6595
7221
  /**
6596
7222
  * Emit a one-time warning when the agent reports v2 capabilities.
6597
7223
  *
@@ -6740,8 +7366,21 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6740
7366
  * with an actionable error message.
6741
7367
  */
6742
7368
  async require(...features) {
6743
- const capabilities = await this.getCapabilities();
6744
- const missing = features.filter((f) => !(0, import_capabilities.resolveFeature)(capabilities, f));
7369
+ await this.assertFeaturesSupported(features);
7370
+ }
7371
+ /**
7372
+ * Throw {@link FeatureUnsupportedError} when a feature is missing, deciding on
7373
+ * live capabilities when the refusal would rest on shared evidence that may
7374
+ * predate a seller upgrade.
7375
+ */
7376
+ async assertFeaturesSupported(requiredFeatures, options) {
7377
+ let capabilities = await this.getCapabilities(options);
7378
+ let missing = requiredFeatures.filter((f) => !(0, import_capabilities.resolveFeature)(capabilities, f));
7379
+ if (missing.length > 0 && this.capabilitiesFromSharedCache) {
7380
+ await this.invalidateDiscoveryCache(options);
7381
+ capabilities = await this.getCapabilities(options);
7382
+ missing = requiredFeatures.filter((f) => !(0, import_capabilities.resolveFeature)(capabilities, f));
7383
+ }
6745
7384
  if (missing.length > 0) {
6746
7385
  throw new import_errors.FeatureUnsupportedError(missing, (0, import_capabilities.listDeclaredFeatures)(capabilities), this.agent.agent_uri);
6747
7386
  }
@@ -6754,7 +7393,10 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6754
7393
  this.cachedCapabilities = void 0;
6755
7394
  this.cachedToolSchemas = void 0;
6756
7395
  this.primedCapabilitiesExpiresAt = void 0;
7396
+ this.capabilitiesFromSharedCache = false;
6757
7397
  this.capabilityEvidenceScopeKey = (0, import_node_crypto.randomUUID)();
7398
+ this.discoveryEpoch++;
7399
+ if (this.config.discoveryCache) await this.invalidateDiscoveryCache();
6758
7400
  return this.getCapabilities();
6759
7401
  }
6760
7402
  /**
@@ -6767,11 +7409,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6767
7409
  if (this.config.validateFeatures === false) return;
6768
7410
  const requiredFeatures = import_capabilities.TASK_FEATURE_MAP[taskName];
6769
7411
  if (!requiredFeatures || requiredFeatures.length === 0) return;
6770
- const capabilities = await this.getCapabilities(options);
6771
- const missing = requiredFeatures.filter((f) => !(0, import_capabilities.resolveFeature)(capabilities, f));
6772
- if (missing.length > 0) {
6773
- throw new import_errors.FeatureUnsupportedError(missing, (0, import_capabilities.listDeclaredFeatures)(capabilities), this.agent.agent_uri);
6774
- }
7412
+ await this.assertFeaturesSupported(requiredFeatures, options);
6775
7413
  }
6776
7414
  /**
6777
7415
  * Fail version-incompatible request shapes before schema validation.