@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/mcp/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  recordCall
3
- } from "../chunk-5TWO73OD.js";
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
- x402Moments
12
- } from "../chunk-U5Z5Z2BQ.js";
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
- verdict = Verdict.of(out.decision);
89
- reasonCodes = out.reasonCodes;
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-NxpD_384.js';
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-B2mfurae.js';
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
- Verdict
10
- } from "../chunk-46QN2KEZ.js";
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
- protocol: "x402",
21
- payee: ctx.payTo ?? ctx.resource,
22
- amount: { value: ctx.amountAtomic, currency: x402Currency(ctx.network, ctx.asset) },
23
- payer: ctx.wallet.toLowerCase()
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
- const verdict = Verdict.of(out.decision);
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
- /** Resolves once the event is sequenced and buffered — not once it is acknowledged. */
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, DEFAULT_TRANSPORT as D, type RedactFn as R, Session as S, Transport as T, ApiClient as a, type ApiClientOptions as b, type SessionBorn as c, Sessions as d, type StartSessionInput as e, TransportClosedError as f, type TransportOptions as g, ephemeralIdentity as h, fileIdentity as i, identityFromSeed as j };
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 };
@@ -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-NxpD_384.js';
4
- import { G as GuardedMiddlewareOptions } from '../middleware-D-ejQIoE.js';
5
- export { a as attachX402 } from '../middleware-D-ejQIoE.js';
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-B2mfurae.js';
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
 
@@ -1,13 +1,14 @@
1
1
  import {
2
- attachX402,
3
2
  guardedPaymentMiddleware
4
- } from "../chunk-W2OY7QXV.js";
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";
@@ -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-NxpD_384.js';
4
- import { G as GuardedMiddlewareOptions } from '../middleware-D-ejQIoE.js';
5
- export { a as attachX402 } from '../middleware-D-ejQIoE.js';
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-B2mfurae.js';
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-W2OY7QXV.js";
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";
@@ -1,9 +1,8 @@
1
- export { A as AttachOptions, b as AttachedX402, C as CorrelationContext, G as GuardedMiddlewareOptions, a as attachX402, g as guardedPaymentMiddleware } from '../middleware-D-ejQIoE.js';
2
- import { S as Session } from '../session-B2mfurae.js';
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-NxpD_384.js';
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
- declare function belticFetch(session: Session, inner?: typeof globalThis.fetch): typeof globalThis.fetch;
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
- /** The minimum an `accepts` entry needs to become a moment; unknown parts are named, never dropped. */
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: Readonlyish<PaymentPayload>): string | undefined;
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: Readonlyish<PaymentRequired>): PaymentMomentPayload;
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: Readonlyish<PaymentRequirements>): PaymentMomentPayload;
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
- /** The signed payment the buyer presented. */
52
- payload(payload: Readonlyish<PaymentPayload>): PaymentMomentPayload;
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 };
@@ -1,67 +1,118 @@
1
1
  import {
2
- attachX402,
3
- guardedPaymentMiddleware
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
- } from "../chunk-U5Z5Z2BQ.js";
16
- import "../chunk-7G5EHNVW.js";
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
- import {
22
- decodePaymentRequiredHeader,
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 sigHeader = headers.get("PAYMENT-SIGNATURE");
33
- if (sigHeader) {
34
- try {
35
- const payload = decodePaymentSignatureHeader(sigHeader);
36
- payload.extensions = { ...payload.extensions ?? {}, [SESSION_EXTENSION]: session.id };
37
- headers.set("PAYMENT-SIGNATURE", encodePaymentSignatureHeader(payload));
38
- await session.emit("payment.presented", x402Moments.payload(payload));
39
- await session.flush();
40
- } catch {
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
- const required = res.status === 402 ? res.headers.get("PAYMENT-REQUIRED") : null;
42
+ if (res.status !== 402) return res;
43
+ const required = await challengeOf(res);
45
44
  if (required) {
46
- try {
47
- await session.emit(
48
- "payment.requested",
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
- x402Moments
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.1.1",
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": {
@@ -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
- };