@attest-ai/core 0.6.0 → 0.9.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/cjs/index.cjs +630 -25
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +117 -6
- package/dist/cjs/proto/index.cjs +81 -0
- package/dist/cjs/proto/index.cjs.map +1 -1
- package/dist/cjs/proto/index.d.cts +102 -1
- package/dist/esm/cli.js +321 -2
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/index.d.ts +117 -6
- package/dist/esm/index.js +619 -25
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/proto/index.d.ts +102 -1
- package/dist/esm/proto/index.js +77 -0
- package/dist/esm/proto/index.js.map +1 -1
- package/package.json +2 -2
package/dist/esm/index.js
CHANGED
|
@@ -31,6 +31,27 @@ function assertionId(id) {
|
|
|
31
31
|
function agentId(id) {
|
|
32
32
|
return id;
|
|
33
33
|
}
|
|
34
|
+
function layerForType(assertionType) {
|
|
35
|
+
switch (assertionType) {
|
|
36
|
+
case "schema":
|
|
37
|
+
return 1;
|
|
38
|
+
case "constraint":
|
|
39
|
+
return 2;
|
|
40
|
+
case "trace":
|
|
41
|
+
case "trace_tree":
|
|
42
|
+
return 3;
|
|
43
|
+
case "content":
|
|
44
|
+
return 4;
|
|
45
|
+
case "embedding":
|
|
46
|
+
return 5;
|
|
47
|
+
case "llm_judge":
|
|
48
|
+
return 6;
|
|
49
|
+
case "plugin":
|
|
50
|
+
return 8;
|
|
51
|
+
default:
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
34
55
|
|
|
35
56
|
// src/proto/errors.ts
|
|
36
57
|
var ProtocolError = class extends Error {
|
|
@@ -57,6 +78,58 @@ var EngineTimeoutError = class extends Error {
|
|
|
57
78
|
this.timeoutMs = timeoutMs;
|
|
58
79
|
}
|
|
59
80
|
};
|
|
81
|
+
var ProtocolDesyncError = class extends Error {
|
|
82
|
+
diagnostics;
|
|
83
|
+
constructor(message, diagnostics) {
|
|
84
|
+
super(message);
|
|
85
|
+
this.name = "ProtocolDesyncError";
|
|
86
|
+
this.diagnostics = diagnostics;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/proto/diagnostics.ts
|
|
91
|
+
var DEFAULT_BUFFER_SIZE = 32;
|
|
92
|
+
var MAX_LINE_PREVIEW = 512;
|
|
93
|
+
var ProtocolDiagnosticBuffer = class {
|
|
94
|
+
capacity;
|
|
95
|
+
entries = [];
|
|
96
|
+
constructor(capacity = DEFAULT_BUFFER_SIZE) {
|
|
97
|
+
if (capacity <= 0) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`ProtocolDiagnosticBuffer capacity must be positive, got ${capacity}`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
this.capacity = capacity;
|
|
103
|
+
}
|
|
104
|
+
push(diagnostic) {
|
|
105
|
+
if (this.entries.length >= this.capacity) {
|
|
106
|
+
this.entries.shift();
|
|
107
|
+
}
|
|
108
|
+
this.entries.push(diagnostic);
|
|
109
|
+
}
|
|
110
|
+
snapshot() {
|
|
111
|
+
return this.entries.slice();
|
|
112
|
+
}
|
|
113
|
+
countWithin(windowMs, nowMs) {
|
|
114
|
+
const cutoff = nowMs - windowMs;
|
|
115
|
+
let count = 0;
|
|
116
|
+
for (let i = this.entries.length - 1; i >= 0; i -= 1) {
|
|
117
|
+
if (this.entries[i].timestampMs >= cutoff) {
|
|
118
|
+
count += 1;
|
|
119
|
+
} else {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return count;
|
|
124
|
+
}
|
|
125
|
+
clear() {
|
|
126
|
+
this.entries.length = 0;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
function previewLine(line) {
|
|
130
|
+
if (line.length <= MAX_LINE_PREVIEW) return line;
|
|
131
|
+
return line.slice(0, MAX_LINE_PREVIEW) + "\u2026";
|
|
132
|
+
}
|
|
60
133
|
|
|
61
134
|
// src/proto/codec.ts
|
|
62
135
|
function encodeRequest(id, method, params) {
|
|
@@ -119,8 +192,8 @@ function extractId(response) {
|
|
|
119
192
|
}
|
|
120
193
|
|
|
121
194
|
// src/version.ts
|
|
122
|
-
var VERSION = "0.
|
|
123
|
-
var ENGINE_VERSION = "0.
|
|
195
|
+
var VERSION = "0.9.0";
|
|
196
|
+
var ENGINE_VERSION = "0.9.0";
|
|
124
197
|
|
|
125
198
|
// src/engine-manager.ts
|
|
126
199
|
import { spawn, execSync } from "child_process";
|
|
@@ -472,12 +545,16 @@ function simulationEvaluateBatch(assertions) {
|
|
|
472
545
|
return {
|
|
473
546
|
results,
|
|
474
547
|
total_cost: 0,
|
|
475
|
-
total_duration_ms: 0
|
|
548
|
+
total_duration_ms: 0,
|
|
549
|
+
simulated: true
|
|
476
550
|
};
|
|
477
551
|
}
|
|
478
552
|
|
|
479
553
|
// src/client.ts
|
|
480
554
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
555
|
+
var DEFAULT_DIAGNOSTIC_BUFFER = 32;
|
|
556
|
+
var DEFAULT_DESYNC_THRESHOLD = 3;
|
|
557
|
+
var DEFAULT_DESYNC_WINDOW_MS = 1e3;
|
|
481
558
|
function resolveTimeoutMs(override) {
|
|
482
559
|
if (override !== void 0) return override;
|
|
483
560
|
const env = process.env["ATTEST_ENGINE_TIMEOUT"];
|
|
@@ -499,6 +576,24 @@ function makeTimeoutPromise(method, timeoutMs) {
|
|
|
499
576
|
}, timeoutMs);
|
|
500
577
|
});
|
|
501
578
|
}
|
|
579
|
+
function debugProtocolEnabled() {
|
|
580
|
+
const flag = process.env["ATTEST_DEBUG_PROTOCOL"];
|
|
581
|
+
return flag === "1" || flag === "true" || flag === "yes";
|
|
582
|
+
}
|
|
583
|
+
function envLoggerDefault() {
|
|
584
|
+
if (debugProtocolEnabled()) {
|
|
585
|
+
return {
|
|
586
|
+
warn: (msg, ...args) => console.warn(msg, ...args),
|
|
587
|
+
error: (msg, ...args) => console.error(msg, ...args)
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
return {
|
|
591
|
+
warn: () => {
|
|
592
|
+
},
|
|
593
|
+
error: () => {
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
}
|
|
502
597
|
var AttestClient = class {
|
|
503
598
|
engine;
|
|
504
599
|
requestId = 0;
|
|
@@ -506,8 +601,34 @@ var AttestClient = class {
|
|
|
506
601
|
writeChain = Promise.resolve();
|
|
507
602
|
readerActive = false;
|
|
508
603
|
lineHandler = null;
|
|
509
|
-
|
|
604
|
+
logger;
|
|
605
|
+
diagnosticBuffer;
|
|
606
|
+
diagnosticListeners = /* @__PURE__ */ new Set();
|
|
607
|
+
desyncThreshold;
|
|
608
|
+
desyncWindowMs;
|
|
609
|
+
desynced = false;
|
|
610
|
+
constructor(engine, options) {
|
|
510
611
|
this.engine = engine;
|
|
612
|
+
this.logger = options?.logger ?? envLoggerDefault();
|
|
613
|
+
this.diagnosticBuffer = new ProtocolDiagnosticBuffer(
|
|
614
|
+
options?.diagnosticBufferSize ?? DEFAULT_DIAGNOSTIC_BUFFER
|
|
615
|
+
);
|
|
616
|
+
this.desyncThreshold = options?.desyncThreshold ?? DEFAULT_DESYNC_THRESHOLD;
|
|
617
|
+
this.desyncWindowMs = options?.desyncWindowMs ?? DEFAULT_DESYNC_WINDOW_MS;
|
|
618
|
+
if (options?.onDiagnostic !== void 0) {
|
|
619
|
+
this.diagnosticListeners.add(options.onDiagnostic);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
/** Subscribe to protocol diagnostics. Returns an unsubscribe function. */
|
|
623
|
+
onProtocolDiagnostic(listener) {
|
|
624
|
+
this.diagnosticListeners.add(listener);
|
|
625
|
+
return () => {
|
|
626
|
+
this.diagnosticListeners.delete(listener);
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
/** Snapshot of the diagnostic ring buffer (most recent last). */
|
|
630
|
+
protocolDiagnostics() {
|
|
631
|
+
return this.diagnosticBuffer.snapshot();
|
|
511
632
|
}
|
|
512
633
|
startReader() {
|
|
513
634
|
if (this.readerActive) return;
|
|
@@ -532,35 +653,101 @@ var AttestClient = class {
|
|
|
532
653
|
this.lineHandler = null;
|
|
533
654
|
this.readerActive = false;
|
|
534
655
|
}
|
|
656
|
+
recordDiagnostic(kind, message, rawLine) {
|
|
657
|
+
const diagnostic = {
|
|
658
|
+
kind,
|
|
659
|
+
message,
|
|
660
|
+
rawLine: previewLine(rawLine),
|
|
661
|
+
timestampMs: Date.now()
|
|
662
|
+
};
|
|
663
|
+
this.diagnosticBuffer.push(diagnostic);
|
|
664
|
+
this.logger.warn(`[attest.protocol] ${kind}: ${message}`);
|
|
665
|
+
for (const listener of this.diagnosticListeners) {
|
|
666
|
+
try {
|
|
667
|
+
listener(diagnostic);
|
|
668
|
+
} catch (err) {
|
|
669
|
+
this.logger.error(
|
|
670
|
+
`[attest.protocol] diagnostic listener threw: ${err instanceof Error ? err.message : String(err)}`
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
this.maybeTriggerDesync(diagnostic.timestampMs);
|
|
675
|
+
}
|
|
676
|
+
maybeTriggerDesync(nowMs) {
|
|
677
|
+
if (this.desynced) return;
|
|
678
|
+
const recent = this.diagnosticBuffer.countWithin(this.desyncWindowMs, nowMs);
|
|
679
|
+
if (recent >= this.desyncThreshold) {
|
|
680
|
+
this.desynced = true;
|
|
681
|
+
const snapshot = this.diagnosticBuffer.snapshot();
|
|
682
|
+
const err = new ProtocolDesyncError(
|
|
683
|
+
`Engine protocol desync: ${recent} unroutable response lines within ${this.desyncWindowMs}ms.`,
|
|
684
|
+
snapshot
|
|
685
|
+
);
|
|
686
|
+
this.logger.error(`[attest.protocol] ${err.message}`);
|
|
687
|
+
this.failAll(err);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
535
690
|
handleLine(line) {
|
|
691
|
+
let response;
|
|
692
|
+
try {
|
|
693
|
+
response = decodeResponse(line);
|
|
694
|
+
} catch (err) {
|
|
695
|
+
this.handleDecodeFailure(line, err);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
536
698
|
let reqId;
|
|
537
699
|
try {
|
|
538
|
-
const response = decodeResponse(line);
|
|
539
700
|
reqId = extractId(response);
|
|
540
|
-
const pending = this.pending.get(reqId);
|
|
541
|
-
if (pending === void 0) return;
|
|
542
|
-
this.pending.delete(reqId);
|
|
543
|
-
try {
|
|
544
|
-
const result = extractResult(response);
|
|
545
|
-
pending.resolve(result);
|
|
546
|
-
} catch (err) {
|
|
547
|
-
pending.reject(err);
|
|
548
|
-
}
|
|
549
701
|
} catch (err) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
702
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
703
|
+
this.recordDiagnostic("missing_id", message, line);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const pending = this.pending.get(reqId);
|
|
707
|
+
if (pending === void 0) {
|
|
708
|
+
this.recordDiagnostic(
|
|
709
|
+
"unknown_id",
|
|
710
|
+
`no pending request for id=${reqId}`,
|
|
711
|
+
line
|
|
712
|
+
);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
this.pending.delete(reqId);
|
|
716
|
+
try {
|
|
717
|
+
const result = extractResult(response);
|
|
718
|
+
pending.resolve(result);
|
|
719
|
+
} catch (err) {
|
|
720
|
+
pending.reject(err);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
handleDecodeFailure(line, err) {
|
|
724
|
+
if (err instanceof ProtocolError) {
|
|
725
|
+
let reqId = -1;
|
|
726
|
+
try {
|
|
727
|
+
const raw = JSON.parse(line.trim());
|
|
728
|
+
const idCandidate = raw["id"];
|
|
729
|
+
if (typeof idCandidate === "number" && Number.isFinite(idCandidate)) {
|
|
730
|
+
reqId = idCandidate;
|
|
561
731
|
}
|
|
732
|
+
} catch {
|
|
733
|
+
reqId = -1;
|
|
734
|
+
}
|
|
735
|
+
const pending = this.pending.get(reqId);
|
|
736
|
+
if (pending !== void 0) {
|
|
737
|
+
this.pending.delete(reqId);
|
|
738
|
+
pending.reject(err);
|
|
739
|
+
return;
|
|
562
740
|
}
|
|
741
|
+
this.recordDiagnostic(
|
|
742
|
+
"non_routable_error",
|
|
743
|
+
`engine error code=${err.code} (${err.errorMessage}) had no matching pending id`,
|
|
744
|
+
line
|
|
745
|
+
);
|
|
746
|
+
return;
|
|
563
747
|
}
|
|
748
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
749
|
+
const kind = classifyDecodeError(message);
|
|
750
|
+
this.recordDiagnostic(kind, message, line);
|
|
564
751
|
}
|
|
565
752
|
failAll(err) {
|
|
566
753
|
for (const pending of this.pending.values()) {
|
|
@@ -569,6 +756,12 @@ var AttestClient = class {
|
|
|
569
756
|
this.pending.clear();
|
|
570
757
|
}
|
|
571
758
|
async sendRequest(method, params, timeoutMs) {
|
|
759
|
+
if (this.desynced) {
|
|
760
|
+
throw new ProtocolDesyncError(
|
|
761
|
+
"Engine protocol is desynced; AttestClient will not send further requests.",
|
|
762
|
+
this.diagnosticBuffer.snapshot()
|
|
763
|
+
);
|
|
764
|
+
}
|
|
572
765
|
const resolvedTimeout = resolveTimeoutMs(timeoutMs);
|
|
573
766
|
if (!this.readerActive) {
|
|
574
767
|
return Promise.race([
|
|
@@ -679,6 +872,13 @@ var AttestClient = class {
|
|
|
679
872
|
return String(raw.message);
|
|
680
873
|
}
|
|
681
874
|
};
|
|
875
|
+
function classifyDecodeError(message) {
|
|
876
|
+
if (message.startsWith("malformed JSON")) return "malformed_json";
|
|
877
|
+
if (message.startsWith("expected JSON object")) return "non_object_response";
|
|
878
|
+
if (message.startsWith("invalid jsonrpc version")) return "invalid_jsonrpc_version";
|
|
879
|
+
if (message.startsWith("empty response line")) return "malformed_json";
|
|
880
|
+
return "malformed_json";
|
|
881
|
+
}
|
|
682
882
|
|
|
683
883
|
// src/trace.ts
|
|
684
884
|
import { randomUUID } from "crypto";
|
|
@@ -800,6 +1000,173 @@ var AgentResult = class {
|
|
|
800
1000
|
}
|
|
801
1001
|
};
|
|
802
1002
|
|
|
1003
|
+
// src/diagnostics.ts
|
|
1004
|
+
var ANSI_RESET = "\x1B[0m";
|
|
1005
|
+
var ANSI_RED = "\x1B[31m";
|
|
1006
|
+
var ANSI_YELLOW = "\x1B[33m";
|
|
1007
|
+
var ANSI_GREEN = "\x1B[32m";
|
|
1008
|
+
var ANSI_DIM = "\x1B[2m";
|
|
1009
|
+
var ANSI_BOLD = "\x1B[1m";
|
|
1010
|
+
var LAYER_NAMES = {
|
|
1011
|
+
1: "schema",
|
|
1012
|
+
2: "constraint",
|
|
1013
|
+
3: "trace",
|
|
1014
|
+
4: "content",
|
|
1015
|
+
5: "embedding",
|
|
1016
|
+
6: "llm_judge",
|
|
1017
|
+
7: "trace_tree",
|
|
1018
|
+
8: "plugin"
|
|
1019
|
+
};
|
|
1020
|
+
function layerLabel(layer, assertionType) {
|
|
1021
|
+
if (layer === 0 && assertionType) {
|
|
1022
|
+
return `L? ${assertionType}`;
|
|
1023
|
+
}
|
|
1024
|
+
const name = LAYER_NAMES[layer] ?? assertionType ?? "uncategorized";
|
|
1025
|
+
return `L${layer} ${name}`;
|
|
1026
|
+
}
|
|
1027
|
+
function statusGlyph(status, color) {
|
|
1028
|
+
let glyph;
|
|
1029
|
+
let c;
|
|
1030
|
+
if (status === STATUS_PASS) {
|
|
1031
|
+
glyph = "PASS";
|
|
1032
|
+
c = ANSI_GREEN;
|
|
1033
|
+
} else if (status === STATUS_SOFT_FAIL) {
|
|
1034
|
+
glyph = "SOFT";
|
|
1035
|
+
c = ANSI_YELLOW;
|
|
1036
|
+
} else if (status === STATUS_HARD_FAIL) {
|
|
1037
|
+
glyph = "FAIL";
|
|
1038
|
+
c = ANSI_RED;
|
|
1039
|
+
} else {
|
|
1040
|
+
glyph = status.toUpperCase() || "????";
|
|
1041
|
+
c = ANSI_DIM;
|
|
1042
|
+
}
|
|
1043
|
+
return color ? `${c}${glyph}${ANSI_RESET}` : glyph;
|
|
1044
|
+
}
|
|
1045
|
+
function bold(s, color) {
|
|
1046
|
+
return color ? `${ANSI_BOLD}${s}${ANSI_RESET}` : s;
|
|
1047
|
+
}
|
|
1048
|
+
function dim(s, color) {
|
|
1049
|
+
return color ? `${ANSI_DIM}${s}${ANSI_RESET}` : s;
|
|
1050
|
+
}
|
|
1051
|
+
function truncate(value, limit = 280) {
|
|
1052
|
+
if (value.length <= limit) {
|
|
1053
|
+
return value;
|
|
1054
|
+
}
|
|
1055
|
+
return value.slice(0, limit - 3) + "...";
|
|
1056
|
+
}
|
|
1057
|
+
function renderAssertionFailure(result, options = {}) {
|
|
1058
|
+
const color = options.color ?? false;
|
|
1059
|
+
const testFile = options.testFile;
|
|
1060
|
+
const layer = result.layer ?? 0;
|
|
1061
|
+
const label = layerLabel(layer, result.type);
|
|
1062
|
+
const lines = [];
|
|
1063
|
+
lines.push(
|
|
1064
|
+
` ${statusGlyph(result.status, color)} ${bold(result.assertion_id, color)} ${dim(`\u2014 ${label}`, color)}`
|
|
1065
|
+
);
|
|
1066
|
+
if (result.trace_node_path) {
|
|
1067
|
+
lines.push(` trace path: ${result.trace_node_path}`);
|
|
1068
|
+
}
|
|
1069
|
+
lines.push(` score: ${result.score.toFixed(3)}`);
|
|
1070
|
+
if (result.expected) {
|
|
1071
|
+
lines.push(` expected: ${truncate(result.expected)}`);
|
|
1072
|
+
}
|
|
1073
|
+
if (result.actual) {
|
|
1074
|
+
lines.push(` actual: ${truncate(result.actual)}`);
|
|
1075
|
+
}
|
|
1076
|
+
if (result.explanation) {
|
|
1077
|
+
lines.push(` detail: ${truncate(result.explanation)}`);
|
|
1078
|
+
}
|
|
1079
|
+
if (result.threshold_source && result.threshold_source !== "static") {
|
|
1080
|
+
lines.push(` threshold: ${result.threshold_source}`);
|
|
1081
|
+
}
|
|
1082
|
+
if (result.judge_metadata) {
|
|
1083
|
+
appendJudgeMetadata(lines, result.judge_metadata);
|
|
1084
|
+
}
|
|
1085
|
+
if (result.suggested_action) {
|
|
1086
|
+
lines.push(` hint: ${truncate(result.suggested_action)}`);
|
|
1087
|
+
}
|
|
1088
|
+
if ((result.cost ?? 0) > 0 || (result.duration_ms ?? 0) > 0) {
|
|
1089
|
+
const cost = result.cost ?? 0;
|
|
1090
|
+
const durMs = result.duration_ms ?? 0;
|
|
1091
|
+
lines.push(` cost/lat: $${cost.toFixed(6)} / ${durMs}ms`);
|
|
1092
|
+
}
|
|
1093
|
+
if (testFile) {
|
|
1094
|
+
lines.push(` source: ${testFile}`);
|
|
1095
|
+
}
|
|
1096
|
+
return lines.join("\n") + "\n";
|
|
1097
|
+
}
|
|
1098
|
+
function appendJudgeMetadata(lines, meta) {
|
|
1099
|
+
if (meta.model) {
|
|
1100
|
+
let rubric = meta.rubric_name ?? "default";
|
|
1101
|
+
if (meta.rubric_version) {
|
|
1102
|
+
rubric = `${rubric} @ ${meta.rubric_version}`;
|
|
1103
|
+
}
|
|
1104
|
+
lines.push(` judge: ${meta.model} / ${rubric}`);
|
|
1105
|
+
}
|
|
1106
|
+
if (meta.prompt_hash) {
|
|
1107
|
+
lines.push(` prompt: #${meta.prompt_hash}`);
|
|
1108
|
+
}
|
|
1109
|
+
const samples = meta.sample_scores ?? [];
|
|
1110
|
+
if (samples.length > 1) {
|
|
1111
|
+
const formatted = samples.map((s) => s.toFixed(2)).join(", ");
|
|
1112
|
+
const mean = (meta.score_mean ?? 0).toFixed(2);
|
|
1113
|
+
const stddev = (meta.score_stddev ?? 0).toFixed(2);
|
|
1114
|
+
const flag = meta.high_variance ? " \u26A0 HIGH VARIANCE" : "";
|
|
1115
|
+
lines.push(
|
|
1116
|
+
` samples: [${formatted}] mean=${mean} stddev=${stddev}${flag}`
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
const probes = meta.bias_probes ?? [];
|
|
1120
|
+
if (probes.length > 0) {
|
|
1121
|
+
const formatted = probes.map((p) => `${p.name} \u0394${p.delta >= 0 ? "+" : ""}${p.delta.toFixed(2)}`).join(", ");
|
|
1122
|
+
lines.push(` bias: ${formatted}`);
|
|
1123
|
+
}
|
|
1124
|
+
if (meta.calibration) {
|
|
1125
|
+
const c = meta.calibration;
|
|
1126
|
+
lines.push(
|
|
1127
|
+
` calibrated: ${c.label_count} labels, agreement=${c.agreement.toFixed(2)}, \u03BA=${c.cohen_kappa.toFixed(2)}`
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
function renderSummary(result, options = {}) {
|
|
1132
|
+
const color = options.color ?? false;
|
|
1133
|
+
const passN = result.passCount;
|
|
1134
|
+
const soft = result.assertionResults.filter(
|
|
1135
|
+
(r) => r.status === STATUS_SOFT_FAIL
|
|
1136
|
+
).length;
|
|
1137
|
+
const hard = result.assertionResults.filter(
|
|
1138
|
+
(r) => r.status === STATUS_HARD_FAIL
|
|
1139
|
+
).length;
|
|
1140
|
+
const parts = [
|
|
1141
|
+
`${statusGlyph(STATUS_PASS, color)} ${passN}`,
|
|
1142
|
+
`${statusGlyph(STATUS_SOFT_FAIL, color)} ${soft}`,
|
|
1143
|
+
`${statusGlyph(STATUS_HARD_FAIL, color)} ${hard}`,
|
|
1144
|
+
`cost $${result.totalCost.toFixed(6)}`,
|
|
1145
|
+
`dur ${result.totalDurationMs}ms`
|
|
1146
|
+
];
|
|
1147
|
+
return parts.join(" | ");
|
|
1148
|
+
}
|
|
1149
|
+
function renderDiagnostics(result, options = {}) {
|
|
1150
|
+
const color = options.color ?? false;
|
|
1151
|
+
const includePassing = options.includePassing ?? false;
|
|
1152
|
+
const rows = includePassing ? result.assertionResults : result.failedAssertions;
|
|
1153
|
+
if (rows.length === 0) {
|
|
1154
|
+
return dim(" (no failures)\n", color);
|
|
1155
|
+
}
|
|
1156
|
+
const out = [""];
|
|
1157
|
+
out.push(
|
|
1158
|
+
bold(
|
|
1159
|
+
`Attest diagnostic \u2014 ${rows.length} of ${result.assertionResults.length} assertions failed:`,
|
|
1160
|
+
color
|
|
1161
|
+
)
|
|
1162
|
+
);
|
|
1163
|
+
for (const r of rows) {
|
|
1164
|
+
out.push(renderAssertionFailure(r, options));
|
|
1165
|
+
}
|
|
1166
|
+
out.push(dim(` Summary: ${renderSummary(result, { color })}`, color));
|
|
1167
|
+
return out.join("\n");
|
|
1168
|
+
}
|
|
1169
|
+
|
|
803
1170
|
// src/trace-tree.ts
|
|
804
1171
|
var TraceTree = class {
|
|
805
1172
|
root;
|
|
@@ -2004,6 +2371,222 @@ var ContinuousEvalRunner = class {
|
|
|
2004
2371
|
}
|
|
2005
2372
|
};
|
|
2006
2373
|
|
|
2374
|
+
// src/calibration.ts
|
|
2375
|
+
import { createHash as createHash2 } from "crypto";
|
|
2376
|
+
function computeAgreement(pairs, threshold) {
|
|
2377
|
+
if (pairs.length === 0) {
|
|
2378
|
+
throw new Error("no labeled pairs available");
|
|
2379
|
+
}
|
|
2380
|
+
if (threshold <= 0 || threshold >= 1) {
|
|
2381
|
+
throw new Error("threshold must be in (0, 1)");
|
|
2382
|
+
}
|
|
2383
|
+
const n = pairs.length;
|
|
2384
|
+
let humanPos = 0;
|
|
2385
|
+
let judgePos = 0;
|
|
2386
|
+
let bothPos = 0;
|
|
2387
|
+
let bothNeg = 0;
|
|
2388
|
+
for (const p of pairs) {
|
|
2389
|
+
const hPos = p.human >= threshold;
|
|
2390
|
+
const jPos = p.judge >= threshold;
|
|
2391
|
+
if (hPos) humanPos++;
|
|
2392
|
+
if (jPos) judgePos++;
|
|
2393
|
+
if (hPos && jPos) bothPos++;
|
|
2394
|
+
if (!hPos && !jPos) bothNeg++;
|
|
2395
|
+
}
|
|
2396
|
+
const humanNeg = n - humanPos;
|
|
2397
|
+
const judgeNeg = n - judgePos;
|
|
2398
|
+
const agreement = (bothPos + bothNeg) / n;
|
|
2399
|
+
const expected = (humanPos * judgePos + humanNeg * judgeNeg) / (n * n);
|
|
2400
|
+
const cohenKappa = expected < 1 ? (agreement - expected) / (1 - expected) : 0;
|
|
2401
|
+
const rocAuc = rocAUC(pairs, threshold);
|
|
2402
|
+
return { threshold, n, agreement, cohenKappa, rocAuc };
|
|
2403
|
+
}
|
|
2404
|
+
function rocAUC(pairs, threshold) {
|
|
2405
|
+
const rows = pairs.map((p) => ({
|
|
2406
|
+
score: p.judge,
|
|
2407
|
+
pos: p.human >= threshold
|
|
2408
|
+
}));
|
|
2409
|
+
const pos = rows.filter((r) => r.pos).length;
|
|
2410
|
+
const neg = rows.length - pos;
|
|
2411
|
+
if (pos === 0 || neg === 0) return 0;
|
|
2412
|
+
rows.sort((a, b) => a.score - b.score);
|
|
2413
|
+
let rankSumPos = 0;
|
|
2414
|
+
let i = 0;
|
|
2415
|
+
while (i < rows.length) {
|
|
2416
|
+
let j = i;
|
|
2417
|
+
while (j < rows.length && rows[j].score === rows[i].score) {
|
|
2418
|
+
j++;
|
|
2419
|
+
}
|
|
2420
|
+
const avgRank = (i + j + 1) / 2;
|
|
2421
|
+
for (let k = i; k < j; k++) {
|
|
2422
|
+
if (rows[k].pos) rankSumPos += avgRank;
|
|
2423
|
+
}
|
|
2424
|
+
i = j;
|
|
2425
|
+
}
|
|
2426
|
+
const auc = (rankSumPos - pos * (pos + 1) / 2) / (pos * neg);
|
|
2427
|
+
if (Number.isNaN(auc) || !Number.isFinite(auc)) return 0;
|
|
2428
|
+
return auc;
|
|
2429
|
+
}
|
|
2430
|
+
function loadLabelsCSV(text) {
|
|
2431
|
+
const rows = parseCSV(text);
|
|
2432
|
+
if (rows.length === 0) {
|
|
2433
|
+
throw new Error("empty CSV");
|
|
2434
|
+
}
|
|
2435
|
+
let start = 0;
|
|
2436
|
+
if (rows[0].length >= 2) {
|
|
2437
|
+
const firstHuman = parseFloatStrict(rows[0][1]);
|
|
2438
|
+
if (firstHuman === null) start = 1;
|
|
2439
|
+
}
|
|
2440
|
+
const out = [];
|
|
2441
|
+
for (let idx = start; idx < rows.length; idx++) {
|
|
2442
|
+
const row = rows[idx];
|
|
2443
|
+
const lineNum = idx + 1;
|
|
2444
|
+
if (row.length === 0) continue;
|
|
2445
|
+
if (row[0].trimStart().startsWith("#")) continue;
|
|
2446
|
+
if (row.length < 2) {
|
|
2447
|
+
throw new Error(
|
|
2448
|
+
`CSV line ${lineNum}: want at least 2 columns (input, human_label)`
|
|
2449
|
+
);
|
|
2450
|
+
}
|
|
2451
|
+
const human = parseFloatStrict(row[1]);
|
|
2452
|
+
if (human === null) {
|
|
2453
|
+
throw new Error(`CSV line ${lineNum}: human_label not a float: ${row[1]}`);
|
|
2454
|
+
}
|
|
2455
|
+
const rec = {
|
|
2456
|
+
input: row[0],
|
|
2457
|
+
humanLabel: human,
|
|
2458
|
+
judgeScore: 0,
|
|
2459
|
+
judgeKnown: false
|
|
2460
|
+
};
|
|
2461
|
+
if (row.length >= 3 && row[2].trim() !== "") {
|
|
2462
|
+
const judge = parseFloatStrict(row[2]);
|
|
2463
|
+
if (judge === null) {
|
|
2464
|
+
throw new Error(
|
|
2465
|
+
`CSV line ${lineNum}: judge_score not a float: ${row[2]}`
|
|
2466
|
+
);
|
|
2467
|
+
}
|
|
2468
|
+
rec.judgeScore = judge;
|
|
2469
|
+
rec.judgeKnown = true;
|
|
2470
|
+
}
|
|
2471
|
+
out.push(rec);
|
|
2472
|
+
}
|
|
2473
|
+
if (out.length === 0) {
|
|
2474
|
+
throw new Error("CSV contained no labeled rows");
|
|
2475
|
+
}
|
|
2476
|
+
return out;
|
|
2477
|
+
}
|
|
2478
|
+
function loadLabelsJSONL(text) {
|
|
2479
|
+
const out = [];
|
|
2480
|
+
const lines = text.split(/\r?\n/);
|
|
2481
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2482
|
+
const stripped = lines[i].trim();
|
|
2483
|
+
if (!stripped) continue;
|
|
2484
|
+
let obj;
|
|
2485
|
+
try {
|
|
2486
|
+
obj = JSON.parse(stripped);
|
|
2487
|
+
} catch (err) {
|
|
2488
|
+
throw new Error(`JSONL line ${i + 1}: ${err.message}`);
|
|
2489
|
+
}
|
|
2490
|
+
if (Object.keys(obj).length === 0) continue;
|
|
2491
|
+
if (obj["human_label"] === void 0) {
|
|
2492
|
+
throw new Error(`JSONL line ${i + 1}: missing human_label`);
|
|
2493
|
+
}
|
|
2494
|
+
const human = parseJSONNumber(obj["human_label"]);
|
|
2495
|
+
if (human === null) {
|
|
2496
|
+
throw new Error(`JSONL line ${i + 1}: human_label is not a number`);
|
|
2497
|
+
}
|
|
2498
|
+
const rec = {
|
|
2499
|
+
input: typeof obj["input"] === "string" ? obj["input"] : "",
|
|
2500
|
+
humanLabel: human,
|
|
2501
|
+
judgeScore: 0,
|
|
2502
|
+
judgeKnown: false
|
|
2503
|
+
};
|
|
2504
|
+
if (obj["judge_score"] !== void 0 && obj["judge_score"] !== null) {
|
|
2505
|
+
const judge = parseJSONNumber(obj["judge_score"]);
|
|
2506
|
+
if (judge === null) {
|
|
2507
|
+
throw new Error(`JSONL line ${i + 1}: judge_score is not a number`);
|
|
2508
|
+
}
|
|
2509
|
+
rec.judgeScore = judge;
|
|
2510
|
+
rec.judgeKnown = true;
|
|
2511
|
+
}
|
|
2512
|
+
out.push(rec);
|
|
2513
|
+
}
|
|
2514
|
+
if (out.length === 0) {
|
|
2515
|
+
throw new Error("JSONL contained no labeled rows");
|
|
2516
|
+
}
|
|
2517
|
+
return out;
|
|
2518
|
+
}
|
|
2519
|
+
function promptHash(text) {
|
|
2520
|
+
return createHash2("sha256").update(text).digest("hex").slice(0, 16);
|
|
2521
|
+
}
|
|
2522
|
+
function parseFloatStrict(raw) {
|
|
2523
|
+
const trimmed = raw.trim();
|
|
2524
|
+
if (trimmed === "") return null;
|
|
2525
|
+
const n = Number(trimmed);
|
|
2526
|
+
if (Number.isNaN(n)) return null;
|
|
2527
|
+
return n;
|
|
2528
|
+
}
|
|
2529
|
+
function parseJSONNumber(value) {
|
|
2530
|
+
if (typeof value !== "number") return null;
|
|
2531
|
+
if (Number.isNaN(value)) return null;
|
|
2532
|
+
return value;
|
|
2533
|
+
}
|
|
2534
|
+
function parseCSV(text) {
|
|
2535
|
+
const rows = [];
|
|
2536
|
+
let current = [];
|
|
2537
|
+
let field = "";
|
|
2538
|
+
let inQuotes = false;
|
|
2539
|
+
let i = 0;
|
|
2540
|
+
while (i < text.length) {
|
|
2541
|
+
const ch = text[i];
|
|
2542
|
+
if (inQuotes) {
|
|
2543
|
+
if (ch === '"') {
|
|
2544
|
+
if (text[i + 1] === '"') {
|
|
2545
|
+
field += '"';
|
|
2546
|
+
i += 2;
|
|
2547
|
+
continue;
|
|
2548
|
+
}
|
|
2549
|
+
inQuotes = false;
|
|
2550
|
+
i++;
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2553
|
+
field += ch;
|
|
2554
|
+
i++;
|
|
2555
|
+
continue;
|
|
2556
|
+
}
|
|
2557
|
+
if (ch === '"') {
|
|
2558
|
+
inQuotes = true;
|
|
2559
|
+
i++;
|
|
2560
|
+
continue;
|
|
2561
|
+
}
|
|
2562
|
+
if (ch === ",") {
|
|
2563
|
+
current.push(field);
|
|
2564
|
+
field = "";
|
|
2565
|
+
i++;
|
|
2566
|
+
continue;
|
|
2567
|
+
}
|
|
2568
|
+
if (ch === "\n") {
|
|
2569
|
+
current.push(field);
|
|
2570
|
+
rows.push(current);
|
|
2571
|
+
current = [];
|
|
2572
|
+
field = "";
|
|
2573
|
+
i++;
|
|
2574
|
+
continue;
|
|
2575
|
+
}
|
|
2576
|
+
if (ch === "\r") {
|
|
2577
|
+
i++;
|
|
2578
|
+
continue;
|
|
2579
|
+
}
|
|
2580
|
+
field += ch;
|
|
2581
|
+
i++;
|
|
2582
|
+
}
|
|
2583
|
+
if (field.length > 0 || current.length > 0) {
|
|
2584
|
+
current.push(field);
|
|
2585
|
+
rows.push(current);
|
|
2586
|
+
}
|
|
2587
|
+
return rows;
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2007
2590
|
// src/personas.ts
|
|
2008
2591
|
var FRIENDLY_USER = {
|
|
2009
2592
|
name: "FriendlyUser",
|
|
@@ -2445,6 +3028,8 @@ export {
|
|
|
2445
3028
|
OllamaAdapter,
|
|
2446
3029
|
OpenAIAdapter,
|
|
2447
3030
|
PluginRegistry,
|
|
3031
|
+
ProtocolDesyncError,
|
|
3032
|
+
ProtocolDiagnosticBuffer,
|
|
2448
3033
|
ProtocolError,
|
|
2449
3034
|
STATUS_HARD_FAIL,
|
|
2450
3035
|
STATUS_PASS,
|
|
@@ -2473,6 +3058,7 @@ export {
|
|
|
2473
3058
|
agentId,
|
|
2474
3059
|
assertionId,
|
|
2475
3060
|
attestExpect,
|
|
3061
|
+
computeAgreement,
|
|
2476
3062
|
config,
|
|
2477
3063
|
decodeResponse,
|
|
2478
3064
|
delegate,
|
|
@@ -2482,6 +3068,14 @@ export {
|
|
|
2482
3068
|
extractResult,
|
|
2483
3069
|
getSampleRate,
|
|
2484
3070
|
isSimulationMode,
|
|
3071
|
+
layerForType,
|
|
3072
|
+
loadLabelsCSV,
|
|
3073
|
+
loadLabelsJSONL,
|
|
3074
|
+
previewLine,
|
|
3075
|
+
promptHash,
|
|
3076
|
+
renderAssertionFailure,
|
|
3077
|
+
renderDiagnostics,
|
|
3078
|
+
renderSummary,
|
|
2485
3079
|
resetConfig,
|
|
2486
3080
|
simulationEvaluateBatch,
|
|
2487
3081
|
tier,
|