@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.
- package/dist/adapter-BEpzr2R3.d.ts +29 -0
- package/dist/ai/index.d.ts +5 -5
- package/dist/ai/index.js +48 -36
- package/dist/chunk-4MG6VNAU.js +276 -0
- package/dist/chunk-FAQ442YH.js +24 -0
- package/dist/{chunk-U5Z5Z2BQ.js → chunk-LM4NIYE5.js} +26 -20
- package/dist/chunk-NJVO2WIV.js +17 -0
- package/dist/{chunk-W2OY7QXV.js → chunk-OKC6VMFH.js} +15 -26
- package/dist/{chunk-SFGM7KOG.js → chunk-X3W2Z5GC.js} +3 -0
- package/dist/index.d.ts +19 -9
- package/dist/index.js +354 -161
- package/dist/mcp/index.d.ts +3 -5
- package/dist/mcp/index.js +42 -34
- package/dist/middleware-9gI0ou2i.d.ts +15 -0
- package/dist/protocol/index.d.ts +2 -3
- package/dist/seller/anti-fraud-gateway.d.ts +11 -4
- package/dist/seller/anti-fraud-gateway.js +13 -14
- package/dist/session-DsBWEP8d.d.ts +485 -0
- package/dist/{index-Bjs3BPPU.d.ts → verdict-6vCyoAHE.d.ts} +25 -1
- package/dist/x402/express.d.ts +4 -6
- package/dist/x402/express.js +8 -7
- package/dist/x402/hono.d.ts +4 -6
- package/dist/x402/hono.js +8 -7
- package/dist/x402/index.d.ts +80 -19
- package/dist/x402/index.js +109 -35
- package/package.json +1 -1
- package/dist/chunk-5TWO73OD.js +0 -35
- package/dist/chunk-VM7MK43J.js +0 -13
- package/dist/client-NxpD_384.d.ts +0 -64
- package/dist/middleware-D-ejQIoE.d.ts +0 -40
- package/dist/session-B2mfurae.d.ts +0 -196
- package/dist/verdict-BAahb5po.d.ts +0 -26
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { a as PaymentSummary, J as JsonObject, P as PaymentMomentPayload } from './index-Bjs3BPPU.js';
|
|
5
|
-
import './verdict-BAahb5po.js';
|
|
1
|
+
import { a as Session } from './session-DsBWEP8d.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-DsBWEP8d.js';
|
|
3
|
+
import { a as PaymentSummary, J as JsonObject, P as PaymentMomentPayload } from './verdict-6vCyoAHE.js';
|
|
6
4
|
import 'zod';
|
|
7
5
|
|
|
6
|
+
/**
|
|
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`.
|
|
11
|
+
*/
|
|
12
|
+
declare class BelticDisabledError extends Error {
|
|
13
|
+
readonly code = "SDK_DISABLED";
|
|
14
|
+
constructor(entry: string);
|
|
15
|
+
}
|
|
16
|
+
|
|
8
17
|
/**
|
|
9
18
|
* The shape the platform judges (`PaymentSummary`) and the shape the record
|
|
10
19
|
* keeps (`PaymentMomentPayload`) share their comparable core: payee, amount,
|
|
@@ -12,18 +21,19 @@ import 'zod';
|
|
|
12
21
|
* sides of one purchase compare (`EVIDENCE_MISMATCH`, GAP-50).
|
|
13
22
|
*/
|
|
14
23
|
|
|
15
|
-
declare function summaryOf(m: PaymentMomentPayload): PaymentSummary;
|
|
24
|
+
declare function summaryOf(m: PaymentSummary | PaymentMomentPayload): PaymentSummary;
|
|
16
25
|
/** A presentation the seller side saw as a signed payment, in any protocol. */
|
|
17
26
|
declare function presentedFrom(summary: PaymentSummary, raw: JsonObject): PaymentMomentPayload;
|
|
18
27
|
|
|
19
28
|
/**
|
|
20
29
|
* One instrumented call = a `*.start` event, the work, a `*.end` event
|
|
21
30
|
* carrying the outcome or the error (GAP-07 correlates them by `callId`).
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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.
|
|
24
34
|
*/
|
|
25
35
|
|
|
26
36
|
type CallKind = 'llm_call' | 'tool_call';
|
|
27
37
|
declare function recordCall<T>(session: Session, kind: CallKind, callId: string, start: JsonObject, run: () => PromiseLike<T>, end?: (result: T) => Promise<JsonObject> | JsonObject): Promise<T>;
|
|
28
38
|
|
|
29
|
-
export { type CallKind, Session, presentedFrom, recordCall, summaryOf };
|
|
39
|
+
export { BelticDisabledError, type CallKind, Session, presentedFrom, recordCall, summaryOf };
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BelticDisabledError,
|
|
3
|
+
DEFAULT_OPEN_RETRY_MS,
|
|
4
|
+
Session,
|
|
5
|
+
Sessions,
|
|
2
6
|
recordCall
|
|
3
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-4MG6VNAU.js";
|
|
4
8
|
import {
|
|
5
|
-
|
|
9
|
+
canonicalBytes,
|
|
10
|
+
canonicalize,
|
|
6
11
|
didKeyFromEd25519,
|
|
7
12
|
fromHex,
|
|
8
13
|
memorySigner,
|
|
14
|
+
sha256,
|
|
9
15
|
toHex
|
|
10
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-X3W2Z5GC.js";
|
|
17
|
+
import {
|
|
18
|
+
Verdict
|
|
19
|
+
} from "./chunk-46QN2KEZ.js";
|
|
11
20
|
import {
|
|
12
21
|
presentedFrom,
|
|
13
22
|
summaryOf
|
|
14
23
|
} from "./chunk-7G5EHNVW.js";
|
|
15
|
-
import "./chunk-46QN2KEZ.js";
|
|
16
24
|
|
|
17
25
|
// src/core/api-client.ts
|
|
18
26
|
var BelticApiError = class extends Error {
|
|
@@ -112,141 +120,256 @@ var MemoryCorrelationStore = class {
|
|
|
112
120
|
}
|
|
113
121
|
};
|
|
114
122
|
|
|
115
|
-
// src/core/
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
get droppedCount() {
|
|
137
|
-
return this.dropped;
|
|
138
|
-
}
|
|
139
|
-
/** Resolves once the event is sequenced and buffered — not once it is acknowledged. */
|
|
140
|
-
async emit(kind, payload) {
|
|
141
|
-
const halted = this.deps.transport.haltedError(this.id, this.source);
|
|
142
|
-
if (halted) throw halted;
|
|
143
|
-
const ts = this.now().toISOString();
|
|
144
|
-
if (!this.deps.transport.hasRoom()) {
|
|
145
|
-
this.dropped++;
|
|
146
|
-
this.droppedFirstTs ??= ts;
|
|
147
|
-
this.droppedLastTs = ts;
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
if (this.dropped > 0) {
|
|
151
|
-
this.deps.transport.enqueue(
|
|
152
|
-
await this.next(
|
|
153
|
-
"transport.gap",
|
|
154
|
-
{ dropped: this.dropped, firstTs: this.droppedFirstTs, lastTs: this.droppedLastTs },
|
|
155
|
-
ts
|
|
156
|
-
)
|
|
157
|
-
);
|
|
158
|
-
this.dropped = 0;
|
|
159
|
-
this.droppedFirstTs = this.droppedLastTs = null;
|
|
160
|
-
}
|
|
161
|
-
const body = payload;
|
|
162
|
-
this.deps.transport.enqueue(
|
|
163
|
-
await this.next(kind, this.deps.redact ? this.deps.redact(kind, body) : body, ts)
|
|
164
|
-
);
|
|
165
|
-
return true;
|
|
123
|
+
// src/core/identity.ts
|
|
124
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
125
|
+
import { dirname } from "path";
|
|
126
|
+
function identityFromSeed(seed, credential) {
|
|
127
|
+
const signer = memorySigner(seed);
|
|
128
|
+
const did = didKeyFromEd25519(signer.publicKey);
|
|
129
|
+
return { did, signer: { ...signer, keyId: did }, ...credential ? { credential } : {} };
|
|
130
|
+
}
|
|
131
|
+
function ephemeralIdentity(credential) {
|
|
132
|
+
return identityFromSeed(memorySigner().seed, credential);
|
|
133
|
+
}
|
|
134
|
+
function fileIdentity(path, credential) {
|
|
135
|
+
let seed;
|
|
136
|
+
try {
|
|
137
|
+
seed = fromHex(JSON.parse(readFileSync(path, "utf8")).seed);
|
|
138
|
+
} catch {
|
|
139
|
+
seed = memorySigner().seed;
|
|
140
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
141
|
+
writeFileSync(path, `${JSON.stringify({ seed: toHex(seed) })}
|
|
142
|
+
`, { mode: 384 });
|
|
166
143
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
144
|
+
return identityFromSeed(seed, credential);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/core/decision.ts
|
|
148
|
+
var Decision = class _Decision {
|
|
149
|
+
constructor(evaluation) {
|
|
150
|
+
this.evaluation = evaluation;
|
|
173
151
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
this.
|
|
186
|
-
|
|
152
|
+
static ABSENT = new _Decision(null);
|
|
153
|
+
static of(evaluation) {
|
|
154
|
+
return new _Decision(evaluation);
|
|
155
|
+
}
|
|
156
|
+
static absent() {
|
|
157
|
+
return _Decision.ABSENT;
|
|
158
|
+
}
|
|
159
|
+
get value() {
|
|
160
|
+
return this.evaluation?.decision ?? null;
|
|
161
|
+
}
|
|
162
|
+
get reasonCodes() {
|
|
163
|
+
return this.evaluation?.reasonCodes ?? [];
|
|
164
|
+
}
|
|
165
|
+
get decisionId() {
|
|
166
|
+
return this.evaluation?.decisionId ?? null;
|
|
167
|
+
}
|
|
168
|
+
get allowed() {
|
|
169
|
+
return this.value === "ALLOW";
|
|
170
|
+
}
|
|
171
|
+
get denied() {
|
|
172
|
+
return this.value === "DENY";
|
|
173
|
+
}
|
|
174
|
+
get review() {
|
|
175
|
+
return this.value === "REVIEW";
|
|
176
|
+
}
|
|
177
|
+
get absent() {
|
|
178
|
+
return this.evaluation === null;
|
|
179
|
+
}
|
|
180
|
+
/** Whether a gate must stop the payment (GAP-52); an absent verdict never blocks. */
|
|
181
|
+
blocks(onReview) {
|
|
182
|
+
return this.evaluation ? Verdict.of(this.evaluation.decision).blocks(onReview) : false;
|
|
183
|
+
}
|
|
184
|
+
/** One sentence for the agent or the person: what Beltic said and why. */
|
|
185
|
+
explain() {
|
|
186
|
+
if (!this.evaluation) return "Beltic could not be asked about this payment.";
|
|
187
|
+
const verb = this.denied ? "denied" : this.review ? "asked for review of" : "allowed";
|
|
188
|
+
const why = this.reasonCodes.length > 0 ? ` (${this.reasonCodes.join(", ")})` : "";
|
|
189
|
+
return `Beltic ${verb} this payment${why}.`;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// src/core/run.ts
|
|
194
|
+
var MEMORY = 256;
|
|
195
|
+
var Memory = class {
|
|
196
|
+
map = /* @__PURE__ */ new Map();
|
|
197
|
+
get(key) {
|
|
198
|
+
return this.map.get(key);
|
|
199
|
+
}
|
|
200
|
+
set(key, value) {
|
|
201
|
+
this.map.delete(key);
|
|
202
|
+
this.map.set(key, value);
|
|
203
|
+
if (this.map.size > MEMORY) this.map.delete(this.map.keys().next().value);
|
|
187
204
|
}
|
|
188
205
|
};
|
|
189
|
-
var
|
|
190
|
-
constructor(deps) {
|
|
206
|
+
var Run = class _Run {
|
|
207
|
+
constructor(deps, key, opts = {}) {
|
|
191
208
|
this.deps = deps;
|
|
209
|
+
this.key = key;
|
|
210
|
+
this.opts = opts;
|
|
211
|
+
}
|
|
212
|
+
opened = null;
|
|
213
|
+
current = null;
|
|
214
|
+
/** JCS hash of the mandate on the chain, and of the one the open input carried. */
|
|
215
|
+
declared = null;
|
|
216
|
+
openedWith = null;
|
|
217
|
+
closed = false;
|
|
218
|
+
timer = null;
|
|
219
|
+
calls = new Memory();
|
|
220
|
+
decisions = new Memory();
|
|
221
|
+
byPayment = new Memory();
|
|
222
|
+
/** The session this run records into — opened on first use, `null` when there is none. */
|
|
223
|
+
session() {
|
|
224
|
+
const next = this.deps.sessions.open(this.key, this.opener);
|
|
225
|
+
this.opened = next;
|
|
226
|
+
return next.then((session) => {
|
|
227
|
+
if (session !== this.current) {
|
|
228
|
+
this.current = session;
|
|
229
|
+
this.declared = this.openedWith;
|
|
230
|
+
}
|
|
231
|
+
return session;
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
opener = async () => {
|
|
235
|
+
const open = this.opts.open;
|
|
236
|
+
const input = typeof open === "function" ? await open() : open ?? {};
|
|
237
|
+
this.openedWith = input.intent ? _Run.hash(input.intent) : null;
|
|
238
|
+
return input;
|
|
239
|
+
};
|
|
240
|
+
/** `intent.declared`, unless the mandate is the one already on the chain. */
|
|
241
|
+
async declare(intent) {
|
|
242
|
+
const session = await this.session();
|
|
243
|
+
if (!session) return false;
|
|
244
|
+
const hash = _Run.hash(intent);
|
|
245
|
+
if (hash === this.declared) return false;
|
|
246
|
+
const ok = await session.emit("intent.declared", intent);
|
|
247
|
+
if (ok) this.declared = hash;
|
|
248
|
+
this.touch();
|
|
249
|
+
return ok;
|
|
192
250
|
}
|
|
193
251
|
/**
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
252
|
+
* The platform's verdict on a payment about to be presented. Asked once
|
|
253
|
+
* per call id: a host that re-runs its approval step reads the same
|
|
254
|
+
* `Decision`. An absent verdict is not memoized, so the next attempt
|
|
255
|
+
* asks again.
|
|
198
256
|
*/
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
if (!
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
await session.emit("
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
257
|
+
async decide(payment, opts = {}) {
|
|
258
|
+
const known = opts.callId ? this.decisions.get(opts.callId) : void 0;
|
|
259
|
+
if (known) return known;
|
|
260
|
+
const session = await this.session();
|
|
261
|
+
if (!session) return Decision.absent();
|
|
262
|
+
if (opts.intent) await this.declare(opts.intent);
|
|
263
|
+
const summary = summaryOf(payment);
|
|
264
|
+
const evaluation = await this.deps.evaluate(session.id, summary);
|
|
265
|
+
if (!evaluation) return Decision.absent();
|
|
266
|
+
const decision = Decision.of(evaluation);
|
|
267
|
+
if (opts.callId) this.decisions.set(opts.callId, decision);
|
|
268
|
+
for (const key of _Run.paymentKeys(summary)) this.byPayment.set(key, decision);
|
|
269
|
+
const call = opts.callId ? this.calls.get(opts.callId)?.call : void 0;
|
|
270
|
+
await session.emit("gateway.decision", {
|
|
271
|
+
gateway: "beltic",
|
|
272
|
+
call: _Run.callOf(call),
|
|
273
|
+
decision: evaluation.decision,
|
|
274
|
+
reasonCodes: [...evaluation.reasonCodes],
|
|
275
|
+
record: {
|
|
276
|
+
decisionId: evaluation.decisionId,
|
|
277
|
+
callId: opts.callId ?? null,
|
|
278
|
+
payment: summary
|
|
279
|
+
}
|
|
219
280
|
});
|
|
220
|
-
|
|
221
|
-
return
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
281
|
+
this.touch();
|
|
282
|
+
return decision;
|
|
283
|
+
}
|
|
284
|
+
/** The decision given for a call id, or absent. */
|
|
285
|
+
decision(callId) {
|
|
286
|
+
return this.decisions.get(callId) ?? Decision.absent();
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* The decision given for a payment with the same comparable core (payee,
|
|
290
|
+
* amount, payer — or payee and amount when one side names no payer), or
|
|
291
|
+
* absent.
|
|
292
|
+
*/
|
|
293
|
+
decisionFor(payment) {
|
|
294
|
+
for (const key of _Run.paymentKeys(summaryOf(payment))) {
|
|
295
|
+
const known = this.byPayment.get(key);
|
|
296
|
+
if (known) return known;
|
|
297
|
+
}
|
|
298
|
+
return Decision.absent();
|
|
299
|
+
}
|
|
300
|
+
/** A tool call the host runs itself, reported as two events by its own call id. */
|
|
301
|
+
tools = {
|
|
302
|
+
start: async (call) => {
|
|
303
|
+
const session = await this.session();
|
|
304
|
+
if (!session) return false;
|
|
305
|
+
const span = session.toolCall(call);
|
|
306
|
+
this.calls.set(call.callId, { call, span });
|
|
307
|
+
this.touch();
|
|
308
|
+
return span.opened;
|
|
309
|
+
},
|
|
310
|
+
end: async (callId, outcome) => {
|
|
311
|
+
const span = this.take(callId);
|
|
312
|
+
if (!span) return false;
|
|
313
|
+
this.touch();
|
|
314
|
+
return span.end(outcome);
|
|
315
|
+
},
|
|
316
|
+
fail: async (callId, error, outcome) => {
|
|
317
|
+
const span = this.take(callId);
|
|
318
|
+
if (!span) return false;
|
|
319
|
+
this.touch();
|
|
320
|
+
return span.fail(error, outcome);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
/** A person's answer about a call, as the decision it was (GAP-75). */
|
|
324
|
+
async humanDecided(callId, input) {
|
|
325
|
+
const session = await this.session();
|
|
326
|
+
if (!session) return false;
|
|
327
|
+
const ok = await session.emit("gateway.decision", {
|
|
328
|
+
gateway: "human",
|
|
329
|
+
call: _Run.callOf(this.calls.get(callId)?.call),
|
|
330
|
+
decision: input.allowed ? "ALLOW" : "DENY",
|
|
331
|
+
reasonCodes: [`USER_${input.outcome.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}`],
|
|
332
|
+
record: {
|
|
333
|
+
callId,
|
|
334
|
+
outcome: input.outcome,
|
|
335
|
+
responder: input.responder ?? null,
|
|
336
|
+
...input.record ?? {}
|
|
337
|
+
}
|
|
228
338
|
});
|
|
229
|
-
|
|
339
|
+
this.touch();
|
|
340
|
+
return ok;
|
|
230
341
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
);
|
|
248
|
-
this.
|
|
249
|
-
|
|
342
|
+
async close(reason = "completed") {
|
|
343
|
+
if (this.closed) return;
|
|
344
|
+
this.closed = true;
|
|
345
|
+
if (this.timer) clearTimeout(this.timer);
|
|
346
|
+
this.deps.onClosed(this);
|
|
347
|
+
const session = this.opened ? await this.opened.catch(() => null) : null;
|
|
348
|
+
await session?.close(reason);
|
|
349
|
+
}
|
|
350
|
+
take(callId) {
|
|
351
|
+
const known = this.calls.get(callId);
|
|
352
|
+
if (!known?.span) return null;
|
|
353
|
+
const { span } = known;
|
|
354
|
+
known.span = null;
|
|
355
|
+
return span;
|
|
356
|
+
}
|
|
357
|
+
touch() {
|
|
358
|
+
if (!this.opts.idleMs || this.closed) return;
|
|
359
|
+
if (this.timer) clearTimeout(this.timer);
|
|
360
|
+
this.timer = setTimeout(() => void this.close("expired"), this.opts.idleMs);
|
|
361
|
+
this.timer.unref?.();
|
|
362
|
+
}
|
|
363
|
+
static hash(intent) {
|
|
364
|
+
return toHex(sha256(canonicalBytes(intent)));
|
|
365
|
+
}
|
|
366
|
+
/** With the payer first, then without it. */
|
|
367
|
+
static paymentKeys(summary) {
|
|
368
|
+
const { payer, ...core } = summary;
|
|
369
|
+
return payer ? [canonicalize(summary), canonicalize(core)] : [canonicalize(core)];
|
|
370
|
+
}
|
|
371
|
+
static callOf(call) {
|
|
372
|
+
return call ? { tool: call.toolName, args: call.input } : { tool: "unknown" };
|
|
250
373
|
}
|
|
251
374
|
};
|
|
252
375
|
|
|
@@ -412,36 +535,122 @@ var Transport = class {
|
|
|
412
535
|
};
|
|
413
536
|
|
|
414
537
|
// src/client.ts
|
|
415
|
-
var SDK_VERSION = "0.
|
|
416
|
-
var
|
|
538
|
+
var SDK_VERSION = "0.3.0";
|
|
539
|
+
var ENV_REQUIRED = ["BELTIC_API_KEY", "BELTIC_BASE_URL", "BELTIC_AGENT_SEED"];
|
|
540
|
+
var ENV_CREDENTIAL = "BELTIC_AGENT_CREDENTIAL";
|
|
541
|
+
var Beltic = class _Beltic {
|
|
417
542
|
api;
|
|
418
543
|
transport;
|
|
419
544
|
sessions;
|
|
420
545
|
identity;
|
|
421
546
|
correlation;
|
|
422
547
|
onReview;
|
|
548
|
+
failOpen;
|
|
549
|
+
/** `false` for a disabled client (GAP-78): nothing is posted, `run`/`sessions.open` answer without a session. */
|
|
550
|
+
enabled;
|
|
551
|
+
onError;
|
|
552
|
+
runs = /* @__PURE__ */ new Map();
|
|
553
|
+
/**
|
|
554
|
+
* The client the environment describes: `BELTIC_API_KEY`,
|
|
555
|
+
* `BELTIC_BASE_URL`, `BELTIC_AGENT_SEED` (64 hex) and optionally
|
|
556
|
+
* `BELTIC_AGENT_CREDENTIAL`, fail-open by default. None set → a disabled
|
|
557
|
+
* client; some set → a configuration error, thrown (GAP-78).
|
|
558
|
+
*/
|
|
559
|
+
static fromEnv(env = _Beltic.processEnv(), opts = {}) {
|
|
560
|
+
const missing = ENV_REQUIRED.filter((name) => !env[name]);
|
|
561
|
+
if (missing.length === ENV_REQUIRED.length) return _Beltic.disabled(opts);
|
|
562
|
+
if (missing.length > 0)
|
|
563
|
+
throw new Error(
|
|
564
|
+
`Beltic.fromEnv: ${missing.join(", ")} missing \u2014 set all of ${ENV_REQUIRED.join(", ")}, or none to disable`
|
|
565
|
+
);
|
|
566
|
+
return new _Beltic({
|
|
567
|
+
failOpen: true,
|
|
568
|
+
...opts,
|
|
569
|
+
apiKey: env.BELTIC_API_KEY,
|
|
570
|
+
baseUrl: env.BELTIC_BASE_URL,
|
|
571
|
+
identity: identityFromSeed(fromHex(env.BELTIC_AGENT_SEED), env[ENV_CREDENTIAL])
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
/** A client that records nothing and never throws into the work: the null object for "no evidence stream" (GAP-78). */
|
|
575
|
+
static disabled(opts = {}) {
|
|
576
|
+
return new _Beltic({
|
|
577
|
+
...opts,
|
|
578
|
+
apiKey: "",
|
|
579
|
+
baseUrl: "http://beltic.disabled.invalid",
|
|
580
|
+
failOpen: true,
|
|
581
|
+
enabled: false
|
|
582
|
+
});
|
|
583
|
+
}
|
|
423
584
|
constructor(opts) {
|
|
585
|
+
this.enabled = opts.enabled ?? true;
|
|
586
|
+
this.failOpen = opts.failOpen ?? false;
|
|
587
|
+
this.onError = opts.onError ?? ((err) => console.error("[beltic]", err));
|
|
424
588
|
this.api = new ApiClient({ ...opts, userAgent: `@belticlabs/agent-risk-sdk/${SDK_VERSION}` });
|
|
425
|
-
this.transport = new Transport(this.api,
|
|
589
|
+
this.transport = new Transport(this.api, {
|
|
590
|
+
onError: this.onError,
|
|
591
|
+
onChainHalted: this.onError,
|
|
592
|
+
...opts.transport
|
|
593
|
+
});
|
|
426
594
|
this.sessions = new Sessions({
|
|
427
595
|
api: this.api,
|
|
428
596
|
transport: this.transport,
|
|
429
597
|
sdkVersion: SDK_VERSION,
|
|
430
598
|
identity: opts.identity,
|
|
431
599
|
redact: opts.redact,
|
|
432
|
-
now: opts.now
|
|
600
|
+
now: opts.now,
|
|
601
|
+
failOpen: this.failOpen,
|
|
602
|
+
onError: this.onError,
|
|
603
|
+
openRetryMs: opts.openRetryMs,
|
|
604
|
+
enabled: this.enabled
|
|
433
605
|
});
|
|
434
606
|
this.identity = opts.identity;
|
|
435
607
|
this.correlation = opts.correlation ?? new MemoryCorrelationStore();
|
|
436
608
|
this.onReview = opts.onReview ?? "abort";
|
|
437
609
|
}
|
|
438
|
-
/**
|
|
610
|
+
/**
|
|
611
|
+
* The platform's verdict on a payment — the seller's before it verifies,
|
|
612
|
+
* the buyer's before it presents. Read-your-writes: the buffered evidence
|
|
613
|
+
* is flushed first so the platform judges what the caller already saw
|
|
614
|
+
* (GAP-16). A recorded moment is accepted as is: only its comparable core
|
|
615
|
+
* (payee, amount, payer) is sent. `null` only under `failOpen`, when the
|
|
616
|
+
* platform could not be asked.
|
|
617
|
+
*/
|
|
439
618
|
async evaluate(sessionId, payment) {
|
|
619
|
+
if (!this.enabled) return null;
|
|
620
|
+
const input = { sessionId, payment: summaryOf(payment) };
|
|
621
|
+
if (!this.failOpen) return this.decide(input);
|
|
622
|
+
try {
|
|
623
|
+
return await this.decide(input);
|
|
624
|
+
} catch (err) {
|
|
625
|
+
this.onError(err);
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
async decide(input) {
|
|
440
630
|
await this.transport.flush();
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
631
|
+
const out = await this.api.post("/v1/evaluate", input);
|
|
632
|
+
return { ...out, verdict: Verdict.of(out.decision) };
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* The run for a key of the host's own — one object per key until it
|
|
636
|
+
* closes (the options count on the first call only). See `Run`.
|
|
637
|
+
*/
|
|
638
|
+
run(key, opts = {}) {
|
|
639
|
+
const existing = this.runs.get(key);
|
|
640
|
+
if (existing) return existing;
|
|
641
|
+
const run = new Run(
|
|
642
|
+
{
|
|
643
|
+
sessions: this.sessions,
|
|
644
|
+
evaluate: (sessionId, payment) => this.evaluate(sessionId, payment),
|
|
645
|
+
onClosed: (closed) => {
|
|
646
|
+
if (this.runs.get(key) === closed) this.runs.delete(key);
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
key,
|
|
650
|
+
opts
|
|
651
|
+
);
|
|
652
|
+
this.runs.set(key, run);
|
|
653
|
+
return run;
|
|
445
654
|
}
|
|
446
655
|
flush() {
|
|
447
656
|
return this.transport.flush();
|
|
@@ -449,41 +658,25 @@ var Beltic = class {
|
|
|
449
658
|
shutdown() {
|
|
450
659
|
return this.transport.close();
|
|
451
660
|
}
|
|
661
|
+
/** `process.env` where there is a `process` (Node); `{}` on workerd, where the shell passes its `env`. */
|
|
662
|
+
static processEnv() {
|
|
663
|
+
return globalThis.process?.env ?? {};
|
|
664
|
+
}
|
|
452
665
|
};
|
|
453
666
|
function createBeltic(opts) {
|
|
454
667
|
return new Beltic(opts);
|
|
455
668
|
}
|
|
456
|
-
|
|
457
|
-
// src/core/identity.ts
|
|
458
|
-
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
459
|
-
import { dirname } from "path";
|
|
460
|
-
function identityFromSeed(seed, credential) {
|
|
461
|
-
const signer = memorySigner(seed);
|
|
462
|
-
const did = didKeyFromEd25519(signer.publicKey);
|
|
463
|
-
return { did, signer: { ...signer, keyId: did }, ...credential ? { credential } : {} };
|
|
464
|
-
}
|
|
465
|
-
function ephemeralIdentity(credential) {
|
|
466
|
-
return identityFromSeed(memorySigner().seed, credential);
|
|
467
|
-
}
|
|
468
|
-
function fileIdentity(path, credential) {
|
|
469
|
-
let seed;
|
|
470
|
-
try {
|
|
471
|
-
seed = fromHex(JSON.parse(readFileSync(path, "utf8")).seed);
|
|
472
|
-
} catch {
|
|
473
|
-
seed = memorySigner().seed;
|
|
474
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
475
|
-
writeFileSync(path, `${JSON.stringify({ seed: toHex(seed) })}
|
|
476
|
-
`, { mode: 384 });
|
|
477
|
-
}
|
|
478
|
-
return identityFromSeed(seed, credential);
|
|
479
|
-
}
|
|
480
669
|
export {
|
|
481
670
|
ApiClient,
|
|
482
671
|
Beltic,
|
|
483
672
|
BelticApiError,
|
|
673
|
+
BelticDisabledError,
|
|
484
674
|
ChainRejectedError,
|
|
675
|
+
DEFAULT_OPEN_RETRY_MS,
|
|
485
676
|
DEFAULT_TRANSPORT,
|
|
677
|
+
Decision,
|
|
486
678
|
MemoryCorrelationStore,
|
|
679
|
+
Run,
|
|
487
680
|
SDK_VERSION,
|
|
488
681
|
Session,
|
|
489
682
|
Sessions,
|
package/dist/mcp/index.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { a as PaymentSummary, P as PaymentMomentPayload } from '../
|
|
2
|
-
import { B as Beltic } from '../
|
|
3
|
-
import { S as Session } from '../session-B2mfurae.js';
|
|
1
|
+
import { a as PaymentSummary, P as PaymentMomentPayload } from '../verdict-6vCyoAHE.js';
|
|
2
|
+
import { B as Beltic, S as SessionSource } from '../session-DsBWEP8d.js';
|
|
4
3
|
import 'zod';
|
|
5
|
-
import '../verdict-BAahb5po.js';
|
|
6
4
|
|
|
7
5
|
interface CallToolParams {
|
|
8
6
|
name: string;
|
|
@@ -13,7 +11,7 @@ interface CallToolParams {
|
|
|
13
11
|
interface McpClientLike {
|
|
14
12
|
callTool(params: CallToolParams, ...rest: unknown[]): Promise<unknown>;
|
|
15
13
|
}
|
|
16
|
-
declare function wrapClient<C extends McpClientLike>(
|
|
14
|
+
declare function wrapClient<C extends McpClientLike>(source: SessionSource, client: C, opts?: {
|
|
17
15
|
server?: string;
|
|
18
16
|
}): C;
|
|
19
17
|
/**
|