@belticlabs/agent-risk-sdk 0.3.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.
- package/dist/{adapter-BEpzr2R3.d.ts → adapter-DEdhsNt-.d.ts} +3 -10
- package/dist/ai/index.d.ts +2 -2
- package/dist/ai/index.js +6 -8
- package/dist/{chunk-46QN2KEZ.js → chunk-4BUUPU3O.js} +0 -9
- package/dist/chunk-GM4KEEZB.js +163 -0
- package/dist/chunk-JSE6JQJC.js +530 -0
- package/dist/index.d.ts +11 -33
- package/dist/index.js +40 -335
- package/dist/protocol/index.d.ts +2 -2
- package/dist/protocol/index.js +1 -5
- package/dist/{session-DsBWEP8d.d.ts → session-D9E-efc0.d.ts} +74 -97
- package/dist/{verdict-6vCyoAHE.d.ts → verdict-DMnbFuS5.d.ts} +1 -17
- package/dist/x402/hono.d.ts +16 -5
- package/dist/x402/hono.js +14 -8
- package/dist/x402/index.d.ts +15 -27
- package/dist/x402/index.js +15 -59
- package/package.json +2 -30
- package/dist/chunk-4MG6VNAU.js +0 -276
- package/dist/chunk-7G5EHNVW.js +0 -17
- package/dist/chunk-FAQ442YH.js +0 -24
- package/dist/chunk-LM4NIYE5.js +0 -92
- package/dist/chunk-NJVO2WIV.js +0 -17
- package/dist/chunk-OKC6VMFH.js +0 -84
- package/dist/mcp/index.d.ts +0 -58
- package/dist/mcp/index.js +0 -135
- package/dist/middleware-9gI0ou2i.d.ts +0 -15
- package/dist/seller/anti-fraud-gateway.d.ts +0 -40
- package/dist/seller/anti-fraud-gateway.js +0 -38
- package/dist/x402/express.d.ts +0 -13
- package/dist/x402/express.js +0 -27
|
@@ -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
|
-
|
|
2
|
-
|
|
3
|
-
import { a as PaymentSummary, J as JsonObject, P as PaymentMomentPayload } from './verdict-6vCyoAHE.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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* no
|
|
10
|
-
*
|
|
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
|
|
13
|
-
readonly code = "
|
|
14
|
-
constructor(
|
|
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 };
|