@belticlabs/agent-risk-sdk 0.4.0 → 0.5.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,15 +1,8 @@
1
- import { D as Decision } from './verdict-CDAsxktI.js';
1
+ import { D as Decision } from './verdict-DMnbFuS5.js';
2
2
  import { x402ResourceServer, x402HTTPResourceServer } from '@x402/core/server';
3
- import { B as Beltic, n as SessionBorn, a as Session } from './session-gK51QVAM.js';
3
+ import { B as Beltic, j as SessionBorn, i as Session } from './session-D9E-efc0.js';
4
4
 
5
- interface CorrelationContext {
6
- path: string;
7
- method: string;
8
- header: (name: string) => string | undefined;
9
- }
10
5
  interface AttachOptions {
11
- /** A key the merchant will see again at verify time, for delegated flows (GAP-31). */
12
- correlate?: ((ctx: CorrelationContext) => string | null) | undefined;
13
6
  /** Called with every decision; the default logs nothing. */
14
7
  onDecision?: ((d: {
15
8
  sessionId: string;
@@ -26,4 +19,4 @@ interface AttachedX402 {
26
19
  }
27
20
  declare function attachX402(beltic: Beltic, server: x402ResourceServer, http?: x402HTTPResourceServer, opts?: AttachOptions): AttachedX402;
28
21
 
29
- export { type AttachOptions as A, type CorrelationContext as C, attachX402 as a, type AttachedX402 as b };
22
+ export { type AttachOptions as A, attachX402 as a, type AttachedX402 as b };
@@ -1,6 +1,6 @@
1
- import { P as PaymentMomentPayload } from '../verdict-CDAsxktI.js';
1
+ import { P as PaymentMomentPayload } from '../verdict-DMnbFuS5.js';
2
2
  import { LanguageModelMiddleware, Tool, ToolSet } from 'ai';
3
- import { S as SessionSource } from '../session-gK51QVAM.js';
3
+ import { S as SessionSource } from '../session-D9E-efc0.js';
4
4
  import 'zod';
5
5
 
6
6
  interface PaymentMoments {
package/dist/ai/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Sessions,
3
3
  recordCall
4
- } from "../chunk-4MG6VNAU.js";
4
+ } from "../chunk-JSE6JQJC.js";
5
5
  import {
6
6
  toJson,
7
7
  toJsonObject
@@ -24,7 +24,6 @@ var PARAM_KEYS = [
24
24
  "stopSequences"
25
25
  ];
26
26
  function middleware(source) {
27
- if (!source) return {};
28
27
  return {
29
28
  wrapGenerate: async ({ doGenerate, params, model }) => {
30
29
  const session = await Sessions.resolve(source);
@@ -80,7 +79,7 @@ function middleware(source) {
80
79
  };
81
80
  }
82
81
  function wrapTool(source, tool, opts = {}) {
83
- if (!source || !tool.execute) return tool;
82
+ if (!tool.execute) return tool;
84
83
  const original = tool.execute;
85
84
  const toolName = opts.name ?? tool.name ?? "tool";
86
85
  const execute = async (input, options) => {
@@ -89,7 +88,7 @@ function wrapTool(source, tool, opts = {}) {
89
88
  return recordCall(
90
89
  session,
91
90
  "tool_call",
92
- options?.toolCallId ?? uuidv7(),
91
+ options.toolCallId,
93
92
  { toolName, input: toJson(input), transport: "local" },
94
93
  async () => {
95
94
  const output = await original(input, options);
@@ -97,20 +97,11 @@ function priceValue(price) {
97
97
  // src/x402/adapter.ts
98
98
  function attachX402(beltic, server, http, opts = {}) {
99
99
  const inFlight = /* @__PURE__ */ new Map();
100
- const resolveSession = async (bound, ctx) => {
101
- const key = opts.correlate?.(ctx) ?? null;
102
- return bound ?? (key ? await beltic.correlation.resolve(key) : null);
103
- };
104
100
  http?.onProtectedRequest(async (ctx, route) => {
105
101
  if (ctx.paymentHeader) return;
106
102
  const bound = ctx.adapter.getHeader(SESSION_HEADER) ?? ctx.adapter.getHeader(SESSION_HEADER.toLowerCase());
107
- const sessionId = await resolveSession(bound ?? null, {
108
- path: ctx.path,
109
- method: ctx.method,
110
- header: (n) => ctx.adapter.getHeader(n)
111
- });
112
- if (!sessionId) return;
113
- const session = await beltic.sessions.ensure(sessionId);
103
+ if (!bound) return;
104
+ const session = await beltic.sessions.ensure(bound);
114
105
  const accepts = Array.isArray(route.accepts) ? route.accepts[0] : route.accepts;
115
106
  await session.emit(
116
107
  "payment.requested",
@@ -124,12 +115,7 @@ function attachX402(beltic, server, http, opts = {}) {
124
115
  server.onBeforeVerify(async (ctx) => {
125
116
  const payload = ctx.paymentPayload;
126
117
  const requirements = ctx.requirements;
127
- const sessionId = await resolveSession(sessionIdOf(payload.extensions), {
128
- path: payload.resource?.url ?? "",
129
- method: "PAY",
130
- header: () => void 0
131
- });
132
- const session = await beltic.sessions.ensure(sessionId);
118
+ const session = await beltic.sessions.ensure(sessionIdOf(payload.extensions));
133
119
  if (session.born === "seller")
134
120
  await session.emit("payment.requested", x402Moments.requirements(requirements));
135
121
  const presented = x402Moments.payload(payload);
@@ -0,0 +1,530 @@
1
+ import {
2
+ Chain
3
+ } from "./chunk-X3W2Z5GC.js";
4
+
5
+ // src/core/api-client.ts
6
+ var TIMEOUT_MS = 1e4;
7
+ var BelticApiError = class extends Error {
8
+ constructor(status, code, message, details, requestId) {
9
+ super(message);
10
+ this.status = status;
11
+ this.code = code;
12
+ this.details = details;
13
+ this.requestId = requestId;
14
+ this.name = "BelticApiError";
15
+ }
16
+ /** 5xx, 429 and network failures are retried by the transport and absorbed by `failOpen`; 4xx are neither (GAP-70). */
17
+ get retryable() {
18
+ return this.status === 0 || this.status >= 500 || this.status === 429;
19
+ }
20
+ };
21
+ var ApiClient = class {
22
+ baseUrl;
23
+ fetchImpl;
24
+ headers;
25
+ constructor(opts) {
26
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
27
+ this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
28
+ this.headers = {
29
+ authorization: `Bearer ${opts.apiKey}`,
30
+ "content-type": "application/json",
31
+ "user-agent": opts.userAgent ?? "@belticlabs/agent-risk-sdk"
32
+ };
33
+ }
34
+ post(path, body, headers = {}) {
35
+ return this.request("POST", path, JSON.stringify(body), headers);
36
+ }
37
+ get(path, query = {}) {
38
+ const qs = Object.entries(query).filter((e) => e[1] !== void 0).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
39
+ return this.request("GET", qs ? `${path}?${qs}` : path, void 0, {});
40
+ }
41
+ async request(method, path, body, extra) {
42
+ const controller = new AbortController();
43
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
44
+ let res;
45
+ try {
46
+ res = await this.fetchImpl(`${this.baseUrl}${path}`, {
47
+ method,
48
+ headers: { ...this.headers, ...extra },
49
+ ...body !== void 0 ? { body } : {},
50
+ signal: controller.signal
51
+ });
52
+ } catch (err) {
53
+ throw new BelticApiError(
54
+ 0,
55
+ "NETWORK",
56
+ `request to ${path} failed: ${err.message}`
57
+ );
58
+ } finally {
59
+ clearTimeout(timer);
60
+ }
61
+ const text = await res.text();
62
+ let json = null;
63
+ try {
64
+ json = text ? JSON.parse(text) : null;
65
+ } catch {
66
+ json = null;
67
+ }
68
+ if (!res.ok) {
69
+ const e = json?.error;
70
+ throw new BelticApiError(
71
+ res.status,
72
+ e?.code ?? `HTTP_${res.status}`,
73
+ e?.message ?? res.statusText,
74
+ e?.details,
75
+ e?.request_id
76
+ );
77
+ }
78
+ return json;
79
+ }
80
+ };
81
+
82
+ // src/core/config-error.ts
83
+ var BelticConfigError = class extends Error {
84
+ code = "CONFIG";
85
+ constructor(message) {
86
+ super(`Beltic: ${message}`);
87
+ this.name = "BelticConfigError";
88
+ }
89
+ };
90
+
91
+ // src/core/record.ts
92
+ function errorOf(err) {
93
+ const e = err;
94
+ return { name: String(e?.name ?? "Error"), message: String(e?.message ?? err) };
95
+ }
96
+ function openCall(session, kind, callId, start) {
97
+ const started = Date.now();
98
+ const opened = session.emit(`${kind}.start`, { callId, ...start });
99
+ const close = async (outcome) => {
100
+ await opened;
101
+ return session.emit(
102
+ `${kind}.end`,
103
+ { callId, ...outcome, durationMs: Date.now() - started }
104
+ );
105
+ };
106
+ return {
107
+ callId,
108
+ opened,
109
+ end: (outcome = {}) => close(outcome),
110
+ fail: (error, outcome = {}) => close({ error: errorOf(error), ...outcome })
111
+ };
112
+ }
113
+ async function recordCall(session, kind, callId, start, run, end = () => ({})) {
114
+ const span = openCall(session, kind, callId, start);
115
+ await span.opened;
116
+ try {
117
+ const result = await run();
118
+ await span.end(await end(result));
119
+ return result;
120
+ } catch (err) {
121
+ await span.fail(err);
122
+ throw err;
123
+ }
124
+ }
125
+
126
+ // src/core/transport.ts
127
+ var MAX_BATCH = 50;
128
+ var DEFAULT_TUNING = {
129
+ flushMs: 1e3,
130
+ maxBuffered: 5e3,
131
+ backoff: { baseMs: 200, maxMs: 3e4, maxAttempts: Number.POSITIVE_INFINITY }
132
+ };
133
+ var ChainRejectedError = class extends Error {
134
+ constructor(sessionId, source, result, options) {
135
+ super(
136
+ `chain ${sessionId}:${source} halted at seq ${result.seq}: ${result.status}${result.code ? ` ${result.code}` : ""}`,
137
+ options
138
+ );
139
+ this.sessionId = sessionId;
140
+ this.source = source;
141
+ this.result = result;
142
+ this.name = "ChainRejectedError";
143
+ }
144
+ /** Halted by retries exhausted on an outage — not by anything the platform rejected (GAP-70). */
145
+ get transient() {
146
+ return this.cause instanceof BelticApiError && this.cause.retryable;
147
+ }
148
+ };
149
+ var TransportClosedError = class extends Error {
150
+ constructor() {
151
+ super("transport is closed");
152
+ this.name = "TransportClosedError";
153
+ }
154
+ };
155
+ var Transport = class {
156
+ constructor(api, opts = {}) {
157
+ this.api = api;
158
+ this.opts = {
159
+ ...DEFAULT_TUNING,
160
+ ...opts,
161
+ backoff: { ...DEFAULT_TUNING.backoff, ...opts.backoff }
162
+ };
163
+ }
164
+ opts;
165
+ chains = /* @__PURE__ */ new Map();
166
+ buffered = 0;
167
+ timer = null;
168
+ closed = false;
169
+ inFlightCount = 0;
170
+ drainWaiters = [];
171
+ /**
172
+ * What `failOpen` may absorb (GAP-70): the platform could not be reached
173
+ * or failed on its side — a network error, a 5xx, a 429, or a chain
174
+ * halted after exhausting its retries on those. Everything the platform
175
+ * *rejected* (a 4xx: bad key, unknown session, invalid payload; a `fork`
176
+ * or `rejected` ack) is a fault of the client and throws in both modes.
177
+ */
178
+ static outage(err) {
179
+ if (err instanceof BelticApiError) return err.retryable;
180
+ if (err instanceof ChainRejectedError) return err.transient;
181
+ return false;
182
+ }
183
+ get size() {
184
+ return this.buffered;
185
+ }
186
+ hasRoom() {
187
+ return !this.closed && this.buffered < this.opts.maxBuffered;
188
+ }
189
+ haltedError(sessionId, source) {
190
+ return this.chains.get(`${sessionId}:${source}`)?.halted ?? null;
191
+ }
192
+ /** Callers check `hasRoom()` first and assign `seq` only then (GAP-38). */
193
+ enqueue(ev) {
194
+ if (this.closed) throw new TransportClosedError();
195
+ const key = `${ev.sessionId}:${ev.source}`;
196
+ let chain = this.chains.get(key);
197
+ if (!chain) {
198
+ chain = { key, pending: [], inFlight: null, attempts: 0, halted: null };
199
+ this.chains.set(key, chain);
200
+ }
201
+ if (chain.halted) throw chain.halted;
202
+ if (!this.hasRoom()) throw new Error("transport buffer is full");
203
+ chain.pending.push(ev);
204
+ this.buffered++;
205
+ if (chain.pending.length >= MAX_BATCH) void this.flushChain(chain);
206
+ else this.schedule();
207
+ }
208
+ /** Send everything pending and wait for every in-flight batch to settle (ack or halt). */
209
+ async flush() {
210
+ this.unschedule();
211
+ for (const chain of this.chains.values()) void this.flushChain(chain);
212
+ await this.drained();
213
+ }
214
+ async close() {
215
+ await this.flush();
216
+ this.closed = true;
217
+ }
218
+ schedule() {
219
+ if (this.timer) return;
220
+ this.timer = setTimeout(() => {
221
+ this.timer = null;
222
+ for (const chain of this.chains.values()) void this.flushChain(chain);
223
+ }, this.opts.flushMs);
224
+ this.timer.unref?.();
225
+ }
226
+ unschedule() {
227
+ if (!this.timer) return;
228
+ clearTimeout(this.timer);
229
+ this.timer = null;
230
+ }
231
+ drained() {
232
+ if (this.inFlightCount === 0 && [...this.chains.values()].every((c) => c.pending.length === 0 || c.halted)) {
233
+ return Promise.resolve();
234
+ }
235
+ return new Promise((resolve) => this.drainWaiters.push(resolve));
236
+ }
237
+ settleWaiters() {
238
+ if (this.inFlightCount > 0) return;
239
+ if (![...this.chains.values()].every((c) => c.pending.length === 0 || c.halted)) return;
240
+ const waiters = this.drainWaiters;
241
+ this.drainWaiters = [];
242
+ for (const w of waiters) w();
243
+ }
244
+ async flushChain(chain) {
245
+ if (chain.inFlight || chain.halted || chain.pending.length === 0) {
246
+ this.settleWaiters();
247
+ return;
248
+ }
249
+ const batch = chain.pending.splice(0, MAX_BATCH);
250
+ chain.inFlight = batch;
251
+ this.inFlightCount++;
252
+ try {
253
+ const ack = await this.send(chain, batch);
254
+ this.buffered -= batch.length;
255
+ const bad = ack.results.find((r) => r.status === "fork" || r.status === "rejected");
256
+ if (bad) {
257
+ const first = batch[0];
258
+ chain.halted = new ChainRejectedError(first.sessionId, first.source, bad);
259
+ this.buffered -= chain.pending.length;
260
+ chain.pending = [];
261
+ this.opts.onChainHalted?.(chain.halted);
262
+ }
263
+ } catch (err) {
264
+ this.buffered -= batch.length + chain.pending.length;
265
+ chain.pending = [];
266
+ const first = batch[0];
267
+ chain.halted = new ChainRejectedError(
268
+ first.sessionId,
269
+ first.source,
270
+ {
271
+ index: 0,
272
+ sessionId: first.sessionId,
273
+ source: first.source,
274
+ seq: first.seq,
275
+ status: "rejected",
276
+ code: "DELIVERY_FAILED"
277
+ },
278
+ { cause: err }
279
+ );
280
+ this.opts.onError?.(err);
281
+ this.opts.onChainHalted?.(chain.halted);
282
+ } finally {
283
+ chain.inFlight = null;
284
+ this.inFlightCount--;
285
+ }
286
+ if (chain.pending.length > 0 && !chain.halted) void this.flushChain(chain);
287
+ else this.settleWaiters();
288
+ }
289
+ async send(chain, batch) {
290
+ const { baseMs, maxMs, maxAttempts } = this.opts.backoff;
291
+ for (let attempt = 0; ; attempt++) {
292
+ try {
293
+ const ack = await this.api.post("/v1/evidence", batch);
294
+ chain.attempts = 0;
295
+ return ack;
296
+ } catch (err) {
297
+ const retryable = err instanceof BelticApiError ? err.retryable : true;
298
+ if (!retryable || attempt + 1 >= maxAttempts) throw err;
299
+ this.opts.onError?.(err);
300
+ const delay = Math.min(maxMs, baseMs * 2 ** attempt) * (0.5 + Math.random() / 2);
301
+ await new Promise((r) => setTimeout(r, delay));
302
+ }
303
+ }
304
+ }
305
+ };
306
+
307
+ // src/core/session.ts
308
+ var Session = class {
309
+ constructor(deps, id, source, expiresAt, born) {
310
+ this.deps = deps;
311
+ this.id = id;
312
+ this.source = source;
313
+ this.expiresAt = expiresAt;
314
+ this.born = born;
315
+ this.chain = Chain.genesis(id, source);
316
+ this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
317
+ }
318
+ chain;
319
+ building = Promise.resolve();
320
+ dropped = 0;
321
+ droppedFirstTs = null;
322
+ droppedLastTs = null;
323
+ closed = false;
324
+ now;
325
+ get head() {
326
+ return this.chain.head;
327
+ }
328
+ get droppedCount() {
329
+ return this.dropped;
330
+ }
331
+ get isClosed() {
332
+ return this.closed;
333
+ }
334
+ /**
335
+ * Resolves once the event is sequenced and buffered — not once it is
336
+ * acknowledged. `false` when the event was dropped, or (fail-open) when
337
+ * the chain halted on an outage.
338
+ */
339
+ async emit(kind, payload) {
340
+ try {
341
+ return await this.chainEvent(kind, payload);
342
+ } catch (err) {
343
+ if (!this.deps.failOpen || !Transport.outage(err)) throw err;
344
+ this.deps.onError?.(err);
345
+ return false;
346
+ }
347
+ }
348
+ /** The tool call whose `execute` the host runs itself; see `ToolCallSpan`. */
349
+ toolCall(call) {
350
+ const { callId, ...start } = call;
351
+ return openCall(this, "tool_call", callId, { transport: "local", ...start });
352
+ }
353
+ async chainEvent(kind, payload) {
354
+ const halted = this.deps.transport.haltedError(this.id, this.source);
355
+ if (halted) throw halted;
356
+ const ts = this.now().toISOString();
357
+ if (!this.deps.transport.hasRoom()) {
358
+ this.dropped++;
359
+ this.droppedFirstTs ??= ts;
360
+ this.droppedLastTs = ts;
361
+ return false;
362
+ }
363
+ if (this.dropped > 0) {
364
+ this.deps.transport.enqueue(
365
+ await this.next(
366
+ "transport.gap",
367
+ { dropped: this.dropped, firstTs: this.droppedFirstTs, lastTs: this.droppedLastTs },
368
+ ts
369
+ )
370
+ );
371
+ this.dropped = 0;
372
+ this.droppedFirstTs = this.droppedLastTs = null;
373
+ }
374
+ this.deps.transport.enqueue(await this.next(kind, payload, ts));
375
+ return true;
376
+ }
377
+ async close(reason = "completed", extra = {}) {
378
+ if (this.closed) return;
379
+ this.closed = true;
380
+ await this.emit("session.close", { reason, ...extra });
381
+ await this.flush();
382
+ this.deps.onClosed?.(this);
383
+ }
384
+ /** Read-your-writes: the platform must hold the evidence before anyone judges it (GAP-16/66). */
385
+ flush() {
386
+ return this.deps.transport.flush();
387
+ }
388
+ /** Serialized: two concurrent emits get consecutive seqs, never the same one. */
389
+ next(kind, payload, ts) {
390
+ const run = this.building.then(async () => {
391
+ const built = await this.chain.append({ ts, kind, payload }, this.deps.signer);
392
+ this.chain = built.chain;
393
+ return built.event;
394
+ });
395
+ this.building = run.catch(() => void 0);
396
+ return run;
397
+ }
398
+ };
399
+ var DEFAULT_OPEN_RETRY_MS = 6e4;
400
+ var Sessions = class {
401
+ constructor(deps) {
402
+ this.deps = deps;
403
+ }
404
+ /**
405
+ * One session object per (session, source) per process: a chain's head
406
+ * lives in it, so two objects for the same chain would both start at
407
+ * seq 0 and fork it. Closed sessions are forgotten; a process restart
408
+ * mid-session still loses the head (GAP-67).
409
+ */
410
+ attached = /* @__PURE__ */ new Map();
411
+ /** Buyer sessions by the host's own key (GAP-71). */
412
+ opened = /* @__PURE__ */ new Map();
413
+ retryAt = 0;
414
+ /** The session behind a source: itself, or the one the run opens (null when the run has none). */
415
+ static resolve(source) {
416
+ return source instanceof Session ? Promise.resolve(source) : source.session();
417
+ }
418
+ /**
419
+ * Buyer half: the evidence session for a key of the host's own (its
420
+ * session, run or conversation id), opened on first use and reused
421
+ * after. A halted chain is reopened as a fresh session that continues
422
+ * the same key; a closed key is forgotten. When the platform cannot be
423
+ * reached, a fail-open client resolves null — the host runs without
424
+ * evidence — until `openRetryMs` has passed (GAP-71); otherwise, and
425
+ * whenever the platform refused, the error is thrown and the next call
426
+ * tries again. The identity is configuration: missing, it throws either
427
+ * way.
428
+ */
429
+ open(key, input = {}) {
430
+ const prior = this.opened.get(key) ?? Promise.resolve(null);
431
+ const next = prior.catch(() => null).then(
432
+ (session) => session && !session.isClosed && !this.deps.transport.haltedError(session.id, session.source) ? session : this.openFresh(input, () => this.forget(key, next))
433
+ );
434
+ this.opened.set(key, next);
435
+ next.catch(() => this.forget(key, next));
436
+ return next;
437
+ }
438
+ forget(key, entry) {
439
+ if (this.opened.get(key) === entry) this.opened.delete(key);
440
+ }
441
+ async openFresh(input, onClosed) {
442
+ const identity = this.identityFor("open");
443
+ const create = async () => this.create(identity, typeof input === "function" ? await input() : input, onClosed);
444
+ if (!this.deps.failOpen) return create();
445
+ if (Date.now() < this.retryAt) return null;
446
+ try {
447
+ const session = await create();
448
+ this.retryAt = 0;
449
+ return session;
450
+ } catch (err) {
451
+ if (!Transport.outage(err)) throw err;
452
+ this.retryAt = Date.now() + (this.deps.openRetryMs ?? DEFAULT_OPEN_RETRY_MS);
453
+ this.deps.onError?.(err);
454
+ return null;
455
+ }
456
+ }
457
+ /** Buyer half: create an AGENT_TRACE session bound to the agent identity, then announce it on the chain. */
458
+ async start(input = {}) {
459
+ return this.create(this.identityFor("start"), input);
460
+ }
461
+ identityFor(entry) {
462
+ const identity = this.deps.identity;
463
+ if (!identity)
464
+ throw new BelticConfigError(
465
+ `sessions.${entry} needs an agent identity (new Beltic({ identity }))`
466
+ );
467
+ return identity;
468
+ }
469
+ async create(identity, input, onClosed) {
470
+ const body = {
471
+ source: "AGENT_TRACE",
472
+ agent: { did: identity.did, credential: identity.credential ?? identity.did },
473
+ ...input.intent ? { intent: input.intent } : {}
474
+ };
475
+ const out = await this.deps.api.post("/v1/sessions", body);
476
+ const session = this.attach(out.sessionId, "AGENT_TRACE", out.expiresAt, "buyer", onClosed);
477
+ await session.emit("session.open", {
478
+ runtime: {
479
+ sdk: "@belticlabs/agent-risk-sdk",
480
+ version: this.deps.sdkVersion,
481
+ ...input.runtime
482
+ },
483
+ ...input.attestations ? { attestations: input.attestations } : {}
484
+ });
485
+ if (input.intent) await session.emit("intent.declared", input.intent);
486
+ return session;
487
+ }
488
+ /** Seller half: emit INTERNAL_NETWORK evidence into a session the buyer bound, or open a seller-born one. */
489
+ async ensure(sessionId) {
490
+ if (sessionId) return this.attach(sessionId, "INTERNAL_NETWORK", null, "buyer");
491
+ const out = await this.deps.api.post("/v1/sessions", {
492
+ source: "INTERNAL_NETWORK"
493
+ });
494
+ return this.attach(out.sessionId, "INTERNAL_NETWORK", out.expiresAt, "seller");
495
+ }
496
+ attach(id, source, expiresAt, born, onClosed) {
497
+ const key = `${id}:${source}`;
498
+ const existing = this.attached.get(key);
499
+ if (existing) return existing;
500
+ const session = new Session(
501
+ {
502
+ transport: this.deps.transport,
503
+ signer: source === "AGENT_TRACE" ? this.deps.identity?.signer : void 0,
504
+ failOpen: this.deps.failOpen,
505
+ onError: this.deps.onError,
506
+ onClosed: () => {
507
+ this.attached.delete(key);
508
+ onClosed?.();
509
+ }
510
+ },
511
+ id,
512
+ source,
513
+ expiresAt,
514
+ born
515
+ );
516
+ this.attached.set(key, session);
517
+ return session;
518
+ }
519
+ };
520
+
521
+ export {
522
+ BelticApiError,
523
+ ApiClient,
524
+ BelticConfigError,
525
+ recordCall,
526
+ ChainRejectedError,
527
+ Transport,
528
+ Session,
529
+ Sessions
530
+ };
package/dist/index.d.ts CHANGED
@@ -1,39 +1,17 @@
1
- import { a as Session } from './session-gK51QVAM.js';
2
- export { A as AgentIdentity, b as ApiClient, c as ApiClientOptions, B as Beltic, d as BelticApiError, e as BelticOptions, C as ChainRejectedError, f as CorrelationStore, D as DEFAULT_OPEN_RETRY_MS, g as DEFAULT_TRANSPORT, h as DecideOptions, i as Decision, E as Env, j as Evaluation, H as HumanDecisionInput, M as MemoryCorrelationStore, O as OpenSessionInput, R as RedactFn, k as Run, l as RunOptions, m as SDK_VERSION, n as SessionBorn, S as SessionSource, o as Sessions, p as StartSessionInput, T as ToolCallSpan, q as Transport, r as TransportClosedError, s as TransportOptions, t as createBeltic, u as ephemeralIdentity, v as fileIdentity, w as identityFromSeed } from './session-gK51QVAM.js';
3
- import { a as PaymentSummary, J as JsonObject, P as PaymentMomentPayload } from './verdict-CDAsxktI.js';
1
+ export { A as AgentIdentity, a as ApiClient, b as ApiClientOptions, B as Beltic, c as BelticApiError, d as BelticOptions, C as ChainRejectedError, D as DecideOptions, e as Decision, E as Env, f as Evaluation, F as FromEnvOptions, H as HumanDecisionInput, O as OpenSessionInput, R as Run, g as RunOptions, h as SDK_VERSION, i as Session, j as SessionBorn, S as SessionSource, k as Sessions, l as StartSessionInput, T as ToolCallSpan, m as Transport, n as TransportOptions, o as TransportTuning, p as identityFromSeed } from './session-D9E-efc0.js';
2
+ import './verdict-DMnbFuS5.js';
4
3
  import 'zod';
5
4
 
6
5
  /**
7
- * Thrown by the two primitives that cannot fail open (`sessions.start`,
8
- * `sessions.ensure`) when the client is disabled: a `Beltic.fromEnv` with
9
- * no `BELTIC_*` configured is a client that records nothing (GAP-78), and
10
- * a caller that insists on a session gets told so rather than a `null`.
6
+ * A client that is not configured is a fault of the host, not a state the
7
+ * SDK runs in: an empty API key, a base URL that does not parse, a buyer
8
+ * entry with no agent identity, a `BELTIC_*` variable missing. Thrown at
9
+ * construction or at the first entry that needs the missing piece, and
10
+ * never absorbed by `failOpen` (GAP-78).
11
11
  */
12
- declare class BelticDisabledError extends Error {
13
- readonly code = "SDK_DISABLED";
14
- constructor(entry: string);
12
+ declare class BelticConfigError extends Error {
13
+ readonly code = "CONFIG";
14
+ constructor(message: string);
15
15
  }
16
16
 
17
- /**
18
- * The shape the platform judges (`PaymentSummary`) and the shape the record
19
- * keeps (`PaymentMomentPayload`) share their comparable core: payee, amount,
20
- * payer. Every protocol adapter builds moments through here so the two
21
- * sides of one purchase compare (`EVIDENCE_MISMATCH`, GAP-50).
22
- */
23
-
24
- declare function summaryOf(m: PaymentSummary | PaymentMomentPayload): PaymentSummary;
25
- /** A presentation the seller side saw as a signed payment, in any protocol. */
26
- declare function presentedFrom(summary: PaymentSummary, raw: JsonObject): PaymentMomentPayload;
27
-
28
- /**
29
- * One instrumented call = a `*.start` event, the work, a `*.end` event
30
- * carrying the outcome or the error (GAP-07 correlates them by `callId`).
31
- * `openCall` is the span; `recordCall` runs the work inside one. The AI
32
- * middleware, the tool wrapper, the MCP client and a host's own tool loop
33
- * (`Session.toolCall`) all record the same way.
34
- */
35
-
36
- type CallKind = 'llm_call' | 'tool_call';
37
- declare function recordCall<T>(session: Session, kind: CallKind, callId: string, start: JsonObject, run: () => PromiseLike<T>, end?: (result: T) => Promise<JsonObject> | JsonObject): Promise<T>;
38
-
39
- export { BelticDisabledError, type CallKind, Session, presentedFrom, recordCall, summaryOf };
17
+ export { BelticConfigError };