@adcp/sdk 9.4.0 → 9.6.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 (69) hide show
  1. package/dist/lib/core/AgentClient.d.ts +2 -2
  2. package/dist/lib/core/AgentClient.d.ts.map +1 -1
  3. package/dist/lib/core/AgentClient.js +4 -4
  4. package/dist/lib/core/AgentClient.js.map +1 -1
  5. package/dist/lib/core/AsyncHandler.d.ts +6 -0
  6. package/dist/lib/core/AsyncHandler.d.ts.map +1 -1
  7. package/dist/lib/core/AsyncHandler.js.map +1 -1
  8. package/dist/lib/core/ConversationTypes.d.ts +21 -0
  9. package/dist/lib/core/ConversationTypes.d.ts.map +1 -1
  10. package/dist/lib/core/SingleAgentClient.d.ts +40 -5
  11. package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
  12. package/dist/lib/core/SingleAgentClient.js +201 -52
  13. package/dist/lib/core/SingleAgentClient.js.map +1 -1
  14. package/dist/lib/core/TaskExecutor.d.ts +1 -1
  15. package/dist/lib/core/TaskExecutor.d.ts.map +1 -1
  16. package/dist/lib/core/TaskExecutor.js +15 -4
  17. package/dist/lib/core/TaskExecutor.js.map +1 -1
  18. package/dist/lib/index.d.ts +2 -2
  19. package/dist/lib/index.d.ts.map +1 -1
  20. package/dist/lib/index.js +3 -2
  21. package/dist/lib/index.js.map +1 -1
  22. package/dist/lib/protocols/a2a.d.ts +1 -1
  23. package/dist/lib/protocols/a2a.d.ts.map +1 -1
  24. package/dist/lib/protocols/a2a.js +66 -35
  25. package/dist/lib/protocols/a2a.js.map +1 -1
  26. package/dist/lib/protocols/abort.d.ts +10 -0
  27. package/dist/lib/protocols/abort.d.ts.map +1 -0
  28. package/dist/lib/protocols/abort.js +107 -0
  29. package/dist/lib/protocols/abort.js.map +1 -0
  30. package/dist/lib/protocols/index.d.ts +10 -0
  31. package/dist/lib/protocols/index.d.ts.map +1 -1
  32. package/dist/lib/protocols/index.js +33 -9
  33. package/dist/lib/protocols/index.js.map +1 -1
  34. package/dist/lib/protocols/mcp-tasks.d.ts +4 -0
  35. package/dist/lib/protocols/mcp-tasks.d.ts.map +1 -1
  36. package/dist/lib/protocols/mcp-tasks.js +27 -11
  37. package/dist/lib/protocols/mcp-tasks.js.map +1 -1
  38. package/dist/lib/protocols/mcp.d.ts +14 -2
  39. package/dist/lib/protocols/mcp.d.ts.map +1 -1
  40. package/dist/lib/protocols/mcp.js +116 -43
  41. package/dist/lib/protocols/mcp.js.map +1 -1
  42. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  43. package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
  44. package/dist/lib/server/create-adcp-server.js +3 -1
  45. package/dist/lib/server/create-adcp-server.js.map +1 -1
  46. package/dist/lib/signing/capability-priming.d.ts.map +1 -1
  47. package/dist/lib/signing/capability-priming.js +5 -1
  48. package/dist/lib/signing/capability-priming.js.map +1 -1
  49. package/dist/lib/substitution/index.d.ts +2 -0
  50. package/dist/lib/substitution/index.d.ts.map +1 -1
  51. package/dist/lib/substitution/index.js +3 -1
  52. package/dist/lib/substitution/index.js.map +1 -1
  53. package/dist/lib/substitution/observer/html-parser.d.ts.map +1 -1
  54. package/dist/lib/substitution/observer/html-parser.js +27 -9
  55. package/dist/lib/substitution/observer/html-parser.js.map +1 -1
  56. package/dist/lib/substitution/translate.d.ts +89 -0
  57. package/dist/lib/substitution/translate.d.ts.map +1 -0
  58. package/dist/lib/substitution/translate.js +144 -0
  59. package/dist/lib/substitution/translate.js.map +1 -0
  60. package/dist/lib/testing/client.js +3 -3
  61. package/dist/lib/testing/client.js.map +1 -1
  62. package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
  63. package/dist/lib/testing/storyboard/runner.js +36 -9
  64. package/dist/lib/testing/storyboard/runner.js.map +1 -1
  65. package/dist/lib/testing/types.d.ts +2 -0
  66. package/dist/lib/testing/types.d.ts.map +1 -1
  67. package/dist/lib/version.d.ts +3 -3
  68. package/dist/lib/version.js +3 -3
  69. package/package.json +1 -1
@@ -46,6 +46,7 @@ const idempotency_1 = require("../utils/idempotency");
46
46
  const TaskExecutor_1 = require("./TaskExecutor");
47
47
  const match_1 = require("./match");
48
48
  const auth_1 = require("../auth");
49
+ const abort_1 = require("../protocols/abort");
49
50
  const errors_1 = require("../errors");
50
51
  const net_1 = require("../net");
51
52
  const authorization_required_1 = require("../auth/oauth/authorization-required");
@@ -55,6 +56,7 @@ const webhooks_1 = require("../webhooks");
55
56
  const response_unwrapper_1 = require("../utils/response-unwrapper");
56
57
  const a2a_discovery_1 = require("../utils/a2a-discovery");
57
58
  const crypto = __importStar(require("crypto"));
59
+ const abort_2 = require("../protocols/abort");
58
60
  const capabilities_1 = require("../utils/capabilities");
59
61
  const request_normalizer_1 = require("../utils/request-normalizer");
60
62
  const validate_user_agent_1 = require("../utils/validate-user-agent");
@@ -179,6 +181,18 @@ function valueMatchesSchemaType(value, propSchema) {
179
181
  return declared.includes(valueType);
180
182
  return false;
181
183
  }
184
+ function productHasPricingOptions(product) {
185
+ if (!product || typeof product !== 'object')
186
+ return false;
187
+ const options = product.pricing_options;
188
+ return Array.isArray(options) && options.length > 0;
189
+ }
190
+ function productIdForPricingDiagnostics(product) {
191
+ if (!product || typeof product !== 'object')
192
+ return undefined;
193
+ const id = product.product_id;
194
+ return typeof id === 'string' ? id : undefined;
195
+ }
182
196
  function propertyListReferenceFromRequest(params) {
183
197
  const value = params.property_list;
184
198
  if (!value || typeof value !== 'object' || Array.isArray(value))
@@ -313,7 +327,8 @@ class SingleAgentClient {
313
327
  * Returns the agent config with the discovered endpoint.
314
328
  * Also computes the canonical base URL by stripping /mcp suffix.
315
329
  */
316
- async ensureEndpointDiscovered() {
330
+ async ensureEndpointDiscovered(options) {
331
+ (0, abort_2.throwIfAborted)(options?.signal);
317
332
  const needsDiscovery = this.normalizedAgent._needsDiscovery;
318
333
  if (!needsDiscovery) {
319
334
  return this.normalizedAgent;
@@ -330,7 +345,7 @@ class SingleAgentClient {
330
345
  return this.discoveredAgent;
331
346
  }
332
347
  // Perform discovery
333
- this.discoveredEndpoint = await this.discoverMCPEndpoint(this.normalizedAgent.agent_uri);
348
+ this.discoveredEndpoint = await this.discoverMCPEndpoint(this.normalizedAgent.agent_uri, options);
334
349
  // Compute canonical base URL by stripping /mcp suffix
335
350
  this.canonicalBaseUrl = this.computeBaseUrl(this.discoveredEndpoint);
336
351
  this.discoveredAgent = {
@@ -345,7 +360,8 @@ class SingleAgentClient {
345
360
  * Fetches the agent card and extracts the canonical URL.
346
361
  * Returns the agent config with the canonical URL.
347
362
  */
348
- async ensureCanonicalUrlResolved() {
363
+ async ensureCanonicalUrlResolved(options) {
364
+ (0, abort_2.throwIfAborted)(options?.signal);
349
365
  const needsCanonicalUrl = this.normalizedAgent._needsCanonicalUrl;
350
366
  if (!needsCanonicalUrl) {
351
367
  return this.normalizedAgent;
@@ -358,7 +374,7 @@ class SingleAgentClient {
358
374
  };
359
375
  }
360
376
  // Fetch agent card to get canonical URL
361
- const canonicalUrl = await this.fetchA2ACanonicalUrl(this.normalizedAgent.agent_uri);
377
+ const canonicalUrl = await this.fetchA2ACanonicalUrl(this.normalizedAgent.agent_uri, options);
362
378
  this.canonicalBaseUrl = canonicalUrl;
363
379
  return {
364
380
  ...this.normalizedAgent,
@@ -372,7 +388,7 @@ class SingleAgentClient {
372
388
  * - If the agent card fetch returns 401, throw AuthenticationRequiredError
373
389
  * - Check for OAuth metadata to provide helpful guidance
374
390
  */
375
- async fetchA2ACanonicalUrl(agentUri) {
391
+ async fetchA2ACanonicalUrl(agentUri, readOptions) {
376
392
  const clientModule = require('@a2a-js/sdk/client');
377
393
  const A2AClient = clientModule.A2AClient;
378
394
  // adcp-client#1804 — wrap A2A card discovery in withResponseSizeLimit so
@@ -381,20 +397,22 @@ class SingleAgentClient {
381
397
  // active ALS slot enforces the cap on the wire call. Matches the same
382
398
  // pattern in `getAgentInfo` (closed #1799 via PR #1802).
383
399
  const { withResponseSizeLimit, wrapFetchWithSizeLimit } = await Promise.resolve().then(() => __importStar(require('../protocols/responseSizeLimit')));
384
- const maxResponseBytes = this.config.transport?.maxResponseBytes;
400
+ const transport = readOptions?.transport ?? this.config.transport;
401
+ const maxResponseBytes = transport?.maxResponseBytes;
402
+ const requestTimeoutMs = (0, abort_2.resolveRequestTimeoutMs)(transport?.requestTimeoutMs, abort_2.DEFAULT_REQUEST_TIMEOUT_MS);
385
403
  const sizeLimitedFetch = wrapFetchWithSizeLimit((input, init) => fetch(input, init));
386
404
  const authToken = this.normalizedAgent.auth_token;
387
405
  let got401 = false;
388
- const fetchImpl = async (url, options) => {
406
+ const fetchImpl = async (url, requestInit) => {
389
407
  const headers = {
390
- ...options?.headers,
408
+ ...requestInit?.headers,
391
409
  ...this.normalizedAgent.headers,
392
410
  ...(authToken && {
393
411
  Authorization: `Bearer ${authToken}`,
394
412
  'x-adcp-auth': authToken,
395
413
  }),
396
414
  };
397
- const response = await sizeLimitedFetch(url, { ...options, headers });
415
+ const response = await (0, abort_2.withAbortSignal)([readOptions?.signal, requestInit?.signal], requestTimeoutMs, signal => sizeLimitedFetch(url, { ...requestInit, headers, signal }));
398
416
  // Track 401 errors for later handling
399
417
  if (response.status === 401) {
400
418
  got401 = true;
@@ -481,18 +499,25 @@ class SingleAgentClient {
481
499
  *
482
500
  * Note: This is async and called lazily on first agent interaction
483
501
  */
484
- async discoverMCPEndpoint(providedUri) {
502
+ async discoverMCPEndpoint(providedUri, options) {
503
+ (0, abort_2.throwIfAborted)(options?.signal);
485
504
  const { connectMCPWithFallback } = await Promise.resolve().then(() => __importStar(require('../protocols/mcp')));
486
505
  const authToken = this.agent.auth_token;
487
506
  const agentHeaders = this.agent.headers;
488
507
  const authHeaders = { ...agentHeaders, ...(0, auth_1.createMCPAuthHeaders)(authToken) };
489
508
  const testEndpoint = async (url) => {
490
509
  try {
491
- const client = await connectMCPWithFallback(new URL(url), authHeaders);
510
+ const client = await connectMCPWithFallback(new URL(url), authHeaders, [], 'endpoint discovery', undefined, {
511
+ signal: options?.signal,
512
+ requestTimeoutMs: options?.transport?.requestTimeoutMs ?? this.config.transport?.requestTimeoutMs,
513
+ });
492
514
  await client.close();
493
515
  return { success: true };
494
516
  }
495
517
  catch (error) {
518
+ if ((0, abort_1.isAbortOrTimeoutError)(error)) {
519
+ throw error;
520
+ }
496
521
  if ((0, errors_1.is401Error)(error)) {
497
522
  return { success: false, status: 401, error };
498
523
  }
@@ -1041,6 +1066,7 @@ class SingleAgentClient {
1041
1066
  * Automatically adapts requests for v2 servers and normalizes responses.
1042
1067
  */
1043
1068
  async executeAndHandle(taskType, handlerName, params, inputHandler, options) {
1069
+ (0, abort_2.throwIfAborted)(options?.signal);
1044
1070
  // Normalize params for backwards compatibility before validation
1045
1071
  let normalizedParams = (0, request_normalizer_1.normalizeRequestParams)(taskType, params, {
1046
1072
  skipIdempotencyAutoInject: options?.skipIdempotencyAutoInject,
@@ -1075,17 +1101,17 @@ class SingleAgentClient {
1075
1101
  this.validateRequest(taskType, normalizedParams);
1076
1102
  }
1077
1103
  // Validate required features before sending request
1078
- await this.validateTaskFeatures(taskType);
1104
+ await this.validateTaskFeatures(taskType, options);
1079
1105
  // Guard mutating calls against pre-v3 sellers when opted in.
1080
1106
  if (this.config.requireV3ForMutations && (0, idempotency_1.isMutatingTask)(taskType)) {
1081
- await this.requireSupportedMajor(taskType);
1107
+ await this.requireSupportedMajor(taskType, options);
1082
1108
  }
1083
1109
  // Check for v3 features used against v2 servers - return empty result if unsupported
1084
- const earlyResult = await this.getEarlyResultForUnsupportedFeatures(taskType, normalizedParams);
1110
+ const earlyResult = await this.getEarlyResultForUnsupportedFeatures(taskType, normalizedParams, options);
1085
1111
  if (earlyResult) {
1086
1112
  return (0, match_1.attachMatch)(earlyResult);
1087
1113
  }
1088
- const agent = await this.ensureEndpointDiscovered();
1114
+ const agent = await this.ensureEndpointDiscovered(options);
1089
1115
  // Schema-driven pre-send validation runs on the unadapted v3 shape so
1090
1116
  // wire-format adapters (e.g. adaptGetProductsRequestForV2) don't strip
1091
1117
  // v3-only fields out from under the v3 bundled schema. Skip the entire
@@ -1095,7 +1121,7 @@ class SingleAgentClient {
1095
1121
  this.executor.validateRequest(taskType, normalizedParams);
1096
1122
  }
1097
1123
  // Adapt request for the detected server and AdCP protocol versions.
1098
- const serverVersion = await this.detectServerVersion();
1124
+ const serverVersion = await this.detectServerVersion(options);
1099
1125
  const inputSchemaStripLogs = [];
1100
1126
  const { params: adaptedParams, driftLogs: adaptDriftLogs } = this.adaptRequest(taskType, normalizedParams, serverVersion, inputSchemaStripLogs);
1101
1127
  // Symmetric to the pre-adapter v3 pass above: when the adapter
@@ -1145,7 +1171,76 @@ class SingleAgentClient {
1145
1171
  }
1146
1172
  return result;
1147
1173
  }
1174
+ /**
1175
+ * Drop products that arrive without a usable `pricing_options[]` array from a
1176
+ * completed `get_products` response. `pricing_options` is required and
1177
+ * non-empty in AdCP 3.1; a product with no pricing model can't be bought, so
1178
+ * the SDK rejects it before callers and completion handlers see the list.
1179
+ *
1180
+ * Controlled by `config.validation.rejectProductsWithoutPricingOptions`
1181
+ * (default `true`) and applied on every completion path via
1182
+ * {@link applyProductPropertyPolicy}, independent of the response validation
1183
+ * mode.
1184
+ */
1185
+ enforceProductPricingOptions(result, taskType) {
1186
+ if (taskType !== 'get_products')
1187
+ return result;
1188
+ if (this.config.validation?.rejectProductsWithoutPricingOptions === false)
1189
+ return result;
1190
+ if (!result.success || result.status !== 'completed' || !result.data)
1191
+ return result;
1192
+ const response = result.data;
1193
+ const products = response.products;
1194
+ if (!Array.isArray(products) || products.length === 0)
1195
+ return result;
1196
+ const kept = [];
1197
+ const rejected = [];
1198
+ products.forEach((product, index) => {
1199
+ if (productHasPricingOptions(product)) {
1200
+ kept.push(product);
1201
+ return;
1202
+ }
1203
+ const productId = productIdForPricingDiagnostics(product);
1204
+ rejected.push({ index, ...(productId ? { product_id: productId } : {}) });
1205
+ });
1206
+ if (rejected.length === 0)
1207
+ return result;
1208
+ const message = `Rejected ${rejected.length} product${rejected.length === 1 ? '' : 's'} without pricing_options`;
1209
+ // Mutate in place (like the property-policy filter path) so the
1210
+ // non-enumerable `match` accessor and result identity survive.
1211
+ result.data = { ...response, products: kept };
1212
+ result.metadata = {
1213
+ ...result.metadata,
1214
+ productPricingPolicy: {
1215
+ ok: true,
1216
+ accepted_count: kept.length,
1217
+ rejected_count: rejected.length,
1218
+ rejected_products: rejected,
1219
+ },
1220
+ };
1221
+ result.debug_logs = [
1222
+ ...(result.debug_logs ?? []),
1223
+ {
1224
+ type: 'warning',
1225
+ message,
1226
+ timestamp: new Date().toISOString(),
1227
+ details: {
1228
+ code: 'product_missing_pricing_options',
1229
+ task: taskType,
1230
+ agent_id: this.agent.id,
1231
+ rejected_count: rejected.length,
1232
+ rejected_products: rejected,
1233
+ },
1234
+ },
1235
+ ];
1236
+ return result;
1237
+ }
1148
1238
  async applyProductPropertyPolicy(result, taskType, requestParams) {
1239
+ // Reject non-transactable products (no pricing_options) before any
1240
+ // property-policy evaluation, regardless of whether a property policy is
1241
+ // configured. This runs on the same completion chokepoint so it covers the
1242
+ // sync, polling, track, and webhook paths uniformly.
1243
+ result = this.enforceProductPricingOptions(result, taskType);
1149
1244
  const policyConfig = this.config.validation?.productPropertyPolicy;
1150
1245
  if (policyConfig === false || taskType !== 'get_products')
1151
1246
  return result;
@@ -1431,6 +1526,9 @@ class SingleAgentClient {
1431
1526
  ...(policyResult.metadata.productPropertyPolicy
1432
1527
  ? { productPropertyPolicy: policyResult.metadata.productPropertyPolicy }
1433
1528
  : {}),
1529
+ ...(policyResult.metadata.productPricingPolicy
1530
+ ? { productPricingPolicy: policyResult.metadata.productPricingPolicy }
1531
+ : {}),
1434
1532
  };
1435
1533
  return {
1436
1534
  result: policyResult.data,
@@ -1584,13 +1682,13 @@ class SingleAgentClient {
1584
1682
  *
1585
1683
  * @returns TaskResult with empty data if v3 features are unsupported, null to proceed normally
1586
1684
  */
1587
- async getEarlyResultForUnsupportedFeatures(taskType, params) {
1685
+ async getEarlyResultForUnsupportedFeatures(taskType, params, options) {
1588
1686
  // Only check for tasks that have v3-specific features
1589
1687
  if (taskType !== 'get_products') {
1590
1688
  return null;
1591
1689
  }
1592
1690
  // Get capabilities to check what the server supports
1593
- const capabilities = await this.getCapabilities();
1691
+ const capabilities = await this.getCapabilities(options);
1594
1692
  // If server is v3, all features are supported - proceed normally
1595
1693
  if (capabilities.version === 'v3') {
1596
1694
  return null;
@@ -1867,7 +1965,7 @@ class SingleAgentClient {
1867
1965
  * @param options - Task execution options
1868
1966
  */
1869
1967
  async getAdcpCapabilities(params, inputHandler, options) {
1870
- const agent = await this.ensureEndpointDiscovered();
1968
+ const agent = await this.ensureEndpointDiscovered(options);
1871
1969
  this.executor.validateRequest('get_adcp_capabilities', params);
1872
1970
  return this.executor.executeTask(agent, 'get_adcp_capabilities', params, inputHandler, options);
1873
1971
  }
@@ -1996,6 +2094,7 @@ class SingleAgentClient {
1996
2094
  * ```
1997
2095
  */
1998
2096
  async executeTask(taskName, params, inputHandler, options) {
2097
+ (0, abort_2.throwIfAborted)(options?.signal);
1999
2098
  const startTime = Date.now();
2000
2099
  try {
2001
2100
  const normalizedParams = (0, request_normalizer_1.normalizeRequestParams)(taskName, params, {
@@ -2007,11 +2106,11 @@ class SingleAgentClient {
2007
2106
  // (get_products / get_signals). `effectiveOptions` carries disableWebhook
2008
2107
  // so no push_notification_config reaches a seller that can't accept it.
2009
2108
  const { options: effectiveOptions, driftLog: webhookDriftLog } = this.suppressPre31DiscoveryWebhook(taskName, options);
2010
- await this.validateTaskFeatures(taskName);
2109
+ await this.validateTaskFeatures(taskName, options);
2011
2110
  if (this.config.requireV3ForMutations && (0, idempotency_1.isMutatingTask)(taskName)) {
2012
- await this.requireSupportedMajor(taskName);
2111
+ await this.requireSupportedMajor(taskName, options);
2013
2112
  }
2014
- const agent = await this.ensureEndpointDiscovered();
2113
+ const agent = await this.ensureEndpointDiscovered(options);
2015
2114
  // Schema-driven pre-send validation runs on the unadapted v3 shape so
2016
2115
  // wire-format adapters (e.g. adaptGetProductsRequestForV2) don't strip
2017
2116
  // v3-only fields out from under the v3 bundled schema. Skip the entire
@@ -2021,7 +2120,7 @@ class SingleAgentClient {
2021
2120
  this.executor.validateRequest(taskName, normalizedParams);
2022
2121
  }
2023
2122
  // Adapt request for the detected server and AdCP protocol versions.
2024
- const serverVersion = await this.detectServerVersion();
2123
+ const serverVersion = await this.detectServerVersion(options);
2025
2124
  const inputSchemaStripLogs = [];
2026
2125
  const { params: adaptedParams, driftLogs: adaptDriftLogs } = this.adaptRequest(taskName, normalizedParams, serverVersion, inputSchemaStripLogs);
2027
2126
  // Symmetric warn-only post-adapter pass against the v2.5 schema bundle.
@@ -2055,7 +2154,8 @@ class SingleAgentClient {
2055
2154
  if (error instanceof errors_1.AuthenticationRequiredError ||
2056
2155
  error instanceof errors_1.TaskTimeoutError ||
2057
2156
  error instanceof errors_1.VersionUnsupportedError ||
2058
- error instanceof errors_1.FeatureUnsupportedError) {
2157
+ error instanceof errors_1.FeatureUnsupportedError ||
2158
+ (0, abort_1.isAbortOrTimeoutError)(error)) {
2059
2159
  throw error;
2060
2160
  }
2061
2161
  // Unexpected pre-flight errors (e.g. a TypeError from response parsing
@@ -2408,17 +2508,25 @@ class SingleAgentClient {
2408
2508
  * });
2409
2509
  * ```
2410
2510
  */
2411
- async getAgentInfo() {
2511
+ async getAgentInfo(options) {
2412
2512
  // adcp-client#1799 — wrap every wire call in the response-size cap so
2413
2513
  // `transport.maxResponseBytes` extends to discovery / tools-list bodies.
2414
2514
  // `withResponseSizeLimit` is a no-op when no cap is configured.
2415
2515
  const { withResponseSizeLimit } = await Promise.resolve().then(() => __importStar(require('../protocols/responseSizeLimit')));
2416
- const maxResponseBytes = this.config.transport?.maxResponseBytes;
2516
+ (0, abort_2.throwIfAborted)(options?.signal);
2517
+ const transport = options?.transport ?? this.config.transport;
2518
+ const maxResponseBytes = transport?.maxResponseBytes;
2519
+ const requestTimeoutMs = (0, abort_2.resolveRequestTimeoutMs)(transport?.requestTimeoutMs, abort_2.DEFAULT_REQUEST_TIMEOUT_MS);
2520
+ const clientRequestTimeoutMs = (0, abort_2.resolveClientRequestTimeoutMs)(transport?.requestTimeoutMs);
2521
+ const mcpRequestOptions = {
2522
+ ...(options?.signal && { signal: options.signal }),
2523
+ ...(clientRequestTimeoutMs !== undefined && { timeout: clientRequestTimeoutMs }),
2524
+ };
2417
2525
  if (this.normalizedAgent.protocol === 'mcp') {
2418
2526
  // In-process: use the pre-connected client instead of opening a new HTTP connection
2419
2527
  if (this.normalizedAgent._inProcessMcpClient) {
2420
2528
  const mcpClient = this.normalizedAgent._inProcessMcpClient;
2421
- const toolsList = await withResponseSizeLimit(maxResponseBytes, () => mcpClient.listTools());
2529
+ const toolsList = await withResponseSizeLimit(maxResponseBytes, () => mcpClient.listTools(undefined, mcpRequestOptions));
2422
2530
  const tools = toolsList.tools.map(tool => ({
2423
2531
  name: tool.name,
2424
2532
  description: tool.description,
@@ -2434,7 +2542,7 @@ class SingleAgentClient {
2434
2542
  };
2435
2543
  }
2436
2544
  // Discover endpoint if needed
2437
- const agent = await this.ensureEndpointDiscovered();
2545
+ const agent = await this.ensureEndpointDiscovered(options);
2438
2546
  // Use the shared connectMCP path so both static bearer AND saved OAuth
2439
2547
  // tokens work. OAuth takes the refresh-capable authProvider branch.
2440
2548
  // Header-only auth (basic, x-api-key, custom routing) lives on
@@ -2443,6 +2551,12 @@ class SingleAgentClient {
2443
2551
  // SDK doesn't emit a competing `Authorization: Bearer …`.
2444
2552
  const { connectMCP } = await Promise.resolve().then(() => __importStar(require('../protocols/mcp')));
2445
2553
  const connectOptions = { agentUrl: agent.agent_uri };
2554
+ if (options?.signal) {
2555
+ connectOptions.signal = options.signal;
2556
+ }
2557
+ if (transport?.requestTimeoutMs !== undefined) {
2558
+ connectOptions.requestTimeoutMs = transport.requestTimeoutMs;
2559
+ }
2446
2560
  if (this.normalizedAgent.headers && Object.keys(this.normalizedAgent.headers).length > 0) {
2447
2561
  connectOptions.customHeaders = this.normalizedAgent.headers;
2448
2562
  }
@@ -2457,7 +2571,7 @@ class SingleAgentClient {
2457
2571
  }
2458
2572
  const { client: mcpClient } = await connectMCP(connectOptions);
2459
2573
  try {
2460
- const toolsList = await withResponseSizeLimit(maxResponseBytes, () => mcpClient.listTools());
2574
+ const toolsList = await withResponseSizeLimit(maxResponseBytes, () => mcpClient.listTools(undefined, mcpRequestOptions));
2461
2575
  const tools = toolsList.tools.map(tool => ({
2462
2576
  name: tool.name,
2463
2577
  description: tool.description,
@@ -2491,17 +2605,39 @@ class SingleAgentClient {
2491
2605
  // calls native `fetch` directly and ignores `transport.maxResponseBytes`.
2492
2606
  const { wrapFetchWithSizeLimit } = await Promise.resolve().then(() => __importStar(require('../protocols/responseSizeLimit')));
2493
2607
  const authToken = this.normalizedAgent.auth_token;
2608
+ const agentHeaders = this.normalizedAgent.headers ?? {};
2494
2609
  const sizeLimitedFetch = wrapFetchWithSizeLimit((input, init) => fetch(input, init));
2495
- const fetchImpl = authToken
2496
- ? async (url, options) => {
2497
- const headers = {
2498
- ...options?.headers,
2499
- Authorization: `Bearer ${authToken}`,
2500
- 'x-adcp-auth': authToken,
2501
- };
2502
- return sizeLimitedFetch(url, { ...options, headers });
2610
+ const normalizeHeaders = (headers) => {
2611
+ const normalized = {};
2612
+ if (!headers)
2613
+ return normalized;
2614
+ if (headers instanceof Headers) {
2615
+ headers.forEach((value, key) => {
2616
+ normalized[key] = value;
2617
+ });
2618
+ }
2619
+ else if (Array.isArray(headers)) {
2620
+ for (const [key, value] of headers) {
2621
+ normalized[key] = value;
2622
+ }
2623
+ }
2624
+ else {
2625
+ Object.assign(normalized, headers);
2503
2626
  }
2504
- : (url, options) => sizeLimitedFetch(url, options);
2627
+ return normalized;
2628
+ };
2629
+ const buildHeaders = (requestInit) => ({
2630
+ ...normalizeHeaders(requestInit?.headers),
2631
+ ...agentHeaders,
2632
+ ...(authToken && {
2633
+ Authorization: `Bearer ${authToken}`,
2634
+ 'x-adcp-auth': authToken,
2635
+ }),
2636
+ });
2637
+ const fetchImpl = async (url, requestInit) => {
2638
+ const headers = buildHeaders(requestInit);
2639
+ return (0, abort_2.withAbortSignal)([options?.signal, requestInit?.signal], requestTimeoutMs, signal => sizeLimitedFetch(url, { ...requestInit, headers, signal }));
2640
+ };
2505
2641
  const cardUrls = (0, a2a_discovery_1.buildCardUrls)(this.normalizedAgent.agent_uri);
2506
2642
  let client;
2507
2643
  let lastCardError = new Error(`A2A agent card not found at ${cardUrls.join(', ')}`);
@@ -2559,14 +2695,15 @@ class SingleAgentClient {
2559
2695
  * }
2560
2696
  * ```
2561
2697
  */
2562
- async getCapabilities() {
2698
+ async getCapabilities(options) {
2699
+ (0, abort_2.throwIfAborted)(options?.signal);
2563
2700
  // Return cached if available
2564
2701
  if (this.cachedCapabilities) {
2565
2702
  this.maybeWarnV2Sunset(this.cachedCapabilities);
2566
2703
  return this.cachedCapabilities;
2567
2704
  }
2568
2705
  // First get tool list to support both detection methods
2569
- const agentInfo = await this.getAgentInfo();
2706
+ const agentInfo = await this.getAgentInfo(options);
2570
2707
  const tools = agentInfo.tools.map(t => ({
2571
2708
  name: t.name,
2572
2709
  description: t.description,
@@ -2585,8 +2722,15 @@ class SingleAgentClient {
2585
2722
  // because normalizeAgentConfig returns early when _inProcessMcpClient is set). The
2586
2723
  // executor then hits ProtocolClient.callTool which reads _inProcessMcpClient directly,
2587
2724
  // so the sentinel adcp-in-process:// URI never reaches validateAgentUrl.
2588
- const agent = await this.ensureEndpointDiscovered();
2589
- const result = await this.executor.executeTask(agent, 'get_adcp_capabilities', {}, undefined);
2725
+ const agent = await this.ensureEndpointDiscovered(options);
2726
+ const result = await this.executor.executeTask(agent, 'get_adcp_capabilities', {}, undefined, options);
2727
+ (0, abort_2.throwIfAborted)(options?.signal);
2728
+ const requestTimeoutMs = (0, abort_2.resolveRequestTimeoutMs)(options?.transport?.requestTimeoutMs ?? this.config.transport?.requestTimeoutMs);
2729
+ if (!result.success &&
2730
+ requestTimeoutMs !== undefined &&
2731
+ /\b(requesttimeout|timeout|timed out)\b/i.test(result.error ?? '')) {
2732
+ throw (0, abort_2.createTimeoutError)(requestTimeoutMs);
2733
+ }
2590
2734
  if (result.success && result.data) {
2591
2735
  this.cachedCapabilities = (0, capabilities_1.augmentCapabilitiesFromTools)((0, capabilities_1.parseCapabilitiesResponse)(result.data), tools);
2592
2736
  this.maybeWarnV2Sunset(this.cachedCapabilities);
@@ -2641,7 +2785,7 @@ class SingleAgentClient {
2641
2785
  `since the agent has the v3-only discovery tool. ` +
2642
2786
  `This client routes to v3 adapters, but calls reading capability details ` +
2643
2787
  `(idempotency TTL, supported_versions, feature flags) will fail until the agent ` +
2644
- `operator fixes the capabilities endpoint at ${this.agent.agent_uri}.`, {
2788
+ `operator fixes the capabilities endpoint.`, {
2645
2789
  success: result.success,
2646
2790
  hasError: !!result.error,
2647
2791
  hasData: !!result.data,
@@ -2651,15 +2795,16 @@ class SingleAgentClient {
2651
2795
  // Re-throw errors that indicate real infrastructure problems —
2652
2796
  // only fall through for tool-execution failures (the agent
2653
2797
  // advertises get_adcp_capabilities but can't actually serve it).
2654
- if (error instanceof errors_1.AuthenticationRequiredError || error instanceof errors_1.TaskTimeoutError) {
2798
+ if (error instanceof errors_1.AuthenticationRequiredError ||
2799
+ error instanceof errors_1.TaskTimeoutError ||
2800
+ (0, abort_1.isAbortOrTimeoutError)(error)) {
2655
2801
  throw error;
2656
2802
  }
2657
2803
  console.warn(`[AdCP] Agent "${this.agent.id}" advertises get_adcp_capabilities but the call ` +
2658
2804
  `threw — treating as v3 (synthetic) since the agent has the v3-only discovery tool. ` +
2659
2805
  `This client routes to v3 adapters, but calls reading capability details ` +
2660
2806
  `(idempotency TTL, supported_versions, feature flags) will fail until the agent ` +
2661
- `operator fixes the capabilities endpoint at ${this.agent.agent_uri}. ` +
2662
- `Error: ${error instanceof Error ? error.message : String(error)}`);
2807
+ `operator fixes the capabilities endpoint.`);
2663
2808
  }
2664
2809
  // Synthesize v3 capabilities from the tool list. Reached only when
2665
2810
  // the executor returned non-v3-shaped data, OR threw a non-auth
@@ -2750,8 +2895,8 @@ class SingleAgentClient {
2750
2895
  *
2751
2896
  * @returns 'v2' or 'v3' based on server capabilities
2752
2897
  */
2753
- async detectServerVersion() {
2754
- const capabilities = await this.getCapabilities();
2898
+ async detectServerVersion(options) {
2899
+ const capabilities = await this.getCapabilities(options);
2755
2900
  return capabilities.version;
2756
2901
  }
2757
2902
  /**
@@ -2848,13 +2993,17 @@ class SingleAgentClient {
2848
2993
  *
2849
2994
  * Skipped when validateFeatures is false or the task has no feature requirements.
2850
2995
  */
2851
- async validateTaskFeatures(taskName) {
2996
+ async validateTaskFeatures(taskName, options) {
2852
2997
  if (this.config.validateFeatures === false)
2853
2998
  return;
2854
2999
  const requiredFeatures = capabilities_1.TASK_FEATURE_MAP[taskName];
2855
3000
  if (!requiredFeatures || requiredFeatures.length === 0)
2856
3001
  return;
2857
- await this.require(...requiredFeatures);
3002
+ const capabilities = await this.getCapabilities(options);
3003
+ const missing = requiredFeatures.filter(f => !(0, capabilities_1.resolveFeature)(capabilities, f));
3004
+ if (missing.length > 0) {
3005
+ throw new errors_1.FeatureUnsupportedError(missing, (0, capabilities_1.listDeclaredFeatures)(capabilities), this.agent.agent_uri);
3006
+ }
2858
3007
  }
2859
3008
  /**
2860
3009
  * Fail version-incompatible request shapes before schema validation.
@@ -2968,10 +3117,10 @@ class SingleAgentClient {
2968
3117
  *
2969
3118
  * Throws `VersionUnsupportedError` with the specific reason on failure.
2970
3119
  */
2971
- async requireSupportedMajor(taskType = 'request') {
3120
+ async requireSupportedMajor(taskType = 'request', options) {
2972
3121
  if (this.isV2Allowed())
2973
3122
  return;
2974
- const capabilities = await this.getCapabilities();
3123
+ const capabilities = await this.getCapabilities(options);
2975
3124
  // Synthetic capabilities — no authoritative `get_adcp_capabilities`
2976
3125
  // response, so the version + idempotency-TTL fields couldn't be read.
2977
3126
  // Route as the synthesized version (v2 when the v3 discovery tool is