@belticlabs/agent-risk-sdk 0.1.0 → 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.
@@ -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-GCKCAKHA.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,5 +1,17 @@
1
- import { Signer } from './base58.js';
2
- import { EvidenceAck, EventResult, EvidenceEvent, EvidenceSource, WireEvidenceKind, JsonObject, ChainHead, PayloadByKind, SessionClosePayload, DeclaredIntent } from './api.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
+
3
+ /**
4
+ * The edge signs event digests (Fraud SDK RFC › Modules › Identity Module);
5
+ * the platform signs its own PLATFORM chain and epoch anchors. Both are the
6
+ * same operation over different keys, so one interface.
7
+ */
8
+ interface Signer {
9
+ /** Raw 32-byte Ed25519 public key. */
10
+ readonly publicKey: Uint8Array;
11
+ /** Stable identifier for logs and key rotation; `did:key` for agents. */
12
+ readonly keyId: string;
13
+ sign(message: Uint8Array): Promise<Uint8Array>;
14
+ }
3
15
 
4
16
  interface ApiClientOptions {
5
17
  baseUrl: string;
@@ -111,6 +123,11 @@ declare class Transport {
111
123
  * (GAP-38): a dropped event never leaves a hole — the next accepted event
112
124
  * is preceded by a `transport.gap` that counts the drops. `redact` is off
113
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).
114
131
  */
115
132
 
116
133
  type RedactFn = (kind: WireEvidenceKind, payload: JsonObject) => JsonObject;
@@ -123,6 +140,23 @@ interface SessionDeps {
123
140
  now?: (() => Date) | undefined;
124
141
  /** Called once the session closed, so the registry can forget it. */
125
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>;
126
160
  }
127
161
  declare class Session {
128
162
  private readonly deps;
@@ -140,8 +174,15 @@ declare class Session {
140
174
  constructor(deps: SessionDeps, id: string, source: EvidenceSource, expiresAt: string | null, born: SessionBorn);
141
175
  get head(): ChainHead | null;
142
176
  get droppedCount(): number;
143
- /** 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
+ */
144
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;
145
186
  close(reason?: SessionClosePayload['reason'], extra?: JsonObject): Promise<void>;
146
187
  /** Read-your-writes: the platform must hold the evidence before anyone judges it (GAP-16/66). */
147
188
  flush(): Promise<void>;
@@ -156,6 +197,8 @@ interface StartSessionInput {
156
197
  };
157
198
  attestations?: JsonObject;
158
199
  }
200
+ /** What `open` sends when it actually opens: a value, or a resolver run only then. */
201
+ type OpenSessionInput = StartSessionInput | (() => StartSessionInput | Promise<StartSessionInput>);
159
202
  interface SessionsDeps {
160
203
  api: ApiClient;
161
204
  transport: Transport;
@@ -163,7 +206,12 @@ interface SessionsDeps {
163
206
  redact?: RedactFn | undefined;
164
207
  now?: (() => Date) | undefined;
165
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;
166
213
  }
214
+ declare const DEFAULT_OPEN_RETRY_MS = 60000;
167
215
  declare class Sessions {
168
216
  private readonly deps;
169
217
  /**
@@ -173,12 +221,30 @@ declare class Sessions {
173
221
  * mid-session still loses the head (GAP-67).
174
222
  */
175
223
  private readonly attached;
224
+ /** Buyer sessions by the host's own key (GAP-71). */
225
+ private readonly opened;
226
+ private retryAt;
176
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;
177
241
  /** Buyer half: create an AGENT_TRACE session bound to the agent identity, then announce it on the chain. */
178
242
  start(input?: StartSessionInput): Promise<Session>;
243
+ private identityFor;
244
+ private create;
179
245
  /** Seller half: emit INTERNAL_NETWORK evidence into a session the buyer bound, or open a seller-born one. */
180
246
  ensure(sessionId?: string | null): Promise<Session>;
181
247
  private attach;
182
248
  }
183
249
 
184
- 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 };
@@ -0,0 +1,26 @@
1
+ import { D as Decision } from './index-Bjs3BPPU.js';
2
+
3
+ /**
4
+ * The verdict as a value (Fraud Engine RFC › API: ALLOW | DENY | REVIEW).
5
+ * Layers combine by severity (GAP-45); a synchronous seller hook turns
6
+ * REVIEW into a stop or a pass according to its `onReview` (GAP-52). Both
7
+ * halves of the SDK and the engine share this one rule.
8
+ */
9
+
10
+ type OnReview = 'abort' | 'allow';
11
+ declare class Verdict {
12
+ readonly value: Decision;
13
+ private constructor();
14
+ static readonly ALLOW: Verdict;
15
+ static readonly REVIEW: Verdict;
16
+ static readonly DENY: Verdict;
17
+ static of(value: Decision): Verdict;
18
+ /** The more severe of the two. */
19
+ atLeast(other: Verdict | Decision): Verdict;
20
+ /** Whether a synchronous gate must stop the call (GAP-52). */
21
+ blocks(onReview: OnReview): boolean;
22
+ /** What the gate effectively did: DENY when it blocked, else the verdict itself. */
23
+ effective(onReview: OnReview): Decision;
24
+ }
25
+
26
+ export { type OnReview as O, Verdict as V };
@@ -1,11 +1,12 @@
1
1
  import { RoutesConfig, x402ResourceServer } from '@x402/core/server';
2
2
  import { RequestHandler } from 'express';
3
- import { B as Beltic } from '../client-CVx9LgJC.js';
4
- import { G as GuardedMiddlewareOptions } from '../middleware-_DSwvNIx.js';
5
- export { a as attachX402 } from '../middleware-_DSwvNIx.js';
6
- import './api.js';
7
- import '../session-BMNB1N1g.js';
8
- import './base58.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
+ import '../index-Bjs3BPPU.js';
7
+ import 'zod';
8
+ import '../verdict-BAahb5po.js';
9
+ import '../session-5TClPLI4.js';
9
10
 
10
11
  /** Seller half for express: `@x402/express`'s payment middleware with the Beltic hooks attached. */
11
12
 
@@ -1,13 +1,14 @@
1
1
  import {
2
- attachX402,
3
2
  guardedPaymentMiddleware
4
- } from "../chunk-YVMJ5CZX.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-46QN2KEZ.js";
10
+ import "../chunk-LM4NIYE5.js";
9
11
  import "../chunk-FQDHFTVR.js";
10
- import "../chunk-GCKCAKHA.js";
11
12
 
12
13
  // src/x402/express.ts
13
14
  import { paymentMiddlewareFromHTTPServer } from "@x402/express";
@@ -1,11 +1,12 @@
1
1
  import { RoutesConfig, x402ResourceServer } from '@x402/core/server';
2
2
  import { MiddlewareHandler } from 'hono';
3
- import { B as Beltic } from '../client-CVx9LgJC.js';
4
- import { G as GuardedMiddlewareOptions } from '../middleware-_DSwvNIx.js';
5
- export { a as attachX402 } from '../middleware-_DSwvNIx.js';
6
- import './api.js';
7
- import '../session-BMNB1N1g.js';
8
- import './base58.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
+ import '../index-Bjs3BPPU.js';
7
+ import 'zod';
8
+ import '../verdict-BAahb5po.js';
9
+ import '../session-5TClPLI4.js';
9
10
 
10
11
  /** Seller half for hono: `@x402/hono`'s payment middleware with the Beltic hooks attached. */
11
12
 
package/dist/x402/hono.js CHANGED
@@ -1,13 +1,14 @@
1
1
  import {
2
- attachX402,
3
2
  guardedPaymentMiddleware
4
- } from "../chunk-YVMJ5CZX.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-46QN2KEZ.js";
10
+ import "../chunk-LM4NIYE5.js";
9
11
  import "../chunk-FQDHFTVR.js";
10
- import "../chunk-GCKCAKHA.js";
11
12
 
12
13
  // src/x402/hono.ts
13
14
  import { paymentMiddlewareFromHTTPServer } from "@x402/hono";
@@ -1,10 +1,10 @@
1
- export { A as AttachOptions, b as AttachedX402, C as CorrelationContext, G as GuardedMiddlewareOptions, a as attachX402, g as guardedPaymentMiddleware } from '../middleware-_DSwvNIx.js';
2
- import { S as Session } from '../session-BMNB1N1g.js';
3
- import { PaymentMomentPayload, JsonObject } from './api.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-CVx9LgJC.js';
7
- import './base58.js';
5
+ import '../client-C-mV_3A0.js';
6
+ import '../verdict-BAahb5po.js';
7
+ import 'zod';
8
8
 
9
9
  /**
10
10
  * Session binding on the x402 rail (Fraud SDK RFC › Protocol Adapter — x402:
@@ -15,40 +15,116 @@ declare const SESSION_EXTENSION = "beltic.sessionId";
15
15
  declare const SESSION_HEADER = "Beltic-Session-Id";
16
16
  declare function sessionIdOf(extensions: Readonly<Record<string, unknown>> | undefined): string | null;
17
17
 
18
- 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;
19
62
 
20
63
  /**
21
64
  * x402 artifacts → protocol moments (Fraud SDK RFC › Protocol Adapter —
22
65
  * x402). The moment is normalized (payee, amount, payer) so both sides of
23
66
  * a purchase compare; the artifact travels whole in `raw`. For x402 the
24
67
  * currency is `<network>/<asset>` (GAP-49) and the value is the atomic
25
- * 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.
26
71
  */
27
72
 
28
- type Readonlyish<T> = {
29
- readonly [K in keyof T]: Readonlyish<T[K]>;
30
- } | T;
31
73
  declare function x402Currency(network: string, asset: string): string;
32
- /** 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
+ */
33
78
  interface AcceptsLike {
34
79
  payTo?: string | undefined;
35
80
  amount?: string | undefined;
81
+ maxAmountRequired?: string | undefined;
36
82
  network?: string | undefined;
37
83
  asset?: string | undefined;
38
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;
39
111
  /** The payer is scheme-specific; the common EVM shapes are read, anything else stays in `raw`. */
40
- declare function payerOf(payload: Readonlyish<PaymentPayload>): string | undefined;
112
+ declare function payerOf(payload: PaymentPayloadLike): string | undefined;
41
113
  declare const x402Moments: {
42
- /** The 402 challenge as the buyer saw it. */
43
- required(required: Readonlyish<PaymentRequired>): PaymentMomentPayload;
114
+ /** The 402 challenge as the buyer saw it, v2 header or v1 body. */
115
+ required(required: PaymentRequiredLike): PaymentMomentPayload;
44
116
  /** The requirements the seller's resource server resolved for a request. */
45
- requirements(req: Readonlyish<PaymentRequirements>): PaymentMomentPayload;
117
+ requirements(req: AcceptsLike): PaymentMomentPayload;
46
118
  /** A route's static `accepts` config, before any payment header exists. */
47
119
  route(route: unknown, raw: JsonObject): PaymentMomentPayload;
48
120
  /** An in-band ask (MRTR `input_required` or a `_meta` envelope) carrying an x402-style `accepts`. */
49
121
  ask(first: AcceptsLike, raw: JsonObject): PaymentMomentPayload;
50
- /** The signed payment the buyer presented. */
51
- 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;
52
128
  };
53
129
 
54
- 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-YVMJ5CZX.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-GCKCAKHA.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.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": {
@@ -16,6 +16,10 @@
16
16
  "types": "./dist/index.d.ts",
17
17
  "default": "./dist/index.js"
18
18
  },
19
+ "./protocol": {
20
+ "types": "./dist/protocol/index.d.ts",
21
+ "default": "./dist/protocol/index.js"
22
+ },
19
23
  "./ai": {
20
24
  "types": "./dist/ai/index.d.ts",
21
25
  "default": "./dist/ai/index.js"
@@ -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
- };