@belticlabs/agent-risk-sdk 0.1.1 → 0.2.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/{middleware-D-ejQIoE.d.ts → adapter-CF-cUYSA.d.ts} +4 -16
- package/dist/ai/index.d.ts +4 -4
- package/dist/ai/index.js +4 -3
- package/dist/chunk-GSH5APZW.js +39 -0
- package/dist/{chunk-U5Z5Z2BQ.js → chunk-LM4NIYE5.js} +26 -20
- package/dist/{chunk-W2OY7QXV.js → chunk-SO6HPRJT.js} +10 -23
- package/dist/chunk-U7HM37CB.js +17 -0
- package/dist/client-C-mV_3A0.d.ts +91 -0
- package/dist/index.d.ts +7 -6
- package/dist/index.js +117 -16
- package/dist/mcp/index.d.ts +3 -3
- package/dist/mcp/index.js +11 -8
- package/dist/middleware-BwAYpBTl.d.ts +15 -0
- package/dist/seller/anti-fraud-gateway.d.ts +11 -2
- package/dist/seller/anti-fraud-gateway.js +13 -14
- package/dist/{session-B2mfurae.d.ts → session-5TClPLI4.d.ts} +57 -3
- package/dist/x402/express.d.ts +4 -4
- package/dist/x402/express.js +6 -5
- package/dist/x402/hono.d.ts +4 -4
- package/dist/x402/hono.js +6 -5
- package/dist/x402/index.d.ts +93 -18
- package/dist/x402/index.js +83 -32
- package/package.json +1 -1
- package/dist/chunk-5TWO73OD.js +0 -35
- package/dist/client-NxpD_384.d.ts +0 -64
package/dist/mcp/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
recordCall
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-GSH5APZW.js";
|
|
4
4
|
import {
|
|
5
5
|
SESSION_EXTENSION
|
|
6
6
|
} from "../chunk-VM7MK43J.js";
|
|
@@ -8,21 +8,22 @@ import {
|
|
|
8
8
|
uuidv7
|
|
9
9
|
} from "../chunk-SFGM7KOG.js";
|
|
10
10
|
import {
|
|
11
|
-
|
|
12
|
-
} from "../chunk-
|
|
11
|
+
Verdict
|
|
12
|
+
} from "../chunk-46QN2KEZ.js";
|
|
13
13
|
import {
|
|
14
14
|
presentedFrom
|
|
15
15
|
} from "../chunk-7G5EHNVW.js";
|
|
16
|
+
import {
|
|
17
|
+
x402Moments
|
|
18
|
+
} from "../chunk-LM4NIYE5.js";
|
|
16
19
|
import {
|
|
17
20
|
toJson,
|
|
18
21
|
toJsonObject
|
|
19
22
|
} from "../chunk-FQDHFTVR.js";
|
|
20
|
-
import {
|
|
21
|
-
Verdict
|
|
22
|
-
} from "../chunk-46QN2KEZ.js";
|
|
23
23
|
|
|
24
24
|
// src/mcp/index.ts
|
|
25
25
|
function wrapClient(session, client, opts = {}) {
|
|
26
|
+
if (!session) return client;
|
|
26
27
|
const original = client.callTool.bind(client);
|
|
27
28
|
const callTool = (params, ...rest) => recordCall(
|
|
28
29
|
session,
|
|
@@ -85,8 +86,10 @@ function withBeltic(beltic, handler, opts) {
|
|
|
85
86
|
presentedFrom(payment, { tool: opts.toolName, args: toJson(args) })
|
|
86
87
|
);
|
|
87
88
|
const out = await beltic.evaluate(session.id, payment);
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
if (out) {
|
|
90
|
+
verdict = out.verdict;
|
|
91
|
+
reasonCodes = out.reasonCodes;
|
|
92
|
+
}
|
|
90
93
|
}
|
|
91
94
|
await session.emit("gateway.decision", {
|
|
92
95
|
gateway,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PaywallConfig } from '@x402/core/server';
|
|
2
|
+
import { A as AttachOptions } from './adapter-CF-cUYSA.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The seller half as one middleware: the framework's `@x402/*` payment
|
|
6
|
+
* middleware over an `x402HTTPResourceServer` with the Beltic hooks
|
|
7
|
+
* attached. `@belticlabs/agent-risk-sdk/hono` and `@belticlabs/agent-risk-sdk/express` differ only in
|
|
8
|
+
* which `paymentMiddlewareFromHTTPServer` they hand in.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
type GuardedMiddlewareOptions = AttachOptions & {
|
|
12
|
+
paywall?: PaywallConfig | undefined;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type { GuardedMiddlewareOptions as G };
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import { B as Beltic } from '../client-
|
|
1
|
+
import { B as Beltic } from '../client-C-mV_3A0.js';
|
|
2
2
|
import '../index-Bjs3BPPU.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
import '../verdict-BAahb5po.js';
|
|
5
|
-
import '../session-
|
|
5
|
+
import '../session-5TClPLI4.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Adapter for the anti-fraud port already in Beltic's `platform` monorepo
|
|
9
|
+
* (`x402-core/gateways/anti-fraud.gateway.interface.ts`), GAP-37. That
|
|
10
|
+
* port is binary and session-less; the mapping is lossy by construction:
|
|
11
|
+
* `REVIEW` becomes a deny unless `onReview` says otherwise, and reason
|
|
12
|
+
* codes travel joined in `reason`. The structural type is copied so this
|
|
13
|
+
* module needs no dependency on `platform`.
|
|
14
|
+
*/
|
|
6
15
|
|
|
7
16
|
interface PaymentRiskContext {
|
|
8
17
|
wallet: string;
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
x402Currency
|
|
3
|
-
} from "../chunk-U5Z5Z2BQ.js";
|
|
4
1
|
import {
|
|
5
2
|
presentedFrom
|
|
6
3
|
} from "../chunk-7G5EHNVW.js";
|
|
7
|
-
import "../chunk-FQDHFTVR.js";
|
|
8
4
|
import {
|
|
9
|
-
|
|
10
|
-
} from "../chunk-
|
|
5
|
+
x402Summary
|
|
6
|
+
} from "../chunk-LM4NIYE5.js";
|
|
7
|
+
import "../chunk-FQDHFTVR.js";
|
|
11
8
|
|
|
12
9
|
// src/seller/anti-fraud-gateway.ts
|
|
13
10
|
var BelticAntiFraudGateway = class {
|
|
@@ -16,12 +13,15 @@ var BelticAntiFraudGateway = class {
|
|
|
16
13
|
}
|
|
17
14
|
async assess(ctx) {
|
|
18
15
|
const session = await this.beltic.sessions.ensure(ctx.sessionId ?? null);
|
|
19
|
-
const payment =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
const payment = x402Summary(
|
|
17
|
+
{
|
|
18
|
+
payTo: ctx.payTo ?? ctx.resource,
|
|
19
|
+
amount: ctx.amountAtomic,
|
|
20
|
+
network: ctx.network,
|
|
21
|
+
asset: ctx.asset
|
|
22
|
+
},
|
|
23
|
+
{ payer: ctx.wallet }
|
|
24
|
+
);
|
|
25
25
|
await session.emit(
|
|
26
26
|
"payment.presented",
|
|
27
27
|
presentedFrom(payment, {
|
|
@@ -30,8 +30,7 @@ var BelticAntiFraudGateway = class {
|
|
|
30
30
|
})
|
|
31
31
|
);
|
|
32
32
|
const out = await this.beltic.evaluate(session.id, payment);
|
|
33
|
-
|
|
34
|
-
return verdict.blocks(this.beltic.onReview) ? { allow: false, reason: `${out.decision}:${out.reasonCodes.join(",")}` } : { allow: true };
|
|
33
|
+
return out?.verdict.blocks(this.beltic.onReview) ? { allow: false, reason: `${out.decision}:${out.reasonCodes.join(",")}` } : { allow: true };
|
|
35
34
|
}
|
|
36
35
|
};
|
|
37
36
|
export {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as EvidenceAck, G as EventResult, R as EvidenceEvent, V as EvidenceSource, aC as WireEvidenceKind, J as JsonObject, C as ChainHead, aa as PayloadByKind, am as SessionClosePayload, v as DeclaredIntent } from './index-Bjs3BPPU.js';
|
|
1
|
+
import { L as EvidenceAck, G as EventResult, R as EvidenceEvent, V as EvidenceSource, aC as WireEvidenceKind, J as JsonObject, C as ChainHead, aa as PayloadByKind, aw as ToolCallStartPayload, b as JsonValue, am as SessionClosePayload, v as DeclaredIntent } from './index-Bjs3BPPU.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* The edge signs event digests (Fraud SDK RFC › Modules › Identity Module);
|
|
@@ -123,6 +123,11 @@ declare class Transport {
|
|
|
123
123
|
* (GAP-38): a dropped event never leaves a hole — the next accepted event
|
|
124
124
|
* is preceded by a `transport.gap` that counts the drops. `redact` is off
|
|
125
125
|
* by default (GAP-33).
|
|
126
|
+
*
|
|
127
|
+
* Evidence is a side channel of the agent's work: with `failOpen` an emit
|
|
128
|
+
* that cannot be chained (halted chain, closed transport) is reported and
|
|
129
|
+
* returns `false` instead of throwing into the model or tool call it
|
|
130
|
+
* observes (GAP-70).
|
|
126
131
|
*/
|
|
127
132
|
|
|
128
133
|
type RedactFn = (kind: WireEvidenceKind, payload: JsonObject) => JsonObject;
|
|
@@ -135,6 +140,23 @@ interface SessionDeps {
|
|
|
135
140
|
now?: (() => Date) | undefined;
|
|
136
141
|
/** Called once the session closed, so the registry can forget it. */
|
|
137
142
|
onClosed?: ((session: Session) => void) | undefined;
|
|
143
|
+
/** Report instead of throw when an event cannot be chained (GAP-70). */
|
|
144
|
+
failOpen?: boolean | undefined;
|
|
145
|
+
onError?: ((err: Error) => void) | undefined;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* One tool call as a span: `tool_call.start` now, `tool_call.end` with the
|
|
149
|
+
* outcome and the elapsed time when the host reports it. For hosts that run
|
|
150
|
+
* their own tool loop and cannot hand the SDK an `execute` to wrap.
|
|
151
|
+
*/
|
|
152
|
+
interface ToolCallSpan {
|
|
153
|
+
readonly callId: string;
|
|
154
|
+
end(outcome?: {
|
|
155
|
+
output?: JsonValue;
|
|
156
|
+
}): Promise<boolean>;
|
|
157
|
+
fail(error: unknown, outcome?: {
|
|
158
|
+
output?: JsonValue;
|
|
159
|
+
}): Promise<boolean>;
|
|
138
160
|
}
|
|
139
161
|
declare class Session {
|
|
140
162
|
private readonly deps;
|
|
@@ -152,8 +174,15 @@ declare class Session {
|
|
|
152
174
|
constructor(deps: SessionDeps, id: string, source: EvidenceSource, expiresAt: string | null, born: SessionBorn);
|
|
153
175
|
get head(): ChainHead | null;
|
|
154
176
|
get droppedCount(): number;
|
|
155
|
-
/**
|
|
177
|
+
/**
|
|
178
|
+
* Resolves once the event is sequenced and buffered — not once it is
|
|
179
|
+
* acknowledged. `false` when the event was dropped, or (fail-open) when
|
|
180
|
+
* the chain can no longer take it.
|
|
181
|
+
*/
|
|
156
182
|
emit<K extends WireEvidenceKind>(kind: K, payload: PayloadByKind[K]): Promise<boolean>;
|
|
183
|
+
/** The tool call whose `execute` the host runs itself; see `ToolCallSpan`. */
|
|
184
|
+
toolCall(call: ToolCallStartPayload): ToolCallSpan;
|
|
185
|
+
private chainEvent;
|
|
157
186
|
close(reason?: SessionClosePayload['reason'], extra?: JsonObject): Promise<void>;
|
|
158
187
|
/** Read-your-writes: the platform must hold the evidence before anyone judges it (GAP-16/66). */
|
|
159
188
|
flush(): Promise<void>;
|
|
@@ -168,6 +197,8 @@ interface StartSessionInput {
|
|
|
168
197
|
};
|
|
169
198
|
attestations?: JsonObject;
|
|
170
199
|
}
|
|
200
|
+
/** What `open` sends when it actually opens: a value, or a resolver run only then. */
|
|
201
|
+
type OpenSessionInput = StartSessionInput | (() => StartSessionInput | Promise<StartSessionInput>);
|
|
171
202
|
interface SessionsDeps {
|
|
172
203
|
api: ApiClient;
|
|
173
204
|
transport: Transport;
|
|
@@ -175,7 +206,12 @@ interface SessionsDeps {
|
|
|
175
206
|
redact?: RedactFn | undefined;
|
|
176
207
|
now?: (() => Date) | undefined;
|
|
177
208
|
sdkVersion: string;
|
|
209
|
+
failOpen?: boolean | undefined;
|
|
210
|
+
onError?: ((err: Error) => void) | undefined;
|
|
211
|
+
/** Fail-open only: after the platform refused to open a session, `open` resolves null for this long (GAP-71). */
|
|
212
|
+
openRetryMs?: number | undefined;
|
|
178
213
|
}
|
|
214
|
+
declare const DEFAULT_OPEN_RETRY_MS = 60000;
|
|
179
215
|
declare class Sessions {
|
|
180
216
|
private readonly deps;
|
|
181
217
|
/**
|
|
@@ -185,12 +221,30 @@ declare class Sessions {
|
|
|
185
221
|
* mid-session still loses the head (GAP-67).
|
|
186
222
|
*/
|
|
187
223
|
private readonly attached;
|
|
224
|
+
/** Buyer sessions by the host's own key (GAP-71). */
|
|
225
|
+
private readonly opened;
|
|
226
|
+
private retryAt;
|
|
188
227
|
constructor(deps: SessionsDeps);
|
|
228
|
+
/**
|
|
229
|
+
* Buyer half: the evidence session for a key of the host's own (its
|
|
230
|
+
* session, run or conversation id), opened on first use and reused
|
|
231
|
+
* after. A halted chain is reopened as a fresh session that continues
|
|
232
|
+
* the same key; a closed key is forgotten. When the platform refuses to
|
|
233
|
+
* open one, a fail-open client resolves null — the host runs without
|
|
234
|
+
* evidence — until `openRetryMs` has passed (GAP-71); otherwise the
|
|
235
|
+
* refusal is thrown and the next call tries again. The identity is
|
|
236
|
+
* required either way: that is configuration.
|
|
237
|
+
*/
|
|
238
|
+
open(key: string, input?: OpenSessionInput): Promise<Session | null>;
|
|
239
|
+
private forget;
|
|
240
|
+
private openFresh;
|
|
189
241
|
/** Buyer half: create an AGENT_TRACE session bound to the agent identity, then announce it on the chain. */
|
|
190
242
|
start(input?: StartSessionInput): Promise<Session>;
|
|
243
|
+
private identityFor;
|
|
244
|
+
private create;
|
|
191
245
|
/** Seller half: emit INTERNAL_NETWORK evidence into a session the buyer bound, or open a seller-born one. */
|
|
192
246
|
ensure(sessionId?: string | null): Promise<Session>;
|
|
193
247
|
private attach;
|
|
194
248
|
}
|
|
195
249
|
|
|
196
|
-
export { type AgentIdentity as A, BelticApiError as B, ChainRejectedError as C,
|
|
250
|
+
export { type AgentIdentity as A, BelticApiError as B, ChainRejectedError as C, DEFAULT_OPEN_RETRY_MS as D, type OpenSessionInput as O, type RedactFn as R, Session as S, type ToolCallSpan as T, ApiClient as a, type ApiClientOptions as b, DEFAULT_TRANSPORT as c, type SessionBorn as d, Sessions as e, type StartSessionInput as f, Transport as g, TransportClosedError as h, type TransportOptions as i, ephemeralIdentity as j, fileIdentity as k, identityFromSeed as l };
|
package/dist/x402/express.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { RoutesConfig, x402ResourceServer } from '@x402/core/server';
|
|
2
2
|
import { RequestHandler } from 'express';
|
|
3
|
-
import { B as Beltic } from '../client-
|
|
4
|
-
import { G as GuardedMiddlewareOptions } from '../middleware-
|
|
5
|
-
export { a as attachX402 } from '../
|
|
3
|
+
import { B as Beltic } from '../client-C-mV_3A0.js';
|
|
4
|
+
import { G as GuardedMiddlewareOptions } from '../middleware-BwAYpBTl.js';
|
|
5
|
+
export { a as attachX402 } from '../adapter-CF-cUYSA.js';
|
|
6
6
|
import '../index-Bjs3BPPU.js';
|
|
7
7
|
import 'zod';
|
|
8
8
|
import '../verdict-BAahb5po.js';
|
|
9
|
-
import '../session-
|
|
9
|
+
import '../session-5TClPLI4.js';
|
|
10
10
|
|
|
11
11
|
/** Seller half for express: `@x402/express`'s payment middleware with the Beltic hooks attached. */
|
|
12
12
|
|
package/dist/x402/express.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
-
attachX402,
|
|
3
2
|
guardedPaymentMiddleware
|
|
4
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-U7HM37CB.js";
|
|
4
|
+
import {
|
|
5
|
+
attachX402
|
|
6
|
+
} from "../chunk-SO6HPRJT.js";
|
|
5
7
|
import "../chunk-VM7MK43J.js";
|
|
6
8
|
import "../chunk-SFGM7KOG.js";
|
|
7
|
-
import "../chunk-U5Z5Z2BQ.js";
|
|
8
|
-
import "../chunk-7G5EHNVW.js";
|
|
9
|
-
import "../chunk-FQDHFTVR.js";
|
|
10
9
|
import "../chunk-46QN2KEZ.js";
|
|
10
|
+
import "../chunk-LM4NIYE5.js";
|
|
11
|
+
import "../chunk-FQDHFTVR.js";
|
|
11
12
|
|
|
12
13
|
// src/x402/express.ts
|
|
13
14
|
import { paymentMiddlewareFromHTTPServer } from "@x402/express";
|
package/dist/x402/hono.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { RoutesConfig, x402ResourceServer } from '@x402/core/server';
|
|
2
2
|
import { MiddlewareHandler } from 'hono';
|
|
3
|
-
import { B as Beltic } from '../client-
|
|
4
|
-
import { G as GuardedMiddlewareOptions } from '../middleware-
|
|
5
|
-
export { a as attachX402 } from '../
|
|
3
|
+
import { B as Beltic } from '../client-C-mV_3A0.js';
|
|
4
|
+
import { G as GuardedMiddlewareOptions } from '../middleware-BwAYpBTl.js';
|
|
5
|
+
export { a as attachX402 } from '../adapter-CF-cUYSA.js';
|
|
6
6
|
import '../index-Bjs3BPPU.js';
|
|
7
7
|
import 'zod';
|
|
8
8
|
import '../verdict-BAahb5po.js';
|
|
9
|
-
import '../session-
|
|
9
|
+
import '../session-5TClPLI4.js';
|
|
10
10
|
|
|
11
11
|
/** Seller half for hono: `@x402/hono`'s payment middleware with the Beltic hooks attached. */
|
|
12
12
|
|
package/dist/x402/hono.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
-
attachX402,
|
|
3
2
|
guardedPaymentMiddleware
|
|
4
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-U7HM37CB.js";
|
|
4
|
+
import {
|
|
5
|
+
attachX402
|
|
6
|
+
} from "../chunk-SO6HPRJT.js";
|
|
5
7
|
import "../chunk-VM7MK43J.js";
|
|
6
8
|
import "../chunk-SFGM7KOG.js";
|
|
7
|
-
import "../chunk-U5Z5Z2BQ.js";
|
|
8
|
-
import "../chunk-7G5EHNVW.js";
|
|
9
|
-
import "../chunk-FQDHFTVR.js";
|
|
10
9
|
import "../chunk-46QN2KEZ.js";
|
|
10
|
+
import "../chunk-LM4NIYE5.js";
|
|
11
|
+
import "../chunk-FQDHFTVR.js";
|
|
11
12
|
|
|
12
13
|
// src/x402/hono.ts
|
|
13
14
|
import { paymentMiddlewareFromHTTPServer } from "@x402/hono";
|
package/dist/x402/index.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
export { A as AttachOptions, b as AttachedX402, C as CorrelationContext,
|
|
2
|
-
import { S as Session } from '../session-
|
|
3
|
-
import { P as PaymentMomentPayload, J as JsonObject } from '../index-Bjs3BPPU.js';
|
|
4
|
-
import { PaymentPayload, PaymentRequired, PaymentRequirements } from '@x402/core/types';
|
|
1
|
+
export { A as AttachOptions, b as AttachedX402, C as CorrelationContext, a as attachX402 } from '../adapter-CF-cUYSA.js';
|
|
2
|
+
import { S as Session } from '../session-5TClPLI4.js';
|
|
3
|
+
import { v as DeclaredIntent, P as PaymentMomentPayload, J as JsonObject, a as PaymentSummary } from '../index-Bjs3BPPU.js';
|
|
5
4
|
import '@x402/core/server';
|
|
6
|
-
import '../client-
|
|
5
|
+
import '../client-C-mV_3A0.js';
|
|
7
6
|
import '../verdict-BAahb5po.js';
|
|
8
7
|
import 'zod';
|
|
9
8
|
|
|
@@ -16,40 +15,116 @@ declare const SESSION_EXTENSION = "beltic.sessionId";
|
|
|
16
15
|
declare const SESSION_HEADER = "Beltic-Session-Id";
|
|
17
16
|
declare function sessionIdOf(extensions: Readonly<Record<string, unknown>> | undefined): string | null;
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Buyer half › the x402 fetch wrapper. Sits *inside* the agent's paying
|
|
20
|
+
* fetch (e.g. `wrapFetchWithPayment(belticFetch(session))`), so it sees the
|
|
21
|
+
* 402 challenge → `payment.requested`, and the retry carrying the payment
|
|
22
|
+
* → `payment.presented`, into which it injects the session binding
|
|
23
|
+
* (GAP-30). It never pays and never decides.
|
|
24
|
+
*
|
|
25
|
+
* Before a request that presents payment leaves, the buffered evidence is
|
|
26
|
+
* flushed: the seller will evaluate as soon as it sees the payment, and
|
|
27
|
+
* the platform must already hold the buyer's side of the story (GAP-66).
|
|
28
|
+
*
|
|
29
|
+
* Both x402 generations are read: v2 (`PAYMENT-REQUIRED` header,
|
|
30
|
+
* `PAYMENT-SIGNATURE` retry, binding in `extensions`) and v1 (challenge in
|
|
31
|
+
* the 402 body, `X-PAYMENT` retry, binding by header only — a v1 payload
|
|
32
|
+
* has no extensions and names no asset, so the challenge this wrapper saw
|
|
33
|
+
* for the same URL supplies it; GAP-72). The header codec is base64 JSON,
|
|
34
|
+
* kept here so a buyer needs no `@x402/*` package to be observed.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The paying fetch, observed. Without a session (the evidence stream is
|
|
39
|
+
* not configured, or the platform refused to open one) the inner fetch is
|
|
40
|
+
* returned as is, so a host wires it unconditionally.
|
|
41
|
+
*/
|
|
42
|
+
declare function belticFetch(session: Session | null | undefined, inner?: typeof globalThis.fetch): typeof globalThis.fetch;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The declared intent for an x402 mandate (Fraud SDK RFC › Session ›
|
|
46
|
+
* `intent.declared`). The cap must be in the currency the rail's moments
|
|
47
|
+
* carry — `<network>/<asset>`, atomic units (GAP-49) — or the platform's
|
|
48
|
+
* spend detectors compare two currencies and never meet; this is the one
|
|
49
|
+
* place a buyer spells that. Everything else is the protocol's own shape.
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
interface X402IntentInput {
|
|
53
|
+
mandate: string;
|
|
54
|
+
network: string;
|
|
55
|
+
asset: string;
|
|
56
|
+
/** Atomic units of `asset`, as x402 carries amounts — a decimal string or a bigint, never a float. */
|
|
57
|
+
maxAmount: string | bigint;
|
|
58
|
+
validUntil: string | Date;
|
|
59
|
+
merchantAllowlist?: string[] | undefined;
|
|
60
|
+
}
|
|
61
|
+
declare function x402Intent(input: X402IntentInput): DeclaredIntent;
|
|
20
62
|
|
|
21
63
|
/**
|
|
22
64
|
* x402 artifacts → protocol moments (Fraud SDK RFC › Protocol Adapter —
|
|
23
65
|
* x402). The moment is normalized (payee, amount, payer) so both sides of
|
|
24
66
|
* a purchase compare; the artifact travels whole in `raw`. For x402 the
|
|
25
67
|
* currency is `<network>/<asset>` (GAP-49) and the value is the atomic
|
|
26
|
-
* amount as the protocol carries it
|
|
68
|
+
* amount as the protocol carries it — `x402Summary` gives a buyer the same
|
|
69
|
+
* normalization for the payment it is about to evaluate, so what it asks
|
|
70
|
+
* about and what the wrapper records are one and the same.
|
|
27
71
|
*/
|
|
28
72
|
|
|
29
|
-
type Readonlyish<T> = {
|
|
30
|
-
readonly [K in keyof T]: Readonlyish<T[K]>;
|
|
31
|
-
} | T;
|
|
32
73
|
declare function x402Currency(network: string, asset: string): string;
|
|
33
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* The minimum an `accepts` entry needs to become a moment; unknown parts
|
|
76
|
+
* are named, never dropped. v1 spells the amount `maxAmountRequired`.
|
|
77
|
+
*/
|
|
34
78
|
interface AcceptsLike {
|
|
35
79
|
payTo?: string | undefined;
|
|
36
80
|
amount?: string | undefined;
|
|
81
|
+
maxAmountRequired?: string | undefined;
|
|
37
82
|
network?: string | undefined;
|
|
38
83
|
asset?: string | undefined;
|
|
39
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* A 402 challenge and a presented payment of either generation, as far as
|
|
87
|
+
* a moment needs them. `@x402/core`'s `PaymentRequired` and
|
|
88
|
+
* `PaymentPayload` satisfy these structurally, so the seller adapter
|
|
89
|
+
* passes its typed values through and the buyer needs no `@x402/*` types.
|
|
90
|
+
*/
|
|
91
|
+
interface PaymentRequiredLike {
|
|
92
|
+
x402Version?: number | undefined;
|
|
93
|
+
accepts?: readonly AcceptsLike[] | undefined;
|
|
94
|
+
}
|
|
95
|
+
interface PaymentPayloadLike {
|
|
96
|
+
x402Version?: number | undefined;
|
|
97
|
+
accepted?: AcceptsLike | undefined;
|
|
98
|
+
/** v1 names the network here, beside the payload, and nowhere else. */
|
|
99
|
+
network?: string | undefined;
|
|
100
|
+
payload?: Readonly<Record<string, unknown>> | undefined;
|
|
101
|
+
extensions?: Readonly<Record<string, unknown>> | undefined;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The one normalization of an x402 `accepts` entry: the payment in the
|
|
105
|
+
* shape `evaluate` takes, with the payer as `payerOf` would read it. Every
|
|
106
|
+
* moment below is this plus its artifact and `raw`.
|
|
107
|
+
*/
|
|
108
|
+
declare function x402Summary(accepts: AcceptsLike | undefined, opts?: {
|
|
109
|
+
payer?: string | undefined;
|
|
110
|
+
}): PaymentSummary;
|
|
40
111
|
/** The payer is scheme-specific; the common EVM shapes are read, anything else stays in `raw`. */
|
|
41
|
-
declare function payerOf(payload:
|
|
112
|
+
declare function payerOf(payload: PaymentPayloadLike): string | undefined;
|
|
42
113
|
declare const x402Moments: {
|
|
43
|
-
/** The 402 challenge as the buyer saw it. */
|
|
44
|
-
required(required:
|
|
114
|
+
/** The 402 challenge as the buyer saw it, v2 header or v1 body. */
|
|
115
|
+
required(required: PaymentRequiredLike): PaymentMomentPayload;
|
|
45
116
|
/** The requirements the seller's resource server resolved for a request. */
|
|
46
|
-
requirements(req:
|
|
117
|
+
requirements(req: AcceptsLike): PaymentMomentPayload;
|
|
47
118
|
/** A route's static `accepts` config, before any payment header exists. */
|
|
48
119
|
route(route: unknown, raw: JsonObject): PaymentMomentPayload;
|
|
49
120
|
/** An in-band ask (MRTR `input_required` or a `_meta` envelope) carrying an x402-style `accepts`. */
|
|
50
121
|
ask(first: AcceptsLike, raw: JsonObject): PaymentMomentPayload;
|
|
51
|
-
/**
|
|
52
|
-
|
|
122
|
+
/**
|
|
123
|
+
* The signed payment the buyer presented. A v2 payload carries the
|
|
124
|
+
* requirement it accepted; a v1 payload does not, so the caller passes
|
|
125
|
+
* the `accepts` entry it answered.
|
|
126
|
+
*/
|
|
127
|
+
payload(payload: PaymentPayloadLike, accepts?: AcceptsLike | undefined): PaymentMomentPayload;
|
|
53
128
|
};
|
|
54
129
|
|
|
55
|
-
export { type AcceptsLike, SESSION_EXTENSION, SESSION_HEADER, belticFetch, payerOf, sessionIdOf, x402Currency, x402Moments };
|
|
130
|
+
export { type AcceptsLike, type PaymentPayloadLike, type PaymentRequiredLike, SESSION_EXTENSION, SESSION_HEADER, type X402IntentInput, belticFetch, payerOf, sessionIdOf, x402Currency, x402Intent, x402Moments, x402Summary };
|
package/dist/x402/index.js
CHANGED
|
@@ -1,67 +1,118 @@
|
|
|
1
1
|
import {
|
|
2
|
-
attachX402
|
|
3
|
-
|
|
4
|
-
} from "../chunk-W2OY7QXV.js";
|
|
2
|
+
attachX402
|
|
3
|
+
} from "../chunk-SO6HPRJT.js";
|
|
5
4
|
import {
|
|
6
5
|
SESSION_EXTENSION,
|
|
7
6
|
SESSION_HEADER,
|
|
8
7
|
sessionIdOf
|
|
9
8
|
} from "../chunk-VM7MK43J.js";
|
|
10
9
|
import "../chunk-SFGM7KOG.js";
|
|
10
|
+
import "../chunk-46QN2KEZ.js";
|
|
11
11
|
import {
|
|
12
12
|
payerOf,
|
|
13
13
|
x402Currency,
|
|
14
|
-
x402Moments
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
x402Moments,
|
|
15
|
+
x402Summary
|
|
16
|
+
} from "../chunk-LM4NIYE5.js";
|
|
17
17
|
import "../chunk-FQDHFTVR.js";
|
|
18
|
-
import "../chunk-46QN2KEZ.js";
|
|
19
18
|
|
|
20
19
|
// src/x402/fetch.ts
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
decodePaymentSignatureHeader,
|
|
24
|
-
encodePaymentSignatureHeader
|
|
25
|
-
} from "@x402/core/http";
|
|
20
|
+
var BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
21
|
+
var CHALLENGE_MEMORY = 32;
|
|
26
22
|
function belticFetch(session, inner = globalThis.fetch) {
|
|
23
|
+
if (!session) return inner;
|
|
24
|
+
const challenges = /* @__PURE__ */ new Map();
|
|
27
25
|
return async (input, init) => {
|
|
26
|
+
const url = urlOf(input);
|
|
28
27
|
const headers = new Headers(
|
|
29
28
|
init?.headers ?? (input instanceof Request ? input.headers : void 0)
|
|
30
29
|
);
|
|
31
30
|
headers.set(SESSION_HEADER, session.id);
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
31
|
+
const signature = headers.get("PAYMENT-SIGNATURE");
|
|
32
|
+
const presented = signature ?? headers.get("X-PAYMENT");
|
|
33
|
+
const payload = presented ? decodeHeader(presented) : null;
|
|
34
|
+
if (payload) {
|
|
35
|
+
const bound = signature ? { ...payload, extensions: { ...payload.extensions, [SESSION_EXTENSION]: session.id } } : payload;
|
|
36
|
+
if (signature) headers.set("PAYMENT-SIGNATURE", encodeHeader(bound));
|
|
37
|
+
const accepts = bound.accepted ?? answeredAccepts(challenges.get(url), bound);
|
|
38
|
+
await session.emit("payment.presented", x402Moments.payload(bound, accepts));
|
|
39
|
+
await session.flush();
|
|
42
40
|
}
|
|
43
41
|
const res = await inner(input, { ...init, headers });
|
|
44
|
-
|
|
42
|
+
if (res.status !== 402) return res;
|
|
43
|
+
const required = await challengeOf(res);
|
|
45
44
|
if (required) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
x402Moments.required(decodePaymentRequiredHeader(required))
|
|
50
|
-
);
|
|
51
|
-
} catch {
|
|
52
|
-
}
|
|
45
|
+
challenges.set(url, required);
|
|
46
|
+
if (challenges.size > CHALLENGE_MEMORY) challenges.delete(challenges.keys().next().value);
|
|
47
|
+
await session.emit("payment.requested", x402Moments.required(required));
|
|
53
48
|
}
|
|
54
49
|
return res;
|
|
55
50
|
};
|
|
56
51
|
}
|
|
52
|
+
function decodeHeader(value) {
|
|
53
|
+
if (!BASE64.test(value)) return null;
|
|
54
|
+
try {
|
|
55
|
+
const parsed = JSON.parse(Buffer.from(value, "base64").toString("utf8"));
|
|
56
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function encodeHeader(value) {
|
|
62
|
+
return Buffer.from(JSON.stringify(value), "utf8").toString("base64");
|
|
63
|
+
}
|
|
64
|
+
function urlOf(input) {
|
|
65
|
+
return typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
66
|
+
}
|
|
67
|
+
async function challengeOf(res) {
|
|
68
|
+
const header = res.headers.get("PAYMENT-REQUIRED");
|
|
69
|
+
if (header) return decodeHeader(header);
|
|
70
|
+
if (!/json/i.test(res.headers.get("content-type") ?? "")) return null;
|
|
71
|
+
try {
|
|
72
|
+
const body = await res.clone().json();
|
|
73
|
+
return Array.isArray(body?.accepts) ? body : null;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function answeredAccepts(required, payload) {
|
|
79
|
+
const auth = payload.payload?.authorization;
|
|
80
|
+
const payTo = typeof auth?.to === "string" ? auth.to : void 0;
|
|
81
|
+
const amount = typeof auth?.value === "string" ? auth.value : void 0;
|
|
82
|
+
const options = required?.accepts ?? [];
|
|
83
|
+
const answered = options.find(
|
|
84
|
+
(a) => a.payTo?.toLowerCase() === payTo?.toLowerCase() && (a.amount ?? a.maxAmountRequired) === amount
|
|
85
|
+
) ?? options[0];
|
|
86
|
+
if (answered) return answered;
|
|
87
|
+
if (!payTo && !amount) return void 0;
|
|
88
|
+
return {
|
|
89
|
+
...payTo ? { payTo } : {},
|
|
90
|
+
...amount ? { amount } : {},
|
|
91
|
+
...payload.network ? { network: payload.network } : {}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/x402/intent.ts
|
|
96
|
+
function x402Intent(input) {
|
|
97
|
+
return {
|
|
98
|
+
mandate: input.mandate,
|
|
99
|
+
maxAmount: {
|
|
100
|
+
value: input.maxAmount.toString(),
|
|
101
|
+
currency: x402Currency(input.network, input.asset)
|
|
102
|
+
},
|
|
103
|
+
validUntil: new Date(input.validUntil).toISOString(),
|
|
104
|
+
...input.merchantAllowlist ? { merchantAllowlist: input.merchantAllowlist } : {}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
57
107
|
export {
|
|
58
108
|
SESSION_EXTENSION,
|
|
59
109
|
SESSION_HEADER,
|
|
60
110
|
attachX402,
|
|
61
111
|
belticFetch,
|
|
62
|
-
guardedPaymentMiddleware,
|
|
63
112
|
payerOf,
|
|
64
113
|
sessionIdOf,
|
|
65
114
|
x402Currency,
|
|
66
|
-
|
|
115
|
+
x402Intent,
|
|
116
|
+
x402Moments,
|
|
117
|
+
x402Summary
|
|
67
118
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@belticlabs/agent-risk-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The Beltic Agent Risk SDK — one client, two halves: instrument the buyer agent (AI SDK, x402 fetch, MCP client) and guard the seller boundary (x402, MCP server, evaluate).",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
package/dist/chunk-5TWO73OD.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
// src/core/record.ts
|
|
2
|
-
function errorOf(err) {
|
|
3
|
-
const e = err;
|
|
4
|
-
return { name: String(e?.name ?? "Error"), message: String(e?.message ?? err) };
|
|
5
|
-
}
|
|
6
|
-
async function recordCall(session, kind, callId, start, run, end = () => ({})) {
|
|
7
|
-
const started = Date.now();
|
|
8
|
-
await session.emit(`${kind}.start`, { callId, ...start });
|
|
9
|
-
try {
|
|
10
|
-
const result = await run();
|
|
11
|
-
await session.emit(
|
|
12
|
-
`${kind}.end`,
|
|
13
|
-
{
|
|
14
|
-
callId,
|
|
15
|
-
...await end(result),
|
|
16
|
-
durationMs: Date.now() - started
|
|
17
|
-
}
|
|
18
|
-
);
|
|
19
|
-
return result;
|
|
20
|
-
} catch (err) {
|
|
21
|
-
await session.emit(
|
|
22
|
-
`${kind}.end`,
|
|
23
|
-
{
|
|
24
|
-
callId,
|
|
25
|
-
error: errorOf(err),
|
|
26
|
-
durationMs: Date.now() - started
|
|
27
|
-
}
|
|
28
|
-
);
|
|
29
|
-
throw err;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export {
|
|
34
|
-
recordCall
|
|
35
|
-
};
|