@lucid-agents/pi-xgate 0.1.8 → 0.1.10

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/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # xgate provider for Pi
2
2
 
3
- A Pi provider extension for wallet-paid inference, supported on Pi **1.1.0** and
4
- **0.85.1**. It uses native
3
+ A Pi provider extension for wallet-paid inference, supported on Pi **1.5.2**,
4
+ **1.1.0** and **0.85.1**. It uses native
5
5
  Anthropic Messages and structured OpenAI Responses through xgate. Pi executes its
6
6
  own tools; xgate never executes local tools. Text, tool calls/results and supported
7
7
  reasoning signatures survive the full agent loop.
@@ -10,10 +10,10 @@ reasoning signatures survive the full agent loop.
10
10
 
11
11
  The release package is `@lucid-agents/pi-xgate`. It includes the client dependency
12
12
  and the reviewed model-capability snapshot; the internal router contracts package
13
- is not required. With Node 24.19 or later and Pi 1.1.0:
13
+ is not required. With Node 24.19 or later and Pi 1.5.2:
14
14
 
15
15
  ```sh
16
- pi install npm:@lucid-agents/pi-xgate@0.1.8
16
+ pi install npm:@lucid-agents/pi-xgate@0.1.10
17
17
  ```
18
18
 
19
19
  For repository development before a release is available:
@@ -24,8 +24,9 @@ pi install ./packages/pi-xgate
24
24
  ```
25
25
 
26
26
  Pi supplies `@earendil-works/pi-ai` and `@earendil-works/pi-coding-agent` as peers.
27
- This release supports exactly 1.1.0 and 0.85.1 (the version TaskMarket's agent
28
- runtime pins); other versions need their own check.
27
+ This release supports exactly 1.5.2, 1.1.0 and 0.85.1 (the version TaskMarket's agent
28
+ runtime pins); other versions need their own check. `pi install` does not enforce
29
+ the peer range, so on another Pi version the extension loads unchecked.
29
30
 
30
31
  ## Connect a wallet broker
31
32
 
@@ -81,9 +82,50 @@ Commands:
81
82
  - `/xgate budget`: show the wallet service's fixed authorization caps.
82
83
  - `/xgate receipts`: recent content-free payment records.
83
84
  - `/xgate reconcile <invocation ID>`: inspect/reconcile the original purchase without
84
- resubmitting inference or payment.
85
+ resubmitting inference or payment. If it reports `authorization_still_live`, the
86
+ router refused the request before funding but the signed Tempo or Base
87
+ authorization has not provably expired yet: run the same command again after it
88
+ expires (Tempo signs about 25 seconds ahead and must then be finalized; Base signs
89
+ up to 120 seconds ahead and must then reach the policy's confirmation level). Do
90
+ not start a replacement purchase. Other errors and their actions are in the wallet
91
+ client's refused before funding sections for
92
+ [Tempo](../xgate-client/README.md#tempo-purchases-refused-before-funding) and
93
+ [Base](../xgate-client/README.md#base-purchases-refused-before-funding).
85
94
  - `/xgate refresh`: reload private configuration and available models.
86
95
 
96
+ ### Rate limits
97
+
98
+ On Tempo the router sponsors the network fee for each purchase's payment channel and
99
+ limits those openings: per wallet **4 a minute** and **13 an hour** (2.00 of
100
+ reserved fee at the current 0.15 cap), with at most 3 unconfirmed at once; across
101
+ all wallets 30 a minute and 66 an hour. Every model call is one purchase and an
102
+ agent turn makes two or more, so an active session reaches these in normal use.
103
+ The full table and what each limit bounds are in the wallet client's
104
+ [Tempo rate limits](../xgate-client/README.md#tempo-rate-limits).
105
+
106
+ A call over a limit is refused before any funds move. The turn ends with an error
107
+ that says it was a rate limit and how long the router suggests waiting, for example:
108
+
109
+ ```text
110
+ Xgate: rate_limited_requires_reconciliation. The router refused this purchase with
111
+ a rate limit and suggests waiting 32 seconds before a new one. Reconcile this
112
+ purchase first; never resend it. Billing invocation: 3f…c1.
113
+ ```
114
+
115
+ The status line shows the same two steps:
116
+ `xgate rate limited (unresolved): /xgate reconcile <invocation ID>, then wait 32s before a new request`.
117
+
118
+ 1. Run `/xgate reconcile <invocation ID>`. The wallet finishes the purchase at a
119
+ charge of zero only after it has proven on chain that the signed authorization
120
+ expired unused; until then the command reports `authorization_still_live` (run
121
+ it again a little later).
122
+ 2. Send the request again as a new turn once the wait has passed. It is a new
123
+ purchase with a new quote.
124
+
125
+ Pi does not retry the turn by itself, and the provider never resends a paid
126
+ request. A host can read the wait from `onPurchaseFailed` as `retryAfterSeconds`.
127
+ A host transport reports one by passing it to `XgatePurchaseError`.
128
+
87
129
  Pi cannot raise the service budget. Authorized ceilings remain consumed after
88
130
  successful settlement even when most of a deposit is returned. A new budget is an
89
131
  explicit wallet-operator action, never automatic rollover or a fresh journal.
@@ -144,7 +186,7 @@ const transport = createTaskMarketTransport({
144
186
  const provider = await createXgateProvider(transport, {
145
187
  sessionId: () => sessionId,
146
188
  onPaid: (r) => show(r.payment), // network, scheme, asset, atomic amounts, channel
147
- onPurchaseFailed: (f) => record(f), // original invocation ID, code, pending
189
+ onPurchaseFailed: (f) => record(f), // original invocation ID, code, pending, retryAfterSeconds
148
190
  });
149
191
  ```
150
192
 
@@ -210,13 +252,14 @@ from the current tool set is rejected rather than silently losing that history.
210
252
 
211
253
  Run `pnpm pi:check` at the repository root. Tests instantiate Pi's real agent core,
212
254
  execute a fixture tool, and verify the next request contains its exact result and
213
- reasoning history. `pnpm packages:check` installs the packed tarballs with Pi 1.1.0
214
- and 0.85.1 and runs each real Pi CLI through the mock broker and an injected
255
+ reasoning history. `pnpm packages:check` installs the packed tarballs with Pi 1.5.2,
256
+ 1.1.0 and 0.85.1 and runs each real Pi CLI through the mock broker and an injected
215
257
  transport, type-checks a consumer of the supported exports, and runs the TaskMarket
216
258
  payer loop suite (`test/installed-loop.test.ts`: continuation, qualification,
217
259
  cancellation, duplicate and lost response) against each installed Pi. Separate wallet tests cover durable financial boundaries.
218
260
  These are fixture tests. The [9 October staging campaign](../../docs/validation/pi-client-staging-20261009.md)
219
- separately exercised the installed Pi CLI with live providers and test tokens.
261
+ separately exercised the installed Pi 1.1.0 CLI with live providers and test tokens;
262
+ Pi 1.5.2 and 0.85.1 have fixture coverage only.
220
263
  That evidence is staging-only; it does not qualify production payment execution.
221
264
  Installing the extension does not publish router qualifications or deploy any service.
222
265
 
@@ -1,4 +1,5 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { type XgatePurchaseFailure } from "./provider.ts";
2
3
  import type { XgatePayment, XgateTransport } from "./transport.ts";
3
4
  export interface XgateExtensionOptions {
4
5
  /**
@@ -10,6 +11,8 @@ export interface XgateExtensionOptions {
10
11
  }
11
12
  /** Rail-neutral: atomic units of the named asset; decimals are not assumed. */
12
13
  export declare function paymentStatus(p: XgatePayment): string;
14
+ /** What to do about a purchase that ended without a verified result. */
15
+ export declare function failureStatus(f: XgatePurchaseFailure): string;
13
16
  /** Pi extension factory. The default export is the standalone wallet-broker extension. */
14
17
  export declare function createXgateExtension(options?: XgateExtensionOptions): (pi: ExtensionAPI) => Promise<void>;
15
18
  declare const _default: (pi: ExtensionAPI) => Promise<void>;
package/dist/extension.js CHANGED
@@ -28,6 +28,13 @@ export function paymentStatus(p) {
28
28
  : "";
29
29
  return `xgate paid ${p.chargedAtomic ?? "?"}/${p.authorizedAtomic} ${p.asset} on ${p.network} (${p.scheme})${left}`;
30
30
  }
31
+ /** What to do about a purchase that ended without a verified result. */
32
+ export function failureStatus(f) {
33
+ const state = f.pending ? "unresolved" : "failed";
34
+ return f.retryAfterSeconds === undefined
35
+ ? `xgate ${state}: ${f.invocationId}`
36
+ : `xgate rate limited (${state}): ${f.pending ? `/xgate reconcile ${f.invocationId}, then ` : ""}wait ${f.retryAfterSeconds}s before a new request`;
37
+ }
31
38
  /** Pi extension factory. The default export is the standalone wallet-broker extension. */
32
39
  export function createXgateExtension(options = {}) {
33
40
  return (pi) => register(pi, options);
@@ -49,7 +56,7 @@ async function register(pi, options) {
49
56
  onPaid: (r) => context?.ui.setStatus("xgate", r.payment
50
57
  ? paymentStatus(r.payment)
51
58
  : `xgate paid ${(Number(r.settlement.chargedAtomic) / 1e6).toFixed(6)}`),
52
- onPurchaseFailed: (f) => context?.ui.setStatus("xgate", `xgate ${f.pending ? "unresolved" : "failed"}: ${f.invocationId}`),
59
+ onPurchaseFailed: (f) => context?.ui.setStatus("xgate", failureStatus(f)),
53
60
  });
54
61
  pi.registerProvider(provider);
55
62
  broker = next;
@@ -843,5 +843,28 @@
843
843
  "gpt-6.1-sol"
844
844
  ],
845
845
  "chat_tools": "unsupported"
846
+ },
847
+ "gpt-6.1-terra": {
848
+ "canonical": "gpt-6.1-terra",
849
+ "price_label": "gpt-6.1-terra",
850
+ "operations": [
851
+ "chat",
852
+ "responses"
853
+ ],
854
+ "reasoning_efforts": [
855
+ "none",
856
+ "low",
857
+ "medium",
858
+ "high",
859
+ "xhigh",
860
+ "max"
861
+ ],
862
+ "cache": "write_priced",
863
+ "tools": true,
864
+ "chat_legacy_max_tokens": false,
865
+ "response_models": [
866
+ "gpt-6.1-terra"
867
+ ],
868
+ "chat_tools": "unsupported"
846
869
  }
847
870
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { createXgateProvider, invocationId, type Broker, type XgateProviderOptions, type XgatePurchaseFailure, } from "./provider.ts";
2
2
  export { XgatePurchaseError, type XgateChannelBalance, type XgatePayment, type XgatePurchaseResult, type XgateResponseHead, type XgateTransport, } from "./transport.ts";
3
3
  export { createXgateExtension, type XgateExtensionOptions, } from "./extension.ts";
4
- export { XgateError } from "@lucid-agents/xgate-client/types";
4
+ export { XgateError, XgateRateLimitedError, } from "@lucid-agents/xgate-client/types";
5
5
  export type { Invocation, PurchaseResult, Route, Settlement, } from "@lucid-agents/xgate-client/types";
6
6
  export { createTaskMarketTransport, type TaskMarketChargeStatus, type TaskMarketFundingBalance, type TaskMarketInvocationState, type TaskMarketInvocationStatus, type TaskMarketPayCallOptions, type TaskMarketPayErrorCode, type TaskMarketPayer, type TaskMarketPayerLimits, type TaskMarketPayRequest, type TaskMarketPayResponse, type TaskMarketPayResult, type TaskMarketPaymentSummary, type TaskMarketPaymentTerms, type TaskMarketTransportOptions, } from "./taskmarket.ts";
package/dist/index.js CHANGED
@@ -3,5 +3,5 @@
3
3
  export { createXgateProvider, invocationId, } from "./provider.js";
4
4
  export { XgatePurchaseError, } from "./transport.js";
5
5
  export { createXgateExtension, } from "./extension.js";
6
- export { XgateError } from "@lucid-agents/xgate-client/types";
6
+ export { XgateError, XgateRateLimitedError, } from "@lucid-agents/xgate-client/types";
7
7
  export { createTaskMarketTransport, } from "./taskmarket.js";
@@ -18,6 +18,11 @@ export interface XgatePurchaseFailure {
18
18
  /** False only when the transport proved no unresolved payment. */
19
19
  pending: boolean;
20
20
  payment?: XgatePayment;
21
+ /**
22
+ * Set when the payee refused the purchase with a rate limit: its suggested wait, in
23
+ * seconds, before a new purchase. Reconciling this invocation still comes first.
24
+ */
25
+ retryAfterSeconds?: number;
21
26
  }
22
27
  /** Stable across automatic retries and payload hooks; a changed paid payload cannot claim a new intent. */
23
28
  export declare function invocationId(model: string, context: PiContext, session: string): string;
package/dist/provider.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { createAssistantMessageEventStream, } from "@earendil-works/pi-ai";
3
- import { XgateError } from "@lucid-agents/xgate-client/types";
3
+ import { XgateError, XgateRateLimitedError, } from "@lucid-agents/xgate-client/types";
4
4
  import { catalogEntries, encode, decode } from "./codec.js";
5
5
  import { XgatePurchaseError, } from "./transport.js";
6
6
  const hash = (value) => createHash("sha256").update(value).digest("hex");
@@ -188,12 +188,18 @@ export async function createXgateProvider(transport, { sessionId: identity = ()
188
188
  // After a verified result the charge is known even if Pi discards the content.
189
189
  const payment = paid?.payment ??
190
190
  (error instanceof XgatePurchaseError ? error.payment : undefined);
191
+ const wait = paid === undefined &&
192
+ (error instanceof XgateRateLimitedError ||
193
+ error instanceof XgatePurchaseError)
194
+ ? error.retryAfterSeconds
195
+ : undefined;
191
196
  const failure = {
192
197
  invocationId: attempted,
193
198
  code: error instanceof XgateError ? error.code : "purchase_failed",
194
199
  pending: paid === undefined &&
195
200
  !(error instanceof XgatePurchaseError && !error.pending),
196
201
  ...(payment ? { payment } : {}),
202
+ ...(wait !== undefined ? { retryAfterSeconds: wait } : {}),
197
203
  };
198
204
  // The original ID is the only reconciliation key. "Billing" also keeps Pi's transient
199
205
  // error classifier from retrying a message whose hex ID happens to contain "500".
@@ -47,7 +47,9 @@ export declare class XgatePurchaseError extends XgateError {
47
47
  readonly invocationId: string;
48
48
  readonly pending: boolean;
49
49
  readonly payment?: XgatePayment;
50
- constructor(code: string, invocationId: string, pending: boolean, payment?: XgatePayment);
50
+ /** The payee's rate-limit hint, in seconds, when that is why the purchase ended. */
51
+ readonly retryAfterSeconds?: number;
52
+ constructor(code: string, invocationId: string, pending: boolean, payment?: XgatePayment, retryAfterSeconds?: number);
51
53
  }
52
54
  /**
53
55
  * Moves one frozen Pi request to xgate and returns the verified result. The provider owns
package/dist/transport.js CHANGED
@@ -7,15 +7,24 @@ export class XgatePurchaseError extends XgateError {
7
7
  invocationId;
8
8
  pending;
9
9
  payment;
10
- constructor(code, invocationId, pending, payment) {
10
+ /** The payee's rate-limit hint, in seconds, when that is why the purchase ended. */
11
+ retryAfterSeconds;
12
+ constructor(code, invocationId, pending, payment, retryAfterSeconds) {
11
13
  super(code);
12
14
  this.name = "XgatePurchaseError";
13
- this.message = pending
14
- ? `Xgate: ${code}. The payment outcome is unknown; do not retry. Reconcile it before another purchase.`
15
- : `Xgate: ${code}. Inspect the payment status before another purchase.`;
15
+ const limited = retryAfterSeconds === undefined
16
+ ? ""
17
+ : ` The payee rate limited it and suggests waiting ${retryAfterSeconds} seconds before a new purchase.`;
18
+ this.message =
19
+ (pending
20
+ ? `Xgate: ${code}. The payment outcome is unknown; do not retry. Reconcile it before another purchase.`
21
+ : `Xgate: ${code}. Inspect the payment status before another purchase.`) +
22
+ limited;
16
23
  this.invocationId = invocationId;
17
24
  this.pending = pending;
18
25
  if (payment)
19
26
  this.payment = payment;
27
+ if (retryAfterSeconds !== undefined)
28
+ this.retryAfterSeconds = retryAfterSeconds;
20
29
  }
21
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lucid-agents/pi-xgate",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -32,11 +32,11 @@
32
32
  "LICENSE"
33
33
  ],
34
34
  "dependencies": {
35
- "@lucid-agents/xgate-client": "0.1.8"
35
+ "@lucid-agents/xgate-client": "0.1.10"
36
36
  },
37
37
  "peerDependencies": {
38
- "@earendil-works/pi-ai": "0.85.1 || 1.1.0",
39
- "@earendil-works/pi-coding-agent": "0.85.1 || 1.1.0"
38
+ "@earendil-works/pi-ai": "0.85.1 || 1.1.0 || 1.5.2",
39
+ "@earendil-works/pi-coding-agent": "0.85.1 || 1.1.0 || 1.5.2"
40
40
  },
41
41
  "engines": {
42
42
  "node": ">=24.19"
@@ -56,10 +56,10 @@
56
56
  "registry": "https://registry.npmjs.org"
57
57
  },
58
58
  "license": "MIT",
59
- "gitHead": "d410140f8e57fd3fc4309a1c54772b8a9f99ccd9",
59
+ "gitHead": "4b2e77819216a41c265171bdaae9291f9b129037",
60
60
  "xgateRelease": {
61
- "version": "v0.1.8",
62
- "sourceSha": "d410140f8e57fd3fc4309a1c54772b8a9f99ccd9",
63
- "candidateId": "rc-d410140f8e57-42908627975-1"
61
+ "version": "v0.1.10",
62
+ "sourceSha": "4b2e77819216a41c265171bdaae9291f9b129037",
63
+ "candidateId": "rc-4b2e77819216-43169339829-1"
64
64
  }
65
65
  }
package/src/extension.ts CHANGED
@@ -7,7 +7,7 @@ import { XgateError } from "@lucid-agents/xgate-client/types";
7
7
  import { readFileSync, lstatSync, mkdirSync, writeFileSync } from "node:fs";
8
8
  import { dirname, isAbsolute, join } from "node:path";
9
9
  import { homedir } from "node:os";
10
- import { createXgateProvider } from "./provider.ts";
10
+ import { createXgateProvider, type XgatePurchaseFailure } from "./provider.ts";
11
11
  import type { XgatePayment, XgateTransport } from "./transport.ts";
12
12
  export interface XgateExtensionOptions {
13
13
  /**
@@ -42,6 +42,13 @@ export function paymentStatus(p: XgatePayment): string {
42
42
  : "";
43
43
  return `xgate paid ${p.chargedAtomic ?? "?"}/${p.authorizedAtomic} ${p.asset} on ${p.network} (${p.scheme})${left}`;
44
44
  }
45
+ /** What to do about a purchase that ended without a verified result. */
46
+ export function failureStatus(f: XgatePurchaseFailure): string {
47
+ const state = f.pending ? "unresolved" : "failed";
48
+ return f.retryAfterSeconds === undefined
49
+ ? `xgate ${state}: ${f.invocationId}`
50
+ : `xgate rate limited (${state}): ${f.pending ? `/xgate reconcile ${f.invocationId}, then ` : ""}wait ${f.retryAfterSeconds}s before a new request`;
51
+ }
45
52
  /** Pi extension factory. The default export is the standalone wallet-broker extension. */
46
53
  export function createXgateExtension(
47
54
  options: XgateExtensionOptions = {},
@@ -74,11 +81,7 @@ async function register(
74
81
  ? paymentStatus(r.payment)
75
82
  : `xgate paid ${(Number(r.settlement.chargedAtomic) / 1e6).toFixed(6)}`,
76
83
  ),
77
- onPurchaseFailed: (f) =>
78
- context?.ui.setStatus(
79
- "xgate",
80
- `xgate ${f.pending ? "unresolved" : "failed"}: ${f.invocationId}`,
81
- ),
84
+ onPurchaseFailed: (f) => context?.ui.setStatus("xgate", failureStatus(f)),
82
85
  });
83
86
  pi.registerProvider(provider);
84
87
  broker = next;
@@ -843,5 +843,28 @@
843
843
  "gpt-6.1-sol"
844
844
  ],
845
845
  "chat_tools": "unsupported"
846
+ },
847
+ "gpt-6.1-terra": {
848
+ "canonical": "gpt-6.1-terra",
849
+ "price_label": "gpt-6.1-terra",
850
+ "operations": [
851
+ "chat",
852
+ "responses"
853
+ ],
854
+ "reasoning_efforts": [
855
+ "none",
856
+ "low",
857
+ "medium",
858
+ "high",
859
+ "xhigh",
860
+ "max"
861
+ ],
862
+ "cache": "write_priced",
863
+ "tools": true,
864
+ "chat_legacy_max_tokens": false,
865
+ "response_models": [
866
+ "gpt-6.1-terra"
867
+ ],
868
+ "chat_tools": "unsupported"
846
869
  }
847
870
  }
package/src/index.ts CHANGED
@@ -19,7 +19,10 @@ export {
19
19
  createXgateExtension,
20
20
  type XgateExtensionOptions,
21
21
  } from "./extension.ts";
22
- export { XgateError } from "@lucid-agents/xgate-client/types";
22
+ export {
23
+ XgateError,
24
+ XgateRateLimitedError,
25
+ } from "@lucid-agents/xgate-client/types";
23
26
  export type {
24
27
  Invocation,
25
28
  PurchaseResult,
package/src/provider.ts CHANGED
@@ -6,7 +6,10 @@ import {
6
6
  type Provider,
7
7
  type SimpleStreamOptions,
8
8
  } from "@earendil-works/pi-ai";
9
- import { XgateError } from "@lucid-agents/xgate-client/types";
9
+ import {
10
+ XgateError,
11
+ XgateRateLimitedError,
12
+ } from "@lucid-agents/xgate-client/types";
10
13
  import { catalogEntries, encode, decode, type PiContext } from "./codec.ts";
11
14
  import {
12
15
  XgatePurchaseError,
@@ -33,6 +36,11 @@ export interface XgatePurchaseFailure {
33
36
  /** False only when the transport proved no unresolved payment. */
34
37
  pending: boolean;
35
38
  payment?: XgatePayment;
39
+ /**
40
+ * Set when the payee refused the purchase with a rate limit: its suggested wait, in
41
+ * seconds, before a new purchase. Reconciling this invocation still comes first.
42
+ */
43
+ retryAfterSeconds?: number;
36
44
  }
37
45
  const zero = () => ({
38
46
  input: 0,
@@ -259,6 +267,12 @@ export async function createXgateProvider(
259
267
  const payment =
260
268
  paid?.payment ??
261
269
  (error instanceof XgatePurchaseError ? error.payment : undefined);
270
+ const wait =
271
+ paid === undefined &&
272
+ (error instanceof XgateRateLimitedError ||
273
+ error instanceof XgatePurchaseError)
274
+ ? error.retryAfterSeconds
275
+ : undefined;
262
276
  const failure: XgatePurchaseFailure = {
263
277
  invocationId: attempted,
264
278
  code: error instanceof XgateError ? error.code : "purchase_failed",
@@ -266,6 +280,7 @@ export async function createXgateProvider(
266
280
  paid === undefined &&
267
281
  !(error instanceof XgatePurchaseError && !error.pending),
268
282
  ...(payment ? { payment } : {}),
283
+ ...(wait !== undefined ? { retryAfterSeconds: wait } : {}),
269
284
  };
270
285
  // The original ID is the only reconciliation key. "Billing" also keeps Pi's transient
271
286
  // error classifier from retrying a message whose hex ID happens to contain "500".
package/src/transport.ts CHANGED
@@ -51,20 +51,31 @@ export class XgatePurchaseError extends XgateError {
51
51
  readonly invocationId: string;
52
52
  readonly pending: boolean;
53
53
  readonly payment?: XgatePayment;
54
+ /** The payee's rate-limit hint, in seconds, when that is why the purchase ended. */
55
+ readonly retryAfterSeconds?: number;
54
56
  constructor(
55
57
  code: string,
56
58
  invocationId: string,
57
59
  pending: boolean,
58
60
  payment?: XgatePayment,
61
+ retryAfterSeconds?: number,
59
62
  ) {
60
63
  super(code);
61
64
  this.name = "XgatePurchaseError";
62
- this.message = pending
63
- ? `Xgate: ${code}. The payment outcome is unknown; do not retry. Reconcile it before another purchase.`
64
- : `Xgate: ${code}. Inspect the payment status before another purchase.`;
65
+ const limited =
66
+ retryAfterSeconds === undefined
67
+ ? ""
68
+ : ` The payee rate limited it and suggests waiting ${retryAfterSeconds} seconds before a new purchase.`;
69
+ this.message =
70
+ (pending
71
+ ? `Xgate: ${code}. The payment outcome is unknown; do not retry. Reconcile it before another purchase.`
72
+ : `Xgate: ${code}. Inspect the payment status before another purchase.`) +
73
+ limited;
65
74
  this.invocationId = invocationId;
66
75
  this.pending = pending;
67
76
  if (payment) this.payment = payment;
77
+ if (retryAfterSeconds !== undefined)
78
+ this.retryAfterSeconds = retryAfterSeconds;
68
79
  }
69
80
  }
70
81
  /**