@agentcash/router 1.10.3 → 1.10.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -541,7 +541,8 @@ var HEADERS = {
541
541
  X402_PAYMENT_LEGACY: "X-PAYMENT",
542
542
  X402_PAYMENT_REQUIRED: "PAYMENT-REQUIRED",
543
543
  X402_PAYMENT_RESPONSE: "PAYMENT-RESPONSE",
544
- MPP_PAYMENT_RECEIPT: "Payment-Receipt"
544
+ MPP_PAYMENT_RECEIPT: "Payment-Receipt",
545
+ REQUEST_ID: "X-Request-ID"
545
546
  };
546
547
  var AUTH_SCHEME = {
547
548
  BEARER: "Bearer ",
@@ -571,19 +572,19 @@ function firePluginHook(plugin, method, ...args) {
571
572
  const result = fn.apply(plugin, args);
572
573
  if (result && typeof result.catch === "function") {
573
574
  result.catch((error) => {
574
- console.error(
575
- `[router] ERROR ${method}: ${error instanceof Error ? error.message : String(error)}`
576
- );
575
+ console.error(`[router] ERROR ${method}: ${formatUnknownError(error)}`);
577
576
  });
578
577
  }
579
578
  return result;
580
579
  } catch (error) {
581
- console.error(
582
- `[router] ERROR ${method}: ${error instanceof Error ? error.message : String(error)}`
583
- );
580
+ console.error(`[router] ERROR ${method}: ${formatUnknownError(error)}`);
584
581
  return void 0;
585
582
  }
586
583
  }
584
+ function formatUnknownError(error) {
585
+ if (error instanceof Error) return error.stack ?? error.message;
586
+ return String(error);
587
+ }
587
588
 
588
589
  // src/plugin/reporter.ts
589
590
  function createReporter(plugin, pluginCtx, route) {
@@ -674,7 +675,8 @@ function firePaymentVerified(ctx, event) {
674
675
  function firePaymentSettled(ctx, event) {
675
676
  firePluginHook(ctx.deps.plugin, "onPaymentSettled", ctx.pluginCtx, event);
676
677
  }
677
- function firePluginResponse(ctx, response, requestBody, responseBody) {
678
+ function firePluginResponse(ctx, response, requestBody, responseBody, failure) {
679
+ attachRequestId(response, ctx.meta.requestId);
678
680
  firePluginHook(ctx.deps.plugin, "onResponse", ctx.pluginCtx, {
679
681
  statusCode: response.status,
680
682
  statusText: response.statusText,
@@ -685,11 +687,9 @@ function firePluginResponse(ctx, response, requestBody, responseBody) {
685
687
  responseBody
686
688
  });
687
689
  if (response.status >= 400 && response.status !== 402) {
688
- firePluginHook(ctx.deps.plugin, "onError", ctx.pluginCtx, {
689
- status: response.status,
690
- message: response.statusText || `HTTP ${response.status}`,
691
- settled: false
692
- });
690
+ const error = buildErrorEvent(ctx, response, failure);
691
+ if (response.status >= 500) logRouterFailure(error);
692
+ firePluginHook(ctx.deps.plugin, "onError", ctx.pluginCtx, error);
693
693
  }
694
694
  }
695
695
  function fireProviderQuota(ctx, response, handlerResult) {
@@ -721,6 +721,67 @@ function computeQuotaLevel(remaining, warn, critical) {
721
721
  if (warn !== void 0 && remaining <= warn) return "warn";
722
722
  return "healthy";
723
723
  }
724
+ function attachRequestId(response, requestId) {
725
+ try {
726
+ if (!response.headers.has(HEADERS.REQUEST_ID)) {
727
+ response.headers.set(HEADERS.REQUEST_ID, requestId);
728
+ }
729
+ } catch {
730
+ }
731
+ }
732
+ function buildErrorEvent(ctx, response, failure) {
733
+ const error = errorDetails(failure?.cause);
734
+ const responseMessage = response.statusText || `HTTP ${response.status}`;
735
+ const message = failure?.message ?? error.message ?? responseMessage;
736
+ return {
737
+ status: response.status,
738
+ message,
739
+ settled: failure?.settled ?? false,
740
+ requestId: ctx.meta.requestId,
741
+ route: ctx.meta.route,
742
+ method: ctx.meta.method,
743
+ duration: Date.now() - ctx.meta.startTime,
744
+ walletAddress: ctx.meta.walletAddress,
745
+ verifiedWallet: ctx.pluginCtx.verifiedWallet,
746
+ clientId: ctx.meta.clientId,
747
+ sessionId: ctx.meta.sessionId,
748
+ errorName: error.name,
749
+ stack: error.stack,
750
+ cause: failure?.cause
751
+ };
752
+ }
753
+ function errorDetails(error) {
754
+ if (error instanceof Error) {
755
+ return {
756
+ message: error.message,
757
+ name: error.name,
758
+ stack: error.stack
759
+ };
760
+ }
761
+ if (typeof error === "object" && error !== null) {
762
+ const record = error;
763
+ return {
764
+ message: typeof record.message === "string" ? record.message : void 0,
765
+ name: typeof record.name === "string" ? record.name : void 0,
766
+ stack: typeof record.stack === "string" ? record.stack : void 0
767
+ };
768
+ }
769
+ if (typeof error === "string") return { message: error };
770
+ return {};
771
+ }
772
+ function logRouterFailure(error) {
773
+ console.error(`[router] ERROR ${error.route ?? "unknown"} ${error.status}: ${error.message}`, {
774
+ requestId: error.requestId,
775
+ method: error.method,
776
+ duration: error.duration,
777
+ walletAddress: error.walletAddress,
778
+ verifiedWallet: error.verifiedWallet,
779
+ clientId: error.clientId,
780
+ sessionId: error.sessionId,
781
+ errorName: error.errorName,
782
+ stack: error.stack
783
+ });
784
+ }
724
785
 
725
786
  // src/pipeline/steps/parse-body.ts
726
787
  async function parseBody(ctx, request = ctx.request) {
@@ -730,20 +791,19 @@ async function parseBody(ctx, request = ctx.request) {
730
791
  raw = await bufferBody(request);
731
792
  } catch (err) {
732
793
  if (!(err instanceof MalformedJsonError)) throw err;
733
- const response2 = import_server.NextResponse.json(
734
- { success: false, error: "Invalid JSON", issues: [] },
735
- { status: 400 }
736
- );
737
- firePluginResponse(ctx, response2);
794
+ const responseBody2 = { success: false, error: "Invalid JSON", issues: [] };
795
+ const response2 = import_server.NextResponse.json(responseBody2, { status: 400 });
796
+ firePluginResponse(ctx, response2, void 0, responseBody2, {
797
+ message: responseBody2.error,
798
+ cause: err
799
+ });
738
800
  return { ok: false, response: response2 };
739
801
  }
740
802
  const result = validateBody(raw, ctx.routeEntry.bodySchema);
741
803
  if (result.success) return { ok: true, data: result.data };
742
- const response = import_server.NextResponse.json(
743
- { success: false, error: result.error, issues: result.issues },
744
- { status: 400 }
745
- );
746
- firePluginResponse(ctx, response);
804
+ const responseBody = { success: false, error: result.error, issues: result.issues };
805
+ const response = import_server.NextResponse.json(responseBody, { status: 400 });
806
+ firePluginResponse(ctx, response, raw, responseBody, { message: result.error });
747
807
  return { ok: false, response };
748
808
  }
749
809
 
@@ -755,11 +815,9 @@ function validateQuery(ctx) {
755
815
  const params = Object.fromEntries(ctx.request.nextUrl.searchParams.entries());
756
816
  const result = validateBody(params, querySchema);
757
817
  if (result.success) return { ok: true, data: result.data };
758
- const response = import_server2.NextResponse.json(
759
- { success: false, error: result.error, issues: result.issues },
760
- { status: 400 }
761
- );
762
- firePluginResponse(ctx, response);
818
+ const responseBody = { success: false, error: result.error, issues: result.issues };
819
+ const response = import_server2.NextResponse.json(responseBody, { status: 400 });
820
+ firePluginResponse(ctx, response, params, responseBody, { message: result.error });
763
821
  return { ok: false, response };
764
822
  }
765
823
 
@@ -778,9 +836,13 @@ function handlerFailureError(response) {
778
836
 
779
837
  // src/pipeline/steps/fail.ts
780
838
  var import_server3 = require("next/server");
781
- function fail(ctx, status, message, requestBody) {
782
- const response = import_server3.NextResponse.json({ success: false, error: message }, { status });
783
- firePluginResponse(ctx, response, requestBody);
839
+ function fail(ctx, status, message, requestBody, failure) {
840
+ const responseBody = { success: false, error: message };
841
+ const response = import_server3.NextResponse.json(responseBody, { status });
842
+ firePluginResponse(ctx, response, requestBody, responseBody, {
843
+ ...failure,
844
+ message: failure?.message ?? message
845
+ });
784
846
  return response;
785
847
  }
786
848
 
@@ -856,9 +918,10 @@ async function runHandler(ctx, handlerCtx) {
856
918
  function errorResult(error) {
857
919
  const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
858
920
  const message = error instanceof Error ? error.message : "Internal error";
921
+ const responseBody = { success: false, error: message };
859
922
  return {
860
- response: import_server4.NextResponse.json({ success: false, error: message }, { status }),
861
- rawResult: void 0,
923
+ response: import_server4.NextResponse.json(responseBody, { status }),
924
+ rawResult: responseBody,
862
925
  handlerError: error
863
926
  };
864
927
  }
@@ -870,9 +933,9 @@ function isThenable(value) {
870
933
  }
871
934
 
872
935
  // src/pipeline/steps/finalize/response.ts
873
- function finalize(ctx, response, rawResult, requestBody) {
936
+ function finalize(ctx, response, rawResult, requestBody, failure) {
874
937
  fireProviderQuota(ctx, response, rawResult);
875
- firePluginResponse(ctx, response, requestBody, rawResult);
938
+ firePluginResponse(ctx, response, requestBody, rawResult, failure);
876
939
  return response;
877
940
  }
878
941
 
@@ -958,7 +1021,9 @@ async function settleAndFinalizeRequest(args) {
958
1021
  });
959
1022
  if (!settle.ok) {
960
1023
  if (onSettleError) await onSettleError(settle.error, settle.failMessage);
961
- return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body);
1024
+ return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body, {
1025
+ cause: settle.error
1026
+ });
962
1027
  }
963
1028
  return runPostSettleEpilogue({
964
1029
  ctx,
@@ -993,7 +1058,9 @@ async function settleAndFinalizeStream(args) {
993
1058
  report
994
1059
  });
995
1060
  if (!settle.ok) {
996
- return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body);
1061
+ return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body, {
1062
+ cause: settle.error
1063
+ });
997
1064
  }
998
1065
  return runPostSettleEpilogue({
999
1066
  ctx,
@@ -1020,7 +1087,13 @@ async function runHandlerOnly(ctx, wallet, account) {
1020
1087
  const validateErr = await runValidate(ctx, body.data);
1021
1088
  if (validateErr) return validateErr;
1022
1089
  const result = await invokeUnauthed(ctx, wallet, account, body.data);
1023
- return finalize(ctx, result.response, result.rawResult, body.data);
1090
+ return finalize(
1091
+ ctx,
1092
+ result.response,
1093
+ result.rawResult,
1094
+ body.data,
1095
+ result.handlerError === void 0 ? void 0 : { cause: result.handlerError }
1096
+ );
1024
1097
  }
1025
1098
 
1026
1099
  // src/pipeline/steps/run-before-settle.ts
@@ -2033,6 +2106,7 @@ function invalidPaymentVerification(failure) {
2033
2106
  }
2034
2107
 
2035
2108
  // src/protocols/x402/strategy.ts
2109
+ var SETTLE_RETRY_DELAYS_MS = [500, 1e3];
2036
2110
  function formatVerifyFailureMessage(failure) {
2037
2111
  if (failure.reason === "permit2_allowance_required") {
2038
2112
  const wallet = failure.payer ?? "<the payer wallet>";
@@ -2123,7 +2197,17 @@ async function settleX402(args) {
2123
2197
  const { payload, requirements } = token;
2124
2198
  const override = routeEntry.billing === "exact" ? void 0 : { amount: billedAmount };
2125
2199
  try {
2126
- const settle = await settleX402Payment(deps.x402Server, payload, requirements, override);
2200
+ let settle = await settleX402Payment(deps.x402Server, payload, requirements, override);
2201
+ for (let attempt = 0; !settle.result?.success && attempt < SETTLE_RETRY_DELAYS_MS.length; attempt++) {
2202
+ report("warn", "Retrying x402 settlement", {
2203
+ attempt: attempt + 1,
2204
+ errorReason: settle.result?.errorReason
2205
+ });
2206
+ await new Promise((resolve) => {
2207
+ setTimeout(resolve, SETTLE_RETRY_DELAYS_MS[attempt]);
2208
+ });
2209
+ settle = await settleX402Payment(deps.x402Server, payload, requirements, override);
2210
+ }
2127
2211
  if (!settle.result?.success) {
2128
2212
  throw Object.assign(
2129
2213
  new Error(settle.result?.errorReason ?? "x402 settlement returned success=false"),
@@ -2459,11 +2543,9 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
2459
2543
  challengePrice = pricing ? await pricing.challengeQuote(body) : "0";
2460
2544
  } catch (err) {
2461
2545
  const message = errorMessage(err, "Price calculation failed");
2462
- const errorResponse = import_server5.NextResponse.json(
2463
- { success: false, error: message },
2464
- { status: errorStatus(err, 500) }
2465
- );
2466
- firePluginResponse(ctx, errorResponse);
2546
+ const responseBody2 = { success: false, error: message };
2547
+ const errorResponse = import_server5.NextResponse.json(responseBody2, { status: errorStatus(err, 500) });
2548
+ firePluginResponse(ctx, errorResponse, body, responseBody2, { message, cause: err });
2467
2549
  return errorResponse;
2468
2550
  }
2469
2551
  const extensions = await buildChallengeExtensions(ctx);
@@ -2495,11 +2577,9 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
2495
2577
  const message = `${strategy.protocol} challenge build failed: ${errorMessage(err, String(err))}`;
2496
2578
  ctx.report("critical", message);
2497
2579
  if (strategy.protocol === "x402") {
2498
- const errorResponse = import_server5.NextResponse.json(
2499
- { success: false, error: message },
2500
- { status: 500 }
2501
- );
2502
- firePluginResponse(ctx, errorResponse);
2580
+ const responseBody2 = { success: false, error: message };
2581
+ const errorResponse = import_server5.NextResponse.json(responseBody2, { status: 500 });
2582
+ firePluginResponse(ctx, errorResponse, body, responseBody2, { message, cause: err });
2503
2583
  return errorResponse;
2504
2584
  }
2505
2585
  }
@@ -2658,10 +2738,11 @@ function toResponse(rawResult) {
2658
2738
  function errorResult2(error) {
2659
2739
  const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
2660
2740
  const message = error instanceof Error ? error.message : "Internal error";
2741
+ const responseBody = { success: false, error: message };
2661
2742
  return {
2662
2743
  kind: "request",
2663
- response: import_server7.NextResponse.json({ success: false, error: message }, { status }),
2664
- rawResult: void 0,
2744
+ response: import_server7.NextResponse.json(responseBody, { status }),
2745
+ rawResult: responseBody,
2665
2746
  handlerError: error
2666
2747
  };
2667
2748
  }
@@ -2792,7 +2873,13 @@ async function runDynamicRequestFlow(args) {
2792
2873
  handlerError: result.handlerError
2793
2874
  };
2794
2875
  if (result.response.status >= 400) {
2795
- return finalize(ctx, result.response, result.rawResult, body);
2876
+ return finalize(
2877
+ ctx,
2878
+ result.response,
2879
+ result.rawResult,
2880
+ body,
2881
+ failureFromCause(result.handlerError)
2882
+ );
2796
2883
  }
2797
2884
  const beforeErr = await runBeforeSettle(ctx, settleScope);
2798
2885
  if (beforeErr) return beforeErr;
@@ -2814,6 +2901,9 @@ async function runDynamicRequestFlow(args) {
2814
2901
  }
2815
2902
  });
2816
2903
  }
2904
+ function failureFromCause(cause) {
2905
+ return cause === void 0 ? void 0 : { cause };
2906
+ }
2817
2907
  function computeBilledAmount(routeEntry, result) {
2818
2908
  if (routeEntry.billing === "upto") {
2819
2909
  const total = result.uptoContext?.atomicTotal() ?? 0n;
@@ -2978,7 +3068,13 @@ async function runStaticRequestFlow(args) {
2978
3068
  if (result.response.status >= 400) {
2979
3069
  const settledScope = settleScope;
2980
3070
  await runSettledHandlerError(ctx, settledScope);
2981
- return finalize(ctx, result.response, result.rawResult, body);
3071
+ return finalize(
3072
+ ctx,
3073
+ result.response,
3074
+ result.rawResult,
3075
+ body,
3076
+ failureFromCause2(result.handlerError)
3077
+ );
2982
3078
  }
2983
3079
  return settleAndFinalizeRequest({
2984
3080
  ctx,
@@ -2991,7 +3087,13 @@ async function runStaticRequestFlow(args) {
2991
3087
  });
2992
3088
  }
2993
3089
  if (result.response.status >= 400) {
2994
- return finalize(ctx, result.response, result.rawResult, body);
3090
+ return finalize(
3091
+ ctx,
3092
+ result.response,
3093
+ result.rawResult,
3094
+ body,
3095
+ failureFromCause2(result.handlerError)
3096
+ );
2995
3097
  }
2996
3098
  const beforeErr = await runBeforeSettle(ctx, settleScope);
2997
3099
  if (beforeErr) return beforeErr;
@@ -3012,6 +3114,9 @@ async function runStaticRequestFlow(args) {
3012
3114
  }
3013
3115
  });
3014
3116
  }
3117
+ function failureFromCause2(cause) {
3118
+ return cause === void 0 ? void 0 : { cause };
3119
+ }
3015
3120
 
3016
3121
  // src/pipeline/flows/static/static-paid.ts
3017
3122
  async function runStaticPaidFlow(ctx) {
@@ -3496,7 +3601,8 @@ var RouteBuilder = class _RouteBuilder {
3496
3601
  providerConfig: void 0,
3497
3602
  validateFn: void 0,
3498
3603
  settlement: void 0,
3499
- mppInfo: void 0
3604
+ mppInfo: void 0,
3605
+ hasCheckout: false
3500
3606
  };
3501
3607
  }
3502
3608
  fork() {
@@ -3587,6 +3693,7 @@ var RouteBuilder = class _RouteBuilder {
3587
3693
  if (resolvedOptions.minPrice) next.#s.minPrice = resolvedOptions.minPrice;
3588
3694
  if (resolvedOptions.payTo) next.#s.payTo = resolvedOptions.payTo;
3589
3695
  if (resolvedOptions.mpp) next.#s.mppInfo = resolvedOptions.mpp;
3696
+ if (resolvedOptions.checkout) next.#s.hasCheckout = true;
3590
3697
  next.#s.billing = billing;
3591
3698
  if (tickCost) next.#s.tickCost = tickCost;
3592
3699
  if (unitType) next.#s.unitType = unitType;
@@ -4020,6 +4127,7 @@ var RouteBuilder = class _RouteBuilder {
4020
4127
  validateFn: this.#s.validateFn,
4021
4128
  settlement: this.#s.settlement,
4022
4129
  mppInfo: this.#s.mppInfo,
4130
+ hasCheckout: this.#s.hasCheckout ? true : void 0,
4023
4131
  tickCost: this.#s.tickCost,
4024
4132
  unitType: this.#s.unitType
4025
4133
  };
@@ -4226,6 +4334,7 @@ function buildOperation(routeKey, entry, tag) {
4226
4334
  const requiresSiwxScheme = entry.authMode === "siwx" || Boolean(entry.siwxEnabled);
4227
4335
  const requiresApiKeyScheme = Boolean(entry.apiKeyResolver) && entry.authMode !== "siwx";
4228
4336
  const pricingInfo = buildPricingInfo(entry);
4337
+ const hasCheckout = entry.hasCheckout === true;
4229
4338
  const operation = {
4230
4339
  operationId: routeKey.replace(/\//g, "_"),
4231
4340
  summary: entry.description ?? routeKey,
@@ -4251,10 +4360,11 @@ function buildOperation(routeKey, entry, tag) {
4251
4360
  }
4252
4361
  }
4253
4362
  };
4254
- if (paymentRequired && (pricingInfo || protocols)) {
4363
+ if (paymentRequired && (pricingInfo || protocols || hasCheckout)) {
4255
4364
  operation["x-payment-info"] = {
4256
4365
  ...pricingInfo ?? {},
4257
- ...protocols && { protocols }
4366
+ ...protocols && { protocols },
4367
+ ...hasCheckout && { has_checkout: true }
4258
4368
  };
4259
4369
  }
4260
4370
  if (requiresSiwxScheme) {
package/dist/index.d.cts CHANGED
@@ -77,13 +77,24 @@ interface ResponseMeta {
77
77
  headers: Record<string, string>;
78
78
  /** Parsed request body (when .body() was used). undefined when no body was parsed. */
79
79
  requestBody?: unknown;
80
- /** Handler return value. undefined for raw Response returns (streams) or error paths. */
80
+ /** Handler return value or structured router-generated error body. */
81
81
  responseBody?: unknown;
82
82
  }
83
83
  interface ErrorEvent {
84
84
  status: number;
85
85
  message: string;
86
86
  settled: boolean;
87
+ requestId?: string;
88
+ route?: string;
89
+ method?: string;
90
+ duration?: number;
91
+ walletAddress?: string | null;
92
+ verifiedWallet?: string | null;
93
+ clientId?: string | null;
94
+ sessionId?: string | null;
95
+ errorName?: string;
96
+ stack?: string;
97
+ cause?: unknown;
87
98
  }
88
99
  interface AuthEvent {
89
100
  /** Authentication mode that was verified */
@@ -216,6 +227,8 @@ interface PaidOptions {
216
227
  payTo?: PayToConfig;
217
228
  /** Override MPP protocol metadata in x-payment-info discovery. */
218
229
  mpp?: MppProtocolInfo;
230
+ /** Signal in discovery that clients should use an explicit checkout flow before payment. */
231
+ checkout?: boolean;
219
232
  }
220
233
  type PaidArg = (PaidOptions & {
221
234
  price: string;
@@ -360,6 +373,7 @@ interface RouteEntry {
360
373
  validateFn?: (body: unknown) => void | Promise<void>;
361
374
  settlement?: SettlementLifecycle;
362
375
  mppInfo?: MppProtocolInfo;
376
+ hasCheckout?: boolean;
363
377
  /** Per-tick cost (decimal-dollar). Required when `metered` is true. */
364
378
  tickCost?: string;
365
379
  /** Cosmetic unit label for 402 challenges and client UIs. */
@@ -543,7 +557,7 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
543
557
  * - `{ price }` — fixed price (object form of the string sugar).
544
558
  * - `{ field, tiers, default? }` — pick a tier from `body[field]`.
545
559
  *
546
- * Common knobs (`protocols`, `maxPrice`, `minPrice`, `payTo`, `mpp`) live
560
+ * Common knobs (`protocols`, `maxPrice`, `minPrice`, `payTo`, `mpp`, `checkout`) live
547
561
  * alongside the pricing shape. For handler-computed billing use `.upTo()`;
548
562
  * for per-tick billing use `.metered()`.
549
563
  *
package/dist/index.d.ts CHANGED
@@ -77,13 +77,24 @@ interface ResponseMeta {
77
77
  headers: Record<string, string>;
78
78
  /** Parsed request body (when .body() was used). undefined when no body was parsed. */
79
79
  requestBody?: unknown;
80
- /** Handler return value. undefined for raw Response returns (streams) or error paths. */
80
+ /** Handler return value or structured router-generated error body. */
81
81
  responseBody?: unknown;
82
82
  }
83
83
  interface ErrorEvent {
84
84
  status: number;
85
85
  message: string;
86
86
  settled: boolean;
87
+ requestId?: string;
88
+ route?: string;
89
+ method?: string;
90
+ duration?: number;
91
+ walletAddress?: string | null;
92
+ verifiedWallet?: string | null;
93
+ clientId?: string | null;
94
+ sessionId?: string | null;
95
+ errorName?: string;
96
+ stack?: string;
97
+ cause?: unknown;
87
98
  }
88
99
  interface AuthEvent {
89
100
  /** Authentication mode that was verified */
@@ -216,6 +227,8 @@ interface PaidOptions {
216
227
  payTo?: PayToConfig;
217
228
  /** Override MPP protocol metadata in x-payment-info discovery. */
218
229
  mpp?: MppProtocolInfo;
230
+ /** Signal in discovery that clients should use an explicit checkout flow before payment. */
231
+ checkout?: boolean;
219
232
  }
220
233
  type PaidArg = (PaidOptions & {
221
234
  price: string;
@@ -360,6 +373,7 @@ interface RouteEntry {
360
373
  validateFn?: (body: unknown) => void | Promise<void>;
361
374
  settlement?: SettlementLifecycle;
362
375
  mppInfo?: MppProtocolInfo;
376
+ hasCheckout?: boolean;
363
377
  /** Per-tick cost (decimal-dollar). Required when `metered` is true. */
364
378
  tickCost?: string;
365
379
  /** Cosmetic unit label for 402 challenges and client UIs. */
@@ -543,7 +557,7 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
543
557
  * - `{ price }` — fixed price (object form of the string sugar).
544
558
  * - `{ field, tiers, default? }` — pick a tier from `body[field]`.
545
559
  *
546
- * Common knobs (`protocols`, `maxPrice`, `minPrice`, `payTo`, `mpp`) live
560
+ * Common knobs (`protocols`, `maxPrice`, `minPrice`, `payTo`, `mpp`, `checkout`) live
547
561
  * alongside the pricing shape. For handler-computed billing use `.upTo()`;
548
562
  * for per-tick billing use `.metered()`.
549
563
  *
package/dist/index.js CHANGED
@@ -499,7 +499,8 @@ var HEADERS = {
499
499
  X402_PAYMENT_LEGACY: "X-PAYMENT",
500
500
  X402_PAYMENT_REQUIRED: "PAYMENT-REQUIRED",
501
501
  X402_PAYMENT_RESPONSE: "PAYMENT-RESPONSE",
502
- MPP_PAYMENT_RECEIPT: "Payment-Receipt"
502
+ MPP_PAYMENT_RECEIPT: "Payment-Receipt",
503
+ REQUEST_ID: "X-Request-ID"
503
504
  };
504
505
  var AUTH_SCHEME = {
505
506
  BEARER: "Bearer ",
@@ -529,19 +530,19 @@ function firePluginHook(plugin, method, ...args) {
529
530
  const result = fn.apply(plugin, args);
530
531
  if (result && typeof result.catch === "function") {
531
532
  result.catch((error) => {
532
- console.error(
533
- `[router] ERROR ${method}: ${error instanceof Error ? error.message : String(error)}`
534
- );
533
+ console.error(`[router] ERROR ${method}: ${formatUnknownError(error)}`);
535
534
  });
536
535
  }
537
536
  return result;
538
537
  } catch (error) {
539
- console.error(
540
- `[router] ERROR ${method}: ${error instanceof Error ? error.message : String(error)}`
541
- );
538
+ console.error(`[router] ERROR ${method}: ${formatUnknownError(error)}`);
542
539
  return void 0;
543
540
  }
544
541
  }
542
+ function formatUnknownError(error) {
543
+ if (error instanceof Error) return error.stack ?? error.message;
544
+ return String(error);
545
+ }
545
546
 
546
547
  // src/plugin/reporter.ts
547
548
  function createReporter(plugin, pluginCtx, route) {
@@ -632,7 +633,8 @@ function firePaymentVerified(ctx, event) {
632
633
  function firePaymentSettled(ctx, event) {
633
634
  firePluginHook(ctx.deps.plugin, "onPaymentSettled", ctx.pluginCtx, event);
634
635
  }
635
- function firePluginResponse(ctx, response, requestBody, responseBody) {
636
+ function firePluginResponse(ctx, response, requestBody, responseBody, failure) {
637
+ attachRequestId(response, ctx.meta.requestId);
636
638
  firePluginHook(ctx.deps.plugin, "onResponse", ctx.pluginCtx, {
637
639
  statusCode: response.status,
638
640
  statusText: response.statusText,
@@ -643,11 +645,9 @@ function firePluginResponse(ctx, response, requestBody, responseBody) {
643
645
  responseBody
644
646
  });
645
647
  if (response.status >= 400 && response.status !== 402) {
646
- firePluginHook(ctx.deps.plugin, "onError", ctx.pluginCtx, {
647
- status: response.status,
648
- message: response.statusText || `HTTP ${response.status}`,
649
- settled: false
650
- });
648
+ const error = buildErrorEvent(ctx, response, failure);
649
+ if (response.status >= 500) logRouterFailure(error);
650
+ firePluginHook(ctx.deps.plugin, "onError", ctx.pluginCtx, error);
651
651
  }
652
652
  }
653
653
  function fireProviderQuota(ctx, response, handlerResult) {
@@ -679,6 +679,67 @@ function computeQuotaLevel(remaining, warn, critical) {
679
679
  if (warn !== void 0 && remaining <= warn) return "warn";
680
680
  return "healthy";
681
681
  }
682
+ function attachRequestId(response, requestId) {
683
+ try {
684
+ if (!response.headers.has(HEADERS.REQUEST_ID)) {
685
+ response.headers.set(HEADERS.REQUEST_ID, requestId);
686
+ }
687
+ } catch {
688
+ }
689
+ }
690
+ function buildErrorEvent(ctx, response, failure) {
691
+ const error = errorDetails(failure?.cause);
692
+ const responseMessage = response.statusText || `HTTP ${response.status}`;
693
+ const message = failure?.message ?? error.message ?? responseMessage;
694
+ return {
695
+ status: response.status,
696
+ message,
697
+ settled: failure?.settled ?? false,
698
+ requestId: ctx.meta.requestId,
699
+ route: ctx.meta.route,
700
+ method: ctx.meta.method,
701
+ duration: Date.now() - ctx.meta.startTime,
702
+ walletAddress: ctx.meta.walletAddress,
703
+ verifiedWallet: ctx.pluginCtx.verifiedWallet,
704
+ clientId: ctx.meta.clientId,
705
+ sessionId: ctx.meta.sessionId,
706
+ errorName: error.name,
707
+ stack: error.stack,
708
+ cause: failure?.cause
709
+ };
710
+ }
711
+ function errorDetails(error) {
712
+ if (error instanceof Error) {
713
+ return {
714
+ message: error.message,
715
+ name: error.name,
716
+ stack: error.stack
717
+ };
718
+ }
719
+ if (typeof error === "object" && error !== null) {
720
+ const record = error;
721
+ return {
722
+ message: typeof record.message === "string" ? record.message : void 0,
723
+ name: typeof record.name === "string" ? record.name : void 0,
724
+ stack: typeof record.stack === "string" ? record.stack : void 0
725
+ };
726
+ }
727
+ if (typeof error === "string") return { message: error };
728
+ return {};
729
+ }
730
+ function logRouterFailure(error) {
731
+ console.error(`[router] ERROR ${error.route ?? "unknown"} ${error.status}: ${error.message}`, {
732
+ requestId: error.requestId,
733
+ method: error.method,
734
+ duration: error.duration,
735
+ walletAddress: error.walletAddress,
736
+ verifiedWallet: error.verifiedWallet,
737
+ clientId: error.clientId,
738
+ sessionId: error.sessionId,
739
+ errorName: error.errorName,
740
+ stack: error.stack
741
+ });
742
+ }
682
743
 
683
744
  // src/pipeline/steps/parse-body.ts
684
745
  async function parseBody(ctx, request = ctx.request) {
@@ -688,20 +749,19 @@ async function parseBody(ctx, request = ctx.request) {
688
749
  raw = await bufferBody(request);
689
750
  } catch (err) {
690
751
  if (!(err instanceof MalformedJsonError)) throw err;
691
- const response2 = NextResponse.json(
692
- { success: false, error: "Invalid JSON", issues: [] },
693
- { status: 400 }
694
- );
695
- firePluginResponse(ctx, response2);
752
+ const responseBody2 = { success: false, error: "Invalid JSON", issues: [] };
753
+ const response2 = NextResponse.json(responseBody2, { status: 400 });
754
+ firePluginResponse(ctx, response2, void 0, responseBody2, {
755
+ message: responseBody2.error,
756
+ cause: err
757
+ });
696
758
  return { ok: false, response: response2 };
697
759
  }
698
760
  const result = validateBody(raw, ctx.routeEntry.bodySchema);
699
761
  if (result.success) return { ok: true, data: result.data };
700
- const response = NextResponse.json(
701
- { success: false, error: result.error, issues: result.issues },
702
- { status: 400 }
703
- );
704
- firePluginResponse(ctx, response);
762
+ const responseBody = { success: false, error: result.error, issues: result.issues };
763
+ const response = NextResponse.json(responseBody, { status: 400 });
764
+ firePluginResponse(ctx, response, raw, responseBody, { message: result.error });
705
765
  return { ok: false, response };
706
766
  }
707
767
 
@@ -713,11 +773,9 @@ function validateQuery(ctx) {
713
773
  const params = Object.fromEntries(ctx.request.nextUrl.searchParams.entries());
714
774
  const result = validateBody(params, querySchema);
715
775
  if (result.success) return { ok: true, data: result.data };
716
- const response = NextResponse2.json(
717
- { success: false, error: result.error, issues: result.issues },
718
- { status: 400 }
719
- );
720
- firePluginResponse(ctx, response);
776
+ const responseBody = { success: false, error: result.error, issues: result.issues };
777
+ const response = NextResponse2.json(responseBody, { status: 400 });
778
+ firePluginResponse(ctx, response, params, responseBody, { message: result.error });
721
779
  return { ok: false, response };
722
780
  }
723
781
 
@@ -736,9 +794,13 @@ function handlerFailureError(response) {
736
794
 
737
795
  // src/pipeline/steps/fail.ts
738
796
  import { NextResponse as NextResponse3 } from "next/server";
739
- function fail(ctx, status, message, requestBody) {
740
- const response = NextResponse3.json({ success: false, error: message }, { status });
741
- firePluginResponse(ctx, response, requestBody);
797
+ function fail(ctx, status, message, requestBody, failure) {
798
+ const responseBody = { success: false, error: message };
799
+ const response = NextResponse3.json(responseBody, { status });
800
+ firePluginResponse(ctx, response, requestBody, responseBody, {
801
+ ...failure,
802
+ message: failure?.message ?? message
803
+ });
742
804
  return response;
743
805
  }
744
806
 
@@ -814,9 +876,10 @@ async function runHandler(ctx, handlerCtx) {
814
876
  function errorResult(error) {
815
877
  const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
816
878
  const message = error instanceof Error ? error.message : "Internal error";
879
+ const responseBody = { success: false, error: message };
817
880
  return {
818
- response: NextResponse4.json({ success: false, error: message }, { status }),
819
- rawResult: void 0,
881
+ response: NextResponse4.json(responseBody, { status }),
882
+ rawResult: responseBody,
820
883
  handlerError: error
821
884
  };
822
885
  }
@@ -828,9 +891,9 @@ function isThenable(value) {
828
891
  }
829
892
 
830
893
  // src/pipeline/steps/finalize/response.ts
831
- function finalize(ctx, response, rawResult, requestBody) {
894
+ function finalize(ctx, response, rawResult, requestBody, failure) {
832
895
  fireProviderQuota(ctx, response, rawResult);
833
- firePluginResponse(ctx, response, requestBody, rawResult);
896
+ firePluginResponse(ctx, response, requestBody, rawResult, failure);
834
897
  return response;
835
898
  }
836
899
 
@@ -916,7 +979,9 @@ async function settleAndFinalizeRequest(args) {
916
979
  });
917
980
  if (!settle.ok) {
918
981
  if (onSettleError) await onSettleError(settle.error, settle.failMessage);
919
- return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body);
982
+ return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body, {
983
+ cause: settle.error
984
+ });
920
985
  }
921
986
  return runPostSettleEpilogue({
922
987
  ctx,
@@ -951,7 +1016,9 @@ async function settleAndFinalizeStream(args) {
951
1016
  report
952
1017
  });
953
1018
  if (!settle.ok) {
954
- return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body);
1019
+ return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body, {
1020
+ cause: settle.error
1021
+ });
955
1022
  }
956
1023
  return runPostSettleEpilogue({
957
1024
  ctx,
@@ -978,7 +1045,13 @@ async function runHandlerOnly(ctx, wallet, account) {
978
1045
  const validateErr = await runValidate(ctx, body.data);
979
1046
  if (validateErr) return validateErr;
980
1047
  const result = await invokeUnauthed(ctx, wallet, account, body.data);
981
- return finalize(ctx, result.response, result.rawResult, body.data);
1048
+ return finalize(
1049
+ ctx,
1050
+ result.response,
1051
+ result.rawResult,
1052
+ body.data,
1053
+ result.handlerError === void 0 ? void 0 : { cause: result.handlerError }
1054
+ );
982
1055
  }
983
1056
 
984
1057
  // src/pipeline/steps/run-before-settle.ts
@@ -1991,6 +2064,7 @@ function invalidPaymentVerification(failure) {
1991
2064
  }
1992
2065
 
1993
2066
  // src/protocols/x402/strategy.ts
2067
+ var SETTLE_RETRY_DELAYS_MS = [500, 1e3];
1994
2068
  function formatVerifyFailureMessage(failure) {
1995
2069
  if (failure.reason === "permit2_allowance_required") {
1996
2070
  const wallet = failure.payer ?? "<the payer wallet>";
@@ -2081,7 +2155,17 @@ async function settleX402(args) {
2081
2155
  const { payload, requirements } = token;
2082
2156
  const override = routeEntry.billing === "exact" ? void 0 : { amount: billedAmount };
2083
2157
  try {
2084
- const settle = await settleX402Payment(deps.x402Server, payload, requirements, override);
2158
+ let settle = await settleX402Payment(deps.x402Server, payload, requirements, override);
2159
+ for (let attempt = 0; !settle.result?.success && attempt < SETTLE_RETRY_DELAYS_MS.length; attempt++) {
2160
+ report("warn", "Retrying x402 settlement", {
2161
+ attempt: attempt + 1,
2162
+ errorReason: settle.result?.errorReason
2163
+ });
2164
+ await new Promise((resolve) => {
2165
+ setTimeout(resolve, SETTLE_RETRY_DELAYS_MS[attempt]);
2166
+ });
2167
+ settle = await settleX402Payment(deps.x402Server, payload, requirements, override);
2168
+ }
2085
2169
  if (!settle.result?.success) {
2086
2170
  throw Object.assign(
2087
2171
  new Error(settle.result?.errorReason ?? "x402 settlement returned success=false"),
@@ -2417,11 +2501,9 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
2417
2501
  challengePrice = pricing ? await pricing.challengeQuote(body) : "0";
2418
2502
  } catch (err) {
2419
2503
  const message = errorMessage(err, "Price calculation failed");
2420
- const errorResponse = NextResponse5.json(
2421
- { success: false, error: message },
2422
- { status: errorStatus(err, 500) }
2423
- );
2424
- firePluginResponse(ctx, errorResponse);
2504
+ const responseBody2 = { success: false, error: message };
2505
+ const errorResponse = NextResponse5.json(responseBody2, { status: errorStatus(err, 500) });
2506
+ firePluginResponse(ctx, errorResponse, body, responseBody2, { message, cause: err });
2425
2507
  return errorResponse;
2426
2508
  }
2427
2509
  const extensions = await buildChallengeExtensions(ctx);
@@ -2453,11 +2535,9 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
2453
2535
  const message = `${strategy.protocol} challenge build failed: ${errorMessage(err, String(err))}`;
2454
2536
  ctx.report("critical", message);
2455
2537
  if (strategy.protocol === "x402") {
2456
- const errorResponse = NextResponse5.json(
2457
- { success: false, error: message },
2458
- { status: 500 }
2459
- );
2460
- firePluginResponse(ctx, errorResponse);
2538
+ const responseBody2 = { success: false, error: message };
2539
+ const errorResponse = NextResponse5.json(responseBody2, { status: 500 });
2540
+ firePluginResponse(ctx, errorResponse, body, responseBody2, { message, cause: err });
2461
2541
  return errorResponse;
2462
2542
  }
2463
2543
  }
@@ -2616,10 +2696,11 @@ function toResponse(rawResult) {
2616
2696
  function errorResult2(error) {
2617
2697
  const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
2618
2698
  const message = error instanceof Error ? error.message : "Internal error";
2699
+ const responseBody = { success: false, error: message };
2619
2700
  return {
2620
2701
  kind: "request",
2621
- response: NextResponse7.json({ success: false, error: message }, { status }),
2622
- rawResult: void 0,
2702
+ response: NextResponse7.json(responseBody, { status }),
2703
+ rawResult: responseBody,
2623
2704
  handlerError: error
2624
2705
  };
2625
2706
  }
@@ -2750,7 +2831,13 @@ async function runDynamicRequestFlow(args) {
2750
2831
  handlerError: result.handlerError
2751
2832
  };
2752
2833
  if (result.response.status >= 400) {
2753
- return finalize(ctx, result.response, result.rawResult, body);
2834
+ return finalize(
2835
+ ctx,
2836
+ result.response,
2837
+ result.rawResult,
2838
+ body,
2839
+ failureFromCause(result.handlerError)
2840
+ );
2754
2841
  }
2755
2842
  const beforeErr = await runBeforeSettle(ctx, settleScope);
2756
2843
  if (beforeErr) return beforeErr;
@@ -2772,6 +2859,9 @@ async function runDynamicRequestFlow(args) {
2772
2859
  }
2773
2860
  });
2774
2861
  }
2862
+ function failureFromCause(cause) {
2863
+ return cause === void 0 ? void 0 : { cause };
2864
+ }
2775
2865
  function computeBilledAmount(routeEntry, result) {
2776
2866
  if (routeEntry.billing === "upto") {
2777
2867
  const total = result.uptoContext?.atomicTotal() ?? 0n;
@@ -2936,7 +3026,13 @@ async function runStaticRequestFlow(args) {
2936
3026
  if (result.response.status >= 400) {
2937
3027
  const settledScope = settleScope;
2938
3028
  await runSettledHandlerError(ctx, settledScope);
2939
- return finalize(ctx, result.response, result.rawResult, body);
3029
+ return finalize(
3030
+ ctx,
3031
+ result.response,
3032
+ result.rawResult,
3033
+ body,
3034
+ failureFromCause2(result.handlerError)
3035
+ );
2940
3036
  }
2941
3037
  return settleAndFinalizeRequest({
2942
3038
  ctx,
@@ -2949,7 +3045,13 @@ async function runStaticRequestFlow(args) {
2949
3045
  });
2950
3046
  }
2951
3047
  if (result.response.status >= 400) {
2952
- return finalize(ctx, result.response, result.rawResult, body);
3048
+ return finalize(
3049
+ ctx,
3050
+ result.response,
3051
+ result.rawResult,
3052
+ body,
3053
+ failureFromCause2(result.handlerError)
3054
+ );
2953
3055
  }
2954
3056
  const beforeErr = await runBeforeSettle(ctx, settleScope);
2955
3057
  if (beforeErr) return beforeErr;
@@ -2970,6 +3072,9 @@ async function runStaticRequestFlow(args) {
2970
3072
  }
2971
3073
  });
2972
3074
  }
3075
+ function failureFromCause2(cause) {
3076
+ return cause === void 0 ? void 0 : { cause };
3077
+ }
2973
3078
 
2974
3079
  // src/pipeline/flows/static/static-paid.ts
2975
3080
  async function runStaticPaidFlow(ctx) {
@@ -3454,7 +3559,8 @@ var RouteBuilder = class _RouteBuilder {
3454
3559
  providerConfig: void 0,
3455
3560
  validateFn: void 0,
3456
3561
  settlement: void 0,
3457
- mppInfo: void 0
3562
+ mppInfo: void 0,
3563
+ hasCheckout: false
3458
3564
  };
3459
3565
  }
3460
3566
  fork() {
@@ -3545,6 +3651,7 @@ var RouteBuilder = class _RouteBuilder {
3545
3651
  if (resolvedOptions.minPrice) next.#s.minPrice = resolvedOptions.minPrice;
3546
3652
  if (resolvedOptions.payTo) next.#s.payTo = resolvedOptions.payTo;
3547
3653
  if (resolvedOptions.mpp) next.#s.mppInfo = resolvedOptions.mpp;
3654
+ if (resolvedOptions.checkout) next.#s.hasCheckout = true;
3548
3655
  next.#s.billing = billing;
3549
3656
  if (tickCost) next.#s.tickCost = tickCost;
3550
3657
  if (unitType) next.#s.unitType = unitType;
@@ -3978,6 +4085,7 @@ var RouteBuilder = class _RouteBuilder {
3978
4085
  validateFn: this.#s.validateFn,
3979
4086
  settlement: this.#s.settlement,
3980
4087
  mppInfo: this.#s.mppInfo,
4088
+ hasCheckout: this.#s.hasCheckout ? true : void 0,
3981
4089
  tickCost: this.#s.tickCost,
3982
4090
  unitType: this.#s.unitType
3983
4091
  };
@@ -4184,6 +4292,7 @@ function buildOperation(routeKey, entry, tag) {
4184
4292
  const requiresSiwxScheme = entry.authMode === "siwx" || Boolean(entry.siwxEnabled);
4185
4293
  const requiresApiKeyScheme = Boolean(entry.apiKeyResolver) && entry.authMode !== "siwx";
4186
4294
  const pricingInfo = buildPricingInfo(entry);
4295
+ const hasCheckout = entry.hasCheckout === true;
4187
4296
  const operation = {
4188
4297
  operationId: routeKey.replace(/\//g, "_"),
4189
4298
  summary: entry.description ?? routeKey,
@@ -4209,10 +4318,11 @@ function buildOperation(routeKey, entry, tag) {
4209
4318
  }
4210
4319
  }
4211
4320
  };
4212
- if (paymentRequired && (pricingInfo || protocols)) {
4321
+ if (paymentRequired && (pricingInfo || protocols || hasCheckout)) {
4213
4322
  operation["x-payment-info"] = {
4214
4323
  ...pricingInfo ?? {},
4215
- ...protocols && { protocols }
4324
+ ...protocols && { protocols },
4325
+ ...hasCheckout && { has_checkout: true }
4216
4326
  };
4217
4327
  }
4218
4328
  if (requiresSiwxScheme) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcash/router",
3
- "version": "1.10.3",
3
+ "version": "1.10.5",
4
4
  "description": "Unified route builder for Next.js App Router APIs with x402, MPP, SIWX, and API key auth",
5
5
  "type": "module",
6
6
  "exports": {