@adcp/sdk 17.0.0-beta.0 → 17.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/lib/adapters/version/3.1/bidding.js +1 -1
  2. package/dist/lib/adapters/version/3.1/bidding.mjs +1 -1
  3. package/dist/lib/core/ResponseValidator.js +1 -1
  4. package/dist/lib/core/ResponseValidator.mjs +1 -1
  5. package/dist/lib/core/SingleAgentClient.d.mts +7 -5
  6. package/dist/lib/core/SingleAgentClient.d.ts +7 -5
  7. package/dist/lib/core/SingleAgentClient.js +57 -32
  8. package/dist/lib/core/SingleAgentClient.mjs +59 -33
  9. package/dist/lib/core/TaskExecutor.js +5 -3
  10. package/dist/lib/core/TaskExecutor.mjs +5 -3
  11. package/dist/lib/errors/index.d.mts +22 -0
  12. package/dist/lib/errors/index.d.ts +22 -0
  13. package/dist/lib/errors/index.js +43 -0
  14. package/dist/lib/errors/index.mjs +42 -0
  15. package/dist/lib/index.d.mts +1 -1
  16. package/dist/lib/index.d.ts +1 -1
  17. package/dist/lib/index.js +2 -0
  18. package/dist/lib/index.mjs +2 -0
  19. package/dist/lib/protocols/mcp-modern.d.mts +2 -0
  20. package/dist/lib/protocols/mcp-modern.d.ts +2 -0
  21. package/dist/lib/protocols/mcp-modern.js +31 -5
  22. package/dist/lib/protocols/mcp-modern.mjs +31 -5
  23. package/dist/lib/protocols/mcp-scope.d.mts +3 -1
  24. package/dist/lib/protocols/mcp-scope.d.ts +3 -1
  25. package/dist/lib/protocols/mcp-scope.js +19 -2
  26. package/dist/lib/protocols/mcp-scope.mjs +18 -2
  27. package/dist/lib/protocols/mcp.js +12 -6
  28. package/dist/lib/protocols/mcp.mjs +13 -6
  29. package/dist/lib/testing/client.d.mts +1 -0
  30. package/dist/lib/testing/client.d.ts +1 -0
  31. package/dist/lib/testing/client.js +6 -5
  32. package/dist/lib/testing/client.mjs +6 -5
  33. package/dist/lib/testing/storyboard/agent-routing.d.mts +5 -2
  34. package/dist/lib/testing/storyboard/agent-routing.d.ts +5 -2
  35. package/dist/lib/testing/storyboard/agent-routing.js +11 -4
  36. package/dist/lib/testing/storyboard/agent-routing.mjs +11 -4
  37. package/dist/lib/testing/storyboard/context.d.mts +13 -3
  38. package/dist/lib/testing/storyboard/context.d.ts +13 -3
  39. package/dist/lib/testing/storyboard/context.js +15 -10
  40. package/dist/lib/testing/storyboard/context.mjs +15 -10
  41. package/dist/lib/testing/storyboard/runner.js +141 -13
  42. package/dist/lib/testing/storyboard/runner.mjs +141 -13
  43. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  44. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  45. package/dist/lib/testing/storyboard/validations.js +4 -1
  46. package/dist/lib/testing/storyboard/validations.mjs +4 -1
  47. package/dist/lib/testing/test-controller.js +3 -0
  48. package/dist/lib/testing/test-controller.mjs +3 -0
  49. package/dist/lib/utils/envelope-status-compat.d.mts +18 -1
  50. package/dist/lib/utils/envelope-status-compat.d.ts +18 -1
  51. package/dist/lib/utils/envelope-status-compat.js +11 -5
  52. package/dist/lib/utils/envelope-status-compat.mjs +11 -5
  53. package/dist/lib/utils/response-unwrapper.js +12 -9
  54. package/dist/lib/utils/response-unwrapper.mjs +12 -9
  55. package/dist/lib/v2/projection/creative-delivery.js +56 -11
  56. package/dist/lib/v2/projection/creative-delivery.mjs +56 -11
  57. package/dist/lib/validation/schema-validator.js +1 -1
  58. package/dist/lib/validation/schema-validator.mjs +1 -1
  59. package/dist/lib/version.d.mts +3 -3
  60. package/dist/lib/version.d.ts +3 -3
  61. package/dist/lib/version.js +3 -3
  62. package/dist/lib/version.mjs +3 -3
  63. package/docs/TYPE-SUMMARY.md +2 -2
  64. package/docs/guides/COLLECTION-FIXTURE-FOUNDATION.md +11 -0
  65. package/docs/guides/DISCOVERY-CACHE-AND-SESSION-REUSE.md +12 -1
  66. package/docs/llms.txt +2 -2
  67. package/docs/migration-14.x-rc-worksheet.md +4 -4
  68. package/docs/migration-16-to-17.md +24 -1
  69. package/docs/migration-reporting-storage-package.md +1 -1
  70. package/package.json +1 -1
@@ -96,7 +96,7 @@ const updateMediaBuyAdapter = {
96
96
  function adaptMediaBuyStatus(toolName) {
97
97
  return (data) => {
98
98
  if (!isObject(data)) return { data };
99
- const adapted = (0, import_envelope_status_compat.injectLegacyEnvelopeStatus)(data, { toolName });
99
+ const adapted = (0, import_envelope_status_compat.injectLegacyEnvelopeStatus)(data, { toolName, adcpVersion: "3.1" });
100
100
  if (adapted === data || adapted.media_buy_status === data.media_buy_status) return { data };
101
101
  return {
102
102
  data: adapted,
@@ -72,7 +72,7 @@ const updateMediaBuyAdapter = {
72
72
  function adaptMediaBuyStatus(toolName) {
73
73
  return (data) => {
74
74
  if (!isObject(data)) return { data };
75
- const adapted = injectLegacyEnvelopeStatus(data, { toolName });
75
+ const adapted = injectLegacyEnvelopeStatus(data, { toolName, adcpVersion: "3.1" });
76
76
  if (adapted === data || adapted.media_buy_status === data.media_buy_status) return { data };
77
77
  return {
78
78
  data: adapted,
@@ -252,7 +252,7 @@ class ResponseValidator {
252
252
  if (!schema) {
253
253
  return null;
254
254
  }
255
- const validatePayload = data && typeof data === "object" && !Array.isArray(data) ? (0, import_envelope_status_compat.injectLegacyEnvelopeStatus)(data, { toolName }) : data;
255
+ const validatePayload = data && typeof data === "object" && !Array.isArray(data) ? (0, import_envelope_status_compat.injectLegacyEnvelopeStatus)(data, { toolName, adcpVersion: responseAdcpVersion }) : data;
256
256
  const result = schema.safeParse((0, import_response_schemas.prepareResponseForSchemaValidation)(toolName, validatePayload, responseAdcpVersion));
257
257
  return result.success ? null : result.error;
258
258
  }
@@ -228,7 +228,7 @@ class ResponseValidator {
228
228
  if (!schema) {
229
229
  return null;
230
230
  }
231
- const validatePayload = data && typeof data === "object" && !Array.isArray(data) ? injectLegacyEnvelopeStatus(data, { toolName }) : data;
231
+ const validatePayload = data && typeof data === "object" && !Array.isArray(data) ? injectLegacyEnvelopeStatus(data, { toolName, adcpVersion: responseAdcpVersion }) : data;
232
232
  const result = schema.safeParse(prepareResponseForSchemaValidation(toolName, validatePayload, responseAdcpVersion));
233
233
  return result.success ? null : result.error;
234
234
  }
@@ -726,6 +726,9 @@ export declare class SingleAgentClient {
726
726
  * - A validated MCP protected-resource metadata chain yields NeedsAuthorizationError
727
727
  * - Otherwise AuthenticationRequiredError carries the advertised challenge
728
728
  *
729
+ * Rate limiting (429, or 503 with Retry-After) stops probing at that URL and
730
+ * throws AgentRateLimitedError carrying the status and Retry-After.
731
+ *
729
732
  * Note: This is async and called lazily on first agent interaction
730
733
  */
731
734
  private discoverMCPEndpoint;
@@ -888,11 +891,10 @@ export declare class SingleAgentClient {
888
891
  * @returns true if signature is valid
889
892
  */
890
893
  verifyWebhookSignature(rawBodyOrPayload: string | Buffer | Uint8Array | unknown, signature: WebhookHeaderValue, timestamp: WebhookHeaderValue): boolean;
891
- /**
892
- * Execute task and call appropriate handler on completion
893
- *
894
- * Adapts requests for maintained AdCP 3 releases and normalizes product responses.
895
- */
894
+ /** One task owns discovery and dispatch sessions unless the caller already owns reuse. */
895
+ private withConnectionScope;
896
+ private withTaskConnectionScope;
897
+ /** Execute a task, adapt maintained AdCP 3 releases, and call the completion handler. */
896
898
  private executeAndHandle;
897
899
  private executeAndHandleWithinDeadline;
898
900
  private finalizeTaskResult;
@@ -726,6 +726,9 @@ export declare class SingleAgentClient {
726
726
  * - A validated MCP protected-resource metadata chain yields NeedsAuthorizationError
727
727
  * - Otherwise AuthenticationRequiredError carries the advertised challenge
728
728
  *
729
+ * Rate limiting (429, or 503 with Retry-After) stops probing at that URL and
730
+ * throws AgentRateLimitedError carrying the status and Retry-After.
731
+ *
729
732
  * Note: This is async and called lazily on first agent interaction
730
733
  */
731
734
  private discoverMCPEndpoint;
@@ -888,11 +891,10 @@ export declare class SingleAgentClient {
888
891
  * @returns true if signature is valid
889
892
  */
890
893
  verifyWebhookSignature(rawBodyOrPayload: string | Buffer | Uint8Array | unknown, signature: WebhookHeaderValue, timestamp: WebhookHeaderValue): boolean;
891
- /**
892
- * Execute task and call appropriate handler on completion
893
- *
894
- * Adapts requests for maintained AdCP 3 releases and normalizes product responses.
895
- */
894
+ /** One task owns discovery and dispatch sessions unless the caller already owns reuse. */
895
+ private withConnectionScope;
896
+ private withTaskConnectionScope;
897
+ /** Execute a task, adapt maintained AdCP 3 releases, and call the completion handler. */
896
898
  private executeAndHandle;
897
899
  private executeAndHandleWithinDeadline;
898
900
  private finalizeTaskResult;
@@ -35,6 +35,7 @@ __export(SingleAgentClient_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(SingleAgentClient_exports);
37
37
  var import_mcp_connection_policy = require('../protocols/mcp-connection-policy.js');
38
+ var import_mcp_scope = require('../protocols/mcp-scope.js');
38
39
  var import_protocol_methods = require('../signing/protocol-methods.js');
39
40
  var import_pricing_adapter = require('../utils/pricing-adapter.js');
40
41
  var import_creative_adapter = require('../utils/creative-adapter.js');
@@ -2199,6 +2200,9 @@ class SingleAgentClient {
2199
2200
  * - A validated MCP protected-resource metadata chain yields NeedsAuthorizationError
2200
2201
  * - Otherwise AuthenticationRequiredError carries the advertised challenge
2201
2202
  *
2203
+ * Rate limiting (429, or 503 with Retry-After) stops probing at that URL and
2204
+ * throws AgentRateLimitedError carrying the status and Retry-After.
2205
+ *
2202
2206
  * Note: This is async and called lazily on first agent interaction
2203
2207
  */
2204
2208
  async discoverMCPEndpoint(providedUri, options) {
@@ -2209,8 +2213,8 @@ class SingleAgentClient {
2209
2213
  }
2210
2214
  async discoverMCPEndpointWithConnectionPolicy(providedUri, options) {
2211
2215
  (0, import_abort2.throwIfAborted)(options?.signal);
2212
- const { connectMCPWithFallback } = await import("../protocols/mcp.js");
2213
- const { probeModernMCPConnection } = await import("../protocols/mcp-modern.js");
2216
+ const { connectMCPWithFallback, withScopedLegacyConnection } = await import("../protocols/mcp.js");
2217
+ const { probeModernMCPConnection, retryAfterOf } = await import("../protocols/mcp-modern.js");
2214
2218
  const transport = (0, import_protocols.normalizeTransportOptions)(options?.transport ?? this.config.transport);
2215
2219
  const usesScopedFetch = this.usesScopedDiscoveryTransport(options);
2216
2220
  const endpointEpoch = this.discoveryEpochSnapshot(options);
@@ -2240,19 +2244,35 @@ class SingleAgentClient {
2240
2244
  }
2241
2245
  return { success: true };
2242
2246
  }
2243
- const client = await connectMCPWithFallback(
2244
- new URL(url),
2245
- authHeaders,
2246
- [],
2247
- "endpoint discovery",
2248
- transport?.trustedFetchFn,
2247
+ const shared = await withScopedLegacyConnection(
2249
2248
  {
2249
+ agentUrl: url,
2250
+ authToken,
2251
+ customHeaders: agentHeaders,
2252
+ authProvider,
2250
2253
  signal: options?.signal,
2251
2254
  requestTimeoutMs: transport?.requestTimeoutMs,
2255
+ fetchFn: transport?.trustedFetchFn,
2252
2256
  allowPrivateIp: transport?.allowPrivateIp
2253
- }
2257
+ },
2258
+ "endpoint discovery",
2259
+ async () => void 0
2254
2260
  );
2255
- await client.close();
2261
+ if (!shared) {
2262
+ const client = await connectMCPWithFallback(
2263
+ new URL(url),
2264
+ authHeaders,
2265
+ [],
2266
+ "endpoint discovery",
2267
+ transport?.trustedFetchFn,
2268
+ {
2269
+ signal: options?.signal,
2270
+ requestTimeoutMs: transport?.requestTimeoutMs,
2271
+ allowPrivateIp: transport?.allowPrivateIp
2272
+ }
2273
+ );
2274
+ await client.close();
2275
+ }
2256
2276
  if (mayRetain()) {
2257
2277
  this.discoveredMcpEra = "legacy";
2258
2278
  this.discoveredMcpEraAt = Date.now();
@@ -2269,7 +2289,8 @@ class SingleAgentClient {
2269
2289
  }
2270
2290
  const errObj = error;
2271
2291
  const status = errObj?.status || errObj?.response?.status || errObj?.cause?.status;
2272
- return { success: false, status, error };
2292
+ const retryAfter = retryAfterOf(error);
2293
+ return { success: false, status, ...retryAfter !== void 0 && { retryAfter }, error };
2273
2294
  }
2274
2295
  };
2275
2296
  const urlsToTry = [];
@@ -2292,6 +2313,9 @@ class SingleAgentClient {
2292
2313
  firstWorkingUrl = url;
2293
2314
  break;
2294
2315
  }
2316
+ if (result.status === 429 || result.status === 503 && result.retryAfter !== void 0) {
2317
+ throw new import_errors.AgentRateLimitedError(url, result.status, result.retryAfter, result.error);
2318
+ }
2295
2319
  if (result.status === 401) {
2296
2320
  got401 = true;
2297
2321
  first401Error ??= result.error;
@@ -3504,17 +3528,24 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
3504
3528
  timestamp
3505
3529
  }).ok;
3506
3530
  }
3507
- /**
3508
- * Execute task and call appropriate handler on completion
3509
- *
3510
- * Adapts requests for maintained AdCP 3 releases and normalizes product responses.
3511
- */
3531
+ /** One task owns discovery and dispatch sessions unless the caller already owns reuse. */
3532
+ withConnectionScope(options, run) {
3533
+ const transport = (0, import_protocols.normalizeTransportOptions)(options?.transport ?? this.config.transport);
3534
+ return (0, import_mcp_connection_policy.withMCPConnectionPolicy)(
3535
+ transport,
3536
+ () => (0, import_mcp_connection_policy.shouldReuseMCPConnection)(transport?.trustedFetchFn !== void 0) ? run() : (0, import_mcp_scope.withMCPConnectionScope)(run, { signal: options?.signal, preserveOnInterruption: true })
3537
+ );
3538
+ }
3539
+ withTaskConnectionScope(options, run) {
3540
+ return (0, import_task_deadline.withTaskDeadline)(options, (effective) => this.withConnectionScope(effective, () => run(effective)));
3541
+ }
3542
+ /** Execute a task, adapt maintained AdCP 3 releases, and call the completion handler. */
3512
3543
  async executeAndHandle(taskType, handlerName, params, inputHandler, options, transformCompletedResponse, legacyFormatConverter, canonicalRequest, projectionCatalogs) {
3513
3544
  const requestParamsSnapshot = structuredClone(params);
3514
3545
  const canonicalRequestSnapshot = canonicalRequest === void 0 ? void 0 : structuredClone(canonicalRequest);
3515
3546
  const deferredProjectionCatalogs = projectionCatalogs ? structuredClone(projectionCatalogs) : void 0;
3516
3547
  const taskOptionsSnapshot = snapshotTaskOptions(options);
3517
- return (0, import_task_deadline.withTaskDeadline)(
3548
+ return this.withTaskConnectionScope(
3518
3549
  taskOptionsSnapshot,
3519
3550
  (effectiveOptions) => this.executeAndHandleWithinDeadline(
3520
3551
  taskType,
@@ -5060,7 +5091,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
5060
5091
  legacyFormatConverter,
5061
5092
  projectionCatalogs ?? this.config.projectionCatalogs
5062
5093
  );
5063
- return (0, import_task_deadline.withTaskDeadline)(snapshotTaskOptions(taskOptions), async (effectiveOptions) => {
5094
+ return this.withTaskConnectionScope(snapshotTaskOptions(taskOptions), async (effectiveOptions) => {
5064
5095
  this.assertRequestSupportedByConfiguredVersion("get_products", params, effectiveOptions);
5065
5096
  let request = await this.applyAccountPolicy("get_products", params, effectiveOptions);
5066
5097
  const legacyAccountId = request.account_id;
@@ -5269,7 +5300,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
5269
5300
  ...snapshotTaskOptions(options),
5270
5301
  ...projectionCatalogs !== void 0 && { projectionCatalogs }
5271
5302
  };
5272
- return (0, import_task_deadline.withTaskDeadline)(
5303
+ return this.withTaskConnectionScope(
5273
5304
  snapshottedOptions,
5274
5305
  (effectiveOptions) => this.createMediaBuyWithinDeadline(requestSnapshot, inputHandler, effectiveOptions)
5275
5306
  );
@@ -5428,7 +5459,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
5428
5459
  ...snapshotTaskOptions(options),
5429
5460
  ...projectionCatalogs !== void 0 && { projectionCatalogs }
5430
5461
  };
5431
- return (0, import_task_deadline.withTaskDeadline)(
5462
+ return this.withTaskConnectionScope(
5432
5463
  snapshottedOptions,
5433
5464
  (effectiveOptions) => this.updateMediaBuyWithinDeadline(requestSnapshot, inputHandler, effectiveOptions)
5434
5465
  );
@@ -5519,7 +5550,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
5519
5550
  ...projectionCatalogs !== void 0 && { projectionCatalogs },
5520
5551
  ...creativeFormatProjection !== void 0 && { creativeFormatProjection }
5521
5552
  };
5522
- return (0, import_task_deadline.withTaskDeadline)(
5553
+ return this.withTaskConnectionScope(
5523
5554
  snapshottedOptions,
5524
5555
  (effectiveOptions) => this.syncCreativesWithinDeadline(requestSnapshot, inputHandler, effectiveOptions)
5525
5556
  );
@@ -6036,7 +6067,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6036
6067
  * @param options - Task execution options
6037
6068
  */
6038
6069
  async getAdcpCapabilities(params, inputHandler, options) {
6039
- return (0, import_task_deadline.withTaskDeadline)(
6070
+ return this.withTaskConnectionScope(
6040
6071
  snapshotTaskOptions(options),
6041
6072
  (effectiveOptions) => this.getAdcpCapabilitiesWithinDeadline(params, inputHandler, effectiveOptions)
6042
6073
  );
@@ -6118,7 +6149,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6118
6149
  );
6119
6150
  }
6120
6151
  async resolveAccount(hints = {}, options, onTaskResult) {
6121
- return (0, import_task_deadline.withTaskDeadline)(snapshotTaskOptions(options), async (effectiveOptions) => {
6152
+ return this.withTaskConnectionScope(snapshotTaskOptions(options), async (effectiveOptions) => {
6122
6153
  const capabilities = await this.getCapabilities(effectiveOptions);
6123
6154
  if (capabilities._synthetic || !capabilities.account) {
6124
6155
  throw new import_errors3.AccountRequiredError(
@@ -6598,7 +6629,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6598
6629
  async executeTaskUnprojected(taskName, params, inputHandler, options, handlerName, beforeDispatch) {
6599
6630
  const requestParamsSnapshot = structuredClone(params);
6600
6631
  const taskOptionsSnapshot = snapshotTaskOptions(options);
6601
- return (0, import_task_deadline.withTaskDeadline)(taskOptionsSnapshot, async (effectiveOptions) => {
6632
+ return this.withTaskConnectionScope(taskOptionsSnapshot, async (effectiveOptions) => {
6602
6633
  const result = await this.executeTaskUnprojectedWithinDeadline(
6603
6634
  taskName,
6604
6635
  requestParamsSnapshot,
@@ -7177,10 +7208,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
7177
7208
  * ```
7178
7209
  */
7179
7210
  async getAgentInfo(options) {
7180
- return (0, import_mcp_connection_policy.withMCPConnectionPolicy)(
7181
- (0, import_protocols.normalizeTransportOptions)(options?.transport ?? this.config.transport),
7182
- () => this.getAgentInfoWithConnectionPolicy(options)
7183
- );
7211
+ return this.withConnectionScope(options, () => this.getAgentInfoWithConnectionPolicy(options));
7184
7212
  }
7185
7213
  async getAgentInfoWithConnectionPolicy(options) {
7186
7214
  (0, import_abort2.throwIfAborted)(options?.signal);
@@ -7431,10 +7459,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
7431
7459
  * ```
7432
7460
  */
7433
7461
  async getCapabilities(options) {
7434
- return (0, import_mcp_connection_policy.withMCPConnectionPolicy)(
7435
- (0, import_protocols.normalizeTransportOptions)(options?.transport ?? this.config.transport),
7436
- () => this.getCapabilitiesWithConnectionPolicy(options)
7437
- );
7462
+ return this.withConnectionScope(options, () => this.getCapabilitiesWithConnectionPolicy(options));
7438
7463
  }
7439
7464
  async getCapabilitiesWithConnectionPolicy(options) {
7440
7465
  (0, import_abort2.throwIfAborted)(options?.signal);
@@ -3,7 +3,8 @@ import { dirname as __adcpDirname } from "node:path";
3
3
  import { createRequire as __adcpCreateRequire } from "node:module";
4
4
  const __dirname = __adcpDirname(__adcpFileURLToPath(import.meta.url));
5
5
  const require2 = __adcpCreateRequire(import.meta.url);
6
- import { withMCPConnectionPolicy } from "../protocols/mcp-connection-policy.mjs";
6
+ import { shouldReuseMCPConnection, withMCPConnectionPolicy } from "../protocols/mcp-connection-policy.mjs";
7
+ import { withMCPConnectionScope } from "../protocols/mcp-scope.mjs";
7
8
  import { hasUnsignedCapabilityBootstrap } from "../signing/protocol-methods.mjs";
8
9
  import { normalizeGetProductsResponse } from "../utils/pricing-adapter.mjs";
9
10
  import { normalizeMediaBuyResponse } from "../utils/creative-adapter.mjs";
@@ -74,6 +75,7 @@ import { getSignedRequestRejection } from "../protocols/signedRequestRejection.m
74
75
  import { withAmbientIdentityHeaders } from "../protocols/mcp-call-context.mjs";
75
76
  import { ProtocolClient, normalizeTransportOptions } from "../protocols/index.mjs";
76
77
  import {
78
+ AgentRateLimitedError,
77
79
  AuthenticationCredentialsRejectedError,
78
80
  AuthenticationRequiredError,
79
81
  ConfigurationError,
@@ -2309,6 +2311,9 @@ class SingleAgentClient {
2309
2311
  * - A validated MCP protected-resource metadata chain yields NeedsAuthorizationError
2310
2312
  * - Otherwise AuthenticationRequiredError carries the advertised challenge
2311
2313
  *
2314
+ * Rate limiting (429, or 503 with Retry-After) stops probing at that URL and
2315
+ * throws AgentRateLimitedError carrying the status and Retry-After.
2316
+ *
2312
2317
  * Note: This is async and called lazily on first agent interaction
2313
2318
  */
2314
2319
  async discoverMCPEndpoint(providedUri, options) {
@@ -2319,8 +2324,8 @@ class SingleAgentClient {
2319
2324
  }
2320
2325
  async discoverMCPEndpointWithConnectionPolicy(providedUri, options) {
2321
2326
  throwIfAborted(options?.signal);
2322
- const { connectMCPWithFallback } = await import("../protocols/mcp.mjs");
2323
- const { probeModernMCPConnection } = await import("../protocols/mcp-modern.mjs");
2327
+ const { connectMCPWithFallback, withScopedLegacyConnection } = await import("../protocols/mcp.mjs");
2328
+ const { probeModernMCPConnection, retryAfterOf } = await import("../protocols/mcp-modern.mjs");
2324
2329
  const transport = normalizeTransportOptions(options?.transport ?? this.config.transport);
2325
2330
  const usesScopedFetch = this.usesScopedDiscoveryTransport(options);
2326
2331
  const endpointEpoch = this.discoveryEpochSnapshot(options);
@@ -2350,19 +2355,35 @@ class SingleAgentClient {
2350
2355
  }
2351
2356
  return { success: true };
2352
2357
  }
2353
- const client = await connectMCPWithFallback(
2354
- new URL(url),
2355
- authHeaders,
2356
- [],
2357
- "endpoint discovery",
2358
- transport?.trustedFetchFn,
2358
+ const shared = await withScopedLegacyConnection(
2359
2359
  {
2360
+ agentUrl: url,
2361
+ authToken,
2362
+ customHeaders: agentHeaders,
2363
+ authProvider,
2360
2364
  signal: options?.signal,
2361
2365
  requestTimeoutMs: transport?.requestTimeoutMs,
2366
+ fetchFn: transport?.trustedFetchFn,
2362
2367
  allowPrivateIp: transport?.allowPrivateIp
2363
- }
2368
+ },
2369
+ "endpoint discovery",
2370
+ async () => void 0
2364
2371
  );
2365
- await client.close();
2372
+ if (!shared) {
2373
+ const client = await connectMCPWithFallback(
2374
+ new URL(url),
2375
+ authHeaders,
2376
+ [],
2377
+ "endpoint discovery",
2378
+ transport?.trustedFetchFn,
2379
+ {
2380
+ signal: options?.signal,
2381
+ requestTimeoutMs: transport?.requestTimeoutMs,
2382
+ allowPrivateIp: transport?.allowPrivateIp
2383
+ }
2384
+ );
2385
+ await client.close();
2386
+ }
2366
2387
  if (mayRetain()) {
2367
2388
  this.discoveredMcpEra = "legacy";
2368
2389
  this.discoveredMcpEraAt = Date.now();
@@ -2379,7 +2400,8 @@ class SingleAgentClient {
2379
2400
  }
2380
2401
  const errObj = error;
2381
2402
  const status = errObj?.status || errObj?.response?.status || errObj?.cause?.status;
2382
- return { success: false, status, error };
2403
+ const retryAfter = retryAfterOf(error);
2404
+ return { success: false, status, ...retryAfter !== void 0 && { retryAfter }, error };
2383
2405
  }
2384
2406
  };
2385
2407
  const urlsToTry = [];
@@ -2402,6 +2424,9 @@ class SingleAgentClient {
2402
2424
  firstWorkingUrl = url;
2403
2425
  break;
2404
2426
  }
2427
+ if (result.status === 429 || result.status === 503 && result.retryAfter !== void 0) {
2428
+ throw new AgentRateLimitedError(url, result.status, result.retryAfter, result.error);
2429
+ }
2405
2430
  if (result.status === 401) {
2406
2431
  got401 = true;
2407
2432
  first401Error ??= result.error;
@@ -3614,17 +3639,24 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
3614
3639
  timestamp
3615
3640
  }).ok;
3616
3641
  }
3617
- /**
3618
- * Execute task and call appropriate handler on completion
3619
- *
3620
- * Adapts requests for maintained AdCP 3 releases and normalizes product responses.
3621
- */
3642
+ /** One task owns discovery and dispatch sessions unless the caller already owns reuse. */
3643
+ withConnectionScope(options, run) {
3644
+ const transport = normalizeTransportOptions(options?.transport ?? this.config.transport);
3645
+ return withMCPConnectionPolicy(
3646
+ transport,
3647
+ () => shouldReuseMCPConnection(transport?.trustedFetchFn !== void 0) ? run() : withMCPConnectionScope(run, { signal: options?.signal, preserveOnInterruption: true })
3648
+ );
3649
+ }
3650
+ withTaskConnectionScope(options, run) {
3651
+ return withTaskDeadline(options, (effective) => this.withConnectionScope(effective, () => run(effective)));
3652
+ }
3653
+ /** Execute a task, adapt maintained AdCP 3 releases, and call the completion handler. */
3622
3654
  async executeAndHandle(taskType, handlerName, params, inputHandler, options, transformCompletedResponse, legacyFormatConverter, canonicalRequest, projectionCatalogs) {
3623
3655
  const requestParamsSnapshot = structuredClone(params);
3624
3656
  const canonicalRequestSnapshot = canonicalRequest === void 0 ? void 0 : structuredClone(canonicalRequest);
3625
3657
  const deferredProjectionCatalogs = projectionCatalogs ? structuredClone(projectionCatalogs) : void 0;
3626
3658
  const taskOptionsSnapshot = snapshotTaskOptions(options);
3627
- return withTaskDeadline(
3659
+ return this.withTaskConnectionScope(
3628
3660
  taskOptionsSnapshot,
3629
3661
  (effectiveOptions) => this.executeAndHandleWithinDeadline(
3630
3662
  taskType,
@@ -5170,7 +5202,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
5170
5202
  legacyFormatConverter,
5171
5203
  projectionCatalogs ?? this.config.projectionCatalogs
5172
5204
  );
5173
- return withTaskDeadline(snapshotTaskOptions(taskOptions), async (effectiveOptions) => {
5205
+ return this.withTaskConnectionScope(snapshotTaskOptions(taskOptions), async (effectiveOptions) => {
5174
5206
  this.assertRequestSupportedByConfiguredVersion("get_products", params, effectiveOptions);
5175
5207
  let request = await this.applyAccountPolicy("get_products", params, effectiveOptions);
5176
5208
  const legacyAccountId = request.account_id;
@@ -5379,7 +5411,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
5379
5411
  ...snapshotTaskOptions(options),
5380
5412
  ...projectionCatalogs !== void 0 && { projectionCatalogs }
5381
5413
  };
5382
- return withTaskDeadline(
5414
+ return this.withTaskConnectionScope(
5383
5415
  snapshottedOptions,
5384
5416
  (effectiveOptions) => this.createMediaBuyWithinDeadline(requestSnapshot, inputHandler, effectiveOptions)
5385
5417
  );
@@ -5538,7 +5570,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
5538
5570
  ...snapshotTaskOptions(options),
5539
5571
  ...projectionCatalogs !== void 0 && { projectionCatalogs }
5540
5572
  };
5541
- return withTaskDeadline(
5573
+ return this.withTaskConnectionScope(
5542
5574
  snapshottedOptions,
5543
5575
  (effectiveOptions) => this.updateMediaBuyWithinDeadline(requestSnapshot, inputHandler, effectiveOptions)
5544
5576
  );
@@ -5629,7 +5661,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
5629
5661
  ...projectionCatalogs !== void 0 && { projectionCatalogs },
5630
5662
  ...creativeFormatProjection !== void 0 && { creativeFormatProjection }
5631
5663
  };
5632
- return withTaskDeadline(
5664
+ return this.withTaskConnectionScope(
5633
5665
  snapshottedOptions,
5634
5666
  (effectiveOptions) => this.syncCreativesWithinDeadline(requestSnapshot, inputHandler, effectiveOptions)
5635
5667
  );
@@ -6146,7 +6178,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6146
6178
  * @param options - Task execution options
6147
6179
  */
6148
6180
  async getAdcpCapabilities(params, inputHandler, options) {
6149
- return withTaskDeadline(
6181
+ return this.withTaskConnectionScope(
6150
6182
  snapshotTaskOptions(options),
6151
6183
  (effectiveOptions) => this.getAdcpCapabilitiesWithinDeadline(params, inputHandler, effectiveOptions)
6152
6184
  );
@@ -6228,7 +6260,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6228
6260
  );
6229
6261
  }
6230
6262
  async resolveAccount(hints = {}, options, onTaskResult) {
6231
- return withTaskDeadline(snapshotTaskOptions(options), async (effectiveOptions) => {
6263
+ return this.withTaskConnectionScope(snapshotTaskOptions(options), async (effectiveOptions) => {
6232
6264
  const capabilities = await this.getCapabilities(effectiveOptions);
6233
6265
  if (capabilities._synthetic || !capabilities.account) {
6234
6266
  throw new AccountRequiredError(
@@ -6708,7 +6740,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6708
6740
  async executeTaskUnprojected(taskName, params, inputHandler, options, handlerName, beforeDispatch) {
6709
6741
  const requestParamsSnapshot = structuredClone(params);
6710
6742
  const taskOptionsSnapshot = snapshotTaskOptions(options);
6711
- return withTaskDeadline(taskOptionsSnapshot, async (effectiveOptions) => {
6743
+ return this.withTaskConnectionScope(taskOptionsSnapshot, async (effectiveOptions) => {
6712
6744
  const result = await this.executeTaskUnprojectedWithinDeadline(
6713
6745
  taskName,
6714
6746
  requestParamsSnapshot,
@@ -7287,10 +7319,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
7287
7319
  * ```
7288
7320
  */
7289
7321
  async getAgentInfo(options) {
7290
- return withMCPConnectionPolicy(
7291
- normalizeTransportOptions(options?.transport ?? this.config.transport),
7292
- () => this.getAgentInfoWithConnectionPolicy(options)
7293
- );
7322
+ return this.withConnectionScope(options, () => this.getAgentInfoWithConnectionPolicy(options));
7294
7323
  }
7295
7324
  async getAgentInfoWithConnectionPolicy(options) {
7296
7325
  throwIfAborted(options?.signal);
@@ -7541,10 +7570,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
7541
7570
  * ```
7542
7571
  */
7543
7572
  async getCapabilities(options) {
7544
- return withMCPConnectionPolicy(
7545
- normalizeTransportOptions(options?.transport ?? this.config.transport),
7546
- () => this.getCapabilitiesWithConnectionPolicy(options)
7547
- );
7573
+ return this.withConnectionScope(options, () => this.getCapabilitiesWithConnectionPolicy(options));
7548
7574
  }
7549
7575
  async getCapabilitiesWithConnectionPolicy(options) {
7550
7576
  throwIfAborted(options?.signal);
@@ -292,7 +292,7 @@ function assertGovernedPayloadHasNoCallbackCredentials(taskName, payload, option
292
292
  scanResult.path
293
293
  );
294
294
  }
295
- function mapTasksGetResponseToTaskInfo(payload) {
295
+ function mapTasksGetResponseToTaskInfo(payload, adcpVersion) {
296
296
  if (payload == null || typeof payload !== "object") {
297
297
  return { taskId: "", status: "unknown", taskType: "unknown", createdAt: Date.now(), updatedAt: Date.now() };
298
298
  }
@@ -312,7 +312,8 @@ function mapTasksGetResponseToTaskInfo(payload) {
312
312
  if (messageField !== void 0) taskInfo.message = messageField;
313
313
  if (flat.result !== void 0) {
314
314
  taskInfo.result = flat.result != null && typeof flat.result === "object" && !Array.isArray(flat.result) ? (0, import_envelope_status_compat.normalizeLegacyMediaBuyStatusForReturn)(flat.result, {
315
- toolName: taskInfo.taskType
315
+ toolName: taskInfo.taskType,
316
+ adcpVersion
316
317
  }) : flat.result;
317
318
  }
318
319
  return taskInfo;
@@ -2554,7 +2555,8 @@ class TaskExecutor {
2554
2555
  }
2555
2556
  }
2556
2557
  );
2557
- const task = mapTasksGetResponseToTaskInfo(response);
2558
+ const resultAdcpVersion = (versionEnvelope ?? this.config.versionEnvelope) === "major-only" ? "3.0" : wireAdcpVersion ?? this.config.wireAdcpVersion ?? this.config.adcpVersion ?? import_version.ADCP_VERSION;
2559
+ const task = mapTasksGetResponseToTaskInfo(response, resultAdcpVersion);
2558
2560
  if (!task.taskId || task.taskId !== taskId) {
2559
2561
  throw new TaskBindingMismatchError(
2560
2562
  "tasks/get returned a task identity that does not match the requested task.",
@@ -278,7 +278,7 @@ function assertGovernedPayloadHasNoCallbackCredentials(taskName, payload, option
278
278
  scanResult.path
279
279
  );
280
280
  }
281
- function mapTasksGetResponseToTaskInfo(payload) {
281
+ function mapTasksGetResponseToTaskInfo(payload, adcpVersion) {
282
282
  if (payload == null || typeof payload !== "object") {
283
283
  return { taskId: "", status: "unknown", taskType: "unknown", createdAt: Date.now(), updatedAt: Date.now() };
284
284
  }
@@ -298,7 +298,8 @@ function mapTasksGetResponseToTaskInfo(payload) {
298
298
  if (messageField !== void 0) taskInfo.message = messageField;
299
299
  if (flat.result !== void 0) {
300
300
  taskInfo.result = flat.result != null && typeof flat.result === "object" && !Array.isArray(flat.result) ? normalizeLegacyMediaBuyStatusForReturn(flat.result, {
301
- toolName: taskInfo.taskType
301
+ toolName: taskInfo.taskType,
302
+ adcpVersion
302
303
  }) : flat.result;
303
304
  }
304
305
  return taskInfo;
@@ -2540,7 +2541,8 @@ class TaskExecutor {
2540
2541
  }
2541
2542
  }
2542
2543
  );
2543
- const task = mapTasksGetResponseToTaskInfo(response);
2544
+ const resultAdcpVersion = (versionEnvelope ?? this.config.versionEnvelope) === "major-only" ? "3.0" : wireAdcpVersion ?? this.config.wireAdcpVersion ?? this.config.adcpVersion ?? ADCP_VERSION;
2545
+ const task = mapTasksGetResponseToTaskInfo(response, resultAdcpVersion);
2544
2546
  if (!task.taskId || task.taskId !== taskId) {
2545
2547
  throw new TaskBindingMismatchError(
2546
2548
  "tasks/get returned a task identity that does not match the requested task.",
@@ -410,6 +410,28 @@ export declare class ResponseTooLargeError extends ADCPError {
410
410
  */
411
411
  contentLengthHeader?: number | undefined);
412
412
  }
413
+ /**
414
+ * Error thrown when an agent rate-limits the SDK's endpoint discovery: a
415
+ * probe answered HTTP 429, or HTTP 503 with a `Retry-After` header.
416
+ *
417
+ * Discovery stops at the first such probe instead of trying the remaining
418
+ * candidate URLs, which would only spend more of the seller's request budget.
419
+ * The rate limit says nothing about whether `agent_uri` is correct.
420
+ *
421
+ * Recovery: `transient`. Wait `retryAfterMs` (when the agent sent
422
+ * `Retry-After`) and retry.
423
+ */
424
+ export declare class AgentRateLimitedError extends ADCPError {
425
+ readonly status: number;
426
+ readonly code = "RATE_LIMITED";
427
+ /** The probed URL, without credentials, query or fragment. */
428
+ readonly agentUrl: string;
429
+ /** `Retry-After` as the agent sent it. Set only when it is valid delta-seconds or an HTTP-date. */
430
+ readonly retryAfter?: string;
431
+ /** Delay from `Retry-After`, in milliseconds. */
432
+ readonly retryAfterMs?: number;
433
+ constructor(agentUrl: string, status: number, retryAfter?: string, cause?: unknown);
434
+ }
413
435
  /**
414
436
  * Error thrown when an `update_media_buy` request is rejected because the
415
437
  * action it maps to isn't currently allowed on the buy (AdCP 3.1, RFC #4480).
@@ -410,6 +410,28 @@ export declare class ResponseTooLargeError extends ADCPError {
410
410
  */
411
411
  contentLengthHeader?: number | undefined);
412
412
  }
413
+ /**
414
+ * Error thrown when an agent rate-limits the SDK's endpoint discovery: a
415
+ * probe answered HTTP 429, or HTTP 503 with a `Retry-After` header.
416
+ *
417
+ * Discovery stops at the first such probe instead of trying the remaining
418
+ * candidate URLs, which would only spend more of the seller's request budget.
419
+ * The rate limit says nothing about whether `agent_uri` is correct.
420
+ *
421
+ * Recovery: `transient`. Wait `retryAfterMs` (when the agent sent
422
+ * `Retry-After`) and retry.
423
+ */
424
+ export declare class AgentRateLimitedError extends ADCPError {
425
+ readonly status: number;
426
+ readonly code = "RATE_LIMITED";
427
+ /** The probed URL, without credentials, query or fragment. */
428
+ readonly agentUrl: string;
429
+ /** `Retry-After` as the agent sent it. Set only when it is valid delta-seconds or an HTTP-date. */
430
+ readonly retryAfter?: string;
431
+ /** Delay from `Retry-After`, in milliseconds. */
432
+ readonly retryAfterMs?: number;
433
+ constructor(agentUrl: string, status: number, retryAfter?: string, cause?: unknown);
434
+ }
413
435
  /**
414
436
  * Error thrown when an `update_media_buy` request is rejected because the
415
437
  * action it maps to isn't currently allowed on the buy (AdCP 3.1, RFC #4480).