@belticlabs/agent-risk-sdk 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/dist/ai/index.d.ts +19 -0
- package/dist/ai/index.js +120 -0
- package/dist/chunk-5TWO73OD.js +35 -0
- package/dist/chunk-7G5EHNVW.js +17 -0
- package/dist/chunk-FQDHFTVR.js +29 -0
- package/dist/chunk-GCKCAKHA.js +401 -0
- package/dist/chunk-SFGM7KOG.js +312 -0
- package/dist/chunk-U5Z5Z2BQ.js +86 -0
- package/dist/chunk-VM7MK43J.js +13 -0
- package/dist/chunk-YVMJ5CZX.js +95 -0
- package/dist/client-CVx9LgJC.d.ts +63 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +499 -0
- package/dist/mcp/index.d.ts +59 -0
- package/dist/mcp/index.js +127 -0
- package/dist/middleware-_DSwvNIx.d.ts +40 -0
- package/dist/seller/anti-fraud-gateway.d.ts +32 -0
- package/dist/seller/anti-fraud-gateway.js +39 -0
- package/dist/session-BMNB1N1g.d.ts +184 -0
- package/dist/x402/express.d.ts +14 -0
- package/dist/x402/express.js +26 -0
- package/dist/x402/hono.d.ts +14 -0
- package/dist/x402/hono.js +20 -0
- package/dist/x402/index.d.ts +54 -0
- package/dist/x402/index.js +67 -0
- package/package.json +106 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
// ../canon/src/uuidv7.ts
|
|
2
|
+
import { randomBytes } from "crypto";
|
|
3
|
+
function uuidv7(now = Date.now()) {
|
|
4
|
+
const b = randomBytes(16);
|
|
5
|
+
b.writeUIntBE(now, 0, 6);
|
|
6
|
+
b[6] = b[6] & 15 | 112;
|
|
7
|
+
b[8] = b[8] & 63 | 128;
|
|
8
|
+
const h = b.toString("hex");
|
|
9
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// ../canon/src/base58.ts
|
|
13
|
+
var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
14
|
+
var MAP = new Map([...ALPHABET].map((c, i) => [c, i]));
|
|
15
|
+
function base58Encode(bytes) {
|
|
16
|
+
let zeros = 0;
|
|
17
|
+
while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
|
|
18
|
+
const digits = [];
|
|
19
|
+
for (let i = zeros; i < bytes.length; i++) {
|
|
20
|
+
let carry = bytes[i];
|
|
21
|
+
for (let j = 0; j < digits.length; j++) {
|
|
22
|
+
carry += digits[j] << 8;
|
|
23
|
+
digits[j] = carry % 58;
|
|
24
|
+
carry = carry / 58 | 0;
|
|
25
|
+
}
|
|
26
|
+
while (carry > 0) {
|
|
27
|
+
digits.push(carry % 58);
|
|
28
|
+
carry = carry / 58 | 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return "1".repeat(zeros) + digits.reverse().map((d) => ALPHABET[d]).join("");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ../canon/src/encoding.ts
|
|
35
|
+
function toHex(buf) {
|
|
36
|
+
return Buffer.from(buf).toString("hex");
|
|
37
|
+
}
|
|
38
|
+
function fromHex(hex) {
|
|
39
|
+
if (!/^[0-9a-f]*$/.test(hex) || hex.length % 2 !== 0) {
|
|
40
|
+
throw new Error(`invalid lowercase hex string: ${hex.slice(0, 32)}\u2026`);
|
|
41
|
+
}
|
|
42
|
+
return Buffer.from(hex, "hex");
|
|
43
|
+
}
|
|
44
|
+
function toB64Url(buf) {
|
|
45
|
+
return Buffer.from(buf).toString("base64url");
|
|
46
|
+
}
|
|
47
|
+
function fromB64Url(s) {
|
|
48
|
+
if (!/^[A-Za-z0-9_-]*$/.test(s)) throw new Error("invalid base64url string");
|
|
49
|
+
return Buffer.from(s, "base64url");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ../canon/src/hash.ts
|
|
53
|
+
import { createHash } from "crypto";
|
|
54
|
+
var LEAF_PREFIX = Buffer.from([0]);
|
|
55
|
+
var NODE_PREFIX = Buffer.from([1]);
|
|
56
|
+
function sha256(...parts) {
|
|
57
|
+
const h = createHash("sha256");
|
|
58
|
+
for (const p of parts) h.update(p);
|
|
59
|
+
return h.digest();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ../canon/src/jcs.ts
|
|
63
|
+
function canonicalize(value) {
|
|
64
|
+
if (value === null) return "null";
|
|
65
|
+
const t = typeof value;
|
|
66
|
+
if (t === "boolean") return value ? "true" : "false";
|
|
67
|
+
if (t === "number") {
|
|
68
|
+
const n = value;
|
|
69
|
+
if (!Number.isFinite(n)) throw new Error("JCS: non-finite number");
|
|
70
|
+
return JSON.stringify(n);
|
|
71
|
+
}
|
|
72
|
+
if (t === "string") return JSON.stringify(value);
|
|
73
|
+
if (Array.isArray(value)) {
|
|
74
|
+
const items = value.map((v) => v === void 0 ? "null" : canonicalize(v));
|
|
75
|
+
return `[${items.join(",")}]`;
|
|
76
|
+
}
|
|
77
|
+
if (t === "object") {
|
|
78
|
+
const obj = value;
|
|
79
|
+
const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
|
|
80
|
+
const parts = keys.map((k) => `${JSON.stringify(k)}:${canonicalize(obj[k])}`);
|
|
81
|
+
return `{${parts.join(",")}}`;
|
|
82
|
+
}
|
|
83
|
+
throw new Error(`JCS: unsupported type ${t}`);
|
|
84
|
+
}
|
|
85
|
+
function canonicalBytes(value) {
|
|
86
|
+
return Buffer.from(canonicalize(value), "utf8");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ../canon/src/digest.ts
|
|
90
|
+
function payloadDigest(payload) {
|
|
91
|
+
return toHex(sha256(canonicalBytes(payload)));
|
|
92
|
+
}
|
|
93
|
+
function digestedEnvelope(env) {
|
|
94
|
+
return {
|
|
95
|
+
sessionId: env.sessionId,
|
|
96
|
+
source: env.source,
|
|
97
|
+
seq: env.seq,
|
|
98
|
+
ts: env.ts,
|
|
99
|
+
kind: env.kind,
|
|
100
|
+
payload: env.payloadDigest,
|
|
101
|
+
prevHash: env.prevHash
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function eventDigest(env) {
|
|
105
|
+
return toHex(sha256(canonicalBytes(digestedEnvelope(env))));
|
|
106
|
+
}
|
|
107
|
+
function genesisHash(sessionId, source) {
|
|
108
|
+
return toHex(sha256(canonicalBytes({ sessionId, source })));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ../canon/src/ed25519.ts
|
|
112
|
+
import {
|
|
113
|
+
createPrivateKey,
|
|
114
|
+
createPublicKey,
|
|
115
|
+
sign as cryptoSign,
|
|
116
|
+
verify as cryptoVerify,
|
|
117
|
+
generateKeyPairSync
|
|
118
|
+
} from "crypto";
|
|
119
|
+
var PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
|
|
120
|
+
var SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
|
|
121
|
+
function generateKeyPair(seed) {
|
|
122
|
+
if (seed) {
|
|
123
|
+
if (seed.length !== 32) throw new Error("ed25519 seed must be 32 bytes");
|
|
124
|
+
return { publicKey: publicFromPrivate(privKeyObject(seed)), privateKey: Buffer.from(seed) };
|
|
125
|
+
}
|
|
126
|
+
const { privateKey } = generateKeyPairSync("ed25519");
|
|
127
|
+
const der = privateKey.export({ format: "der", type: "pkcs8" });
|
|
128
|
+
const rawSeed = der.subarray(der.length - 32);
|
|
129
|
+
return { publicKey: publicFromPrivate(privateKey), privateKey: Buffer.from(rawSeed) };
|
|
130
|
+
}
|
|
131
|
+
function publicFromPrivate(priv) {
|
|
132
|
+
const pub = createPublicKey(priv);
|
|
133
|
+
const der = pub.export({ format: "der", type: "spki" });
|
|
134
|
+
return Buffer.from(der.subarray(der.length - 32));
|
|
135
|
+
}
|
|
136
|
+
function privKeyObject(seed) {
|
|
137
|
+
return createPrivateKey({
|
|
138
|
+
key: Buffer.concat([PKCS8_PREFIX, Buffer.from(seed)]),
|
|
139
|
+
format: "der",
|
|
140
|
+
type: "pkcs8"
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function pubKeyObject(raw) {
|
|
144
|
+
if (raw.length !== 32) throw new Error("ed25519 public key must be 32 bytes");
|
|
145
|
+
return createPublicKey({
|
|
146
|
+
key: Buffer.concat([SPKI_PREFIX, Buffer.from(raw)]),
|
|
147
|
+
format: "der",
|
|
148
|
+
type: "spki"
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function sign(message, privateSeed) {
|
|
152
|
+
return cryptoSign(null, Buffer.from(message), privKeyObject(privateSeed));
|
|
153
|
+
}
|
|
154
|
+
function verify(message, signature, publicKey) {
|
|
155
|
+
try {
|
|
156
|
+
return cryptoVerify(
|
|
157
|
+
null,
|
|
158
|
+
Buffer.from(message),
|
|
159
|
+
pubKeyObject(publicKey),
|
|
160
|
+
Buffer.from(signature)
|
|
161
|
+
);
|
|
162
|
+
} catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function derivePublicKey(privateSeed) {
|
|
167
|
+
return publicFromPrivate(privKeyObject(privateSeed));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ../canon/src/signer.ts
|
|
171
|
+
var SIG_PREFIX = "ed25519:";
|
|
172
|
+
function encodeSig(raw) {
|
|
173
|
+
if (raw.length !== 64) throw new Error("ed25519 signature must be 64 bytes");
|
|
174
|
+
return `${SIG_PREFIX}${toB64Url(raw)}`;
|
|
175
|
+
}
|
|
176
|
+
function decodeSig(sig) {
|
|
177
|
+
if (!sig.startsWith(SIG_PREFIX)) throw new Error("unsupported signature algorithm");
|
|
178
|
+
const raw = fromB64Url(sig.slice(SIG_PREFIX.length));
|
|
179
|
+
if (raw.length !== 64) throw new Error("ed25519 signature must be 64 bytes");
|
|
180
|
+
return raw;
|
|
181
|
+
}
|
|
182
|
+
function verifySig(message, sig, publicKey) {
|
|
183
|
+
try {
|
|
184
|
+
return verify(message, decodeSig(sig), publicKey);
|
|
185
|
+
} catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function memorySigner(seed, keyId) {
|
|
190
|
+
const kp = seed ? { publicKey: derivePublicKey(seed), privateKey: Buffer.from(seed) } : generateKeyPair();
|
|
191
|
+
return {
|
|
192
|
+
seed: kp.privateKey,
|
|
193
|
+
publicKey: kp.publicKey,
|
|
194
|
+
keyId: keyId ?? `ed25519:${toB64Url(kp.publicKey)}`,
|
|
195
|
+
async sign(message) {
|
|
196
|
+
return sign(message, kp.privateKey);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ../canon/src/chain.ts
|
|
202
|
+
function toEnvelope(ev) {
|
|
203
|
+
const env = {
|
|
204
|
+
sessionId: ev.sessionId,
|
|
205
|
+
source: ev.source,
|
|
206
|
+
seq: ev.seq,
|
|
207
|
+
ts: ev.ts,
|
|
208
|
+
kind: ev.kind,
|
|
209
|
+
payloadDigest: "payloadDigest" in ev ? ev.payloadDigest : payloadDigest(ev.payload),
|
|
210
|
+
prevHash: ev.prevHash
|
|
211
|
+
};
|
|
212
|
+
if (ev.sig !== void 0) env.sig = ev.sig;
|
|
213
|
+
return env;
|
|
214
|
+
}
|
|
215
|
+
var Chain = class _Chain {
|
|
216
|
+
constructor(sessionId, source, head) {
|
|
217
|
+
this.sessionId = sessionId;
|
|
218
|
+
this.source = source;
|
|
219
|
+
this.head = head;
|
|
220
|
+
}
|
|
221
|
+
static genesis(sessionId, source) {
|
|
222
|
+
return new _Chain(sessionId, source, null);
|
|
223
|
+
}
|
|
224
|
+
static at(sessionId, source, head) {
|
|
225
|
+
return new _Chain(sessionId, source, head);
|
|
226
|
+
}
|
|
227
|
+
get nextSeq() {
|
|
228
|
+
return this.head ? this.head.seq + 1 : 0;
|
|
229
|
+
}
|
|
230
|
+
/** `prevHash` the next event must carry (GAP-01). */
|
|
231
|
+
get nextPrevHash() {
|
|
232
|
+
return this.head ? this.head.digest : genesisHash(this.sessionId, this.source);
|
|
233
|
+
}
|
|
234
|
+
advance(head) {
|
|
235
|
+
return new _Chain(this.sessionId, this.source, head);
|
|
236
|
+
}
|
|
237
|
+
async append(input, signer) {
|
|
238
|
+
const seq = input.seq ?? this.nextSeq;
|
|
239
|
+
if (seq !== this.nextSeq) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`chain ${this.sessionId}:${this.source} expects seq ${this.nextSeq}, got ${seq}`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
const event = {
|
|
245
|
+
sessionId: this.sessionId,
|
|
246
|
+
source: this.source,
|
|
247
|
+
seq,
|
|
248
|
+
ts: input.ts,
|
|
249
|
+
kind: input.kind,
|
|
250
|
+
payload: input.payload,
|
|
251
|
+
prevHash: this.nextPrevHash
|
|
252
|
+
};
|
|
253
|
+
const envelope = toEnvelope(event);
|
|
254
|
+
const digest = eventDigest(envelope);
|
|
255
|
+
if (signer) {
|
|
256
|
+
const sig = encodeSig(await signer.sign(fromHex(digest)));
|
|
257
|
+
event.sig = sig;
|
|
258
|
+
envelope.sig = sig;
|
|
259
|
+
}
|
|
260
|
+
return { event, envelope, digest, chain: this.advance({ seq, digest }) };
|
|
261
|
+
}
|
|
262
|
+
/** Streaming verification of one link against this head. */
|
|
263
|
+
verify(ev, opts) {
|
|
264
|
+
const envelope = toEnvelope(ev);
|
|
265
|
+
const digest = eventDigest(envelope);
|
|
266
|
+
if (envelope.seq !== this.nextSeq) return { ok: false, code: "SEQ_GAP", digest, envelope };
|
|
267
|
+
if (envelope.prevHash !== this.nextPrevHash) {
|
|
268
|
+
return { ok: false, code: "PREV_HASH_MISMATCH", digest, envelope };
|
|
269
|
+
}
|
|
270
|
+
if (opts.requireSig && !envelope.sig)
|
|
271
|
+
return { ok: false, code: "SIG_MISSING", digest, envelope };
|
|
272
|
+
if (envelope.sig && (opts.requireSig || opts.publicKey)) {
|
|
273
|
+
if (!opts.publicKey || !verifySig(fromHex(digest), envelope.sig, opts.publicKey)) {
|
|
274
|
+
return { ok: false, code: "SIG_INVALID", digest, envelope };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return { ok: true, digest, envelope };
|
|
278
|
+
}
|
|
279
|
+
/** Verify a run of events starting from this head; the result names the first broken link. */
|
|
280
|
+
verifyAll(events, opts) {
|
|
281
|
+
let chain = this;
|
|
282
|
+
const digests = [];
|
|
283
|
+
for (const ev of events) {
|
|
284
|
+
if (ev.sessionId !== this.sessionId || ev.source !== this.source) {
|
|
285
|
+
return { ok: false, at: ev.seq, code: "PREV_HASH_MISMATCH", head: chain.head, digests };
|
|
286
|
+
}
|
|
287
|
+
const r = chain.verify(ev, opts);
|
|
288
|
+
if (!r.ok) return { ok: false, at: ev.seq, code: r.code, head: chain.head, digests };
|
|
289
|
+
chain = chain.advance({ seq: ev.seq, digest: r.digest });
|
|
290
|
+
digests.push(r.digest);
|
|
291
|
+
}
|
|
292
|
+
return { ok: true, head: chain.head, digests };
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// ../canon/src/did-key.ts
|
|
297
|
+
var ED25519_PUB_MULTICODEC = Buffer.from([237, 1]);
|
|
298
|
+
var PREFIX = "did:key:z";
|
|
299
|
+
function didKeyFromEd25519(publicKey) {
|
|
300
|
+
if (publicKey.length !== 32) throw new Error("ed25519 public key must be 32 bytes");
|
|
301
|
+
return PREFIX + base58Encode(Buffer.concat([ED25519_PUB_MULTICODEC, Buffer.from(publicKey)]));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export {
|
|
305
|
+
toHex,
|
|
306
|
+
fromHex,
|
|
307
|
+
payloadDigest,
|
|
308
|
+
memorySigner,
|
|
309
|
+
Chain,
|
|
310
|
+
didKeyFromEd25519,
|
|
311
|
+
uuidv7
|
|
312
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
toJsonObject
|
|
3
|
+
} from "./chunk-FQDHFTVR.js";
|
|
4
|
+
|
|
5
|
+
// src/x402/moments.ts
|
|
6
|
+
function x402Currency(network, asset) {
|
|
7
|
+
return `${network}/${asset}`;
|
|
8
|
+
}
|
|
9
|
+
function fromAccepts(a, artifact, raw) {
|
|
10
|
+
return {
|
|
11
|
+
protocol: "x402",
|
|
12
|
+
payee: a?.payTo ?? "unknown",
|
|
13
|
+
amount: {
|
|
14
|
+
value: a?.amount ?? "0",
|
|
15
|
+
currency: a ? x402Currency(a.network ?? "unknown", a.asset ?? "unknown") : "unknown"
|
|
16
|
+
},
|
|
17
|
+
artifact,
|
|
18
|
+
raw
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function payerOf(payload) {
|
|
22
|
+
const p = payload.payload;
|
|
23
|
+
const auth = p.authorization;
|
|
24
|
+
const candidates = [
|
|
25
|
+
auth?.from,
|
|
26
|
+
p.from,
|
|
27
|
+
p.payer,
|
|
28
|
+
p.signer,
|
|
29
|
+
p.permit?.owner
|
|
30
|
+
];
|
|
31
|
+
const hit = candidates.find((c) => typeof c === "string" && c.length > 0);
|
|
32
|
+
return typeof hit === "string" ? hit.toLowerCase() : void 0;
|
|
33
|
+
}
|
|
34
|
+
var x402Moments = {
|
|
35
|
+
/** The 402 challenge as the buyer saw it. */
|
|
36
|
+
required(required) {
|
|
37
|
+
const r = required;
|
|
38
|
+
return fromAccepts(r.accepts[0], "http-402", toJsonObject(r));
|
|
39
|
+
},
|
|
40
|
+
/** The requirements the seller's resource server resolved for a request. */
|
|
41
|
+
requirements(req) {
|
|
42
|
+
const r = req;
|
|
43
|
+
return fromAccepts(r, "http-402", toJsonObject(r));
|
|
44
|
+
},
|
|
45
|
+
/** A route's static `accepts` config, before any payment header exists. */
|
|
46
|
+
route(route, raw) {
|
|
47
|
+
const accepts = route ?? {};
|
|
48
|
+
const extra = accepts.extra;
|
|
49
|
+
return fromAccepts(
|
|
50
|
+
{
|
|
51
|
+
payTo: typeof accepts.payTo === "string" ? accepts.payTo : "dynamic",
|
|
52
|
+
amount: priceValue(accepts.price),
|
|
53
|
+
network: accepts.network,
|
|
54
|
+
asset: extra?.asset ?? "route"
|
|
55
|
+
},
|
|
56
|
+
"http-402",
|
|
57
|
+
raw
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
/** An in-band ask (MRTR `input_required` or a `_meta` envelope) carrying an x402-style `accepts`. */
|
|
61
|
+
ask(first, raw) {
|
|
62
|
+
return fromAccepts(first, "mrtr-input-required", raw);
|
|
63
|
+
},
|
|
64
|
+
/** The signed payment the buyer presented. */
|
|
65
|
+
payload(payload) {
|
|
66
|
+
const p = payload;
|
|
67
|
+
const payer = payerOf(p);
|
|
68
|
+
return {
|
|
69
|
+
...fromAccepts(p.accepted, "payment-signature", toJsonObject(p)),
|
|
70
|
+
...payer ? { payer } : {}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
function priceValue(price) {
|
|
75
|
+
if (typeof price === "string") return price.replace(/[^0-9.]/g, "") || "0";
|
|
76
|
+
if (typeof price === "number") return String(price);
|
|
77
|
+
if (price && typeof price === "object" && typeof price.amount === "string")
|
|
78
|
+
return price.amount;
|
|
79
|
+
return "0";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export {
|
|
83
|
+
x402Currency,
|
|
84
|
+
payerOf,
|
|
85
|
+
x402Moments
|
|
86
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/x402/binding.ts
|
|
2
|
+
var SESSION_EXTENSION = "beltic.sessionId";
|
|
3
|
+
var SESSION_HEADER = "Beltic-Session-Id";
|
|
4
|
+
function sessionIdOf(extensions) {
|
|
5
|
+
const v = extensions?.[SESSION_EXTENSION];
|
|
6
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
SESSION_EXTENSION,
|
|
11
|
+
SESSION_HEADER,
|
|
12
|
+
sessionIdOf
|
|
13
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SESSION_HEADER,
|
|
3
|
+
sessionIdOf
|
|
4
|
+
} from "./chunk-VM7MK43J.js";
|
|
5
|
+
import {
|
|
6
|
+
payloadDigest
|
|
7
|
+
} from "./chunk-SFGM7KOG.js";
|
|
8
|
+
import {
|
|
9
|
+
x402Moments
|
|
10
|
+
} from "./chunk-U5Z5Z2BQ.js";
|
|
11
|
+
import {
|
|
12
|
+
summaryOf
|
|
13
|
+
} from "./chunk-7G5EHNVW.js";
|
|
14
|
+
import {
|
|
15
|
+
Verdict
|
|
16
|
+
} from "./chunk-GCKCAKHA.js";
|
|
17
|
+
|
|
18
|
+
// src/x402/adapter.ts
|
|
19
|
+
function attachX402(beltic, server, http, opts = {}) {
|
|
20
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
21
|
+
const resolveSession = async (bound, ctx) => {
|
|
22
|
+
const key = opts.correlate?.(ctx) ?? null;
|
|
23
|
+
return bound ?? (key ? await beltic.correlation.resolve(key) : null);
|
|
24
|
+
};
|
|
25
|
+
http?.onProtectedRequest(async (ctx, route) => {
|
|
26
|
+
if (ctx.paymentHeader) return;
|
|
27
|
+
const bound = ctx.adapter.getHeader(SESSION_HEADER) ?? ctx.adapter.getHeader(SESSION_HEADER.toLowerCase());
|
|
28
|
+
const sessionId = await resolveSession(bound ?? null, {
|
|
29
|
+
path: ctx.path,
|
|
30
|
+
method: ctx.method,
|
|
31
|
+
header: (n) => ctx.adapter.getHeader(n)
|
|
32
|
+
});
|
|
33
|
+
if (!sessionId) return;
|
|
34
|
+
const session = await beltic.sessions.ensure(sessionId);
|
|
35
|
+
const accepts = Array.isArray(route.accepts) ? route.accepts[0] : route.accepts;
|
|
36
|
+
await session.emit(
|
|
37
|
+
"payment.requested",
|
|
38
|
+
x402Moments.route(accepts, {
|
|
39
|
+
path: ctx.path,
|
|
40
|
+
method: ctx.method,
|
|
41
|
+
route: JSON.parse(JSON.stringify(route))
|
|
42
|
+
})
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
server.onBeforeVerify(async (ctx) => {
|
|
46
|
+
const payload = ctx.paymentPayload;
|
|
47
|
+
const requirements = ctx.requirements;
|
|
48
|
+
const sessionId = await resolveSession(sessionIdOf(payload.extensions), {
|
|
49
|
+
path: payload.resource?.url ?? "",
|
|
50
|
+
method: "PAY",
|
|
51
|
+
header: () => void 0
|
|
52
|
+
});
|
|
53
|
+
const session = await beltic.sessions.ensure(sessionId);
|
|
54
|
+
if (session.born === "seller")
|
|
55
|
+
await session.emit("payment.requested", x402Moments.requirements(requirements));
|
|
56
|
+
const presented = x402Moments.payload(payload);
|
|
57
|
+
await session.emit("payment.presented", presented);
|
|
58
|
+
inFlight.set(payloadDigest(payload), session);
|
|
59
|
+
const out = await beltic.evaluate(session.id, summaryOf(presented));
|
|
60
|
+
opts.onDecision?.({
|
|
61
|
+
sessionId: session.id,
|
|
62
|
+
decision: out.decision,
|
|
63
|
+
reasonCodes: out.reasonCodes,
|
|
64
|
+
born: session.born
|
|
65
|
+
});
|
|
66
|
+
const verdict = Verdict.of(out.decision);
|
|
67
|
+
if (verdict.blocks(beltic.onReview)) {
|
|
68
|
+
return { abort: true, reason: `BELTIC_${verdict.value}`, message: out.reasonCodes.join(",") };
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
});
|
|
72
|
+
server.onAfterSettle(async (ctx) => {
|
|
73
|
+
const key = payloadDigest(ctx.paymentPayload);
|
|
74
|
+
const session = inFlight.get(key);
|
|
75
|
+
if (!session) return;
|
|
76
|
+
inFlight.delete(key);
|
|
77
|
+
if (ctx.result.success) await session.close("settled", { transaction: ctx.result.transaction });
|
|
78
|
+
});
|
|
79
|
+
return { inFlight };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/x402/middleware.ts
|
|
83
|
+
import {
|
|
84
|
+
x402HTTPResourceServer
|
|
85
|
+
} from "@x402/core/server";
|
|
86
|
+
function guardedPaymentMiddleware(fromHTTPServer, beltic, routes, server, opts = {}) {
|
|
87
|
+
const http = new x402HTTPResourceServer(server, routes);
|
|
88
|
+
attachX402(beltic, server, http, opts);
|
|
89
|
+
return fromHTTPServer(http, opts.paywall);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
attachX402,
|
|
94
|
+
guardedPaymentMiddleware
|
|
95
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { OnReview, PaymentSummary, EvaluateOutput } from './api.js';
|
|
2
|
+
import { a as ApiClient, T as Transport, d as Sessions, A as AgentIdentity, b as ApiClientOptions, g as TransportOptions, R as RedactFn } from './session-BMNB1N1g.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Correlation without binding (Fraud SDK RFC › Protocol Adapter — x402:
|
|
6
|
+
* "binding travels on the call that initiates the purchase, not
|
|
7
|
+
* necessarily on the payment artifact"). The merchant binds a key it will
|
|
8
|
+
* see again (a checkout session id, a challenge nonce) to the buyer's
|
|
9
|
+
* session; the adapter resolves it when the settlement arrives (GAP-31).
|
|
10
|
+
*/
|
|
11
|
+
interface CorrelationStore {
|
|
12
|
+
bind(key: string, sessionId: string, ttlMs?: number): Promise<void>;
|
|
13
|
+
resolve(key: string): Promise<string | null>;
|
|
14
|
+
}
|
|
15
|
+
declare class MemoryCorrelationStore implements CorrelationStore {
|
|
16
|
+
private readonly defaultTtlMs;
|
|
17
|
+
private readonly entries;
|
|
18
|
+
constructor(defaultTtlMs?: number);
|
|
19
|
+
bind(key: string, sessionId: string, ttlMs?: number): Promise<void>;
|
|
20
|
+
resolve(key: string): Promise<string | null>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* One SDK, two halves (Fraud SDK RFC › Summary). `Beltic` is the single
|
|
25
|
+
* client: sessions and evidence for both halves, `evaluate` for the seller
|
|
26
|
+
* half. Protocol integrations are plain functions behind subpath exports,
|
|
27
|
+
* each pulling exactly one optional peer:
|
|
28
|
+
*
|
|
29
|
+
* @belticlabs/agent-risk-sdk/ai → middleware(session), wrapTools(session, …)
|
|
30
|
+
* @belticlabs/agent-risk-sdk/x402 → belticFetch(session), attachX402(beltic, …)
|
|
31
|
+
* @belticlabs/agent-risk-sdk/hono → belticPaymentMiddleware(beltic, …) (and /express)
|
|
32
|
+
* @belticlabs/agent-risk-sdk/mcp → wrapClient(session, …), wrapServer(beltic, …)
|
|
33
|
+
*
|
|
34
|
+
* Neither half decides risk locally: verdicts are platform-side.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
declare const SDK_VERSION = "0.1.0";
|
|
38
|
+
interface BelticOptions extends Omit<ApiClientOptions, 'userAgent'> {
|
|
39
|
+
/** Buyer half. Without it, `sessions.start` is unavailable; the seller half works. */
|
|
40
|
+
identity?: AgentIdentity | undefined;
|
|
41
|
+
transport?: Partial<TransportOptions> | undefined;
|
|
42
|
+
/** What a synchronous seller hook does with REVIEW (GAP-52). */
|
|
43
|
+
onReview?: OnReview | undefined;
|
|
44
|
+
correlation?: CorrelationStore | undefined;
|
|
45
|
+
redact?: RedactFn | undefined;
|
|
46
|
+
now?: (() => Date) | undefined;
|
|
47
|
+
}
|
|
48
|
+
declare class Beltic {
|
|
49
|
+
readonly api: ApiClient;
|
|
50
|
+
readonly transport: Transport;
|
|
51
|
+
readonly sessions: Sessions;
|
|
52
|
+
readonly identity: AgentIdentity | undefined;
|
|
53
|
+
readonly correlation: CorrelationStore;
|
|
54
|
+
readonly onReview: OnReview;
|
|
55
|
+
constructor(opts: BelticOptions);
|
|
56
|
+
/** Read-your-writes: the platform must hold the evidence before it judges it (GAP-16). */
|
|
57
|
+
evaluate(sessionId: string, payment: PaymentSummary): Promise<EvaluateOutput>;
|
|
58
|
+
flush(): Promise<void>;
|
|
59
|
+
shutdown(): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
declare function createBeltic(opts: BelticOptions): Beltic;
|
|
62
|
+
|
|
63
|
+
export { Beltic as B, type CorrelationStore as C, MemoryCorrelationStore as M, SDK_VERSION as S, type BelticOptions as a, createBeltic as c };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export { B as Beltic, a as BelticOptions, C as CorrelationStore, M as MemoryCorrelationStore, S as SDK_VERSION, c as createBeltic } from './client-CVx9LgJC.js';
|
|
2
|
+
import { S as Session } from './session-BMNB1N1g.js';
|
|
3
|
+
export { A as AgentIdentity, a as ApiClient, b as ApiClientOptions, B as BelticApiError, C as ChainRejectedError, D as DEFAULT_TRANSPORT, R as RedactFn, c as SessionBorn, d as Sessions, e as StartSessionInput, T as Transport, f as TransportClosedError, g as TransportOptions, h as ephemeralIdentity, i as fileIdentity, j as identityFromSeed } from './session-BMNB1N1g.js';
|
|
4
|
+
import { PaymentSummary, JsonObject, PaymentMomentPayload } from './api.js';
|
|
5
|
+
import './base58.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The shape the platform judges (`PaymentSummary`) and the shape the record
|
|
9
|
+
* keeps (`PaymentMomentPayload`) share their comparable core: payee, amount,
|
|
10
|
+
* payer. Every protocol adapter builds moments through here so the two
|
|
11
|
+
* sides of one purchase compare (`EVIDENCE_MISMATCH`, GAP-50).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
declare function summaryOf(m: PaymentMomentPayload): PaymentSummary;
|
|
15
|
+
/** A presentation the seller side saw as a signed payment, in any protocol. */
|
|
16
|
+
declare function presentedFrom(summary: PaymentSummary, raw: JsonObject): PaymentMomentPayload;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* One instrumented call = a `*.start` event, the work, a `*.end` event
|
|
20
|
+
* carrying the outcome or the error (GAP-07 correlates them by `callId`).
|
|
21
|
+
* The AI middleware, the tool wrapper and the MCP client all record the
|
|
22
|
+
* same way.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
type CallKind = 'llm_call' | 'tool_call';
|
|
26
|
+
declare function recordCall<T>(session: Session, kind: CallKind, callId: string, start: JsonObject, run: () => PromiseLike<T>, end?: (result: T) => Promise<JsonObject> | JsonObject): Promise<T>;
|
|
27
|
+
|
|
28
|
+
export { type CallKind, Session, presentedFrom, recordCall, summaryOf };
|