@nevermined-io/payments 1.6.0 → 1.8.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 (88) hide show
  1. package/dist/a2a/agent-card.d.ts +26 -0
  2. package/dist/a2a/agent-card.d.ts.map +1 -1
  3. package/dist/a2a/agent-card.js +36 -1
  4. package/dist/a2a/agent-card.js.map +1 -1
  5. package/dist/a2a/paymentsClient.d.ts +41 -1
  6. package/dist/a2a/paymentsClient.d.ts.map +1 -1
  7. package/dist/a2a/paymentsClient.js +120 -8
  8. package/dist/a2a/paymentsClient.js.map +1 -1
  9. package/dist/a2a/paymentsRequestHandler.d.ts +25 -2
  10. package/dist/a2a/paymentsRequestHandler.d.ts.map +1 -1
  11. package/dist/a2a/paymentsRequestHandler.js +240 -20
  12. package/dist/a2a/paymentsRequestHandler.js.map +1 -1
  13. package/dist/a2a/server.d.ts +2 -2
  14. package/dist/a2a/server.d.ts.map +1 -1
  15. package/dist/a2a/server.js +70 -20
  16. package/dist/a2a/server.js.map +1 -1
  17. package/dist/a2a/types.d.ts +31 -1
  18. package/dist/a2a/types.d.ts.map +1 -1
  19. package/dist/a2a/types.js.map +1 -1
  20. package/dist/a2a/x402-a2a.d.ts +142 -0
  21. package/dist/a2a/x402-a2a.d.ts.map +1 -0
  22. package/dist/a2a/x402-a2a.js +254 -0
  23. package/dist/a2a/x402-a2a.js.map +1 -0
  24. package/dist/api/agents-api.d.ts +19 -0
  25. package/dist/api/agents-api.d.ts.map +1 -1
  26. package/dist/api/agents-api.js +30 -3
  27. package/dist/api/agents-api.js.map +1 -1
  28. package/dist/api/base-payments.d.ts +6 -4
  29. package/dist/api/base-payments.d.ts.map +1 -1
  30. package/dist/api/base-payments.js +10 -0
  31. package/dist/api/base-payments.js.map +1 -1
  32. package/dist/api/contracts-api.js +1 -1
  33. package/dist/api/contracts-api.js.map +1 -1
  34. package/dist/api/nvm-api.d.ts +1 -0
  35. package/dist/api/nvm-api.d.ts.map +1 -1
  36. package/dist/api/nvm-api.js +4 -0
  37. package/dist/api/nvm-api.js.map +1 -1
  38. package/dist/api/plans-api.d.ts +12 -2
  39. package/dist/api/plans-api.d.ts.map +1 -1
  40. package/dist/api/plans-api.js +12 -12
  41. package/dist/api/plans-api.js.map +1 -1
  42. package/dist/common/api-version.d.ts +24 -0
  43. package/dist/common/api-version.d.ts.map +1 -0
  44. package/dist/common/api-version.js +24 -0
  45. package/dist/common/api-version.js.map +1 -0
  46. package/dist/common/types.d.ts +73 -18
  47. package/dist/common/types.d.ts.map +1 -1
  48. package/dist/common/types.js.map +1 -1
  49. package/dist/index.d.ts +3 -0
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +4 -0
  52. package/dist/index.js.map +1 -1
  53. package/dist/mcp/core/auth.d.ts +14 -0
  54. package/dist/mcp/core/auth.d.ts.map +1 -1
  55. package/dist/mcp/core/auth.js +56 -23
  56. package/dist/mcp/core/auth.js.map +1 -1
  57. package/dist/mcp/core/paywall.d.ts +6 -0
  58. package/dist/mcp/core/paywall.d.ts.map +1 -1
  59. package/dist/mcp/core/paywall.js +170 -84
  60. package/dist/mcp/core/paywall.js.map +1 -1
  61. package/dist/mcp/utils/errors.d.ts +26 -0
  62. package/dist/mcp/utils/errors.d.ts.map +1 -1
  63. package/dist/mcp/utils/errors.js +32 -0
  64. package/dist/mcp/utils/errors.js.map +1 -1
  65. package/dist/mcp/utils/meta.d.ts +54 -0
  66. package/dist/mcp/utils/meta.d.ts.map +1 -0
  67. package/dist/mcp/utils/meta.js +72 -0
  68. package/dist/mcp/utils/meta.js.map +1 -0
  69. package/dist/payments.d.ts +4 -3
  70. package/dist/payments.d.ts.map +1 -1
  71. package/dist/payments.js +5 -3
  72. package/dist/payments.js.map +1 -1
  73. package/dist/utils.d.ts +27 -0
  74. package/dist/utils.d.ts.map +1 -1
  75. package/dist/utils.js +34 -0
  76. package/dist/utils.js.map +1 -1
  77. package/dist/x402/facilitator-api.d.ts +21 -0
  78. package/dist/x402/facilitator-api.d.ts.map +1 -1
  79. package/dist/x402/facilitator-api.js +39 -0
  80. package/dist/x402/facilitator-api.js.map +1 -1
  81. package/dist/x402/index.d.ts +1 -1
  82. package/dist/x402/index.d.ts.map +1 -1
  83. package/dist/x402/index.js.map +1 -1
  84. package/dist/x402/token.d.ts +13 -10
  85. package/dist/x402/token.d.ts.map +1 -1
  86. package/dist/x402/token.js +46 -16
  87. package/dist/x402/token.js.map +1 -1
  88. package/package.json +2 -2
@@ -3,7 +3,8 @@ import { v4 as uuidv4 } from 'uuid';
3
3
  import { PaymentsError } from '../common/payments.error.js';
4
4
  import { isValidScheme } from '../common/types.js';
5
5
  import { decodeAccessToken } from '../utils.js';
6
- import { buildPaymentRequired } from '../x402/facilitator-api.js';
6
+ import { buildPaymentRequired, } from '../x402/facilitator-api.js';
7
+ import { x402A2AUtils } from './x402-a2a.js';
7
8
  const terminalStates = ['completed', 'failed', 'canceled', 'rejected'];
8
9
  /**
9
10
  * PaymentsRequestHandler extends DefaultRequestHandler to add payments validation and burning.
@@ -109,7 +110,9 @@ export class PaymentsRequestHandler extends DefaultRequestHandler {
109
110
  throw PaymentsError.unauthorized('Cannot determine subscriberAddress from token (expected payload.authorization.from)');
110
111
  }
111
112
  const agentId = paymentExtension?.params?.agentId;
112
- const scheme = isValidScheme(decodedAccessToken.accepted?.scheme) ? decodedAccessToken.accepted.scheme : 'nvm:erc4337';
113
+ const scheme = isValidScheme(decodedAccessToken.accepted?.scheme)
114
+ ? decodedAccessToken.accepted.scheme
115
+ : 'nvm:erc4337';
113
116
  const paymentRequired = buildPaymentRequired(planId, {
114
117
  endpoint: endpoint || '',
115
118
  agentId,
@@ -172,7 +175,9 @@ export class PaymentsRequestHandler extends DefaultRequestHandler {
172
175
  if (!planId) {
173
176
  throw PaymentsError.unauthorized('Plan ID not found in agent card.');
174
177
  }
175
- const scheme = isValidScheme(decodedAccessToken.accepted?.scheme) ? decodedAccessToken.accepted.scheme : 'nvm:erc4337';
178
+ const scheme = isValidScheme(decodedAccessToken.accepted?.scheme)
179
+ ? decodedAccessToken.accepted.scheme
180
+ : 'nvm:erc4337';
176
181
  // Build paymentRequired using the helper
177
182
  const paymentRequired = buildPaymentRequired(planId, {
178
183
  endpoint: httpContext?.urlRequested,
@@ -217,11 +222,17 @@ export class PaymentsRequestHandler extends DefaultRequestHandler {
217
222
  if (!httpContext) {
218
223
  throw A2AError.internalError('HTTP context not found for task or message.');
219
224
  }
220
- // 2. Extract bearer token and validate presence of required fields
225
+ // 2. Extract bearer token and validate presence of required fields.
226
+ // This path only runs for an *authorized* context (a token was supplied), so
227
+ // both bearerToken and validation must be present — assert the invariant
228
+ // explicitly instead of carrying an `undefined as any`.
221
229
  const { bearerToken, validation } = httpContext;
222
230
  if (!bearerToken) {
223
231
  throw PaymentsError.unauthorized('Missing bearer token for payment validation.');
224
232
  }
233
+ if (!validation) {
234
+ throw PaymentsError.unauthorized('Missing validation context for payment.');
235
+ }
225
236
  // 3. Validate credits before executing the task
226
237
  const agentCard = await this.getAgentCard();
227
238
  const agentId = agentCard.capabilities?.extensions?.find((ext) => ext.uri === 'urn:nevermined:payment')?.params?.agentId;
@@ -271,6 +282,85 @@ export class PaymentsRequestHandler extends DefaultRequestHandler {
271
282
  resultManager,
272
283
  };
273
284
  }
285
+ /**
286
+ * x402 v2 A2A transport: if the request is payment-gated and arrived with no
287
+ * token (the middleware stored a `paymentRequired` on the HTTP context), build
288
+ * and return the spec-shaped `input-required` task carrying the
289
+ * X402PaymentRequired object under `x402.payment.required` — WITHOUT executing
290
+ * the agent. Returns `undefined` when a token was present (normal flow).
291
+ *
292
+ * @param params - The incoming message send parameters
293
+ * @returns The `input-required` Task to return to the client, or `undefined`
294
+ */
295
+ async buildPaymentRequiredTaskIfNeeded(params) {
296
+ const message = params.message;
297
+ if (!message) {
298
+ return undefined;
299
+ }
300
+ const incomingTaskId = message.taskId;
301
+ const httpContext = incomingTaskId
302
+ ? this.getHttpRequestContextForTask(incomingTaskId)
303
+ : this.getHttpRequestContextForMessage(message.messageId);
304
+ if (!httpContext?.paymentRequired) {
305
+ return undefined;
306
+ }
307
+ // Correlate the input-required task with the incoming taskId when present so
308
+ // the client's follow-up payment payload (same taskId) maps back to it.
309
+ const taskId = incomingTaskId || uuidv4();
310
+ const contextId = message.contextId || uuidv4();
311
+ const task = {
312
+ kind: 'task',
313
+ id: taskId,
314
+ contextId,
315
+ status: { state: 'submitted', timestamp: new Date().toISOString() },
316
+ history: [message],
317
+ };
318
+ const paymentRequiredTask = x402A2AUtils.createPaymentRequiredTask(task, httpContext.paymentRequired);
319
+ // Persist the input-required task so the client's follow-up (same taskId)
320
+ // can correlate to it (otherwise the SDK's _createRequestContext raises
321
+ // "Task not found"). The follow-up carries the in-band payload, so the
322
+ // middleware overwrites this taskId's HTTP context with the authorized one.
323
+ await this.getTaskStore().save(paymentRequiredTask);
324
+ if (!incomingTaskId) {
325
+ this.deleteHttpRequestContextForMessage(message.messageId);
326
+ }
327
+ return paymentRequiredTask;
328
+ }
329
+ /**
330
+ * Stamp x402 settlement state onto the final task's metadata, in band, per the
331
+ * x402 v2 A2A transport. Only applied when the token arrived in band (so the
332
+ * legacy `payment-signature` header path is unchanged). On success the task
333
+ * carries `x402.payment.status: payment-completed` + `x402.payment.receipts`;
334
+ * on failure `payment-failed` + `x402.payment.error` + receipts.
335
+ *
336
+ * @param task - The current task (mutated in place)
337
+ * @param httpContext - The request's HTTP context
338
+ * @param settlement - The settlement result, or undefined when none ran
339
+ */
340
+ recordInBandSettlement(task, httpContext, settlement, settlementDeferred = false) {
341
+ if (!task || !httpContext?.inBand) {
342
+ return;
343
+ }
344
+ if (settlement && settlement.success === false) {
345
+ x402A2AUtils.recordPaymentFailure(task, settlement.errorReason || 'SETTLEMENT_FAILED', settlement);
346
+ }
347
+ else if (settlement) {
348
+ x402A2AUtils.recordPaymentSuccess(task, settlement);
349
+ }
350
+ else if (settlementDeferred) {
351
+ // No in-band settlement result because redemption is BATCHED: the payload was
352
+ // verified but on-chain settlement is deferred out-of-band (this handler never
353
+ // confirms it). Mark payment-verified + the deferred marker, NOT
354
+ // payment-completed — so the client knows it will be charged out-of-band
355
+ // rather than reading a completed task as "nothing owed".
356
+ x402A2AUtils.recordPaymentDeferred(task);
357
+ }
358
+ else {
359
+ // Defensive default: verified, but no settlement result AND not batch-deferred
360
+ // (e.g. a settle that returned nothing). Record a bare verify.
361
+ x402A2AUtils.recordPaymentVerified(task);
362
+ }
363
+ }
274
364
  /**
275
365
  * Processes streaming events with finalization (credits burning and push notifications).
276
366
  * Similar to processEventsWithFinalization but yields events for streaming.
@@ -281,7 +371,7 @@ export class PaymentsRequestHandler extends DefaultRequestHandler {
281
371
  * @param validation - The validation result
282
372
  * @returns Async generator yielding processed events
283
373
  */
284
- async *processStreamingEventsWithFinalization(taskId, resultManager, eventQueue, bearerToken) {
374
+ async *processStreamingEventsWithFinalization(taskId, resultManager, eventQueue, bearerToken, requestHttpContext) {
285
375
  try {
286
376
  for await (const event of eventQueue.events()) {
287
377
  await resultManager.processEvent(event);
@@ -298,21 +388,75 @@ export class PaymentsRequestHandler extends DefaultRequestHandler {
298
388
  // Get redemption configuration from server (not from client metadata)
299
389
  const redemptionConfig = await this.getRedemptionConfig();
300
390
  if (!redemptionConfig.useBatch) {
301
- // Get HTTP context if available
302
- const httpContext = this.getHttpRequestContextForTask(event.taskId);
391
+ // Prefer the per-request context (authoritative in-band flag); fall
392
+ // back to a per-taskId lookup for executors that mint their own task id.
393
+ const httpContext = requestHttpContext ?? this.getHttpRequestContextForTask(event.taskId);
303
394
  // Execute redemption with server configuration for non-batch requests
304
395
  const response = await this.executeRedemption(bearerToken, BigInt(event.metadata.creditsUsed), httpContext);
305
396
  // Update event metadata with response data
306
397
  if (response && event.metadata) {
307
- event.metadata.txHash = response.txHash;
398
+ event.metadata.txHash = response.txHash ?? response.transaction;
308
399
  event.metadata.creditsCharged = response.amountOfCredits
309
400
  ? Number(response.amountOfCredits)
310
401
  : event.metadata.creditsUsed;
311
402
  }
403
+ // x402 v2 A2A transport: stamp the settlement receipt onto the
404
+ // persisted task in band. NOTE: a stream cannot retract the event
405
+ // it already yielded above, so an in-band settlement is reflected
406
+ // on the saved task (visible via tasks/get + resubscribe) — it
407
+ // cannot withhold content the way a non-streaming result does.
408
+ if (httpContext?.inBand) {
409
+ const task = resultManager.getCurrentTask();
410
+ if (task) {
411
+ this.recordInBandSettlement(task, httpContext, response);
412
+ await resultManager.processEvent(task);
413
+ }
414
+ }
415
+ }
416
+ else {
417
+ // Batch redemption: on-chain settlement is deferred out-of-band. The
418
+ // stream already yielded the content (can't retract), but stamp the
419
+ // PERSISTED task with payment-verified + the deferred marker so a
420
+ // streaming client (via tasks/get + resubscribe) isn't left reading a
421
+ // completed task as "nothing owed" — matching the non-streaming path.
422
+ const httpContext = requestHttpContext ?? this.getHttpRequestContextForTask(event.taskId);
423
+ if (httpContext?.inBand) {
424
+ const task = resultManager.getCurrentTask();
425
+ if (task) {
426
+ this.recordInBandSettlement(task, httpContext, undefined, true);
427
+ await resultManager.processEvent(task);
428
+ }
429
+ }
312
430
  }
313
431
  }
314
432
  catch (err) {
315
- // Do nothing
433
+ // x402 v2 A2A: settlement failed AFTER the paid event was already
434
+ // streamed to the client (a stream cannot retract it). Do NOT swallow
435
+ // it — log, and stamp payment-failed on the persisted task so
436
+ // tasks/get + resubscribe reflect the failure (mirrors non-streaming).
437
+ console.error('[PaymentsA2A] streaming settlement failed after execution:', err);
438
+ const httpContext = requestHttpContext ?? this.getHttpRequestContextForTask(event.taskId);
439
+ if (httpContext?.inBand) {
440
+ try {
441
+ const task = resultManager.getCurrentTask();
442
+ if (task) {
443
+ const errorReason = err instanceof Error ? err.message : String(err);
444
+ if (task.status)
445
+ task.status.state = 'failed';
446
+ task.artifacts = undefined;
447
+ this.recordInBandSettlement(task, httpContext, {
448
+ success: false,
449
+ errorReason,
450
+ transaction: '',
451
+ network: '',
452
+ });
453
+ await resultManager.processEvent(task);
454
+ }
455
+ }
456
+ catch (stampErr) {
457
+ console.error('[PaymentsA2A] failed to stamp streaming payment-failed:', stampErr);
458
+ }
459
+ }
316
460
  }
317
461
  }
318
462
  // Handle push notification
@@ -345,7 +489,7 @@ export class PaymentsRequestHandler extends DefaultRequestHandler {
345
489
  * Processes all events, calling handleTaskFinalization when a terminal status-update event is received.
346
490
  * In async mode, it can be launched in background.
347
491
  */
348
- async processEventsWithFinalization(taskId, resultManager, eventQueue, bearerToken, validation, options) {
492
+ async processEventsWithFinalization(taskId, resultManager, eventQueue, bearerToken, validation, options, requestHttpContext) {
349
493
  let firstResultSent = false;
350
494
  try {
351
495
  for await (const event of eventQueue.events()) {
@@ -353,8 +497,11 @@ export class PaymentsRequestHandler extends DefaultRequestHandler {
353
497
  if (event.kind === 'status-update' &&
354
498
  event.final &&
355
499
  terminalStates.includes(event.status?.state)) {
356
- // Get HTTP context if available
357
- const httpContext = this.getHttpRequestContextForTask(event.taskId);
500
+ // Prefer the per-request HTTP context (authoritative for this request,
501
+ // carries the in-band flag). Fall back to a per-taskId lookup for
502
+ // executors that mint their own task id (the event's taskId may then
503
+ // differ from the request's generated one).
504
+ const httpContext = requestHttpContext ?? this.getHttpRequestContextForTask(event.taskId);
358
505
  await this.handleTaskFinalization(resultManager, event, bearerToken, httpContext);
359
506
  }
360
507
  await resultManager.processEvent(event);
@@ -387,8 +534,14 @@ export class PaymentsRequestHandler extends DefaultRequestHandler {
387
534
  * @returns The resulting message or task
388
535
  */
389
536
  async sendMessage(params) {
537
+ // x402 v2 A2A transport: a payment-gated request with no token returns an
538
+ // `input-required` task (in band) instead of executing the agent.
539
+ const paymentRequiredTask = await this.buildPaymentRequiredTaskIfNeeded(params);
540
+ if (paymentRequiredTask) {
541
+ return paymentRequiredTask;
542
+ }
390
543
  // Create PaymentsRequestContext and related data
391
- const { paymentsRequestContext, taskId, bearerToken, validation, requestContext, finalMessageForAgent, eventBus, eventQueue, resultManager, } = await this.createPaymentsRequestContext(params, false);
544
+ const { paymentsRequestContext, taskId, httpContext, bearerToken, validation, requestContext, finalMessageForAgent, eventBus, eventQueue, resultManager, } = await this.createPaymentsRequestContext(params, false);
392
545
  this.agentExecutor
393
546
  .execute(paymentsRequestContext, eventBus)
394
547
  .catch((err) => {
@@ -429,7 +582,7 @@ export class PaymentsRequestHandler extends DefaultRequestHandler {
429
582
  // The blocking parameter comes from params.configuration.blocking
430
583
  const isBlocking = params.configuration?.blocking !== false; // Default to blocking if not specified
431
584
  if (isBlocking) {
432
- await this.processEventsWithFinalization(taskId, resultManager, eventQueue, bearerToken, validation);
585
+ await this.processEventsWithFinalization(taskId, resultManager, eventQueue, bearerToken, validation, undefined, httpContext);
433
586
  const finalResult = resultManager.getFinalResult();
434
587
  if (!finalResult) {
435
588
  throw A2AError.internalError('Agent execution finished without a result, and no task context found.');
@@ -446,7 +599,7 @@ export class PaymentsRequestHandler extends DefaultRequestHandler {
446
599
  this.processEventsWithFinalization(validTaskId, resultManager, eventQueue, bearerToken, validation, {
447
600
  firstResultResolver: resolve,
448
601
  firstResultRejector: reject,
449
- });
602
+ }, httpContext);
450
603
  });
451
604
  }
452
605
  }
@@ -467,37 +620,97 @@ export class PaymentsRequestHandler extends DefaultRequestHandler {
467
620
  (typeof creditsToBurn === 'string' ||
468
621
  typeof creditsToBurn === 'number' ||
469
622
  typeof creditsToBurn === 'bigint')) {
623
+ let settlement;
624
+ let settlementError;
625
+ let settlementDeferred = false;
470
626
  try {
471
627
  // Get redemption configuration from server (not from client metadata)
472
628
  const redemptionConfig = await this.getRedemptionConfig();
629
+ // Batch redemption defers on-chain settlement out-of-band (no receipt here).
630
+ settlementDeferred = redemptionConfig.useBatch ?? false;
473
631
  if (!redemptionConfig.useBatch) {
474
632
  // Execute redemption with server configuration for non-batch requests
475
633
  const response = await this.executeRedemption(bearerToken, BigInt(creditsToBurn), httpContext);
634
+ settlement = response;
476
635
  // Update event metadata with redemption results
477
636
  event.metadata = {
478
637
  ...event.metadata,
479
- txHash: response.txHash,
638
+ txHash: response.txHash ?? response.transaction,
480
639
  // Store the actual credits charged (especially important for margin-based)
481
640
  creditsCharged: response.amountOfCredits
482
641
  ? Number(response.amountOfCredits)
483
642
  : creditsToBurn,
484
643
  };
485
644
  }
645
+ }
646
+ catch (err) {
647
+ // Settlement failed AFTER the agent executed. Always log it (the legacy
648
+ // header path has no other surface for it). For the in-band x402 v2 A2A
649
+ // path it is additionally surfaced below as payment-failed + suppressed
650
+ // content; the legacy header path still delivers the result (no retract).
651
+ console.error('[PaymentsA2A] settlement failed after execution:', err);
652
+ settlementError = err;
653
+ }
654
+ // Stamp the in-band x402 state onto the FINAL event so it survives when the
655
+ // caller processes this event into the task's status (mutating the task
656
+ // here would be overwritten by the final event). The event carries a Task
657
+ // shape under `event.status`; the helpers mutate `event.status.message`.
658
+ if (httpContext?.inBand && settlementError) {
659
+ // x402 v2 A2A transport: a settlement failure after execution must NOT
660
+ // deliver paid content — replace the agent's status message with a
661
+ // failed one carrying payment-failed metadata + an error receipt.
662
+ const errorReason = settlementError instanceof Error ? settlementError.message : String(settlementError);
663
+ event.status = {
664
+ state: 'failed',
665
+ message: {
666
+ kind: 'message',
667
+ messageId: uuidv4(),
668
+ role: 'agent',
669
+ parts: [{ kind: 'text', text: `Payment settlement failed: ${errorReason}` }],
670
+ taskId: event.taskId,
671
+ contextId: event.contextId,
672
+ metadata: {},
673
+ },
674
+ timestamp: new Date().toISOString(),
675
+ };
676
+ this.recordInBandSettlement({ status: event.status }, httpContext, {
677
+ success: false,
678
+ errorReason,
679
+ transaction: '',
680
+ network: settlement?.network || '',
681
+ });
682
+ }
683
+ else if (httpContext?.inBand) {
684
+ // Stamp in-band settlement state onto the final event's status message.
685
+ this.recordInBandSettlement({ status: event.status }, httpContext, settlement, settlementDeferred);
686
+ }
687
+ try {
486
688
  // Always update task metadata and process the task
487
689
  const task = resultManager.getCurrentTask();
488
690
  if (task) {
489
- // Update task metadata with current event metadata (from executor or redemption)
691
+ // Update task metadata with current event metadata (executor / redemption).
490
692
  task.metadata = {
491
693
  ...task.metadata,
492
694
  ...event.metadata,
493
695
  };
696
+ if (httpContext?.inBand && settlementError) {
697
+ // x402 v2 A2A transport: a settlement failure must never deliver paid
698
+ // content. The agent's status message was already replaced above; also
699
+ // drop any artifacts it emitted so the paid result cannot surface there
700
+ // (mirrors the Python SDK's _apply_inband_settlement). History is rebuilt
701
+ // by processEvent below from the replaced (failed) status, so it carries
702
+ // no paid content.
703
+ task.artifacts = undefined;
704
+ }
494
705
  await resultManager.processEvent(task);
495
706
  // Delete http context associated with the task
496
707
  this.deleteHttpRequestContextForTask(event.taskId);
497
708
  }
498
709
  }
499
710
  catch (err) {
500
- // Do nothing
711
+ // This block persists the payment-failed/suppressed task; if it throws the
712
+ // suppression itself failed, so log loudly rather than swallow.
713
+ console.error('[PaymentsA2A] failed to persist finalized task:', err);
501
714
  }
502
715
  }
503
716
  try {
@@ -519,8 +732,15 @@ export class PaymentsRequestHandler extends DefaultRequestHandler {
519
732
  * @returns Async generator of events
520
733
  */
521
734
  async *sendMessageStream(params) {
735
+ // x402 v2 A2A transport: a payment-gated request with no token yields a
736
+ // single `input-required` task (in band) instead of executing the agent.
737
+ const paymentRequiredTask = await this.buildPaymentRequiredTaskIfNeeded(params);
738
+ if (paymentRequiredTask) {
739
+ yield paymentRequiredTask;
740
+ return;
741
+ }
522
742
  // Create PaymentsRequestContext and related data
523
- const { paymentsRequestContext, taskId, bearerToken, requestContext, finalMessageForAgent, eventBus, eventQueue, resultManager, } = await this.createPaymentsRequestContext(params, true);
743
+ const { paymentsRequestContext, taskId, httpContext, bearerToken, requestContext, finalMessageForAgent, eventBus, eventQueue, resultManager, } = await this.createPaymentsRequestContext(params, true);
524
744
  this.agentExecutor
525
745
  .execute(paymentsRequestContext, eventBus)
526
746
  .catch((err) => {
@@ -547,7 +767,7 @@ export class PaymentsRequestHandler extends DefaultRequestHandler {
547
767
  eventBus.publish(errorTaskStatus);
548
768
  });
549
769
  // Process streaming events with finalization
550
- yield* this.processStreamingEventsWithFinalization(taskId, resultManager, eventQueue, bearerToken);
770
+ yield* this.processStreamingEventsWithFinalization(taskId, resultManager, eventQueue, bearerToken, httpContext);
551
771
  }
552
772
  /**
553
773
  * Sends a push notification when a task reaches a terminal state.