@belticlabs/agent-risk-sdk 0.1.1 → 0.3.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,55 +1,116 @@
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-BEpzr2R3.js';
2
+ import { S as SessionSource } from '../session-DsBWEP8d.js';
3
+ import { v as DeclaredIntent, P as PaymentMomentPayload, J as JsonObject, a as PaymentSummary } from '../verdict-6vCyoAHE.js';
5
4
  import '@x402/core/server';
6
- import '../client-NxpD_384.js';
7
- import '../verdict-BAahb5po.js';
8
5
  import 'zod';
9
6
 
10
7
  /**
11
8
  * Session binding on the x402 rail (Fraud SDK RFC › Protocol Adapter — x402:
12
9
  * "`PAYMENT-SIGNATURE` extension `beltic.sessionId`"). Where exactly it
13
10
  * lives is GAP-30; that it is outside the wallet-signed payload is GAP-56.
11
+ * A presentation the buyer had evaluated first also carries the
12
+ * `decisionId` the platform answered with, next to the session (GAP-80).
14
13
  */
15
14
  declare const SESSION_EXTENSION = "beltic.sessionId";
16
15
  declare const SESSION_HEADER = "Beltic-Session-Id";
16
+ declare const DECISION_EXTENSION = "beltic.decisionId";
17
+ declare const DECISION_HEADER = "Beltic-Decision-Id";
17
18
  declare function sessionIdOf(extensions: Readonly<Record<string, unknown>> | undefined): string | null;
19
+ declare function decisionIdOf(extensions: Readonly<Record<string, unknown>> | undefined): string | null;
18
20
 
19
- declare function belticFetch(session: Session, inner?: typeof globalThis.fetch): typeof globalThis.fetch;
21
+ /**
22
+ * The paying fetch, observed. Takes a session or the run that owns one;
23
+ * without either (the evidence stream is not configured, or the platform
24
+ * refused to open one) the inner fetch is returned as is — or runs as is
25
+ * per request when the run has no session yet — so a host wires it
26
+ * unconditionally.
27
+ */
28
+ declare function belticFetch(source: SessionSource, inner?: typeof globalThis.fetch): typeof globalThis.fetch;
29
+
30
+ /**
31
+ * The declared intent for an x402 mandate (Fraud SDK RFC › Session ›
32
+ * `intent.declared`). The cap must be in the currency the rail's moments
33
+ * carry — `<network>/<asset>`, atomic units (GAP-49) — or the platform's
34
+ * spend detectors compare two currencies and never meet; this is the one
35
+ * place a buyer spells that. Everything else is the protocol's own shape.
36
+ */
37
+
38
+ interface X402IntentInput {
39
+ mandate: string;
40
+ network: string;
41
+ asset: string;
42
+ /** Atomic units of `asset`, as x402 carries amounts — a decimal string or a bigint, never a float. */
43
+ maxAmount: string | bigint;
44
+ validUntil: string | Date;
45
+ merchantAllowlist?: string[] | undefined;
46
+ }
47
+ declare function x402Intent(input: X402IntentInput): DeclaredIntent;
20
48
 
21
49
  /**
22
50
  * x402 artifacts → protocol moments (Fraud SDK RFC › Protocol Adapter —
23
51
  * x402). The moment is normalized (payee, amount, payer) so both sides of
24
52
  * a purchase compare; the artifact travels whole in `raw`. For x402 the
25
53
  * currency is `<network>/<asset>` (GAP-49) and the value is the atomic
26
- * amount as the protocol carries it.
54
+ * amount as the protocol carries it — `x402Summary` gives a buyer the same
55
+ * normalization for the payment it is about to evaluate, so what it asks
56
+ * about and what the wrapper records are one and the same.
27
57
  */
28
58
 
29
- type Readonlyish<T> = {
30
- readonly [K in keyof T]: Readonlyish<T[K]>;
31
- } | T;
32
59
  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. */
60
+ /**
61
+ * The minimum an `accepts` entry needs to become a moment; unknown parts
62
+ * are named, never dropped. v1 spells the amount `maxAmountRequired`.
63
+ */
34
64
  interface AcceptsLike {
35
65
  payTo?: string | undefined;
36
66
  amount?: string | undefined;
67
+ maxAmountRequired?: string | undefined;
37
68
  network?: string | undefined;
38
69
  asset?: string | undefined;
39
70
  }
71
+ /**
72
+ * A 402 challenge and a presented payment of either generation, as far as
73
+ * a moment needs them. `@x402/core`'s `PaymentRequired` and
74
+ * `PaymentPayload` satisfy these structurally, so the seller adapter
75
+ * passes its typed values through and the buyer needs no `@x402/*` types.
76
+ */
77
+ interface PaymentRequiredLike {
78
+ x402Version?: number | undefined;
79
+ accepts?: readonly AcceptsLike[] | undefined;
80
+ }
81
+ interface PaymentPayloadLike {
82
+ x402Version?: number | undefined;
83
+ accepted?: AcceptsLike | undefined;
84
+ /** v1 names the network here, beside the payload, and nowhere else. */
85
+ network?: string | undefined;
86
+ payload?: Readonly<Record<string, unknown>> | undefined;
87
+ extensions?: Readonly<Record<string, unknown>> | undefined;
88
+ }
89
+ /**
90
+ * The one normalization of an x402 `accepts` entry: the payment in the
91
+ * shape `evaluate` takes, with the payer as `payerOf` would read it. Every
92
+ * moment below is this plus its artifact and `raw`.
93
+ */
94
+ declare function x402Summary(accepts: AcceptsLike | undefined, opts?: {
95
+ payer?: string | undefined;
96
+ }): PaymentSummary;
40
97
  /** 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;
98
+ declare function payerOf(payload: PaymentPayloadLike): string | undefined;
42
99
  declare const x402Moments: {
43
- /** The 402 challenge as the buyer saw it. */
44
- required(required: Readonlyish<PaymentRequired>): PaymentMomentPayload;
100
+ /** The 402 challenge as the buyer saw it, v2 header or v1 body. */
101
+ required(required: PaymentRequiredLike): PaymentMomentPayload;
45
102
  /** The requirements the seller's resource server resolved for a request. */
46
- requirements(req: Readonlyish<PaymentRequirements>): PaymentMomentPayload;
103
+ requirements(req: AcceptsLike): PaymentMomentPayload;
47
104
  /** A route's static `accepts` config, before any payment header exists. */
48
105
  route(route: unknown, raw: JsonObject): PaymentMomentPayload;
49
106
  /** An in-band ask (MRTR `input_required` or a `_meta` envelope) carrying an x402-style `accepts`. */
50
107
  ask(first: AcceptsLike, raw: JsonObject): PaymentMomentPayload;
51
- /** The signed payment the buyer presented. */
52
- payload(payload: Readonlyish<PaymentPayload>): PaymentMomentPayload;
108
+ /**
109
+ * The signed payment the buyer presented. A v2 payload carries the
110
+ * requirement it accepted; a v1 payload does not, so the caller passes
111
+ * the `accepts` entry it answered.
112
+ */
113
+ payload(payload: PaymentPayloadLike, accepts?: AcceptsLike | undefined): PaymentMomentPayload;
53
114
  };
54
115
 
55
- export { type AcceptsLike, SESSION_EXTENSION, SESSION_HEADER, belticFetch, payerOf, sessionIdOf, x402Currency, x402Moments };
116
+ export { type AcceptsLike, DECISION_EXTENSION, DECISION_HEADER, type PaymentPayloadLike, type PaymentRequiredLike, SESSION_EXTENSION, SESSION_HEADER, type X402IntentInput, belticFetch, decisionIdOf, payerOf, sessionIdOf, x402Currency, x402Intent, x402Moments, x402Summary };
@@ -1,67 +1,141 @@
1
1
  import {
2
- attachX402,
3
- guardedPaymentMiddleware
4
- } from "../chunk-W2OY7QXV.js";
2
+ attachX402
3
+ } from "../chunk-OKC6VMFH.js";
4
+ import {
5
+ Session,
6
+ Sessions
7
+ } from "../chunk-4MG6VNAU.js";
5
8
  import {
9
+ DECISION_EXTENSION,
10
+ DECISION_HEADER,
6
11
  SESSION_EXTENSION,
7
12
  SESSION_HEADER,
13
+ decisionIdOf,
8
14
  sessionIdOf
9
- } from "../chunk-VM7MK43J.js";
10
- import "../chunk-SFGM7KOG.js";
15
+ } from "../chunk-FAQ442YH.js";
16
+ import "../chunk-X3W2Z5GC.js";
17
+ import "../chunk-46QN2KEZ.js";
11
18
  import {
12
19
  payerOf,
13
20
  x402Currency,
14
- x402Moments
15
- } from "../chunk-U5Z5Z2BQ.js";
16
- import "../chunk-7G5EHNVW.js";
21
+ x402Moments,
22
+ x402Summary
23
+ } from "../chunk-LM4NIYE5.js";
17
24
  import "../chunk-FQDHFTVR.js";
18
- import "../chunk-46QN2KEZ.js";
19
25
 
20
26
  // src/x402/fetch.ts
21
- import {
22
- decodePaymentRequiredHeader,
23
- decodePaymentSignatureHeader,
24
- encodePaymentSignatureHeader
25
- } from "@x402/core/http";
26
- function belticFetch(session, inner = globalThis.fetch) {
27
+ var BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
28
+ var CHALLENGE_MEMORY = 32;
29
+ function belticFetch(source, inner = globalThis.fetch) {
30
+ if (!source) return inner;
31
+ const run = source instanceof Session ? null : source;
32
+ const challenges = /* @__PURE__ */ new Map();
27
33
  return async (input, init) => {
34
+ const session = await Sessions.resolve(source);
35
+ if (!session) return inner(input, init);
36
+ const url = urlOf(input);
28
37
  const headers = new Headers(
29
38
  init?.headers ?? (input instanceof Request ? input.headers : void 0)
30
39
  );
31
40
  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
- }
41
+ const signature = headers.get("PAYMENT-SIGNATURE");
42
+ const presented = signature ?? headers.get("X-PAYMENT");
43
+ const payload = presented ? decodeHeader(presented) : null;
44
+ if (payload) {
45
+ const accepts = payload.accepted ?? answeredAccepts(challenges.get(url), payload);
46
+ const decision = run?.decisionFor(x402Moments.payload(payload, accepts));
47
+ const decisionId = decision && !decision.absent ? decision.decisionId : null;
48
+ const bound = signature ? {
49
+ ...payload,
50
+ extensions: {
51
+ ...payload.extensions,
52
+ [SESSION_EXTENSION]: session.id,
53
+ ...decisionId ? { [DECISION_EXTENSION]: decisionId } : {}
54
+ }
55
+ } : payload;
56
+ if (signature) headers.set("PAYMENT-SIGNATURE", encodeHeader(bound));
57
+ else if (decisionId) headers.set(DECISION_HEADER, decisionId);
58
+ await session.emit("payment.presented", x402Moments.payload(bound, accepts));
59
+ await session.flush();
42
60
  }
43
61
  const res = await inner(input, { ...init, headers });
44
- const required = res.status === 402 ? res.headers.get("PAYMENT-REQUIRED") : null;
62
+ if (res.status !== 402) return res;
63
+ const required = await challengeOf(res);
45
64
  if (required) {
46
- try {
47
- await session.emit(
48
- "payment.requested",
49
- x402Moments.required(decodePaymentRequiredHeader(required))
50
- );
51
- } catch {
52
- }
65
+ challenges.set(url, required);
66
+ if (challenges.size > CHALLENGE_MEMORY) challenges.delete(challenges.keys().next().value);
67
+ await session.emit("payment.requested", x402Moments.required(required));
53
68
  }
54
69
  return res;
55
70
  };
56
71
  }
72
+ function decodeHeader(value) {
73
+ if (!BASE64.test(value)) return null;
74
+ try {
75
+ const parsed = JSON.parse(Buffer.from(value, "base64").toString("utf8"));
76
+ return parsed && typeof parsed === "object" ? parsed : null;
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+ function encodeHeader(value) {
82
+ return Buffer.from(JSON.stringify(value), "utf8").toString("base64");
83
+ }
84
+ function urlOf(input) {
85
+ return typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
86
+ }
87
+ async function challengeOf(res) {
88
+ const header = res.headers.get("PAYMENT-REQUIRED");
89
+ if (header) return decodeHeader(header);
90
+ if (!/json/i.test(res.headers.get("content-type") ?? "")) return null;
91
+ try {
92
+ const body = await res.clone().json();
93
+ return Array.isArray(body?.accepts) ? body : null;
94
+ } catch {
95
+ return null;
96
+ }
97
+ }
98
+ function answeredAccepts(required, payload) {
99
+ const auth = payload.payload?.authorization;
100
+ const payTo = typeof auth?.to === "string" ? auth.to : void 0;
101
+ const amount = typeof auth?.value === "string" ? auth.value : void 0;
102
+ const options = required?.accepts ?? [];
103
+ const answered = options.find(
104
+ (a) => a.payTo?.toLowerCase() === payTo?.toLowerCase() && (a.amount ?? a.maxAmountRequired) === amount
105
+ ) ?? options[0];
106
+ if (answered) return answered;
107
+ if (!payTo && !amount) return void 0;
108
+ return {
109
+ ...payTo ? { payTo } : {},
110
+ ...amount ? { amount } : {},
111
+ ...payload.network ? { network: payload.network } : {}
112
+ };
113
+ }
114
+
115
+ // src/x402/intent.ts
116
+ function x402Intent(input) {
117
+ return {
118
+ mandate: input.mandate,
119
+ maxAmount: {
120
+ value: input.maxAmount.toString(),
121
+ currency: x402Currency(input.network, input.asset)
122
+ },
123
+ validUntil: new Date(input.validUntil).toISOString(),
124
+ ...input.merchantAllowlist ? { merchantAllowlist: input.merchantAllowlist } : {}
125
+ };
126
+ }
57
127
  export {
128
+ DECISION_EXTENSION,
129
+ DECISION_HEADER,
58
130
  SESSION_EXTENSION,
59
131
  SESSION_HEADER,
60
132
  attachX402,
61
133
  belticFetch,
62
- guardedPaymentMiddleware,
134
+ decisionIdOf,
63
135
  payerOf,
64
136
  sessionIdOf,
65
137
  x402Currency,
66
- x402Moments
138
+ x402Intent,
139
+ x402Moments,
140
+ x402Summary
67
141
  };
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.3.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
- };
@@ -1,13 +0,0 @@
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
- };
@@ -1,64 +0,0 @@
1
- import { a as PaymentSummary, B as EvaluateOutput } from './index-Bjs3BPPU.js';
2
- import { O as OnReview } from './verdict-BAahb5po.js';
3
- import { a as ApiClient, T as Transport, d as Sessions, A as AgentIdentity, b as ApiClientOptions, g as TransportOptions, R as RedactFn } from './session-B2mfurae.js';
4
-
5
- /**
6
- * Correlation without binding (Fraud SDK RFC › Protocol Adapter — x402:
7
- * "binding travels on the call that initiates the purchase, not
8
- * necessarily on the payment artifact"). The merchant binds a key it will
9
- * see again (a checkout session id, a challenge nonce) to the buyer's
10
- * session; the adapter resolves it when the settlement arrives (GAP-31).
11
- */
12
- interface CorrelationStore {
13
- bind(key: string, sessionId: string, ttlMs?: number): Promise<void>;
14
- resolve(key: string): Promise<string | null>;
15
- }
16
- declare class MemoryCorrelationStore implements CorrelationStore {
17
- private readonly defaultTtlMs;
18
- private readonly entries;
19
- constructor(defaultTtlMs?: number);
20
- bind(key: string, sessionId: string, ttlMs?: number): Promise<void>;
21
- resolve(key: string): Promise<string | null>;
22
- }
23
-
24
- /**
25
- * One SDK, two halves (Fraud SDK RFC › Summary). `Beltic` is the single
26
- * client: sessions and evidence for both halves, `evaluate` for the seller
27
- * half. Protocol integrations are plain functions behind subpath exports,
28
- * each pulling exactly one optional peer:
29
- *
30
- * @belticlabs/agent-risk-sdk/ai → middleware(session), wrapTools(session, …)
31
- * @belticlabs/agent-risk-sdk/x402 → belticFetch(session), attachX402(beltic, …)
32
- * @belticlabs/agent-risk-sdk/hono → belticPaymentMiddleware(beltic, …) (and /express)
33
- * @belticlabs/agent-risk-sdk/mcp → wrapClient(session, …), wrapServer(beltic, …)
34
- *
35
- * Neither half decides risk locally: verdicts are platform-side.
36
- */
37
-
38
- declare const SDK_VERSION = "0.1.1";
39
- interface BelticOptions extends Omit<ApiClientOptions, 'userAgent'> {
40
- /** Buyer half. Without it, `sessions.start` is unavailable; the seller half works. */
41
- identity?: AgentIdentity | undefined;
42
- transport?: Partial<TransportOptions> | undefined;
43
- /** What a synchronous seller hook does with REVIEW (GAP-52). */
44
- onReview?: OnReview | undefined;
45
- correlation?: CorrelationStore | undefined;
46
- redact?: RedactFn | undefined;
47
- now?: (() => Date) | undefined;
48
- }
49
- declare class Beltic {
50
- readonly api: ApiClient;
51
- readonly transport: Transport;
52
- readonly sessions: Sessions;
53
- readonly identity: AgentIdentity | undefined;
54
- readonly correlation: CorrelationStore;
55
- readonly onReview: OnReview;
56
- constructor(opts: BelticOptions);
57
- /** Read-your-writes: the platform must hold the evidence before it judges it (GAP-16). */
58
- evaluate(sessionId: string, payment: PaymentSummary): Promise<EvaluateOutput>;
59
- flush(): Promise<void>;
60
- shutdown(): Promise<void>;
61
- }
62
- declare function createBeltic(opts: BelticOptions): Beltic;
63
-
64
- export { Beltic as B, type CorrelationStore as C, MemoryCorrelationStore as M, SDK_VERSION as S, type BelticOptions as a, createBeltic as c };
@@ -1,40 +0,0 @@
1
- import { x402ResourceServer, x402HTTPResourceServer, PaywallConfig, RoutesConfig } from '@x402/core/server';
2
- import { B as Beltic } from './client-NxpD_384.js';
3
- import { D as Decision } from './index-Bjs3BPPU.js';
4
- import { c as SessionBorn, S as Session } from './session-B2mfurae.js';
5
-
6
- interface CorrelationContext {
7
- path: string;
8
- method: string;
9
- header: (name: string) => string | undefined;
10
- }
11
- interface AttachOptions {
12
- /** A key the merchant will see again at verify time, for delegated flows (GAP-31). */
13
- correlate?: ((ctx: CorrelationContext) => string | null) | undefined;
14
- /** Called with every decision; the default logs nothing. */
15
- onDecision?: ((d: {
16
- sessionId: string;
17
- decision: Decision;
18
- reasonCodes: string[];
19
- born: SessionBorn;
20
- }) => void) | undefined;
21
- }
22
- interface AttachedX402 {
23
- /** Sessions resolved at verify time, keyed by payment digest — for tests and settle hooks. */
24
- readonly inFlight: ReadonlyMap<string, Session>;
25
- }
26
- declare function attachX402(beltic: Beltic, server: x402ResourceServer, http?: x402HTTPResourceServer, opts?: AttachOptions): AttachedX402;
27
-
28
- /**
29
- * The seller half as one middleware: the framework's `@x402/*` payment
30
- * middleware over an `x402HTTPResourceServer` with the Beltic hooks
31
- * attached. `@belticlabs/agent-risk-sdk/hono` and `@belticlabs/agent-risk-sdk/express` differ only in
32
- * which `paymentMiddlewareFromHTTPServer` they hand in.
33
- */
34
-
35
- type GuardedMiddlewareOptions = AttachOptions & {
36
- paywall?: PaywallConfig | undefined;
37
- };
38
- declare function guardedPaymentMiddleware<M>(fromHTTPServer: (http: x402HTTPResourceServer, paywall?: PaywallConfig) => M, beltic: Beltic, routes: RoutesConfig, server: x402ResourceServer, opts?: GuardedMiddlewareOptions): M;
39
-
40
- export { type AttachOptions as A, type CorrelationContext as C, type GuardedMiddlewareOptions as G, attachX402 as a, type AttachedX402 as b, guardedPaymentMiddleware as g };
@@ -1,196 +0,0 @@
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';
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
- }
15
-
16
- interface ApiClientOptions {
17
- baseUrl: string;
18
- apiKey: string;
19
- fetch?: typeof globalThis.fetch;
20
- timeoutMs?: number;
21
- userAgent?: string;
22
- }
23
- declare class BelticApiError extends Error {
24
- readonly status: number;
25
- readonly code: string;
26
- readonly details?: unknown | undefined;
27
- readonly requestId?: string | undefined;
28
- constructor(status: number, code: string, message: string, details?: unknown | undefined, requestId?: string | undefined);
29
- /** 5xx and network failures are retried by the transport; 4xx are not. */
30
- get retryable(): boolean;
31
- }
32
- declare class ApiClient {
33
- private readonly baseUrl;
34
- private readonly fetchImpl;
35
- private readonly timeoutMs;
36
- private readonly headers;
37
- constructor(opts: ApiClientOptions);
38
- post<T>(path: string, body: unknown, headers?: Record<string, string>): Promise<T>;
39
- get<T>(path: string, query?: Record<string, string | undefined>): Promise<T>;
40
- private request;
41
- }
42
-
43
- interface AgentIdentity {
44
- did: string;
45
- signer: Signer;
46
- /** Opaque credential presented at session start (stored, not verified this phase). */
47
- credential?: string;
48
- }
49
- declare function identityFromSeed(seed: Uint8Array, credential?: string): AgentIdentity;
50
- declare function ephemeralIdentity(credential?: string): AgentIdentity;
51
- /** A JSON keystore on disk: `{ "seed": "<64 hex>" }`, created 0600 when missing. */
52
- declare function fileIdentity(path: string, credential?: string): AgentIdentity;
53
-
54
- /**
55
- * Transport (Fraud SDK RFC › Modules: "buffering, batching, chained delivery
56
- * to the Collector"). Contract as assumed in GAP-18/38: one FIFO per
57
- * chain; at most one batch in flight per chain, so order is preserved;
58
- * exponential backoff on network / 5xx; a `fork` or `rejected` ack halts
59
- * the chain and surfaces `ChainRejectedError` — an SDK must not silently
60
- * keep chaining onto a head the platform never accepted.
61
- */
62
-
63
- interface TransportOptions {
64
- maxBatch: number;
65
- flushMs: number;
66
- /** Total buffered events across chains; beyond this new events are dropped (GAP-38). */
67
- maxBuffered: number;
68
- backoff: {
69
- baseMs: number;
70
- maxMs: number;
71
- maxAttempts: number;
72
- };
73
- onAck?: (ack: EvidenceAck) => void;
74
- onError?: (err: Error) => void;
75
- onChainHalted?: (err: ChainRejectedError) => void;
76
- setTimeout?: typeof globalThis.setTimeout;
77
- clearTimeout?: typeof globalThis.clearTimeout;
78
- }
79
- declare const DEFAULT_TRANSPORT: TransportOptions;
80
- declare class ChainRejectedError extends Error {
81
- readonly sessionId: string;
82
- readonly source: string;
83
- readonly result: EventResult;
84
- constructor(sessionId: string, source: string, result: EventResult);
85
- }
86
- declare class TransportClosedError extends Error {
87
- constructor();
88
- }
89
- declare class Transport {
90
- private readonly api;
91
- private readonly opts;
92
- private readonly chains;
93
- private buffered;
94
- private timer;
95
- private closed;
96
- private inFlightCount;
97
- private drainWaiters;
98
- constructor(api: ApiClient, opts?: Partial<TransportOptions>);
99
- get size(): number;
100
- hasRoom(): boolean;
101
- haltedError(sessionId: string, source: string): ChainRejectedError | null;
102
- /** Callers check `hasRoom()` first and assign `seq` only then (GAP-38). */
103
- enqueue(ev: EvidenceEvent): void;
104
- /** Send everything pending and wait for every in-flight batch to settle (ack or halt). */
105
- flush(): Promise<void>;
106
- close(): Promise<void>;
107
- private schedule;
108
- private unschedule;
109
- private drained;
110
- private settleWaiters;
111
- private flushChain;
112
- private send;
113
- }
114
-
115
- /**
116
- * A risk session as the SDK sees it: one chain per (sessionId, source),
117
- * built at the edge (Fraud SDK RFC › Wire contract). The buyer half opens
118
- * AGENT_TRACE sessions and announces them with `session.open` (seq 0) and
119
- * `intent.declared` (seq 1; GAP-23/60); the seller half attaches to a bound
120
- * session or opens its own INTERNAL_NETWORK session (GAP-13).
121
- *
122
- * `seq` is handed out only when the transport has room for the event
123
- * (GAP-38): a dropped event never leaves a hole — the next accepted event
124
- * is preceded by a `transport.gap` that counts the drops. `redact` is off
125
- * by default (GAP-33).
126
- */
127
-
128
- type RedactFn = (kind: WireEvidenceKind, payload: JsonObject) => JsonObject;
129
- /** Who created the session — the seller half treats a bound session as buyer-born. */
130
- type SessionBorn = 'buyer' | 'seller';
131
- interface SessionDeps {
132
- transport: Transport;
133
- signer?: Signer | undefined;
134
- redact?: RedactFn | undefined;
135
- now?: (() => Date) | undefined;
136
- /** Called once the session closed, so the registry can forget it. */
137
- onClosed?: ((session: Session) => void) | undefined;
138
- }
139
- declare class Session {
140
- private readonly deps;
141
- readonly id: string;
142
- readonly source: EvidenceSource;
143
- readonly expiresAt: string | null;
144
- readonly born: SessionBorn;
145
- private chain;
146
- private building;
147
- private dropped;
148
- private droppedFirstTs;
149
- private droppedLastTs;
150
- private closed;
151
- private readonly now;
152
- constructor(deps: SessionDeps, id: string, source: EvidenceSource, expiresAt: string | null, born: SessionBorn);
153
- get head(): ChainHead | null;
154
- get droppedCount(): number;
155
- /** Resolves once the event is sequenced and buffered — not once it is acknowledged. */
156
- emit<K extends WireEvidenceKind>(kind: K, payload: PayloadByKind[K]): Promise<boolean>;
157
- close(reason?: SessionClosePayload['reason'], extra?: JsonObject): Promise<void>;
158
- /** Read-your-writes: the platform must hold the evidence before anyone judges it (GAP-16/66). */
159
- flush(): Promise<void>;
160
- /** Serialized: two concurrent emits get consecutive seqs, never the same one. */
161
- private next;
162
- }
163
- interface StartSessionInput {
164
- intent?: DeclaredIntent;
165
- runtime?: {
166
- framework?: string;
167
- model?: string;
168
- };
169
- attestations?: JsonObject;
170
- }
171
- interface SessionsDeps {
172
- api: ApiClient;
173
- transport: Transport;
174
- identity?: AgentIdentity | undefined;
175
- redact?: RedactFn | undefined;
176
- now?: (() => Date) | undefined;
177
- sdkVersion: string;
178
- }
179
- declare class Sessions {
180
- private readonly deps;
181
- /**
182
- * One session object per (session, source) per process: a chain's head
183
- * lives in it, so two objects for the same chain would both start at
184
- * seq 0 and fork it. Closed sessions are forgotten; a process restart
185
- * mid-session still loses the head (GAP-67).
186
- */
187
- private readonly attached;
188
- constructor(deps: SessionsDeps);
189
- /** Buyer half: create an AGENT_TRACE session bound to the agent identity, then announce it on the chain. */
190
- start(input?: StartSessionInput): Promise<Session>;
191
- /** Seller half: emit INTERNAL_NETWORK evidence into a session the buyer bound, or open a seller-born one. */
192
- ensure(sessionId?: string | null): Promise<Session>;
193
- private attach;
194
- }
195
-
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 };