@atbash/sdk 0.3.24 → 0.4.0-dev.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/index.mjs ADDED
@@ -0,0 +1,1334 @@
1
+ // src-ts/native.ts
2
+ import { createRequire } from "module";
3
+ var anchor = typeof __filename !== "undefined" ? __filename : import.meta.url;
4
+ var require2 = createRequire(anchor);
5
+ var native = require2("../index.js");
6
+
7
+ // src-ts/client.ts
8
+ import { randomBytes } from "crypto";
9
+
10
+ // src-ts/constants.ts
11
+ var DEFAULT_ENDPOINT = native.DEFAULT_ENDPOINT;
12
+ var DEFAULT_CHROMIA_NODE_URLS = Object.freeze(
13
+ native.defaultChromiaNodeUrls()
14
+ );
15
+ var DEFAULT_BLOCKCHAIN_RID = native.DEFAULT_BLOCKCHAIN_RID;
16
+
17
+ // src-ts/chain-config.ts
18
+ var DEFAULT_PRIVATE_NODE_URLS = Object.freeze(
19
+ native.defaultPrivateNodeUrls()
20
+ );
21
+ var DEFAULT_PRIVATE_BLOCKCHAIN_RID = native.DEFAULT_PRIVATE_BLOCKCHAIN_RID;
22
+ var PUBLIC_CHAIN = {
23
+ network: "public",
24
+ blockchainRid: DEFAULT_BLOCKCHAIN_RID,
25
+ nodeUrls: DEFAULT_CHROMIA_NODE_URLS
26
+ };
27
+ var PRIVATE_CHAIN = {
28
+ network: "private",
29
+ blockchainRid: DEFAULT_PRIVATE_BLOCKCHAIN_RID,
30
+ nodeUrls: DEFAULT_PRIVATE_NODE_URLS
31
+ };
32
+
33
+ // src-ts/endpoint.ts
34
+ var ALLOWED_JUDGE_HOSTS = /* @__PURE__ */ new Set([
35
+ "atbash.ai",
36
+ "www.atbash.ai",
37
+ "chromia-verified-ai-dev-two.vercel.app"
38
+ ]);
39
+ function validateJudgeEndpoint(judge) {
40
+ const policy = judge?.policy === "self-hosted" ? "self-hosted" : "default";
41
+ const candidate = judge?.endpoint?.trim() || DEFAULT_ENDPOINT;
42
+ let parsed;
43
+ try {
44
+ parsed = new URL(candidate);
45
+ } catch {
46
+ throw new Error(
47
+ `[atbash] invalid judge endpoint URL: ${candidate}. Refusing to load \u2014 fix the URL or omit it to use the default (${DEFAULT_ENDPOINT}).`
48
+ );
49
+ }
50
+ if (parsed.protocol !== "https:") {
51
+ throw new Error(
52
+ `[atbash] judge endpoint must use https:// (got "${parsed.protocol}"). Refusing to load \u2014 plaintext endpoints leak verdicts and enable trivial MITM bypass.`
53
+ );
54
+ }
55
+ if (parsed.username || parsed.password) {
56
+ throw new Error(
57
+ `[atbash] judge endpoint must not contain credentials (user:pass@host). Refusing to load \u2014 credentials embedded in URLs leak to logs and process listings.`
58
+ );
59
+ }
60
+ const normalisedUrl = parsed.origin;
61
+ if (policy === "self-hosted") {
62
+ const verifyPubKey = judge?.verifyPubKey;
63
+ const key = verifyPubKey?.trim().toLowerCase();
64
+ if (!key || !/^[0-9a-f]{66}$/.test(key)) {
65
+ throw new Error(
66
+ `[atbash] judge endpoint policy "self-hosted" requires verifyPubKey to be a 66-hex-char compressed secp256k1 pubkey. Refusing to load \u2014 self-hosted judges must produce signed responses so the SDK can detect a malicious or compromised judge.`
67
+ );
68
+ }
69
+ return { url: normalisedUrl, policy, verifyPubKey: key };
70
+ }
71
+ if (!ALLOWED_JUDGE_HOSTS.has(parsed.hostname.toLowerCase())) {
72
+ throw new Error(
73
+ `[atbash] judge endpoint hostname "${parsed.hostname}" is not in the trusted allowlist. Allowed: ${[...ALLOWED_JUDGE_HOSTS].join(", ")}. To use a self-hosted judge, set BOTH policy="self-hosted" AND verifyPubKey to the 66-hex pubkey of your judge's response-signing key. Refusing to load \u2014 silent endpoint redirection is a known attack vector (F-003).`
74
+ );
75
+ }
76
+ return { url: normalisedUrl, policy, verifyPubKey: null };
77
+ }
78
+
79
+ // src-ts/errors.ts
80
+ var AtbashAPIError = class extends Error {
81
+ /** HTTP status code (or 0 if the request never completed). */
82
+ status;
83
+ /** Raw response body text (may be empty). */
84
+ body;
85
+ constructor(status, body, statusText = "", endpoint = DEFAULT_ENDPOINT) {
86
+ super(enrich(status, body, statusText, endpoint));
87
+ this.name = "AtbashAPIError";
88
+ this.status = status;
89
+ this.body = body;
90
+ }
91
+ };
92
+ var SignatureVerificationError = class extends Error {
93
+ constructor(message) {
94
+ super(message);
95
+ this.name = "SignatureVerificationError";
96
+ }
97
+ };
98
+ function enrich(status, body, statusText, endpoint) {
99
+ const dashboard = endpoint.replace(/\/+$/, "") || DEFAULT_ENDPOINT;
100
+ let msg = `API error ${status}: ${body || statusText}`;
101
+ const lowered = body.toLowerCase();
102
+ if (lowered.includes("agent not registered")) {
103
+ msg += `
104
+ \u2192 Onboard the agent at ${dashboard}/risk-engine/agents`;
105
+ } else if (lowered.includes("agent has no policy") || lowered.includes("no policy configured")) {
106
+ msg += `
107
+ \u2192 Attach a policy at ${dashboard}/risk-engine/agents`;
108
+ } else if (lowered.includes("agent is jailed") || lowered.includes("jailed")) {
109
+ msg += `
110
+ \u2192 Unjail the agent at ${dashboard}/risk-engine/agents`;
111
+ } else if (lowered.includes("audit tier") || lowered.includes("verdict disabled") || lowered.includes("verdict not supported")) {
112
+ msg += `
113
+ \u2192 Upgrade the org tier at ${dashboard}/risk-engine/settings`;
114
+ } else if (status >= 400 && status < 500) {
115
+ msg += `
116
+ \u2192 Dashboard: ${dashboard}/risk-engine/feed`;
117
+ }
118
+ return msg;
119
+ }
120
+
121
+ // src-ts/http/client.ts
122
+ var HttpClient = class {
123
+ baseUrl;
124
+ timeoutMs;
125
+ constructor(baseUrl, timeoutMs) {
126
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
127
+ this.timeoutMs = timeoutMs;
128
+ }
129
+ buildUrl(path, query) {
130
+ const url = new URL(this.baseUrl + path);
131
+ if (query) {
132
+ for (const [k, v] of Object.entries(query)) {
133
+ if (v !== void 0 && v !== null && v !== "") {
134
+ url.searchParams.set(k, String(v));
135
+ }
136
+ }
137
+ }
138
+ return url.toString();
139
+ }
140
+ async get(path, query, headers) {
141
+ return this.fetch(this.buildUrl(path, query), {
142
+ method: "GET",
143
+ ...headers && { headers }
144
+ });
145
+ }
146
+ async post(path, body, headers) {
147
+ return this.fetch(this.buildUrl(path), {
148
+ method: "POST",
149
+ headers: { "Content-Type": "application/json", ...headers },
150
+ body: JSON.stringify(body)
151
+ });
152
+ }
153
+ async fetch(url, init) {
154
+ return fetch(url, { ...init, signal: AbortSignal.timeout(this.timeoutMs) });
155
+ }
156
+ };
157
+
158
+ // src-ts/keyLoader.ts
159
+ import { readFileSync } from "fs";
160
+ import { homedir } from "os";
161
+ import { join } from "path";
162
+ var DEFAULT_KEY_PATH_REL = ".config/atbash/guard-client-key";
163
+ function resolveKeyPath(input) {
164
+ if (input) return expandHome(input);
165
+ const home = process.env.HOME || homedir() || "";
166
+ return join(home, DEFAULT_KEY_PATH_REL);
167
+ }
168
+ function expandHome(p) {
169
+ if (!p.startsWith("~/")) return p;
170
+ const home = process.env.HOME || homedir() || "";
171
+ return join(home, p.slice(2));
172
+ }
173
+ function readKeyFile(keyPath) {
174
+ const content = String(readFileSync(keyPath, "utf8") || "").trim();
175
+ let privKey = "";
176
+ let pubKey = "";
177
+ if (content.startsWith("{")) {
178
+ const creds = JSON.parse(content);
179
+ privKey = String(
180
+ creds.privKey || creds.privkey || creds.privateKey || ""
181
+ ).trim();
182
+ pubKey = String(
183
+ creds.pubKey || creds.pubkey || creds.publicKey || ""
184
+ ).trim();
185
+ } else {
186
+ for (const line of content.split(/\r?\n/)) {
187
+ if (line.startsWith("privkey="))
188
+ privKey = line.slice("privkey=".length).trim();
189
+ if (line.startsWith("pubkey="))
190
+ pubKey = line.slice("pubkey=".length).trim();
191
+ }
192
+ }
193
+ if (!privKey || !pubKey) {
194
+ throw new Error(`atbash key file missing priv/pub key fields: ${keyPath}`);
195
+ }
196
+ privKey = privKey.replace(/^0x/, "");
197
+ return { privKey, pubKey };
198
+ }
199
+ function loadAgentFromFile(keyPath) {
200
+ const resolved = resolveKeyPath(keyPath);
201
+ const { privKey } = readKeyFile(resolved);
202
+ return native.loadAgent(privKey);
203
+ }
204
+
205
+ // src-ts/normalize.ts
206
+ function normalizeVerdict(raw) {
207
+ if (raw === null || raw === void 0) return "No verdict";
208
+ const v = String(raw).toUpperCase();
209
+ if (v === "ALLOW" || v === "GREEN") return "ALLOW";
210
+ if (v === "HOLD" || v === "YELLOW") return "HOLD";
211
+ if (v === "BLOCK" || v === "RED") return "BLOCK";
212
+ return "HOLD";
213
+ }
214
+ function normalizeStatus(raw) {
215
+ const s = String(raw ?? "").toLowerCase();
216
+ if (s === "pending" || s === "answered" || s === "error") return s;
217
+ return "error";
218
+ }
219
+ function pubkeyToHex(val) {
220
+ if (!val) return "";
221
+ if (typeof val === "string") return val;
222
+ if (val instanceof Uint8Array) return Buffer.from(val).toString("hex");
223
+ if (typeof val === "object") {
224
+ const data = val.data;
225
+ if (Array.isArray(data)) return Buffer.from(data).toString("hex");
226
+ }
227
+ return "";
228
+ }
229
+
230
+ // src-ts/opentel/telemetry.ts
231
+ import { readFileSync as readFileSync2 } from "fs";
232
+ import { homedir as homedir2 } from "os";
233
+ import { join as join2 } from "path";
234
+ import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
235
+ import { resourceFromAttributes } from "@opentelemetry/resources";
236
+ import {
237
+ MeterProvider,
238
+ PeriodicExportingMetricReader
239
+ } from "@opentelemetry/sdk-metrics";
240
+ var meterProvider = null;
241
+ var callCounter = null;
242
+ var durationHistogram = null;
243
+ var defaultSource = "sdk";
244
+ function isTelemetryOptedOut() {
245
+ try {
246
+ const home = process.env.HOME || homedir2() || "";
247
+ const filePath = join2(home, ".config", "atbash", "telemetry.json");
248
+ const raw = readFileSync2(filePath, "utf-8").trim();
249
+ if (!raw) return false;
250
+ const config = JSON.parse(raw);
251
+ return config.enabled === false;
252
+ } catch {
253
+ return false;
254
+ }
255
+ }
256
+ function autoInit() {
257
+ if (meterProvider) return;
258
+ if (isTelemetryOptedOut()) return;
259
+ setupTelemetry({ enabled: true });
260
+ }
261
+ function setupTelemetry(config) {
262
+ if (!config.enabled) return;
263
+ if (meterProvider) return;
264
+ if (isTelemetryOptedOut()) return;
265
+ defaultSource = config.source ?? "sdk";
266
+ const ATBASH_HONEYCOMB_KEY = "YOUR_INGEST_KEY_HERE";
267
+ const apiKey = process.env.HONEYCOMB_API_KEY ?? ATBASH_HONEYCOMB_KEY;
268
+ const exporter = new OTLPMetricExporter({
269
+ url: "https://api.honeycomb.io/v1/metrics",
270
+ headers: {
271
+ "x-honeycomb-team": apiKey
272
+ }
273
+ });
274
+ const reader = new PeriodicExportingMetricReader({
275
+ exporter,
276
+ exportIntervalMillis: config.exportIntervalMs ?? 6e4
277
+ });
278
+ meterProvider = new MeterProvider({
279
+ resource: resourceFromAttributes({
280
+ "service.name": "atbash-sdk"
281
+ }),
282
+ readers: [reader]
283
+ });
284
+ const meter = meterProvider.getMeter("atbash-sdk");
285
+ callCounter = meter.createCounter("atbash.sdk.function.calls", {
286
+ description: "Number of SDK function calls"
287
+ });
288
+ durationHistogram = meter.createHistogram("atbash.sdk.function.duration_ms", {
289
+ description: "SDK function execution duration",
290
+ unit: "ms"
291
+ });
292
+ }
293
+ function recordCall(functionName, source, agentPubkey) {
294
+ autoInit();
295
+ if (!callCounter) return;
296
+ callCounter.add(1, {
297
+ "function.name": functionName,
298
+ source: source ?? defaultSource,
299
+ ...agentPubkey && { "agent.pubkey": agentPubkey }
300
+ });
301
+ }
302
+ function recordDuration(functionName, durationMs, status, source) {
303
+ if (!durationHistogram) return;
304
+ durationHistogram.record(durationMs, {
305
+ "function.name": functionName,
306
+ status,
307
+ source: source ?? defaultSource
308
+ });
309
+ }
310
+ async function flushTelemetry() {
311
+ if (!meterProvider) return;
312
+ await meterProvider.forceFlush();
313
+ }
314
+ async function shutdownTelemetry() {
315
+ if (!meterProvider) return;
316
+ await meterProvider.shutdown();
317
+ meterProvider = null;
318
+ callCounter = null;
319
+ durationHistogram = null;
320
+ }
321
+
322
+ // src-ts/userConfig.ts
323
+ import {
324
+ chmodSync,
325
+ existsSync,
326
+ mkdirSync,
327
+ readFileSync as readFileSync3,
328
+ writeFileSync
329
+ } from "fs";
330
+ import { homedir as homedir3 } from "os";
331
+ import { join as join3 } from "path";
332
+ var ENV_MAP = {
333
+ agentKey: "ATBASH_AGENT_KEY",
334
+ orgName: "ATBASH_ORG_NAME",
335
+ judgeEndpoint: "ATBASH_ENDPOINT",
336
+ blockchainRid: "ATBASH_BLOCKCHAIN_RID",
337
+ provider: "ATBASH_PROVIDER",
338
+ providerModel: "ATBASH_PROVIDER_MODEL"
339
+ };
340
+ function getConfigDir() {
341
+ const home = process.env.HOME || homedir3() || "";
342
+ return join3(home, ".config", "atbash");
343
+ }
344
+ function getConfigPath() {
345
+ return join3(getConfigDir(), "config.json");
346
+ }
347
+ function loadUserConfig() {
348
+ try {
349
+ const p = getConfigPath();
350
+ if (!existsSync(p)) return {};
351
+ const raw = readFileSync3(p, "utf-8").trim();
352
+ if (!raw) return {};
353
+ return JSON.parse(raw);
354
+ } catch (err) {
355
+ console.error("Failed to load config file", err);
356
+ return {};
357
+ }
358
+ }
359
+ function saveUserConfig(config) {
360
+ const dir = getConfigDir();
361
+ if (!existsSync(dir)) {
362
+ mkdirSync(dir, { recursive: true, mode: 448 });
363
+ }
364
+ const filePath = getConfigPath();
365
+ writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", {
366
+ mode: 384
367
+ });
368
+ chmodSync(filePath, 384);
369
+ }
370
+ function resolve(key, flagValue) {
371
+ if (flagValue) return flagValue;
372
+ const envName = ENV_MAP[key];
373
+ if (envName) {
374
+ const envVal = process.env[envName];
375
+ if (envVal) return envVal;
376
+ }
377
+ const fileVal = loadUserConfig()[key];
378
+ if (fileVal != null) return String(fileVal);
379
+ return "";
380
+ }
381
+
382
+ // src-ts/client.ts
383
+ function generateToolCallId() {
384
+ return `tc-${Date.now()}-${randomBytes(4).toString("hex")}`;
385
+ }
386
+ var Atbash = class _Atbash {
387
+ auth;
388
+ endpoint;
389
+ nodeUrls;
390
+ blockchainRid;
391
+ /** Default org name used by `auditToolCall` / `judgeAction`. */
392
+ orgName;
393
+ /** Default judge response-signing pubkey, if configured (see fromConfig). */
394
+ verifyPubKey;
395
+ /** When true (default), `auditToolCall` denies on any error. */
396
+ failClosed;
397
+ logger;
398
+ http;
399
+ /**
400
+ * Per-client cache of resolved chains. Keyed by orgName so repeated
401
+ * calls don't re-hit the dashboard. Cleared by `clearChainCache()`.
402
+ */
403
+ _chainCache = /* @__PURE__ */ new Map();
404
+ /**
405
+ * Cached bearer token for risk-engine / insurance read calls. Built
406
+ * lazily as a signed `log_tool_call` tx and refreshed every 4 min so
407
+ * server-side replay protection windows never expire it mid-session.
408
+ */
409
+ _authBearer = null;
410
+ constructor(privkey, options = {}) {
411
+ this.auth = native.loadAgent(privkey);
412
+ this.endpoint = (options.endpoint ?? DEFAULT_ENDPOINT).replace(/\/+$/, "") || DEFAULT_ENDPOINT;
413
+ this.nodeUrls = options.nodeUrls ? [...options.nodeUrls] : DEFAULT_CHROMIA_NODE_URLS;
414
+ this.blockchainRid = options.blockchainRid ?? native.DEFAULT_BLOCKCHAIN_RID;
415
+ this.orgName = options.orgName;
416
+ this.verifyPubKey = options.verifyPubKey;
417
+ this.failClosed = options.failClosed !== false;
418
+ this.logger = options.logger ?? {};
419
+ this.http = new HttpClient(this.endpoint, options.timeoutMs ?? 3e4);
420
+ if (this.endpoint !== DEFAULT_ENDPOINT) {
421
+ this.logger.warn?.("[atbash] running on non-default judge endpoint", {
422
+ endpoint: this.endpoint,
423
+ verifying: this.verifyPubKey ? "with response-signature pubkey configured" : "without signature verification"
424
+ });
425
+ }
426
+ }
427
+ /**
428
+ * Construct from resolved config: explicit overrides → env vars → the
429
+ * `~/.config/atbash/config.json` file (see userConfig.resolve). The private
430
+ * key comes from `agentKey` (override/env/file) or, failing that, the agent
431
+ * key file (`~/.config/atbash/guard-client-key`). The judge endpoint is
432
+ * validated against the trusted allowlist / self-hosted policy; a
433
+ * self-hosted endpoint's `verifyPubKey` becomes the client default.
434
+ */
435
+ static fromConfig(options = {}) {
436
+ const validated = validateJudgeEndpoint(
437
+ options.judge ?? { endpoint: resolve("judgeEndpoint") || void 0 }
438
+ );
439
+ const agentKey = resolve("agentKey", options.agentKey);
440
+ const auth = agentKey ? native.loadAgent(agentKey) : loadAgentFromFile(options.keyPath);
441
+ const blockchainRid = resolve("blockchainRid", options.blockchainRid) || void 0;
442
+ return new _Atbash(auth.privkey, {
443
+ endpoint: validated.url,
444
+ blockchainRid,
445
+ timeoutMs: options.timeoutMs,
446
+ nodeUrls: options.nodeUrls,
447
+ orgName: options.orgName,
448
+ verifyPubKey: validated.verifyPubKey ?? void 0,
449
+ failClosed: options.failClosed,
450
+ logger: options.logger
451
+ });
452
+ }
453
+ get pubkey() {
454
+ return this.auth.pubkey;
455
+ }
456
+ get privkey() {
457
+ return this.auth.privkey;
458
+ }
459
+ /* ── agent existence (/api/ai/exists) ──────────────────────────────────── */
460
+ /** GET /api/ai/exists?pubkey=… — defaults to this client's pubkey. */
461
+ async checkAgentExists(pubkey) {
462
+ const pk = pubkey ?? this.auth.pubkey;
463
+ return this.track("checkAgentExists", pk, async () => {
464
+ const resp = await this.http.get(
465
+ "/api/ai/exists",
466
+ { pubkey: pk },
467
+ this.authHeaders()
468
+ );
469
+ await this.raiseIfError(resp);
470
+ const data = await this.json(resp);
471
+ return Boolean(data?.registered);
472
+ });
473
+ }
474
+ /* ── log_tool_call (sign-only) ─────────────────────────────────────────── */
475
+ /**
476
+ * Pre-flight `checkAgentExists`, then sign `log_tool_call` locally and
477
+ * return the signed tx hex. The server broadcasts to chain.
478
+ */
479
+ async logToolCall(action, context = "", options = {}) {
480
+ const start = performance.now();
481
+ recordCall("logToolCall", void 0, this.auth.pubkey);
482
+ let exists;
483
+ try {
484
+ exists = await this.checkAgentExists();
485
+ } catch (err) {
486
+ recordDuration("logToolCall", performance.now() - start, "error");
487
+ return { success: false, toolCallId: null, error: errorMessage(err) };
488
+ }
489
+ if (!exists) {
490
+ recordDuration("logToolCall", performance.now() - start, "error");
491
+ return {
492
+ success: false,
493
+ toolCallId: null,
494
+ error: "Agent not registered. Onboard the agent at the dashboard before submitting actions."
495
+ };
496
+ }
497
+ const toolCallId = generateToolCallId();
498
+ const brid = options.chainOpts?.blockchainRid ?? this.blockchainRid;
499
+ try {
500
+ const signedHex = native.signLogToolCall(
501
+ toolCallId,
502
+ action,
503
+ context,
504
+ options.toolName ?? "",
505
+ options.toolArgsJson ?? "",
506
+ this.auth.privkey,
507
+ brid
508
+ );
509
+ recordDuration("logToolCall", performance.now() - start, "success");
510
+ return { success: true, toolCallId, signedHex };
511
+ } catch (err) {
512
+ recordDuration("logToolCall", performance.now() - start, "error");
513
+ return { success: false, toolCallId: null, error: errorMessage(err) };
514
+ }
515
+ }
516
+ /* ── judge_action ──────────────────────────────────────────────────────── */
517
+ /**
518
+ * Sign log_tool_call + optionally judge_action, POST /api/v1/judge.
519
+ *
520
+ * `verifyPubKey` checks the `X-Atbash-Signature` header against the exact
521
+ * response bytes via the Rust core's `verifySignature`.
522
+ */
523
+ async judgeAction(action, context = "", options = {}) {
524
+ return this.track(
525
+ "judgeAction",
526
+ this.auth.pubkey,
527
+ () => this._judgeAction(action, context, options)
528
+ );
529
+ }
530
+ async _judgeAction(action, context, options) {
531
+ if (!action?.trim()) {
532
+ throw new Error("action is required and cannot be empty.");
533
+ }
534
+ let chainOpts = options.chainOpts;
535
+ if (options.orgName) {
536
+ const mapNetwork = await this.getActiveNetworkForOrg(options.orgName);
537
+ if (mapNetwork) {
538
+ chainOpts = { network: mapNetwork };
539
+ } else if (!chainOpts?.blockchainRid) {
540
+ const resolved = await this.resolveChainFromMap(options.orgName, null);
541
+ chainOpts = { ...chainOpts, network: resolved.network };
542
+ }
543
+ }
544
+ const brid = this.bridFromChainOpts(chainOpts);
545
+ const logResult = await this.logToolCall(action, context, {
546
+ toolName: options.toolName,
547
+ toolArgsJson: options.toolArgsJson,
548
+ chainOpts
549
+ });
550
+ if (!logResult.success || !logResult.toolCallId || !logResult.signedHex) {
551
+ throw new Error(logResult.error || "Failed to sign log_tool_call");
552
+ }
553
+ let signedJudgeAction;
554
+ if (!options.provider) {
555
+ const judgmentId = generateToolCallId();
556
+ signedJudgeAction = native.signJudgeAction(
557
+ judgmentId,
558
+ action,
559
+ context || "",
560
+ "",
561
+ this.auth.privkey,
562
+ brid
563
+ );
564
+ }
565
+ const body = {
566
+ tool_call_id: logResult.toolCallId,
567
+ agent_pubkey: this.auth.pubkey,
568
+ action,
569
+ signed_log_tool_call: logResult.signedHex
570
+ };
571
+ if (signedJudgeAction) body.signed_judge_action = signedJudgeAction;
572
+ if (context) body.context = context;
573
+ if (options.provider) body.provider = options.provider;
574
+ if (options.toolName) body.tool_name = options.toolName;
575
+ if (options.model) body.model = options.model;
576
+ let resp;
577
+ try {
578
+ resp = await this.http.post("/api/v1/judge", body);
579
+ } catch (err) {
580
+ throw this.transportError(err);
581
+ }
582
+ if (!resp.ok) throw await this.httpError(resp);
583
+ const bodyBytes = Buffer.from(await resp.arrayBuffer());
584
+ const verifyPubKey = options.verifyPubKey ?? this.verifyPubKey;
585
+ if (verifyPubKey !== void 0) {
586
+ const sig = resp.headers.get("X-Atbash-Signature");
587
+ if (!sig) {
588
+ throw new SignatureVerificationError(
589
+ "missing X-Atbash-Signature header"
590
+ );
591
+ }
592
+ let ok;
593
+ try {
594
+ ok = native.verifySignature(bodyBytes, sig, verifyPubKey);
595
+ } catch (err) {
596
+ throw new SignatureVerificationError(
597
+ `signature verification threw: ${errorMessage(err)}`
598
+ );
599
+ }
600
+ if (!ok) {
601
+ throw new SignatureVerificationError(
602
+ "signature does not verify against configured verifyPubKey"
603
+ );
604
+ }
605
+ }
606
+ const data = parseJson(bodyBytes);
607
+ return {
608
+ verdict: normalizeVerdict(data.verdict),
609
+ actionType: String(data.action_type ?? ""),
610
+ reason: String(data.reason ?? ""),
611
+ confidence: Number(data.confidence ?? 0),
612
+ provider: String(data.provider ?? ""),
613
+ latencyMs: Number(data.latency_ms ?? 0),
614
+ toolCallId: String(data.tool_call_id ?? logResult.toolCallId),
615
+ onChain: Boolean(data.on_chain)
616
+ };
617
+ }
618
+ /* ── audit_tool_call (redact → judge → decision) ───────────────────────── */
619
+ /**
620
+ * High-level guard: redact secrets, submit for judgement, and collapse the
621
+ * result into an allow/deny `Decision`. Fails closed by default — any error
622
+ * (judge unreachable, unrecognized verdict) denies unless `failClosed` is
623
+ * explicitly false.
624
+ */
625
+ async auditToolCall(input) {
626
+ const toolName = input.toolName || "unknown";
627
+ const argsRedaction = native.redactSecrets(stringifyArgs(input.args));
628
+ const ctxRedaction = native.redactSecrets(input.context ?? toolName);
629
+ const argsJson = argsRedaction.redacted;
630
+ const actionText = truncate(argsJson);
631
+ const contextText = ctxRedaction.redacted;
632
+ const totalRedactions = argsRedaction.found.length + ctxRedaction.found.length;
633
+ if (totalRedactions > 0) {
634
+ const kinds = [
635
+ .../* @__PURE__ */ new Set([
636
+ ...argsRedaction.found.map((f) => f.kind),
637
+ ...ctxRedaction.found.map((f) => f.kind)
638
+ ])
639
+ ];
640
+ this.logger.warn?.("[atbash] redacted secrets before judge call", {
641
+ tool: toolName,
642
+ count: totalRedactions,
643
+ kinds
644
+ });
645
+ }
646
+ try {
647
+ this.logger.info?.("[atbash] judge API called", { tool: toolName });
648
+ const result = await this.judgeAction(actionText, contextText, {
649
+ toolName,
650
+ toolArgsJson: argsJson,
651
+ orgName: this.orgName
652
+ });
653
+ if (result.verdict === "No verdict") {
654
+ return {
655
+ allow: true,
656
+ verdict: "ALLOW",
657
+ reason: result.reason || "audit tier \u2014 request logged on-chain, no AI enforcement",
658
+ toolCallId: result.toolCallId
659
+ };
660
+ }
661
+ const action = result.actionType;
662
+ if (action === "block") {
663
+ return {
664
+ allow: false,
665
+ verdict: "BLOCK",
666
+ reason: result.reason,
667
+ toolCallId: result.toolCallId
668
+ };
669
+ }
670
+ if (action === "hold_for_user_confirm") {
671
+ return {
672
+ allow: false,
673
+ verdict: "HOLD",
674
+ reason: result.reason || "held for human confirmation",
675
+ toolCallId: result.toolCallId
676
+ };
677
+ }
678
+ if (action === "allow") {
679
+ if (result.verdict === "HOLD") {
680
+ return {
681
+ allow: false,
682
+ verdict: "HOLD",
683
+ reason: result.reason,
684
+ toolCallId: result.toolCallId
685
+ };
686
+ }
687
+ if (result.verdict === "BLOCK") {
688
+ return {
689
+ allow: false,
690
+ verdict: "BLOCK",
691
+ reason: result.reason,
692
+ toolCallId: result.toolCallId
693
+ };
694
+ }
695
+ return {
696
+ allow: true,
697
+ verdict: "ALLOW",
698
+ reason: result.reason,
699
+ toolCallId: result.toolCallId
700
+ };
701
+ }
702
+ return this.fail(
703
+ "unrecognized action_type from judge",
704
+ result.toolCallId
705
+ );
706
+ } catch (err) {
707
+ const message = errorMessage(err);
708
+ this.logger.warn?.("[atbash] judge API failed", { reason: message });
709
+ return this.fail(message);
710
+ }
711
+ }
712
+ fail(reason, toolCallId) {
713
+ return { allow: !this.failClosed, verdict: "ERROR", reason, toolCallId };
714
+ }
715
+ /* ── judgment status ───────────────────────────────────────────────────── */
716
+ async getJudgmentStatus(judgmentId, agentPubkey) {
717
+ const pk = agentPubkey ?? this.auth.pubkey;
718
+ return this.track("getJudgmentStatus", pk, async () => {
719
+ const resp = await this.http.get(
720
+ "/api/v1/judge",
721
+ { tool_call_id: judgmentId, agent_pubkey: pk },
722
+ this.authHeaders()
723
+ );
724
+ await this.raiseIfError(resp);
725
+ const data = await this.json(resp) ?? {};
726
+ return {
727
+ status: normalizeStatus(data.status),
728
+ verdict: normalizeVerdict(data.verdict),
729
+ reason: String(data.reason ?? ""),
730
+ judgmentId: String(data.judgmentId ?? judgmentId),
731
+ onChain: optBool(data.onChain),
732
+ cached: optBool(data.cached),
733
+ responseTimeMs: optNumber(data.responseTimeMs)
734
+ };
735
+ });
736
+ }
737
+ /* ── risk-engine queries (action-dispatched GET) ───────────────────────── */
738
+ getToolCalls(maxCount) {
739
+ return this.track(
740
+ "getToolCalls",
741
+ void 0,
742
+ () => this.riskEngineRecords("tool-calls", { limit: maxCount })
743
+ );
744
+ }
745
+ getOrgToolCalls(orgName, maxCount) {
746
+ return this.track(
747
+ "getOrgToolCalls",
748
+ void 0,
749
+ () => this.riskEngineRecords("org-tool-calls", {
750
+ org: orgName,
751
+ limit: maxCount
752
+ })
753
+ );
754
+ }
755
+ getAgentToolCalls(agentPubkey, maxCount) {
756
+ return this.track(
757
+ "getAgentToolCalls",
758
+ agentPubkey,
759
+ () => this.riskEngineRecords("agent-tool-calls", {
760
+ agent: agentPubkey,
761
+ limit: maxCount
762
+ })
763
+ );
764
+ }
765
+ async getToolCallCount() {
766
+ return this.track("getToolCallCount", void 0, async () => {
767
+ const raw = await this.riskEngineGet("tool-call-count", {});
768
+ const n = Number(raw);
769
+ return Number.isFinite(n) ? n : 0;
770
+ });
771
+ }
772
+ async getToolCallFull(toolCallId) {
773
+ return this.track("getToolCallFull", void 0, async () => {
774
+ const raw = await this.riskEngineGet("tool-call-full", {
775
+ tool_call_id: toolCallId
776
+ });
777
+ if (!isRecord(raw)) return null;
778
+ return toToolCallFull(raw);
779
+ });
780
+ }
781
+ async getOrgTierInfo(orgName) {
782
+ return this.track("getOrgTierInfo", void 0, async () => {
783
+ const raw = await this.riskEngineGet("org-tier-info", { org: orgName });
784
+ if (!isRecord(raw)) return null;
785
+ return {
786
+ orgName: String(raw.org_name ?? ""),
787
+ tier: String(raw.tier ?? ""),
788
+ verdictEnabled: Boolean(raw.verdict_enabled),
789
+ enforcementEnabled: Boolean(raw.enforcement_enabled)
790
+ };
791
+ });
792
+ }
793
+ async getPendingHeldActions(orgName, maxCount) {
794
+ return this.track("getPendingHeldActions", void 0, async () => {
795
+ const raw = await this.riskEngineGet("pending-held-actions", {
796
+ org: orgName,
797
+ limit: maxCount
798
+ });
799
+ if (!Array.isArray(raw)) return [];
800
+ return raw.map((item) => toHeldAction(item));
801
+ });
802
+ }
803
+ async getHeldActionReviews(orgName, maxCount) {
804
+ return this.track("getHeldActionReviews", void 0, async () => {
805
+ const raw = await this.riskEngineGet("held-action-reviews", {
806
+ org: orgName,
807
+ limit: maxCount
808
+ });
809
+ if (!Array.isArray(raw)) return [];
810
+ return raw.map(
811
+ (item) => toHeldActionReview(item)
812
+ );
813
+ });
814
+ }
815
+ /* ── risk-engine batched (action-dispatched POST) ──────────────────────── */
816
+ getAgentDetail(agentPubkey) {
817
+ return this.track(
818
+ "getAgentDetail",
819
+ agentPubkey,
820
+ () => this.riskEnginePost({ action: "agent-detail-batch", agent: agentPubkey })
821
+ );
822
+ }
823
+ async getAgentPolicy(agentPubkey) {
824
+ return this.track("getAgentPolicy", agentPubkey, async () => {
825
+ const raw = await this.riskEnginePost({
826
+ action: "agent-policy-batch",
827
+ agent: agentPubkey
828
+ });
829
+ return {
830
+ policy: String(raw.policy ?? ""),
831
+ isJailed: Boolean(raw.is_jailed),
832
+ isCustom: Boolean(raw.is_custom),
833
+ defaultPolicy: String(raw.default_policy ?? "")
834
+ };
835
+ });
836
+ }
837
+ /* ── safety stats (/api/insurance?action=safety-stats) ─────────────────── */
838
+ async getSafetyStats() {
839
+ return this.track("getSafetyStats", void 0, async () => {
840
+ const resp = await this.http.get(
841
+ "/api/insurance",
842
+ { action: "safety-stats" },
843
+ this.authHeaders()
844
+ );
845
+ await this.raiseIfError(resp);
846
+ const data = await this.json(resp) ?? {};
847
+ if (isRecord(data.data)) return data.data;
848
+ return data;
849
+ });
850
+ }
851
+ /* ── chain resolution (org → chain) ────────────────────────────────────── */
852
+ /**
853
+ * Org's subscription on a specific chain. The `network` arg selects
854
+ * which chain to query; without it, the dashboard picks the default.
855
+ * Returns null when the org has no record on that chain.
856
+ */
857
+ async getOrgSubscription(orgName, network) {
858
+ return this.track("getOrgSubscription", void 0, async () => {
859
+ const params = { org: orgName };
860
+ if (network) params.network = network;
861
+ const raw = await this.riskEngineGet("org-subscription", params);
862
+ if (!isRecord(raw)) return null;
863
+ return coerceOrgSubscription(raw, orgName);
864
+ });
865
+ }
866
+ /**
867
+ * Read the org's active network from the dashboard's off-chain
868
+ * `org_networks` map. The map is the authoritative source after a
869
+ * plan switch — subscription rows on the source chain go stale, but
870
+ * the map is updated on every assign. Returns null when there's no
871
+ * entry (caller falls back to per-chain subscription resolution).
872
+ */
873
+ async getActiveNetworkForOrg(orgName) {
874
+ try {
875
+ const resp = await this.http.get(
876
+ "/api/org-network",
877
+ { org: orgName },
878
+ this.authHeaders()
879
+ );
880
+ if (resp.status !== 200) return null;
881
+ const data = await this.json(resp);
882
+ if (data?.network === "public" || data?.network === "private") {
883
+ return data.network;
884
+ }
885
+ return null;
886
+ } catch {
887
+ return null;
888
+ }
889
+ }
890
+ /**
891
+ * Resolve which chain an org's actions should run against. Cached
892
+ * per-client by orgName. Resolution order:
893
+ * 1. `org_networks` map (authoritative).
894
+ * 2. Per-chain subscription fallback — public + private records
895
+ * are fetched in parallel, with `is_private_blockchain` and
896
+ * `assigned_at` reconciling mixed states.
897
+ * Defaults to the public chain when nothing else resolves.
898
+ */
899
+ async resolveChainForOrg(orgName) {
900
+ const cached = this._chainCache.get(orgName);
901
+ if (cached) return cached;
902
+ const mapNetwork = await this.getActiveNetworkForOrg(orgName);
903
+ return this.resolveChainFromMap(orgName, mapNetwork);
904
+ }
905
+ /**
906
+ * Resolve a chain given an already-fetched `org_networks` map result.
907
+ * Split out from {@link resolveChainForOrg} so callers that have already
908
+ * queried the map (the judge path) don't fetch /api/org-network twice.
909
+ * Caches per orgName like its caller.
910
+ */
911
+ async resolveChainFromMap(orgName, mapNetwork) {
912
+ const cached = this._chainCache.get(orgName);
913
+ if (cached) return cached;
914
+ if (mapNetwork) {
915
+ const chain = mapNetwork === "private" ? PRIVATE_CHAIN : PUBLIC_CHAIN;
916
+ this._chainCache.set(orgName, chain);
917
+ return chain;
918
+ }
919
+ try {
920
+ const [pubSub, privSub] = await Promise.all([
921
+ this.getOrgSubscription(orgName, "public").catch(() => null),
922
+ this.getOrgSubscription(orgName, "private").catch(() => null)
923
+ ]);
924
+ if (pubSub?.is_private_blockchain) {
925
+ this._chainCache.set(orgName, PRIVATE_CHAIN);
926
+ return PRIVATE_CHAIN;
927
+ }
928
+ if (pubSub && privSub) {
929
+ const chain = privSub.assigned_at > pubSub.assigned_at ? PRIVATE_CHAIN : PUBLIC_CHAIN;
930
+ this._chainCache.set(orgName, chain);
931
+ return chain;
932
+ }
933
+ if (pubSub) {
934
+ this._chainCache.set(orgName, PUBLIC_CHAIN);
935
+ return PUBLIC_CHAIN;
936
+ }
937
+ if (privSub?.is_private_blockchain) {
938
+ this._chainCache.set(orgName, PRIVATE_CHAIN);
939
+ return PRIVATE_CHAIN;
940
+ }
941
+ } catch {
942
+ }
943
+ this._chainCache.set(orgName, PUBLIC_CHAIN);
944
+ return PUBLIC_CHAIN;
945
+ }
946
+ /** Drop any cached chain resolutions. Useful in tests. */
947
+ clearChainCache() {
948
+ this._chainCache.clear();
949
+ }
950
+ /* ── internals ─────────────────────────────────────────────────────────── */
951
+ /**
952
+ * Wrap an SDK method body in telemetry — records the call at start
953
+ * and a success/error duration at end. Re-throws on failure so the
954
+ * caller sees the original exception. Pass `agentPubkey` when the
955
+ * method is keyed to a specific agent; tracked methods that don't
956
+ * depend on an agent (read queries) pass `undefined`.
957
+ */
958
+ async track(name, agentPubkey, fn) {
959
+ const start = performance.now();
960
+ recordCall(name, void 0, agentPubkey);
961
+ try {
962
+ const result = await fn();
963
+ recordDuration(name, performance.now() - start, "success");
964
+ return result;
965
+ } catch (err) {
966
+ recordDuration(name, performance.now() - start, "error");
967
+ throw err;
968
+ }
969
+ }
970
+ /**
971
+ * Pick the BRID for a given per-call chain override. `blockchainRid`
972
+ * takes precedence; otherwise `network` maps to one of the known
973
+ * chains; otherwise the client's default.
974
+ */
975
+ bridFromChainOpts(chainOpts) {
976
+ if (chainOpts?.blockchainRid) return chainOpts.blockchainRid;
977
+ if (chainOpts?.network === "private") return PRIVATE_CHAIN.blockchainRid;
978
+ if (chainOpts?.network === "public") return PUBLIC_CHAIN.blockchainRid;
979
+ return this.blockchainRid;
980
+ }
981
+ /**
982
+ * Get-or-create a Bearer token for dashboard reads. The token is a
983
+ * signed `log_tool_call` op (locally signed, never submitted) — the
984
+ * dashboard verifies the signature against the agent's pubkey. Cached
985
+ * for 4 minutes; refreshed after that so a long-lived client never
986
+ * trips the server's replay window.
987
+ */
988
+ getAuthBearer() {
989
+ const now = Date.now();
990
+ if (this._authBearer && now - this._authBearer.issuedAt < 4 * 60 * 1e3) {
991
+ return this._authBearer.hex;
992
+ }
993
+ const nonce = `auth-${now.toString(36)}-${randomBytes(4).toString("hex")}`;
994
+ const hex = native.signLogToolCall(
995
+ nonce,
996
+ `auth:${now}`,
997
+ "",
998
+ "auth-bearer",
999
+ "",
1000
+ this.auth.privkey,
1001
+ this.blockchainRid
1002
+ );
1003
+ this._authBearer = { hex, issuedAt: now };
1004
+ return hex;
1005
+ }
1006
+ authHeaders() {
1007
+ return { Authorization: `Bearer ${this.getAuthBearer()}` };
1008
+ }
1009
+ async riskEngineGet(action, params) {
1010
+ let resp;
1011
+ try {
1012
+ resp = await this.http.get(
1013
+ "/api/risk-engine",
1014
+ { action, ...params },
1015
+ this.authHeaders()
1016
+ );
1017
+ } catch (err) {
1018
+ throw this.transportError(err);
1019
+ }
1020
+ if (resp.status !== 200) throw await this.httpError(resp);
1021
+ return this.json(resp);
1022
+ }
1023
+ async riskEnginePost(body) {
1024
+ let resp;
1025
+ try {
1026
+ resp = await this.http.post("/api/risk-engine", body, this.authHeaders());
1027
+ } catch (err) {
1028
+ throw this.transportError(err);
1029
+ }
1030
+ if (resp.status !== 200) throw await this.httpError(resp);
1031
+ const data = await this.json(resp);
1032
+ return isRecord(data) ? data : {};
1033
+ }
1034
+ async riskEngineRecords(action, params) {
1035
+ const raw = await this.riskEngineGet(action, params);
1036
+ if (!Array.isArray(raw)) return [];
1037
+ return raw.map((item) => toToolCallRecord(item));
1038
+ }
1039
+ async raiseIfError(resp) {
1040
+ if (resp.ok) return;
1041
+ throw await this.httpError(resp);
1042
+ }
1043
+ /** Wrap a failed HTTP *response* (non-2xx / non-200) as an AtbashAPIError. */
1044
+ async httpError(resp) {
1045
+ return new AtbashAPIError(
1046
+ resp.status,
1047
+ await safeText(resp),
1048
+ resp.statusText,
1049
+ this.endpoint
1050
+ );
1051
+ }
1052
+ /** Wrap a *transport* failure (fetch threw, no response) as an AtbashAPIError. */
1053
+ transportError(err) {
1054
+ return new AtbashAPIError(0, errorMessage(err), "", this.endpoint);
1055
+ }
1056
+ async json(resp) {
1057
+ const text = await safeText(resp);
1058
+ if (!text) return null;
1059
+ const parsed = tryParseJson(text);
1060
+ return parsed === void 0 ? null : parsed;
1061
+ }
1062
+ /* ── crypto / redaction passthroughs (Rust core) ───────────────────────── */
1063
+ static generateKeypair() {
1064
+ return native.generateKeypair();
1065
+ }
1066
+ static isValidPrivateKey(hex) {
1067
+ return native.isValidPrivateKey(hex);
1068
+ }
1069
+ static derivePublicKey(privkey) {
1070
+ return native.derivePublicKey(privkey);
1071
+ }
1072
+ static redactSecrets(text) {
1073
+ return native.redactSecrets(text);
1074
+ }
1075
+ static normalizeForMatching(text) {
1076
+ return native.normalizeForMatching(text);
1077
+ }
1078
+ static containsEvasionCharacters(text) {
1079
+ return native.containsEvasionCharacters(text);
1080
+ }
1081
+ };
1082
+ function baseToolCall(raw) {
1083
+ return {
1084
+ toolCallId: String(raw.tool_call_id ?? ""),
1085
+ agentPubkey: pubkeyToHex(raw.agent_pubkey),
1086
+ toolName: String(raw.tool_name ?? ""),
1087
+ commandText: String(raw.command_text ?? ""),
1088
+ contextText: String(raw.context_text ?? ""),
1089
+ orgName: String(raw.org_name ?? "")
1090
+ };
1091
+ }
1092
+ function toToolCallRecord(raw) {
1093
+ return {
1094
+ ...baseToolCall(raw),
1095
+ toolArgsJson: String(raw.tool_args_json ?? ""),
1096
+ rowid: Number(raw.rowid ?? 0)
1097
+ };
1098
+ }
1099
+ function toToolCallFull(raw) {
1100
+ return {
1101
+ ...baseToolCall(raw),
1102
+ toolArgsJson: optString(raw.tool_args_json),
1103
+ createdAt: optNumber(raw.created_at),
1104
+ actionType: optString(raw.action_type),
1105
+ resultStatus: optString(raw.result_status),
1106
+ verdictColor: optString(raw.verdict_color),
1107
+ verdictReason: optString(raw.verdict_reason),
1108
+ verdictSource: optString(raw.verdict_source),
1109
+ verdictResponseTimeMs: optNumber(raw.verdict_response_time_ms)
1110
+ };
1111
+ }
1112
+ function toHeldAction(raw) {
1113
+ return {
1114
+ judgmentId: String(raw.judgment_id ?? ""),
1115
+ agentPubkey: pubkeyToHex(raw.agent_pubkey),
1116
+ actionText: String(raw.action_text ?? ""),
1117
+ actionContext: String(raw.action_context ?? ""),
1118
+ verdict: normalizeVerdict(raw.verdict),
1119
+ reason: String(raw.reason ?? ""),
1120
+ createdAt: Number(raw.created_at ?? 0)
1121
+ };
1122
+ }
1123
+ function toHeldActionReview(raw) {
1124
+ return {
1125
+ judgmentId: String(raw.judgment_id ?? ""),
1126
+ actionText: String(raw.action_text ?? ""),
1127
+ status: String(raw.status ?? ""),
1128
+ reviewNote: String(raw.review_note ?? ""),
1129
+ reviewedAt: Number(raw.reviewed_at ?? 0),
1130
+ createdAt: Number(raw.created_at ?? 0),
1131
+ reviewedBy: optString(raw.reviewed_by) || pubkeyToHex(raw.reviewed_by) || void 0
1132
+ };
1133
+ }
1134
+ function coerceOrgSubscription(raw, orgName) {
1135
+ return {
1136
+ org_name: String(raw.org_name ?? orgName),
1137
+ subscription_name: String(raw.subscription_name ?? ""),
1138
+ agent_number: Number(raw.agent_number ?? 0),
1139
+ is_private_blockchain: Boolean(raw.is_private_blockchain),
1140
+ monthly_price: Number(raw.monthly_price ?? 0),
1141
+ yearly_price: Number(raw.yearly_price ?? 0),
1142
+ duration_months: Number(raw.duration_months ?? 0),
1143
+ assigned_at: Number(raw.assigned_at ?? 0),
1144
+ expires_at: Number(raw.expires_at ?? 0),
1145
+ is_active: Boolean(raw.is_active)
1146
+ };
1147
+ }
1148
+ function isRecord(v) {
1149
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1150
+ }
1151
+ function optString(v) {
1152
+ if (v === null || v === void 0) return void 0;
1153
+ return typeof v === "string" ? v : String(v);
1154
+ }
1155
+ function optNumber(v) {
1156
+ if (v === null || v === void 0) return void 0;
1157
+ const n = Number(v);
1158
+ return Number.isFinite(n) ? n : void 0;
1159
+ }
1160
+ function optBool(v) {
1161
+ if (v === null || v === void 0) return void 0;
1162
+ return Boolean(v);
1163
+ }
1164
+ function tryParseJson(text) {
1165
+ try {
1166
+ return JSON.parse(text);
1167
+ } catch {
1168
+ return void 0;
1169
+ }
1170
+ }
1171
+ function parseJson(bytes) {
1172
+ const parsed = tryParseJson(bytes.toString("utf-8"));
1173
+ return parsed === void 0 ? {} : parsed;
1174
+ }
1175
+ async function safeText(resp) {
1176
+ try {
1177
+ return await resp.text();
1178
+ } catch {
1179
+ return "";
1180
+ }
1181
+ }
1182
+ function errorMessage(err) {
1183
+ return err instanceof Error ? err.message : String(err);
1184
+ }
1185
+ function stringifyArgs(args) {
1186
+ if (args === null || args === void 0) return "";
1187
+ if (typeof args === "string") return args;
1188
+ try {
1189
+ return JSON.stringify(args);
1190
+ } catch {
1191
+ return String(args);
1192
+ }
1193
+ }
1194
+ var MAX_ACTION_LEN = 4e3;
1195
+ function truncate(text) {
1196
+ if (text.length <= MAX_ACTION_LEN) return text;
1197
+ return text.slice(0, MAX_ACTION_LEN) + "\u2026";
1198
+ }
1199
+
1200
+ // src-ts/redact.ts
1201
+ function redactJsonStrings(value) {
1202
+ if (typeof value === "string") {
1203
+ return native.redactSecrets(value).redacted;
1204
+ }
1205
+ if (Array.isArray(value)) {
1206
+ return value.map((v) => redactJsonStrings(v));
1207
+ }
1208
+ if (value !== null && typeof value === "object") {
1209
+ const out = {};
1210
+ for (const [k, v] of Object.entries(value)) {
1211
+ out[k] = redactJsonStrings(v);
1212
+ }
1213
+ return out;
1214
+ }
1215
+ return value;
1216
+ }
1217
+
1218
+ // src-ts/signature.ts
1219
+ function verifyJudgeResponseSignature(bodyBytes, signatureHex, pubKeyHex) {
1220
+ if (!signatureHex) {
1221
+ return { ok: false, reason: "missing X-Atbash-Signature header" };
1222
+ }
1223
+ const body = Buffer.isBuffer(bodyBytes) ? bodyBytes : Buffer.from(bodyBytes);
1224
+ let isValid;
1225
+ try {
1226
+ isValid = native.verifySignature(body, signatureHex, pubKeyHex);
1227
+ } catch (err) {
1228
+ const message = err instanceof Error ? err.message : String(err ?? "");
1229
+ if (message.includes("signature is not hex") || message.includes("signature length out of range")) {
1230
+ return { ok: false, reason: "malformed signature header" };
1231
+ }
1232
+ return { ok: false, reason: `signature verification threw: ${message}` };
1233
+ }
1234
+ return isValid ? { ok: true } : {
1235
+ ok: false,
1236
+ reason: "signature does not verify against configured verifyPubKey"
1237
+ };
1238
+ }
1239
+
1240
+ // src-ts/index.ts
1241
+ function isValidPrivateKey(hex) {
1242
+ return native.isValidPrivateKey(hex);
1243
+ }
1244
+ function derivePublicKey(privkey) {
1245
+ return native.derivePublicKey(privkey);
1246
+ }
1247
+ function generateKeypair() {
1248
+ return native.generateKeypair();
1249
+ }
1250
+ function loadAgent(privkey) {
1251
+ return native.loadAgent(privkey);
1252
+ }
1253
+ function signLogToolCall(toolCallId, action, context, toolName, toolArgsJson, privkey, blockchainRid) {
1254
+ return native.signLogToolCall(
1255
+ toolCallId,
1256
+ action,
1257
+ context,
1258
+ toolName,
1259
+ toolArgsJson,
1260
+ privkey,
1261
+ blockchainRid
1262
+ );
1263
+ }
1264
+ function signJudgeAction(judgmentId, action, context, extra, privkey, blockchainRid) {
1265
+ return native.signJudgeAction(
1266
+ judgmentId,
1267
+ action,
1268
+ context,
1269
+ extra,
1270
+ privkey,
1271
+ blockchainRid
1272
+ );
1273
+ }
1274
+ function verifySignature(body, signatureHex, pubkeyHex) {
1275
+ return native.verifySignature(body, signatureHex, pubkeyHex);
1276
+ }
1277
+ function normalizeForMatching(text) {
1278
+ return native.normalizeForMatching(text);
1279
+ }
1280
+ function containsEvasionCharacters(text) {
1281
+ return native.containsEvasionCharacters(text);
1282
+ }
1283
+ function redactSecrets(text) {
1284
+ return native.redactSecrets(text);
1285
+ }
1286
+ function containsSecret(text) {
1287
+ return native.containsSecret(text);
1288
+ }
1289
+ function createMemorySnapshot(entries, takenAt) {
1290
+ return native.createMemorySnapshot(entries, takenAt);
1291
+ }
1292
+ function diffMemorySnapshots(before, after) {
1293
+ return native.diffMemorySnapshots(before, after);
1294
+ }
1295
+ export {
1296
+ Atbash,
1297
+ AtbashAPIError,
1298
+ DEFAULT_BLOCKCHAIN_RID,
1299
+ DEFAULT_CHROMIA_NODE_URLS,
1300
+ DEFAULT_ENDPOINT,
1301
+ SignatureVerificationError,
1302
+ containsEvasionCharacters,
1303
+ containsSecret,
1304
+ createMemorySnapshot,
1305
+ derivePublicKey,
1306
+ diffMemorySnapshots,
1307
+ flushTelemetry,
1308
+ generateKeypair,
1309
+ getConfigDir,
1310
+ getConfigPath,
1311
+ isValidPrivateKey,
1312
+ loadAgent,
1313
+ loadAgentFromFile,
1314
+ loadUserConfig,
1315
+ normalizeForMatching,
1316
+ normalizeStatus,
1317
+ normalizeVerdict,
1318
+ pubkeyToHex,
1319
+ recordCall,
1320
+ recordDuration,
1321
+ redactJsonStrings,
1322
+ redactSecrets,
1323
+ resolve,
1324
+ resolveKeyPath,
1325
+ saveUserConfig,
1326
+ setupTelemetry,
1327
+ shutdownTelemetry,
1328
+ signJudgeAction,
1329
+ signLogToolCall,
1330
+ validateJudgeEndpoint,
1331
+ verifyJudgeResponseSignature,
1332
+ verifySignature
1333
+ };
1334
+ //# sourceMappingURL=index.mjs.map