@goassay-ai/sdk 0.0.0-bootstrap.0 → 0.1.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.
- package/README.md +250 -2
- package/dist/client.d.ts +88 -0
- package/dist/client.js +158 -0
- package/dist/core/binding.d.ts +56 -0
- package/dist/core/binding.js +132 -0
- package/dist/core/caip.d.ts +15 -0
- package/dist/core/caip.js +31 -0
- package/dist/core/chain.d.ts +37 -0
- package/dist/core/chain.js +148 -0
- package/dist/core/clock.d.ts +12 -0
- package/dist/core/clock.js +25 -0
- package/dist/core/errors.d.ts +9 -0
- package/dist/core/errors.js +13 -0
- package/dist/core/guard-types.d.ts +174 -0
- package/dist/core/guard-types.js +69 -0
- package/dist/core/guards.d.ts +54 -0
- package/dist/core/guards.js +408 -0
- package/dist/core/hex.d.ts +14 -0
- package/dist/core/hex.js +42 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/core/index.js +16 -0
- package/dist/core/meta.d.ts +38 -0
- package/dist/core/meta.js +79 -0
- package/dist/core/voucher.d.ts +116 -0
- package/dist/core/voucher.js +137 -0
- package/dist/errors.d.ts +149 -0
- package/dist/errors.js +205 -0
- package/dist/http.d.ts +52 -0
- package/dist/http.js +139 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +4 -0
- package/dist/mcp-url.d.ts +17 -0
- package/dist/mcp-url.js +98 -0
- package/dist/mcp.d.ts +23 -0
- package/dist/mcp.js +121 -0
- package/dist/networks.d.ts +5 -0
- package/dist/networks.js +46 -0
- package/dist/resources/context.d.ts +48 -0
- package/dist/resources/context.js +28 -0
- package/dist/resources/paid-calls.d.ts +82 -0
- package/dist/resources/paid-calls.js +156 -0
- package/dist/resources/receipts.d.ts +25 -0
- package/dist/resources/receipts.js +21 -0
- package/dist/resources/tools.d.ts +96 -0
- package/dist/resources/tools.js +241 -0
- package/dist/rest.d.ts +17 -0
- package/dist/rest.js +43 -0
- package/dist/schemes/eip155/bind.d.ts +17 -0
- package/dist/schemes/eip155/bind.js +59 -0
- package/dist/schemes/eip155/index.d.ts +71 -0
- package/dist/schemes/eip155/index.js +264 -0
- package/dist/schemes/types.d.ts +54 -0
- package/dist/schemes/types.js +1 -0
- package/dist/signer.d.ts +39 -0
- package/dist/signer.js +29 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +62 -4
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `_meta` keys Assay reads and writes on MCP messages (spec section 3), and pure helpers.
|
|
3
|
+
*/
|
|
4
|
+
export const IDEMPOTENCY_META = "assay/idempotency-key";
|
|
5
|
+
export const RAIL_META = "assay/rail";
|
|
6
|
+
export const VOUCHER_META = "assay/voucher";
|
|
7
|
+
export const PAYMENT_REQUIRED_META = "x402/paymentRequired";
|
|
8
|
+
export const ERROR_META = "assay/error";
|
|
9
|
+
export const RECEIPT_META = "assay/receipt";
|
|
10
|
+
export const REPLAYED_META = "assay/replayed";
|
|
11
|
+
export const PRICE_META = "assay/price";
|
|
12
|
+
export const PAYMENT_REQUIRED_CODE = "PAYMENT_REQUIRED";
|
|
13
|
+
export const DELEGATE_INACTIVE_CODE = "PAYMENT_VOUCHER_DELEGATE_INACTIVE";
|
|
14
|
+
export const IN_PROGRESS_CODE = "IDEMPOTENCY_IN_PROGRESS";
|
|
15
|
+
/** The meta tools every Assay MCP URL serves. */
|
|
16
|
+
export const SEARCH_TOOLS = "search_tools";
|
|
17
|
+
export const GET_TOOL = "get_tool";
|
|
18
|
+
export const CALL_TOOL = "call_tool";
|
|
19
|
+
const IDEMPOTENCY_KEY = /^[\x20-\x7e]{1,128}$/;
|
|
20
|
+
const UINT = /^(0|[1-9][0-9]*)$/;
|
|
21
|
+
const TOOL_NAME = /^[A-Za-z0-9_-]{1,128}$/;
|
|
22
|
+
export function isObject(v) {
|
|
23
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
24
|
+
}
|
|
25
|
+
/** 1 to 128 printable ASCII characters (spec section 8). */
|
|
26
|
+
export function isIdempotencyKey(value) {
|
|
27
|
+
return typeof value === "string" && IDEMPOTENCY_KEY.test(value);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A tool name as sent to the server, unchanged: a standard `verb_noun`, or a provider tool's
|
|
31
|
+
* `{handle}__{tool}` (for example `acme__get_quote`). The server decides what it serves.
|
|
32
|
+
*/
|
|
33
|
+
export function isToolName(value) {
|
|
34
|
+
return typeof value === "string" && TOOL_NAME.test(value);
|
|
35
|
+
}
|
|
36
|
+
export function quoteOf(price) {
|
|
37
|
+
if (!isObject(price))
|
|
38
|
+
return null;
|
|
39
|
+
const { maxAmount, asset } = price;
|
|
40
|
+
if (typeof maxAmount !== "string" || !UINT.test(maxAmount) || maxAmount.length > 30)
|
|
41
|
+
return null;
|
|
42
|
+
if (typeof asset !== "string" || asset.length === 0 || asset.length > 200)
|
|
43
|
+
return null;
|
|
44
|
+
return { maxAmount, asset };
|
|
45
|
+
}
|
|
46
|
+
/** The structured output of a meta tool result (`structuredContent`, else the JSON text block). */
|
|
47
|
+
export function structuredOf(result) {
|
|
48
|
+
if (!isObject(result) || result.isError === true)
|
|
49
|
+
return null;
|
|
50
|
+
if (isObject(result.structuredContent))
|
|
51
|
+
return result.structuredContent;
|
|
52
|
+
const first = Array.isArray(result.content) ? result.content[0] : undefined;
|
|
53
|
+
if (isObject(first) && first.type === "text" && typeof first.text === "string" && first.text.length <= 1_000_000) {
|
|
54
|
+
try {
|
|
55
|
+
const parsed = JSON.parse(first.text);
|
|
56
|
+
return isObject(parsed) ? parsed : null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/** The voucher envelope of the signed retry (spec section 6.4). */
|
|
65
|
+
export function voucherEnvelope(voucher, signature, providerId, contractVersion) {
|
|
66
|
+
return {
|
|
67
|
+
voucher,
|
|
68
|
+
signature,
|
|
69
|
+
providerId,
|
|
70
|
+
...(typeof contractVersion === "number" && Number.isSafeInteger(contractVersion) && contractVersion >= 1
|
|
71
|
+
? { contractVersion }
|
|
72
|
+
: {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** The payment key as people compare it: the last 8 hex digits, upper case, in two groups. */
|
|
76
|
+
export function fingerprint(address) {
|
|
77
|
+
const tail = address.slice(-8).toUpperCase();
|
|
78
|
+
return `${tail.slice(0, 4)} ${tail.slice(4)}`;
|
|
79
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { type Address, type Hex } from "./hex.js";
|
|
2
|
+
/** The escrow's EIP-712 domain name and version (spec section 6.1). */
|
|
3
|
+
export declare const VOUCHER_DOMAIN_NAME = "Assay Settlement";
|
|
4
|
+
export declare const VOUCHER_DOMAIN_VERSION = "2";
|
|
5
|
+
/** The lifetime a client asks for: well inside the server's 300 s ceiling. */
|
|
6
|
+
export declare const VOUCHER_LIFETIME_SECONDS = 240;
|
|
7
|
+
/** The eight signed fields. The order is the type hash. */
|
|
8
|
+
export declare const VOUCHER_TYPES: {
|
|
9
|
+
readonly Voucher: readonly [{
|
|
10
|
+
readonly name: "payer";
|
|
11
|
+
readonly type: "address";
|
|
12
|
+
}, {
|
|
13
|
+
readonly name: "provider";
|
|
14
|
+
readonly type: "address";
|
|
15
|
+
}, {
|
|
16
|
+
readonly name: "amount";
|
|
17
|
+
readonly type: "uint64";
|
|
18
|
+
}, {
|
|
19
|
+
readonly name: "resourceHash";
|
|
20
|
+
readonly type: "bytes32";
|
|
21
|
+
}, {
|
|
22
|
+
readonly name: "requestHash";
|
|
23
|
+
readonly type: "bytes32";
|
|
24
|
+
}, {
|
|
25
|
+
readonly name: "seq";
|
|
26
|
+
readonly type: "uint64";
|
|
27
|
+
}, {
|
|
28
|
+
readonly name: "issuedAt";
|
|
29
|
+
readonly type: "uint64";
|
|
30
|
+
}, {
|
|
31
|
+
readonly name: "expiresAt";
|
|
32
|
+
readonly type: "uint64";
|
|
33
|
+
}];
|
|
34
|
+
};
|
|
35
|
+
export interface Money {
|
|
36
|
+
amount: string;
|
|
37
|
+
asset: string;
|
|
38
|
+
decimals: number;
|
|
39
|
+
}
|
|
40
|
+
/** `_meta["x402/paymentRequired"]` on rail B (spec section 4). */
|
|
41
|
+
export interface VoucherRequirement {
|
|
42
|
+
rail: "B";
|
|
43
|
+
scheme: "voucher";
|
|
44
|
+
network: string;
|
|
45
|
+
payer: string;
|
|
46
|
+
payTo: string;
|
|
47
|
+
provider: string;
|
|
48
|
+
providerId: string;
|
|
49
|
+
amount: Money;
|
|
50
|
+
contractVersion?: number;
|
|
51
|
+
resource: string;
|
|
52
|
+
requestHash: string;
|
|
53
|
+
seq: string;
|
|
54
|
+
maxTimeoutSeconds: number;
|
|
55
|
+
signers?: string[];
|
|
56
|
+
delegate?: string | null;
|
|
57
|
+
}
|
|
58
|
+
/** The voucher as it goes on the wire in `_meta["assay/voucher"].voucher` (spec section 6.3). */
|
|
59
|
+
export interface WireVoucher {
|
|
60
|
+
payer: string;
|
|
61
|
+
payTo: string;
|
|
62
|
+
provider: string;
|
|
63
|
+
amount: Money;
|
|
64
|
+
resource: string;
|
|
65
|
+
requestHash: string;
|
|
66
|
+
seq: string;
|
|
67
|
+
issuedAt: number;
|
|
68
|
+
expiresAt: number;
|
|
69
|
+
}
|
|
70
|
+
export interface VoucherTypedData {
|
|
71
|
+
domain: {
|
|
72
|
+
name: string;
|
|
73
|
+
version: string;
|
|
74
|
+
chainId: number;
|
|
75
|
+
verifyingContract: Address;
|
|
76
|
+
};
|
|
77
|
+
types: typeof VOUCHER_TYPES;
|
|
78
|
+
primaryType: "Voucher";
|
|
79
|
+
message: {
|
|
80
|
+
payer: Address;
|
|
81
|
+
provider: Address;
|
|
82
|
+
amount: bigint;
|
|
83
|
+
resourceHash: Hex;
|
|
84
|
+
requestHash: Hex;
|
|
85
|
+
seq: bigint;
|
|
86
|
+
issuedAt: bigint;
|
|
87
|
+
expiresAt: bigint;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** A network entry of the client's own built-in table. Never taken from the server. */
|
|
91
|
+
export interface NetworkEntry {
|
|
92
|
+
/** CAIP-2, `eip155:<chainId>`. */
|
|
93
|
+
caip2: string;
|
|
94
|
+
/** CAIP-10 escrow account. */
|
|
95
|
+
escrow: string;
|
|
96
|
+
/** CAIP-19 erc20 asset. */
|
|
97
|
+
asset: string;
|
|
98
|
+
decimals: number;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Builds the wire voucher and the EIP-712 typed data the payment key signs (spec section 6),
|
|
102
|
+
* from a rail-B requirement, a corrected `issuedAt` and the client's own network entry.
|
|
103
|
+
*
|
|
104
|
+
* - `resourceHash = keccak256(utf8(resource))`; `requestHash` is the requirement's 32 bytes
|
|
105
|
+
* without `sha256:`;
|
|
106
|
+
* - `expiresAt = issuedAt + min(240, maxTimeoutSeconds)`;
|
|
107
|
+
* - the domain is `{name: "Assay Settlement", version: "2", chainId, verifyingContract}`, from
|
|
108
|
+
* `net` only.
|
|
109
|
+
*
|
|
110
|
+
* Throws `SignerRefusal` for a requirement on another network, a `payTo` other than the escrow,
|
|
111
|
+
* an amount in another asset or scale, and anything that cannot be encoded.
|
|
112
|
+
*/
|
|
113
|
+
export declare function buildVoucher(requirement: VoucherRequirement, issuedAtSeconds: number, net: NetworkEntry): {
|
|
114
|
+
wire: WireVoucher;
|
|
115
|
+
typedData: VoucherTypedData;
|
|
116
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { parseCaip10, parseCaip19Erc20 } from "./caip.js";
|
|
2
|
+
import { getAddress, keccakUtf8 } from "./hex.js";
|
|
3
|
+
import { SignerRefusal } from "./errors.js";
|
|
4
|
+
/** The escrow's EIP-712 domain name and version (spec section 6.1). */
|
|
5
|
+
export const VOUCHER_DOMAIN_NAME = "Assay Settlement";
|
|
6
|
+
export const VOUCHER_DOMAIN_VERSION = "2";
|
|
7
|
+
/** The lifetime a client asks for: well inside the server's 300 s ceiling. */
|
|
8
|
+
export const VOUCHER_LIFETIME_SECONDS = 240;
|
|
9
|
+
/** The eight signed fields. The order is the type hash. */
|
|
10
|
+
export const VOUCHER_TYPES = {
|
|
11
|
+
Voucher: [
|
|
12
|
+
{ name: "payer", type: "address" },
|
|
13
|
+
{ name: "provider", type: "address" },
|
|
14
|
+
{ name: "amount", type: "uint64" },
|
|
15
|
+
{ name: "resourceHash", type: "bytes32" },
|
|
16
|
+
{ name: "requestHash", type: "bytes32" },
|
|
17
|
+
{ name: "seq", type: "uint64" },
|
|
18
|
+
{ name: "issuedAt", type: "uint64" },
|
|
19
|
+
{ name: "expiresAt", type: "uint64" },
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
const MAX_UINT64 = 2n ** 64n - 1n;
|
|
23
|
+
const CAIP2 = /^eip155:([1-9][0-9]*)$/;
|
|
24
|
+
function refuse(field, reason) {
|
|
25
|
+
throw new SignerRefusal("the voucher", field, reason);
|
|
26
|
+
}
|
|
27
|
+
function uint64(field, value) {
|
|
28
|
+
const ok = (typeof value === "string" && /^(0|[1-9][0-9]*)$/.test(value)) ||
|
|
29
|
+
(typeof value === "number" && Number.isSafeInteger(value) && value >= 0);
|
|
30
|
+
if (!ok)
|
|
31
|
+
refuse(field, "is not a non-negative integer");
|
|
32
|
+
const n = BigInt(value);
|
|
33
|
+
if (n > MAX_UINT64)
|
|
34
|
+
refuse(field, "does not fit uint64");
|
|
35
|
+
return n;
|
|
36
|
+
}
|
|
37
|
+
function account(field, value, chainId) {
|
|
38
|
+
const parsed = typeof value === "string" ? parseCaip10(value) : null;
|
|
39
|
+
if (!parsed)
|
|
40
|
+
refuse(field, "is not a checksummed CAIP-10 account");
|
|
41
|
+
if (parsed.chainId !== chainId)
|
|
42
|
+
refuse(field, "is not on the requirement's network");
|
|
43
|
+
return getAddress(parsed.address);
|
|
44
|
+
}
|
|
45
|
+
/** `sha256:<64 hex>` or bare 64 hex, as 32 bytes. */
|
|
46
|
+
function requestHashBytes(value) {
|
|
47
|
+
const hex = typeof value === "string" ? value.replace(/^sha256:/, "") : "";
|
|
48
|
+
if (!/^[0-9a-fA-F]{64}$/.test(hex))
|
|
49
|
+
refuse("requestHash", "is not 32 bytes of hex");
|
|
50
|
+
return `0x${hex.toLowerCase()}`;
|
|
51
|
+
}
|
|
52
|
+
function resolve(net) {
|
|
53
|
+
const chain = typeof net?.caip2 === "string" ? CAIP2.exec(net.caip2) : null;
|
|
54
|
+
if (!chain)
|
|
55
|
+
refuse("network", "is not an eip155 CAIP-2 chain id");
|
|
56
|
+
const chainId = Number(chain[1]);
|
|
57
|
+
const escrow = typeof net.escrow === "string" ? parseCaip10(net.escrow) : null;
|
|
58
|
+
if (!escrow || escrow.chainId !== chainId)
|
|
59
|
+
refuse("network.escrow", "is not a CAIP-10 account on the network's chain");
|
|
60
|
+
const asset = typeof net.asset === "string" ? parseCaip19Erc20(net.asset) : null;
|
|
61
|
+
if (!asset || asset.chainId !== chainId)
|
|
62
|
+
refuse("network.asset", "is not a CAIP-19 erc20 asset on the network's chain");
|
|
63
|
+
if (!Number.isInteger(net.decimals) || net.decimals < 0 || net.decimals > 36)
|
|
64
|
+
refuse("network.decimals", "is invalid");
|
|
65
|
+
return { chainId, escrow: getAddress(escrow.address) };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Builds the wire voucher and the EIP-712 typed data the payment key signs (spec section 6),
|
|
69
|
+
* from a rail-B requirement, a corrected `issuedAt` and the client's own network entry.
|
|
70
|
+
*
|
|
71
|
+
* - `resourceHash = keccak256(utf8(resource))`; `requestHash` is the requirement's 32 bytes
|
|
72
|
+
* without `sha256:`;
|
|
73
|
+
* - `expiresAt = issuedAt + min(240, maxTimeoutSeconds)`;
|
|
74
|
+
* - the domain is `{name: "Assay Settlement", version: "2", chainId, verifyingContract}`, from
|
|
75
|
+
* `net` only.
|
|
76
|
+
*
|
|
77
|
+
* Throws `SignerRefusal` for a requirement on another network, a `payTo` other than the escrow,
|
|
78
|
+
* an amount in another asset or scale, and anything that cannot be encoded.
|
|
79
|
+
*/
|
|
80
|
+
export function buildVoucher(requirement, issuedAtSeconds, net) {
|
|
81
|
+
const { chainId, escrow: verifyingContract } = resolve(net);
|
|
82
|
+
if (requirement.network !== net.caip2)
|
|
83
|
+
refuse("network", "is not the client's network");
|
|
84
|
+
const payTo = account("payTo", requirement.payTo, chainId);
|
|
85
|
+
if (payTo !== verifyingContract)
|
|
86
|
+
refuse("payTo", "is not the built-in escrow");
|
|
87
|
+
const payer = account("payer", requirement.payer, chainId);
|
|
88
|
+
const provider = account("provider", requirement.provider, chainId);
|
|
89
|
+
const money = requirement.amount;
|
|
90
|
+
if (typeof money !== "object" || money === null)
|
|
91
|
+
refuse("amount", "is missing");
|
|
92
|
+
const amount = uint64("amount", requirement.amount.amount);
|
|
93
|
+
if (requirement.amount.asset !== net.asset)
|
|
94
|
+
refuse("amount.asset", "is not the built-in asset");
|
|
95
|
+
if (requirement.amount.decimals !== net.decimals)
|
|
96
|
+
refuse("amount.decimals", "are not the built-in decimals");
|
|
97
|
+
const seq = uint64("seq", requirement.seq);
|
|
98
|
+
if (typeof requirement.resource !== "string" || requirement.resource.length === 0)
|
|
99
|
+
refuse("resource", "is empty");
|
|
100
|
+
const requestHash = requestHashBytes(requirement.requestHash);
|
|
101
|
+
const maxTimeout = requirement.maxTimeoutSeconds;
|
|
102
|
+
if (!Number.isSafeInteger(maxTimeout) || maxTimeout < 1)
|
|
103
|
+
refuse("maxTimeoutSeconds", "is not a positive integer");
|
|
104
|
+
if (!Number.isSafeInteger(issuedAtSeconds) || issuedAtSeconds <= 0)
|
|
105
|
+
refuse("issuedAt", "is not a positive integer");
|
|
106
|
+
const issuedAt = issuedAtSeconds;
|
|
107
|
+
const expiresAt = issuedAt + Math.min(VOUCHER_LIFETIME_SECONDS, maxTimeout);
|
|
108
|
+
// Built fresh: nothing the server sent beyond the checked fields reaches the wire.
|
|
109
|
+
const wire = {
|
|
110
|
+
payer: requirement.payer,
|
|
111
|
+
payTo: requirement.payTo,
|
|
112
|
+
provider: requirement.provider,
|
|
113
|
+
amount: { amount: amount.toString(), asset: net.asset, decimals: net.decimals },
|
|
114
|
+
resource: requirement.resource,
|
|
115
|
+
requestHash: requirement.requestHash,
|
|
116
|
+
seq: seq.toString(),
|
|
117
|
+
issuedAt,
|
|
118
|
+
expiresAt,
|
|
119
|
+
};
|
|
120
|
+
const typedData = {
|
|
121
|
+
domain: { name: VOUCHER_DOMAIN_NAME, version: VOUCHER_DOMAIN_VERSION, chainId, verifyingContract },
|
|
122
|
+
types: VOUCHER_TYPES,
|
|
123
|
+
primaryType: "Voucher",
|
|
124
|
+
message: {
|
|
125
|
+
payer,
|
|
126
|
+
provider,
|
|
127
|
+
amount,
|
|
128
|
+
// The UTF-8 bytes: a resource starting with `0x` is still a string.
|
|
129
|
+
resourceHash: keccakUtf8(requirement.resource),
|
|
130
|
+
requestHash,
|
|
131
|
+
seq,
|
|
132
|
+
issuedAt: BigInt(issuedAt),
|
|
133
|
+
expiresAt: BigInt(expiresAt),
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
return { wire, typedData };
|
|
137
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SDK's errors. Every error the SDK raises is an {@link AssayError} with a stable `code`;
|
|
3
|
+
* branch on the class or on `code`, never on the message. A server code this release does not
|
|
4
|
+
* know still arrives, as the nearest class (or `AssayError` itself).
|
|
5
|
+
*
|
|
6
|
+
* ```text
|
|
7
|
+
* AssayError
|
|
8
|
+
* ├─ ConfigError local setup (missing API key, bad option)
|
|
9
|
+
* ├─ AuthError 401, or an AUTH_* refusal
|
|
10
|
+
* │ ├─ ScopeMissingError 403 AUTH_SCOPE_MISSING (.requiredScope)
|
|
11
|
+
* │ └─ HumanSessionRequiredError 403 AUTH_HUMAN_SESSION_REQUIRED: only a signed-in person may do this
|
|
12
|
+
* ├─ McpUrlNotFoundError 404 MCP_URL_NOT_FOUND: the MCP host serves no such URL for this key
|
|
13
|
+
* ├─ DelegateAlreadyBoundError 409 ACCOUNT_DELEGATE_ALREADY_BOUND: the key is bound to another API key
|
|
14
|
+
* ├─ DelegateBindConflictError 409 ACCOUNT_DELEGATE_BIND_CONFLICT: another key won a first bind; retry once
|
|
15
|
+
* ├─ PaymentError a PAYMENT_* refusal from the server
|
|
16
|
+
* │ ├─ PaymentRequiredError the tool is paid and no signer was given (.requirement)
|
|
17
|
+
* │ ├─ PaymentSchemeUnsupportedError no signer for the requirement's network (.network)
|
|
18
|
+
* │ └─ PaymentRefusedError refused locally before signing (.guard); nothing signed
|
|
19
|
+
* │ └─ PaymentNetworkUnknownError the network is not in the SDK's network table (.network)
|
|
20
|
+
* ├─ RateLimitError 429, RATE_LIMITED, quota or velocity limits (.retryAfterMs)
|
|
21
|
+
* ├─ NetworkError no answer: connection failure
|
|
22
|
+
* │ └─ TimeoutError
|
|
23
|
+
* └─ ServerError 5xx or an answer the SDK cannot read
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export interface AssayErrorInit {
|
|
27
|
+
retryable?: boolean;
|
|
28
|
+
status?: number;
|
|
29
|
+
fixUrl?: string;
|
|
30
|
+
details?: Record<string, unknown>;
|
|
31
|
+
cause?: unknown;
|
|
32
|
+
}
|
|
33
|
+
export declare class AssayError extends Error {
|
|
34
|
+
name: string;
|
|
35
|
+
/** A stable code, from the server (`assay/error.code`, REST `error.code`) or the SDK. */
|
|
36
|
+
readonly code: string;
|
|
37
|
+
/** Whether the same call can succeed later. */
|
|
38
|
+
readonly retryable: boolean;
|
|
39
|
+
/** The HTTP status, when the error came with one. */
|
|
40
|
+
readonly status: number | undefined;
|
|
41
|
+
/** A page that fixes the cause, when the server named one. */
|
|
42
|
+
readonly fixUrl: string | undefined;
|
|
43
|
+
/** Extra structured data the server sent (never a key or a signature). */
|
|
44
|
+
readonly details: Record<string, unknown> | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* On an error out of `tools.call`: the idempotency key of that logical call. Call again with
|
|
47
|
+
* `{ idempotencyKey: err.idempotencyKey }` to retry safely: Assay replays a served call instead
|
|
48
|
+
* of charging it twice.
|
|
49
|
+
*/
|
|
50
|
+
idempotencyKey: string | undefined;
|
|
51
|
+
constructor(code: string, message: string, init?: AssayErrorInit);
|
|
52
|
+
}
|
|
53
|
+
export declare class ConfigError extends AssayError {
|
|
54
|
+
name: string;
|
|
55
|
+
constructor(message: string);
|
|
56
|
+
}
|
|
57
|
+
export declare class AuthError extends AssayError {
|
|
58
|
+
name: string;
|
|
59
|
+
}
|
|
60
|
+
export declare class ScopeMissingError extends AuthError {
|
|
61
|
+
name: string;
|
|
62
|
+
/** The scope the API key lacks, for example `payments`, when the server named it. */
|
|
63
|
+
readonly requiredScope: string | undefined;
|
|
64
|
+
constructor(message: string, init?: AssayErrorInit & {
|
|
65
|
+
requiredScope?: string;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export declare class HumanSessionRequiredError extends AuthError {
|
|
69
|
+
name: string;
|
|
70
|
+
constructor(message: string, init?: AssayErrorInit);
|
|
71
|
+
}
|
|
72
|
+
export declare const MCP_URL_NOT_FOUND = "MCP_URL_NOT_FOUND";
|
|
73
|
+
/**
|
|
74
|
+
* The MCP host serves no such URL for this API key (`MCP_URL_NOT_FOUND`, or any 404 from it): the
|
|
75
|
+
* tool set, version, topic or maker does not exist, is not shared with you, or no longer serves.
|
|
76
|
+
* Not retried.
|
|
77
|
+
*/
|
|
78
|
+
export declare class McpUrlNotFoundError extends AssayError {
|
|
79
|
+
name: string;
|
|
80
|
+
constructor(message?: string, init?: AssayErrorInit);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The payment key is already bound (or waiting for approval) on another live API key; nothing was
|
|
84
|
+
* bound. Use a key of this agent's own, or revoke the other API key first.
|
|
85
|
+
*/
|
|
86
|
+
export declare class DelegateAlreadyBoundError extends AssayError {
|
|
87
|
+
name: string;
|
|
88
|
+
constructor(message: string, init?: AssayErrorInit);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Another payment key was bound to this API key at the same moment (two first binds raced);
|
|
92
|
+
* nothing was bound. Retryable once: a second `bind()` asks for a new challenge and parks this key
|
|
93
|
+
* as `pendingDelegate` (`bindStatus: "PENDING_APPROVAL"`) until a person approves it.
|
|
94
|
+
*/
|
|
95
|
+
export declare class DelegateBindConflictError extends AssayError {
|
|
96
|
+
name: string;
|
|
97
|
+
constructor(message: string, init?: AssayErrorInit);
|
|
98
|
+
}
|
|
99
|
+
export declare class PaymentError extends AssayError {
|
|
100
|
+
name: string;
|
|
101
|
+
}
|
|
102
|
+
export declare class PaymentRequiredError extends PaymentError {
|
|
103
|
+
name: string;
|
|
104
|
+
/** `_meta["x402/paymentRequired"]` as the server sent it. Untrusted. */
|
|
105
|
+
readonly requirement: unknown;
|
|
106
|
+
constructor(message: string, requirement: unknown, init?: AssayErrorInit);
|
|
107
|
+
}
|
|
108
|
+
export declare class PaymentSchemeUnsupportedError extends PaymentError {
|
|
109
|
+
name: string;
|
|
110
|
+
/** The CAIP-2 network the requirement is for. */
|
|
111
|
+
readonly network: string;
|
|
112
|
+
constructor(network: string);
|
|
113
|
+
}
|
|
114
|
+
export declare class PaymentRefusedError extends PaymentError {
|
|
115
|
+
name: string;
|
|
116
|
+
/** The local guard that refused (`G1`..`G12`), or `onPayment` for the caller's hook. */
|
|
117
|
+
readonly guard: string;
|
|
118
|
+
constructor(guard: string, code: string, reason: string, init?: AssayErrorInit);
|
|
119
|
+
}
|
|
120
|
+
export declare class PaymentNetworkUnknownError extends PaymentRefusedError {
|
|
121
|
+
name: string;
|
|
122
|
+
/** The CAIP-2 network asked for. */
|
|
123
|
+
readonly network: string;
|
|
124
|
+
constructor(network: string, message?: string);
|
|
125
|
+
}
|
|
126
|
+
export declare class RateLimitError extends AssayError {
|
|
127
|
+
name: string;
|
|
128
|
+
/** How long to wait before trying again, when the server said. */
|
|
129
|
+
readonly retryAfterMs: number | undefined;
|
|
130
|
+
constructor(code: string, message: string, init?: AssayErrorInit & {
|
|
131
|
+
retryAfterMs?: number;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
export declare class NetworkError extends AssayError {
|
|
135
|
+
name: string;
|
|
136
|
+
constructor(message: string, cause?: unknown, code?: string);
|
|
137
|
+
}
|
|
138
|
+
export declare class TimeoutError extends NetworkError {
|
|
139
|
+
name: string;
|
|
140
|
+
constructor(message?: string, cause?: unknown);
|
|
141
|
+
}
|
|
142
|
+
export declare class ServerError extends AssayError {
|
|
143
|
+
name: string;
|
|
144
|
+
}
|
|
145
|
+
export interface ServerErrorFields extends AssayErrorInit {
|
|
146
|
+
retryAfterMs?: number;
|
|
147
|
+
}
|
|
148
|
+
/** The typed error for a server refusal, chosen by code first, then by HTTP status. */
|
|
149
|
+
export declare function errorFromServer(code: string | undefined, message: string, f?: ServerErrorFields): AssayError;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SDK's errors. Every error the SDK raises is an {@link AssayError} with a stable `code`;
|
|
3
|
+
* branch on the class or on `code`, never on the message. A server code this release does not
|
|
4
|
+
* know still arrives, as the nearest class (or `AssayError` itself).
|
|
5
|
+
*
|
|
6
|
+
* ```text
|
|
7
|
+
* AssayError
|
|
8
|
+
* ├─ ConfigError local setup (missing API key, bad option)
|
|
9
|
+
* ├─ AuthError 401, or an AUTH_* refusal
|
|
10
|
+
* │ ├─ ScopeMissingError 403 AUTH_SCOPE_MISSING (.requiredScope)
|
|
11
|
+
* │ └─ HumanSessionRequiredError 403 AUTH_HUMAN_SESSION_REQUIRED: only a signed-in person may do this
|
|
12
|
+
* ├─ McpUrlNotFoundError 404 MCP_URL_NOT_FOUND: the MCP host serves no such URL for this key
|
|
13
|
+
* ├─ DelegateAlreadyBoundError 409 ACCOUNT_DELEGATE_ALREADY_BOUND: the key is bound to another API key
|
|
14
|
+
* ├─ DelegateBindConflictError 409 ACCOUNT_DELEGATE_BIND_CONFLICT: another key won a first bind; retry once
|
|
15
|
+
* ├─ PaymentError a PAYMENT_* refusal from the server
|
|
16
|
+
* │ ├─ PaymentRequiredError the tool is paid and no signer was given (.requirement)
|
|
17
|
+
* │ ├─ PaymentSchemeUnsupportedError no signer for the requirement's network (.network)
|
|
18
|
+
* │ └─ PaymentRefusedError refused locally before signing (.guard); nothing signed
|
|
19
|
+
* │ └─ PaymentNetworkUnknownError the network is not in the SDK's network table (.network)
|
|
20
|
+
* ├─ RateLimitError 429, RATE_LIMITED, quota or velocity limits (.retryAfterMs)
|
|
21
|
+
* ├─ NetworkError no answer: connection failure
|
|
22
|
+
* │ └─ TimeoutError
|
|
23
|
+
* └─ ServerError 5xx or an answer the SDK cannot read
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export class AssayError extends Error {
|
|
27
|
+
name = "AssayError";
|
|
28
|
+
/** A stable code, from the server (`assay/error.code`, REST `error.code`) or the SDK. */
|
|
29
|
+
code;
|
|
30
|
+
/** Whether the same call can succeed later. */
|
|
31
|
+
retryable;
|
|
32
|
+
/** The HTTP status, when the error came with one. */
|
|
33
|
+
status;
|
|
34
|
+
/** A page that fixes the cause, when the server named one. */
|
|
35
|
+
fixUrl;
|
|
36
|
+
/** Extra structured data the server sent (never a key or a signature). */
|
|
37
|
+
details;
|
|
38
|
+
/**
|
|
39
|
+
* On an error out of `tools.call`: the idempotency key of that logical call. Call again with
|
|
40
|
+
* `{ idempotencyKey: err.idempotencyKey }` to retry safely: Assay replays a served call instead
|
|
41
|
+
* of charging it twice.
|
|
42
|
+
*/
|
|
43
|
+
idempotencyKey;
|
|
44
|
+
constructor(code, message, init = {}) {
|
|
45
|
+
super(message, init.cause === undefined ? undefined : { cause: init.cause });
|
|
46
|
+
this.code = code;
|
|
47
|
+
this.retryable = init.retryable ?? false;
|
|
48
|
+
this.status = init.status;
|
|
49
|
+
this.fixUrl = init.fixUrl;
|
|
50
|
+
this.details = init.details;
|
|
51
|
+
this.idempotencyKey = undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export class ConfigError extends AssayError {
|
|
55
|
+
name = "ConfigError";
|
|
56
|
+
constructor(message) {
|
|
57
|
+
super("CONFIG_INVALID", message);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export class AuthError extends AssayError {
|
|
61
|
+
name = "AuthError";
|
|
62
|
+
}
|
|
63
|
+
export class ScopeMissingError extends AuthError {
|
|
64
|
+
name = "ScopeMissingError";
|
|
65
|
+
/** The scope the API key lacks, for example `payments`, when the server named it. */
|
|
66
|
+
requiredScope;
|
|
67
|
+
constructor(message, init = {}) {
|
|
68
|
+
super("AUTH_SCOPE_MISSING", message, { ...init, status: init.status ?? 403 });
|
|
69
|
+
this.requiredScope = init.requiredScope;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export class HumanSessionRequiredError extends AuthError {
|
|
73
|
+
name = "HumanSessionRequiredError";
|
|
74
|
+
constructor(message, init = {}) {
|
|
75
|
+
super("AUTH_HUMAN_SESSION_REQUIRED", message, { ...init, status: init.status ?? 403 });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export const MCP_URL_NOT_FOUND = "MCP_URL_NOT_FOUND";
|
|
79
|
+
/**
|
|
80
|
+
* The MCP host serves no such URL for this API key (`MCP_URL_NOT_FOUND`, or any 404 from it): the
|
|
81
|
+
* tool set, version, topic or maker does not exist, is not shared with you, or no longer serves.
|
|
82
|
+
* Not retried.
|
|
83
|
+
*/
|
|
84
|
+
export class McpUrlNotFoundError extends AssayError {
|
|
85
|
+
name = "McpUrlNotFoundError";
|
|
86
|
+
constructor(message = "the MCP host serves no such URL for this API key", init = {}) {
|
|
87
|
+
super(MCP_URL_NOT_FOUND, message, { ...init, status: init.status ?? 404 });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The payment key is already bound (or waiting for approval) on another live API key; nothing was
|
|
92
|
+
* bound. Use a key of this agent's own, or revoke the other API key first.
|
|
93
|
+
*/
|
|
94
|
+
export class DelegateAlreadyBoundError extends AssayError {
|
|
95
|
+
name = "DelegateAlreadyBoundError";
|
|
96
|
+
constructor(message, init = {}) {
|
|
97
|
+
super("ACCOUNT_DELEGATE_ALREADY_BOUND", message, { ...init, status: init.status ?? 409 });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Another payment key was bound to this API key at the same moment (two first binds raced);
|
|
102
|
+
* nothing was bound. Retryable once: a second `bind()` asks for a new challenge and parks this key
|
|
103
|
+
* as `pendingDelegate` (`bindStatus: "PENDING_APPROVAL"`) until a person approves it.
|
|
104
|
+
*/
|
|
105
|
+
export class DelegateBindConflictError extends AssayError {
|
|
106
|
+
name = "DelegateBindConflictError";
|
|
107
|
+
constructor(message, init = {}) {
|
|
108
|
+
super("ACCOUNT_DELEGATE_BIND_CONFLICT", message, { ...init, retryable: true, status: init.status ?? 409 });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export class PaymentError extends AssayError {
|
|
112
|
+
name = "PaymentError";
|
|
113
|
+
}
|
|
114
|
+
export class PaymentRequiredError extends PaymentError {
|
|
115
|
+
name = "PaymentRequiredError";
|
|
116
|
+
/** `_meta["x402/paymentRequired"]` as the server sent it. Untrusted. */
|
|
117
|
+
requirement;
|
|
118
|
+
constructor(message, requirement, init = {}) {
|
|
119
|
+
super("PAYMENT_REQUIRED", message, init);
|
|
120
|
+
this.requirement = requirement;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export class PaymentSchemeUnsupportedError extends PaymentError {
|
|
124
|
+
name = "PaymentSchemeUnsupportedError";
|
|
125
|
+
/** The CAIP-2 network the requirement is for. */
|
|
126
|
+
network;
|
|
127
|
+
constructor(network) {
|
|
128
|
+
super("PAYMENT_SCHEME_UNSUPPORTED", `no signer for network ${network}; pass a signer for this network`);
|
|
129
|
+
this.network = network;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export class PaymentRefusedError extends PaymentError {
|
|
133
|
+
name = "PaymentRefusedError";
|
|
134
|
+
/** The local guard that refused (`G1`..`G12`), or `onPayment` for the caller's hook. */
|
|
135
|
+
guard;
|
|
136
|
+
constructor(guard, code, reason, init = {}) {
|
|
137
|
+
super(code, `payment refused before signing: ${reason}`, init);
|
|
138
|
+
this.guard = guard;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export class PaymentNetworkUnknownError extends PaymentRefusedError {
|
|
142
|
+
name = "PaymentNetworkUnknownError";
|
|
143
|
+
/** The CAIP-2 network asked for. */
|
|
144
|
+
network;
|
|
145
|
+
constructor(network, message) {
|
|
146
|
+
super("G2", "G2_NETWORK", message ??
|
|
147
|
+
`the network ${network} is not in this SDK's network table (pass networks for another environment or chain)`);
|
|
148
|
+
this.network = network;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export class RateLimitError extends AssayError {
|
|
152
|
+
name = "RateLimitError";
|
|
153
|
+
/** How long to wait before trying again, when the server said. */
|
|
154
|
+
retryAfterMs;
|
|
155
|
+
constructor(code, message, init = {}) {
|
|
156
|
+
super(code, message, { retryable: true, ...init });
|
|
157
|
+
this.retryAfterMs = init.retryAfterMs;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export class NetworkError extends AssayError {
|
|
161
|
+
name = "NetworkError";
|
|
162
|
+
constructor(message, cause, code = "NETWORK_ERROR") {
|
|
163
|
+
super(code, message, { retryable: true, cause });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
export class TimeoutError extends NetworkError {
|
|
167
|
+
name = "TimeoutError";
|
|
168
|
+
constructor(message = "the request timed out", cause) {
|
|
169
|
+
super(message, cause, "TIMEOUT");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
export class ServerError extends AssayError {
|
|
173
|
+
name = "ServerError";
|
|
174
|
+
}
|
|
175
|
+
const RATE_LIMIT_CODES = new Set([
|
|
176
|
+
"RATE_LIMITED",
|
|
177
|
+
"TOO_MANY_REQUESTS",
|
|
178
|
+
"MCP_QUOTA_EXCEEDED",
|
|
179
|
+
"MCP_VELOCITY_LIMIT_EXCEEDED",
|
|
180
|
+
]);
|
|
181
|
+
/** The typed error for a server refusal, chosen by code first, then by HTTP status. */
|
|
182
|
+
export function errorFromServer(code, message, f = {}) {
|
|
183
|
+
const c = code ?? (f.status !== undefined ? `HTTP_${f.status}` : "UNKNOWN");
|
|
184
|
+
if (c === "AUTH_SCOPE_MISSING") {
|
|
185
|
+
const scope = f.details?.requiredScope;
|
|
186
|
+
return new ScopeMissingError(message, { ...f, ...(typeof scope === "string" ? { requiredScope: scope } : {}) });
|
|
187
|
+
}
|
|
188
|
+
if (c === MCP_URL_NOT_FOUND)
|
|
189
|
+
return new McpUrlNotFoundError(message, f);
|
|
190
|
+
if (c === "AUTH_HUMAN_SESSION_REQUIRED")
|
|
191
|
+
return new HumanSessionRequiredError(message, f);
|
|
192
|
+
if (c === "ACCOUNT_DELEGATE_ALREADY_BOUND")
|
|
193
|
+
return new DelegateAlreadyBoundError(message, f);
|
|
194
|
+
if (c === "ACCOUNT_DELEGATE_BIND_CONFLICT")
|
|
195
|
+
return new DelegateBindConflictError(message, f);
|
|
196
|
+
if (RATE_LIMIT_CODES.has(c) || f.status === 429)
|
|
197
|
+
return new RateLimitError(c, message, f);
|
|
198
|
+
if (c.startsWith("AUTH_") || f.status === 401)
|
|
199
|
+
return new AuthError(c, message, f);
|
|
200
|
+
if (c.startsWith("PAYMENT_"))
|
|
201
|
+
return new PaymentError(c, message, f);
|
|
202
|
+
if (f.status !== undefined && f.status >= 500)
|
|
203
|
+
return new ServerError(c, message, { retryable: true, ...f });
|
|
204
|
+
return new AssayError(c, message, f);
|
|
205
|
+
}
|