@attest-ai/core 1.0.1 → 1.1.0-rc.1
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 +1033 -273
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +324 -101
- package/dist/cjs/proto/index.cjs +8 -1
- package/dist/cjs/proto/index.cjs.map +1 -1
- package/dist/cjs/proto/index.d.cts +17 -1
- package/dist/esm/cli.d.ts +3 -1
- package/dist/esm/cli.js +219 -80
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/index.d.ts +324 -101
- package/dist/esm/index.js +1026 -278
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/proto/index.d.ts +17 -1
- package/dist/esm/proto/index.js +6 -1
- package/dist/esm/proto/index.js.map +1 -1
- package/engine-checksums.json +10 -0
- package/package.json +4 -2
package/dist/cjs/index.cjs
CHANGED
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
AgentResult: () => AgentResult,
|
|
36
36
|
AlertDispatcher: () => AlertDispatcher,
|
|
37
37
|
AnthropicAdapter: () => AnthropicAdapter,
|
|
38
|
+
AssertionIdAssigner: () => AssertionIdAssigner,
|
|
38
39
|
AttestClient: () => AttestClient,
|
|
39
40
|
CONFUSED_USER: () => CONFUSED_USER,
|
|
40
41
|
COOPERATIVE_USER: () => COOPERATIVE_USER,
|
|
@@ -54,6 +55,7 @@ __export(index_exports, {
|
|
|
54
55
|
FRIENDLY_USER: () => FRIENDLY_USER,
|
|
55
56
|
FailureClass: () => FailureClass,
|
|
56
57
|
GeminiAdapter: () => GeminiAdapter,
|
|
58
|
+
ID_SCHEME: () => ID_SCHEME,
|
|
57
59
|
LangChainAdapter: () => LangChainAdapter,
|
|
58
60
|
ManualAdapter: () => ManualAdapter,
|
|
59
61
|
MockToolRegistry: () => MockToolRegistry,
|
|
@@ -70,7 +72,9 @@ __export(index_exports, {
|
|
|
70
72
|
ProtocolError: () => ProtocolError,
|
|
71
73
|
REASON_BUDGET_EXHAUSTED: () => REASON_BUDGET_EXHAUSTED,
|
|
72
74
|
REASON_JUDGE_ABSTAINED: () => REASON_JUDGE_ABSTAINED,
|
|
75
|
+
REASON_JUDGE_MALFORMED: () => REASON_JUDGE_MALFORMED,
|
|
73
76
|
REASON_MISSING_EVIDENCE: () => REASON_MISSING_EVIDENCE,
|
|
77
|
+
REASON_PROVIDER_AUTH: () => REASON_PROVIDER_AUTH,
|
|
74
78
|
REASON_PROVIDER_NOT_CONFIGURED: () => REASON_PROVIDER_NOT_CONFIGURED,
|
|
75
79
|
REASON_PROVIDER_UNAVAILABLE: () => REASON_PROVIDER_UNAVAILABLE,
|
|
76
80
|
REASON_REPLAY_MISS: () => REASON_REPLAY_MISS,
|
|
@@ -112,7 +116,10 @@ __export(index_exports, {
|
|
|
112
116
|
agent: () => agent,
|
|
113
117
|
agentId: () => agentId,
|
|
114
118
|
assertionId: () => assertionId,
|
|
119
|
+
assertionIdOf: () => assertionIdOf,
|
|
115
120
|
attestExpect: () => attestExpect,
|
|
121
|
+
canonicalIdInput: () => canonicalIdInput,
|
|
122
|
+
canonicalSpec: () => canonicalSpec,
|
|
116
123
|
computeAgreement: () => computeAgreement,
|
|
117
124
|
config: () => config,
|
|
118
125
|
decodeResponse: () => decodeResponse,
|
|
@@ -149,13 +156,16 @@ __export(index_exports, {
|
|
|
149
156
|
renderVerdictDiagnostics: () => renderVerdictDiagnostics,
|
|
150
157
|
renderVitest: () => renderVitest,
|
|
151
158
|
repeat: () => repeat,
|
|
159
|
+
replayRun: () => replayRun,
|
|
152
160
|
requireSimulationCapability: () => requireSimulationCapability,
|
|
153
161
|
resetConfig: () => resetConfig,
|
|
154
162
|
resolveMode: () => resolveMode,
|
|
155
163
|
scenario: () => scenario,
|
|
164
|
+
setChainObserver: () => setChainObserver,
|
|
156
165
|
simulationEnvEnabled: () => simulationEnvEnabled,
|
|
157
166
|
startReplay: () => startReplay,
|
|
158
167
|
startWorld: () => startWorld,
|
|
168
|
+
submitPluginResults: () => submitPluginResults,
|
|
159
169
|
sumUsage: () => sumUsage,
|
|
160
170
|
testPathFor: () => testPathFor,
|
|
161
171
|
tier: () => tier,
|
|
@@ -182,6 +192,8 @@ var REASON_MISSING_EVIDENCE = "missing_evidence";
|
|
|
182
192
|
var REASON_SHORT_CIRCUITED = "short_circuited";
|
|
183
193
|
var REASON_BUDGET_EXHAUSTED = "budget_exhausted";
|
|
184
194
|
var REASON_JUDGE_ABSTAINED = "judge_abstained";
|
|
195
|
+
var REASON_JUDGE_MALFORMED = "judge_malformed";
|
|
196
|
+
var REASON_PROVIDER_AUTH = "provider_auth";
|
|
185
197
|
var REASON_REPLAY_MISS = "replay_miss";
|
|
186
198
|
var OUTCOME_PASS = "pass";
|
|
187
199
|
var OUTCOME_FAIL = "fail";
|
|
@@ -217,7 +229,8 @@ var FailureClass = {
|
|
|
217
229
|
FlakyJudge: "flaky_judge",
|
|
218
230
|
BadRubric: "bad_rubric",
|
|
219
231
|
MissingTraceData: "missing_trace_data",
|
|
220
|
-
StochasticVariance: "stochastic_variance"
|
|
232
|
+
StochasticVariance: "stochastic_variance",
|
|
233
|
+
SpecError: "spec_error"
|
|
221
234
|
};
|
|
222
235
|
function layerForType(assertionType) {
|
|
223
236
|
switch (assertionType) {
|
|
@@ -387,8 +400,8 @@ function extractId(response) {
|
|
|
387
400
|
}
|
|
388
401
|
|
|
389
402
|
// src/version.ts
|
|
390
|
-
var VERSION = "1.0.1";
|
|
391
|
-
var ENGINE_VERSION = "1.0.1";
|
|
403
|
+
var VERSION = "1.1.0-rc.1";
|
|
404
|
+
var ENGINE_VERSION = "1.1.0-rc.1";
|
|
392
405
|
|
|
393
406
|
// src/engine-manager.ts
|
|
394
407
|
var import_node_child_process = require("child_process");
|
|
@@ -513,6 +526,7 @@ var DEFAULT_TIMEOUT_MS = 3e4;
|
|
|
513
526
|
var DEFAULT_DIAGNOSTIC_BUFFER = 32;
|
|
514
527
|
var DEFAULT_DESYNC_THRESHOLD = 3;
|
|
515
528
|
var DEFAULT_DESYNC_WINDOW_MS = 1e3;
|
|
529
|
+
var MAX_TIMEOUT_ENV_SECONDS = 3600;
|
|
516
530
|
function resolveTimeoutMs(override) {
|
|
517
531
|
if (override !== void 0) return override;
|
|
518
532
|
const env = process.env["ATTEST_ENGINE_TIMEOUT"];
|
|
@@ -520,10 +534,15 @@ function resolveTimeoutMs(override) {
|
|
|
520
534
|
const parsed = Number(env);
|
|
521
535
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
522
536
|
throw new Error(
|
|
523
|
-
`ATTEST_ENGINE_TIMEOUT must be a positive number of
|
|
537
|
+
`ATTEST_ENGINE_TIMEOUT must be a positive number of seconds, got: '${env}'`
|
|
524
538
|
);
|
|
525
539
|
}
|
|
526
|
-
|
|
540
|
+
if (parsed > MAX_TIMEOUT_ENV_SECONDS) {
|
|
541
|
+
throw new Error(
|
|
542
|
+
`ATTEST_ENGINE_TIMEOUT is in seconds since 1.1.0; got '${env}', which exceeds ${MAX_TIMEOUT_ENV_SECONDS}. A millisecond value such as 30000 must now be 30.`
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
return parsed * 1e3;
|
|
527
546
|
}
|
|
528
547
|
return DEFAULT_TIMEOUT_MS;
|
|
529
548
|
}
|
|
@@ -598,10 +617,10 @@ var AttestClient = class {
|
|
|
598
617
|
rl.on("line", this.lineHandler);
|
|
599
618
|
rl.on("close", () => {
|
|
600
619
|
if (this.attachedReader !== rl) return;
|
|
601
|
-
const
|
|
620
|
+
const tail2 = this.engine.stderrTail ?? "";
|
|
602
621
|
this.failAll(
|
|
603
|
-
new Error(
|
|
604
|
-
${
|
|
622
|
+
new Error(tail2 === "" ? "Engine closed stdout." : `Engine closed stdout. stderr:
|
|
623
|
+
${tail2}`)
|
|
605
624
|
);
|
|
606
625
|
this.readerActive = false;
|
|
607
626
|
this.attachedReader = null;
|
|
@@ -751,9 +770,9 @@ ${tail}`)
|
|
|
751
770
|
}
|
|
752
771
|
const resolvedTimeout = resolveTimeoutMs(timeoutMs);
|
|
753
772
|
if (this.engine.isRunning === false) {
|
|
754
|
-
const
|
|
755
|
-
throw new Error(`Engine process is not running.${
|
|
756
|
-
${
|
|
773
|
+
const tail2 = this.engine.stderrTail ?? "";
|
|
774
|
+
throw new Error(`Engine process is not running.${tail2 === "" ? "" : ` stderr:
|
|
775
|
+
${tail2}`}`);
|
|
757
776
|
}
|
|
758
777
|
const cp = this.engine.childProcess;
|
|
759
778
|
if (cp === null || cp.stdin === null) {
|
|
@@ -795,10 +814,10 @@ ${tail}`}`);
|
|
|
795
814
|
}
|
|
796
815
|
/** A failed pipe write names the engine's stderr tail; the original error stays as `cause` and `code`. */
|
|
797
816
|
describeWriteError(err) {
|
|
798
|
-
const
|
|
817
|
+
const tail2 = this.engine.stderrTail ?? "";
|
|
799
818
|
const wrapped = new Error(
|
|
800
|
-
`Failed to write to the engine: ${err.message}.${
|
|
801
|
-
${
|
|
819
|
+
`Failed to write to the engine: ${err.message}.${tail2 === "" ? "" : ` stderr:
|
|
820
|
+
${tail2}`}`,
|
|
802
821
|
{ cause: err }
|
|
803
822
|
);
|
|
804
823
|
return Object.assign(wrapped, { code: err.code });
|
|
@@ -894,7 +913,11 @@ ${tail}`}`,
|
|
|
894
913
|
}
|
|
895
914
|
const raw = await this.sendRequest(
|
|
896
915
|
"query_drift",
|
|
897
|
-
{
|
|
916
|
+
{
|
|
917
|
+
assertion_id: assertionId2,
|
|
918
|
+
window_size: windowSize,
|
|
919
|
+
...options2?.staticThreshold !== void 0 ? { static_threshold: options2.staticThreshold } : {}
|
|
920
|
+
},
|
|
898
921
|
options2?.timeout
|
|
899
922
|
);
|
|
900
923
|
return raw.report;
|
|
@@ -957,14 +980,19 @@ function requireSimulationCapability(init) {
|
|
|
957
980
|
|
|
958
981
|
// src/engine-downloader.ts
|
|
959
982
|
var https = __toESM(require("https"), 1);
|
|
960
|
-
var http = __toESM(require("http"), 1);
|
|
961
983
|
var fs = __toESM(require("fs"), 1);
|
|
962
984
|
var path = __toESM(require("path"), 1);
|
|
963
985
|
var os = __toESM(require("os"), 1);
|
|
964
986
|
var crypto = __toESM(require("crypto"), 1);
|
|
987
|
+
var import_node_url = require("url");
|
|
988
|
+
var import_meta = {};
|
|
965
989
|
var RELEASE_BASE = `https://github.com/attest-framework/attest/releases/download/v${ENGINE_VERSION}`;
|
|
966
|
-
var
|
|
990
|
+
var TABLE_NAME = "engine-checksums.json";
|
|
967
991
|
var MAX_REDIRECTS = 5;
|
|
992
|
+
var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
993
|
+
var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
|
|
994
|
+
var DEFAULT_TOTAL_TIMEOUT_MS = 12e4;
|
|
995
|
+
var SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
968
996
|
function versionedBinDir() {
|
|
969
997
|
return path.join(os.homedir(), ".attest", "bin", ENGINE_VERSION);
|
|
970
998
|
}
|
|
@@ -997,73 +1025,131 @@ function cachedEnginePath() {
|
|
|
997
1025
|
const binPath = path.join(versionedBinDir(), binaryFilename());
|
|
998
1026
|
return fs.statSync(binPath, { throwIfNoEntry: false })?.isFile() ? binPath : null;
|
|
999
1027
|
}
|
|
1000
|
-
function
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
const
|
|
1004
|
-
if (
|
|
1005
|
-
|
|
1006
|
-
if (match) {
|
|
1007
|
-
checksums.set(match[2], match[1]);
|
|
1008
|
-
}
|
|
1028
|
+
function packageRoot() {
|
|
1029
|
+
let dir = typeof __dirname === "string" ? __dirname : path.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
1030
|
+
while (!fs.existsSync(path.join(dir, "package.json"))) {
|
|
1031
|
+
const parent = path.dirname(dir);
|
|
1032
|
+
if (parent === dir) return dir;
|
|
1033
|
+
dir = parent;
|
|
1009
1034
|
}
|
|
1010
|
-
return
|
|
1035
|
+
return dir;
|
|
1011
1036
|
}
|
|
1012
|
-
function
|
|
1013
|
-
if (
|
|
1014
|
-
|
|
1015
|
-
|
|
1037
|
+
function embeddedHash(key, tablePath) {
|
|
1038
|
+
if (!fs.existsSync(tablePath)) {
|
|
1039
|
+
throw new Error(
|
|
1040
|
+
`This @attest-ai/core install has no embedded engine hash table (${TABLE_NAME}), which is how a source or workspace install looks, so it never downloads an engine. Point ATTEST_ENGINE_PATH at an engine binary, or build one with \`make engine\` and set ATTEST_ENGINE_PATH=./bin/attest-engine.`
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
let hash;
|
|
1044
|
+
try {
|
|
1045
|
+
const table = JSON.parse(fs.readFileSync(tablePath, "utf-8"));
|
|
1046
|
+
hash = table[ENGINE_VERSION]?.[key];
|
|
1047
|
+
} catch (err) {
|
|
1048
|
+
throw new Error(
|
|
1049
|
+
`@attest-ai/core ${VERSION} ships an unreadable engine hash table at ${tablePath}: ${String(err)}`
|
|
1016
1050
|
);
|
|
1017
1051
|
}
|
|
1052
|
+
if (typeof hash !== "string" || !SHA256_HEX.test(hash)) {
|
|
1053
|
+
throw new Error(
|
|
1054
|
+
`@attest-ai/core ${VERSION} ships no valid engine hash for v${ENGINE_VERSION} on ${key} in ${tablePath}.`
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
return hash;
|
|
1058
|
+
}
|
|
1059
|
+
function httpsGet(url, o) {
|
|
1018
1060
|
return new Promise((resolve5, reject) => {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
if (
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
resolve5(
|
|
1061
|
+
let current;
|
|
1062
|
+
let done = false;
|
|
1063
|
+
const finish = (err, data) => {
|
|
1064
|
+
if (done) return;
|
|
1065
|
+
done = true;
|
|
1066
|
+
clearTimeout(deadline);
|
|
1067
|
+
if (err) {
|
|
1068
|
+
current?.destroy();
|
|
1069
|
+
reject(err);
|
|
1070
|
+
} else {
|
|
1071
|
+
resolve5(data);
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
const deadline = setTimeout(
|
|
1075
|
+
() => finish(new Error(`Timed out after ${o.totalTimeoutMs}ms fetching ${url}`)),
|
|
1076
|
+
o.totalTimeoutMs
|
|
1077
|
+
);
|
|
1078
|
+
const hop = (target, redirects) => {
|
|
1079
|
+
if (redirects > MAX_REDIRECTS) {
|
|
1080
|
+
finish(new Error(`Too many redirects (>${MAX_REDIRECTS}) following ${url}`));
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
let parsed;
|
|
1084
|
+
try {
|
|
1085
|
+
parsed = new URL(target);
|
|
1086
|
+
} catch {
|
|
1087
|
+
finish(new Error(`Invalid URL: ${target}`));
|
|
1030
1088
|
return;
|
|
1031
1089
|
}
|
|
1032
|
-
if (
|
|
1033
|
-
|
|
1034
|
-
reject(new Error(`HTTP ${status} fetching ${url}`));
|
|
1090
|
+
if (parsed.protocol !== "https:") {
|
|
1091
|
+
finish(new Error(`Refusing to download over a non-https URL: ${target}`));
|
|
1035
1092
|
return;
|
|
1036
1093
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1094
|
+
current = https.get(parsed, { ca: o.ca, timeout: o.connectTimeoutMs }, (res) => {
|
|
1095
|
+
const status = res.statusCode ?? 0;
|
|
1096
|
+
if (REDIRECT_STATUSES.has(status)) {
|
|
1097
|
+
res.resume();
|
|
1098
|
+
const location = res.headers.location;
|
|
1099
|
+
if (!location) {
|
|
1100
|
+
finish(new Error(`Redirect ${status} without Location header from ${target}`));
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
let next;
|
|
1104
|
+
try {
|
|
1105
|
+
next = new URL(location, parsed).toString();
|
|
1106
|
+
} catch {
|
|
1107
|
+
finish(new Error(`Invalid redirect Location ${JSON.stringify(location)} from ${target}`));
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
hop(next, redirects + 1);
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
if (status !== 200) {
|
|
1114
|
+
res.resume();
|
|
1115
|
+
finish(new Error(`HTTP ${status} fetching ${target}`));
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const chunks = [];
|
|
1119
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
1120
|
+
res.on("end", () => finish(null, Buffer.concat(chunks)));
|
|
1121
|
+
res.on("error", (err) => finish(err));
|
|
1122
|
+
});
|
|
1123
|
+
current.on(
|
|
1124
|
+
"timeout",
|
|
1125
|
+
() => finish(new Error(`No response within ${o.connectTimeoutMs}ms from ${target}`))
|
|
1126
|
+
);
|
|
1127
|
+
current.on("error", (err) => finish(err));
|
|
1128
|
+
};
|
|
1129
|
+
hop(url, 0);
|
|
1042
1130
|
});
|
|
1043
1131
|
}
|
|
1044
|
-
async function downloadEngine() {
|
|
1132
|
+
async function downloadEngine(options2 = {}) {
|
|
1045
1133
|
const key = platformKey();
|
|
1046
1134
|
const filename = `attest-engine-${key}${os.platform() === "win32" ? ".exe" : ""}`;
|
|
1047
|
-
const
|
|
1135
|
+
const expectedHash = embeddedHash(key, options2.tablePath ?? path.join(packageRoot(), TABLE_NAME));
|
|
1136
|
+
const binaryUrl = `${options2.releaseBase ?? RELEASE_BASE}/${filename}`;
|
|
1048
1137
|
process.stderr.write(`[attest] Downloading engine v${ENGINE_VERSION} for ${key}...
|
|
1049
1138
|
`);
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
);
|
|
1139
|
+
let data;
|
|
1140
|
+
try {
|
|
1141
|
+
data = await httpsGet(binaryUrl, {
|
|
1142
|
+
ca: options2.ca,
|
|
1143
|
+
connectTimeoutMs: options2.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS,
|
|
1144
|
+
totalTimeoutMs: options2.totalTimeoutMs ?? DEFAULT_TOTAL_TIMEOUT_MS
|
|
1145
|
+
});
|
|
1146
|
+
} catch (err) {
|
|
1147
|
+
throw new Error(`Failed to download engine from ${binaryUrl}: ${err.message}`);
|
|
1059
1148
|
}
|
|
1060
|
-
process.stderr.write(`[attest] Downloading ${binaryUrl}...
|
|
1061
|
-
`);
|
|
1062
|
-
const data = await httpsGet(binaryUrl);
|
|
1063
1149
|
const actualHash = crypto.createHash("sha256").update(data).digest("hex");
|
|
1064
1150
|
if (actualHash !== expectedHash) {
|
|
1065
1151
|
throw new Error(
|
|
1066
|
-
|
|
1152
|
+
`${filename} does not match the hash shipped in @attest-ai/core ${VERSION}: expected ${expectedHash}, got ${actualHash}. Refusing to install it. Retry, or set ATTEST_ENGINE_PATH to a binary you trust.`
|
|
1067
1153
|
);
|
|
1068
1154
|
}
|
|
1069
1155
|
process.stderr.write(`[attest] Checksum verified.
|
|
@@ -1968,12 +2054,26 @@ var WorldChecks = class {
|
|
|
1968
2054
|
);
|
|
1969
2055
|
}
|
|
1970
2056
|
};
|
|
2057
|
+
var chainObserver;
|
|
2058
|
+
function setChainObserver(observer) {
|
|
2059
|
+
chainObserver = observer;
|
|
2060
|
+
}
|
|
1971
2061
|
var ExpectChain = class {
|
|
1972
2062
|
_result;
|
|
1973
2063
|
_assertions = [];
|
|
1974
2064
|
_worldBytes = 0;
|
|
2065
|
+
_evaluated = false;
|
|
1975
2066
|
constructor(result) {
|
|
1976
2067
|
this._result = result;
|
|
2068
|
+
chainObserver?.(this);
|
|
2069
|
+
}
|
|
2070
|
+
/** True once the chain was handed to an evaluation. */
|
|
2071
|
+
get evaluated() {
|
|
2072
|
+
return this._evaluated;
|
|
2073
|
+
}
|
|
2074
|
+
/** Record that the chain was handed to an evaluation. */
|
|
2075
|
+
markEvaluated() {
|
|
2076
|
+
this._evaluated = true;
|
|
1977
2077
|
}
|
|
1978
2078
|
get assertions() {
|
|
1979
2079
|
return [...this._assertions];
|
|
@@ -2911,11 +3011,101 @@ function attestExpect(result) {
|
|
|
2911
3011
|
return new ExpectChain(new AgentResult(result));
|
|
2912
3012
|
}
|
|
2913
3013
|
|
|
3014
|
+
// src/assertion-id.ts
|
|
3015
|
+
var import_node_crypto3 = require("crypto");
|
|
3016
|
+
var ID_SCHEME = 2;
|
|
3017
|
+
function lonelySurrogateAt(text) {
|
|
3018
|
+
for (let i = 0; i < text.length; i++) {
|
|
3019
|
+
const unit = text.charCodeAt(i);
|
|
3020
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
3021
|
+
const next = text.charCodeAt(i + 1);
|
|
3022
|
+
if (next >= 56320 && next <= 57343) {
|
|
3023
|
+
i++;
|
|
3024
|
+
continue;
|
|
3025
|
+
}
|
|
3026
|
+
return i;
|
|
3027
|
+
}
|
|
3028
|
+
if (unit >= 56320 && unit <= 57343) return i;
|
|
3029
|
+
}
|
|
3030
|
+
return -1;
|
|
3031
|
+
}
|
|
3032
|
+
function describe(identity) {
|
|
3033
|
+
if (identity === void 0) return "";
|
|
3034
|
+
return ` (assertion type ${JSON.stringify(identity.type)}, scope ${JSON.stringify(identity.scopeId)})`;
|
|
3035
|
+
}
|
|
3036
|
+
function jcsString(text, where, identity) {
|
|
3037
|
+
const at = lonelySurrogateAt(text);
|
|
3038
|
+
if (at >= 0) {
|
|
3039
|
+
throw new Error(
|
|
3040
|
+
`assertion id: ${where} contains an unpaired surrogate at UTF-16 index ${at}; it has no UTF-8 form${describe(identity)}`
|
|
3041
|
+
);
|
|
3042
|
+
}
|
|
3043
|
+
return JSON.stringify(text);
|
|
3044
|
+
}
|
|
3045
|
+
function jcs(value, where, identity) {
|
|
3046
|
+
if (value === null) return "null";
|
|
3047
|
+
switch (typeof value) {
|
|
3048
|
+
case "string":
|
|
3049
|
+
return jcsString(value, where, identity);
|
|
3050
|
+
case "boolean":
|
|
3051
|
+
return value ? "true" : "false";
|
|
3052
|
+
case "number":
|
|
3053
|
+
if (!Number.isFinite(value)) {
|
|
3054
|
+
throw new Error(`assertion id: ${where} is ${String(value)}, which JSON cannot represent${describe(identity)}`);
|
|
3055
|
+
}
|
|
3056
|
+
return String(value);
|
|
3057
|
+
case "object": {
|
|
3058
|
+
if (Array.isArray(value)) {
|
|
3059
|
+
return `[${value.map((item, i) => jcs(item === void 0 ? null : item, `${where}[${i}]`, identity)).join(",")}]`;
|
|
3060
|
+
}
|
|
3061
|
+
const record2 = value;
|
|
3062
|
+
const keys = Object.keys(record2).filter((k) => record2[k] !== void 0).sort();
|
|
3063
|
+
const members = keys.map((key) => {
|
|
3064
|
+
const quoted = jcsString(key, `${where} key ${JSON.stringify(key)}`, identity);
|
|
3065
|
+
return `${quoted}:${jcs(record2[key], `${where}.${key}`, identity)}`;
|
|
3066
|
+
});
|
|
3067
|
+
return `{${members.join(",")}}`;
|
|
3068
|
+
}
|
|
3069
|
+
default:
|
|
3070
|
+
throw new Error(`assertion id: ${where} has non-JSON type ${typeof value}${describe(identity)}`);
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
function canonicalSpec(spec, identity) {
|
|
3074
|
+
return jcs(spec ?? {}, "spec", identity);
|
|
3075
|
+
}
|
|
3076
|
+
function canonicalIdInput(scopeId, type, spec, occurrenceIndex) {
|
|
3077
|
+
const identity = { scopeId, type, spec };
|
|
3078
|
+
if (!Number.isInteger(occurrenceIndex) || occurrenceIndex < 0) {
|
|
3079
|
+
throw new Error(`assertion id: occurrence index must be an integer >= 0, got ${String(occurrenceIndex)}`);
|
|
3080
|
+
}
|
|
3081
|
+
return `[${jcsString(scopeId, "scope_id", identity)},${jcsString(type, "assertion_type", identity)},${canonicalSpec(spec, identity)},${occurrenceIndex}]`;
|
|
3082
|
+
}
|
|
3083
|
+
function assertionIdOf(scopeId, type, spec, occurrenceIndex) {
|
|
3084
|
+
const input = canonicalIdInput(scopeId, type, spec, occurrenceIndex);
|
|
3085
|
+
return `a2_${(0, import_node_crypto3.createHash)("sha256").update(input, "utf8").digest("hex").slice(0, 32)}`;
|
|
3086
|
+
}
|
|
3087
|
+
var AssertionIdAssigner = class {
|
|
3088
|
+
constructor(scopeId) {
|
|
3089
|
+
this.scopeId = scopeId;
|
|
3090
|
+
if (scopeId === "") throw new Error("assertion id: scope id must not be empty");
|
|
3091
|
+
}
|
|
3092
|
+
scopeId;
|
|
3093
|
+
seen = /* @__PURE__ */ new Map();
|
|
3094
|
+
next(type, spec) {
|
|
3095
|
+
const key = `${jcsString(type, "assertion_type", { scopeId: this.scopeId, type, spec })}\0${canonicalSpec(spec, { scopeId: this.scopeId, type, spec })}`;
|
|
3096
|
+
const occurrence = this.seen.get(key) ?? 0;
|
|
3097
|
+
const id = assertionIdOf(this.scopeId, type, spec, occurrence);
|
|
3098
|
+
this.seen.set(key, occurrence + 1);
|
|
3099
|
+
return id;
|
|
3100
|
+
}
|
|
3101
|
+
};
|
|
3102
|
+
|
|
2914
3103
|
// src/capture.ts
|
|
2915
3104
|
var import_node_child_process2 = require("child_process");
|
|
2916
3105
|
var fs2 = __toESM(require("fs"), 1);
|
|
3106
|
+
var os2 = __toESM(require("os"), 1);
|
|
2917
3107
|
var path2 = __toESM(require("path"), 1);
|
|
2918
|
-
var
|
|
3108
|
+
var import_node_url2 = require("url");
|
|
2919
3109
|
var FIXTURE_VERSION = 1;
|
|
2920
3110
|
var DEFAULT_FIXTURE_DIR = path2.join("tests", "attest", "fixtures");
|
|
2921
3111
|
var CaptureError = class extends Error {
|
|
@@ -2928,7 +3118,7 @@ var CaptureError = class extends Error {
|
|
|
2928
3118
|
}
|
|
2929
3119
|
};
|
|
2930
3120
|
function toPath(p) {
|
|
2931
|
-
return p instanceof URL ? (0,
|
|
3121
|
+
return p instanceof URL ? (0, import_node_url2.fileURLToPath)(p) : p;
|
|
2932
3122
|
}
|
|
2933
3123
|
function loadFixture(file) {
|
|
2934
3124
|
const p = toPath(file);
|
|
@@ -3052,7 +3242,75 @@ function testPathFor(fixturePath) {
|
|
|
3052
3242
|
const name = path2.basename(fixturePath).replace(/\.attest\.json$/, "");
|
|
3053
3243
|
return path2.join(path2.dirname(path2.dirname(fixturePath)), `${name}.test.ts`);
|
|
3054
3244
|
}
|
|
3055
|
-
function
|
|
3245
|
+
function relUrl(target, testPath) {
|
|
3246
|
+
const rel = path2.relative(path2.dirname(testPath), target).split(path2.sep).join("/");
|
|
3247
|
+
const url = rel === "" ? "." : rel;
|
|
3248
|
+
return `new URL(${ts(url.startsWith(".") ? url : `./${url}`)}, import.meta.url)`;
|
|
3249
|
+
}
|
|
3250
|
+
function chainLines(calls, subject, param) {
|
|
3251
|
+
const lines = [`function contracts(${param}): ExpectChain {`, ` return attestExpect(${subject})`];
|
|
3252
|
+
for (const [c, dsl] of calls) {
|
|
3253
|
+
lines.push(` // ${comment(`${c.id}: ${c.description}`)}`, ` ${dsl}`);
|
|
3254
|
+
}
|
|
3255
|
+
lines[lines.length - 1] += ";";
|
|
3256
|
+
lines.push("}");
|
|
3257
|
+
return lines;
|
|
3258
|
+
}
|
|
3259
|
+
function skippedLines(skipped) {
|
|
3260
|
+
if (skipped.length === 0) return [];
|
|
3261
|
+
return ["", "// Drafted contracts without a TypeScript DSL call (left out):", ...skipped.map((s) => `// - ${comment(s)}`)];
|
|
3262
|
+
}
|
|
3263
|
+
function renderReplay(fixture, calls, skipped, fixturePath, testPath, rerun) {
|
|
3264
|
+
const meaning = fixture.verification.expect_fail ? [
|
|
3265
|
+
"// The recording is an incident: these contracts failed on it. The test passes once",
|
|
3266
|
+
"// the new run satisfies them, so it fails again if the agent regresses."
|
|
3267
|
+
] : ["// The test fails when the agent's behavior stops satisfying them."];
|
|
3268
|
+
const world = rerun.world === void 0 ? "undefined" : relUrl(rerun.world, testPath);
|
|
3269
|
+
const lines = [
|
|
3270
|
+
`// Replay test drafted by \`attest capture\` from fixture ${comment(ts(fixture.name))}.`,
|
|
3271
|
+
"//",
|
|
3272
|
+
"// Re-runs COMMAND, the source command of the fixture, under strict replay (the engine's",
|
|
3273
|
+
"// `replay --mode strict --no-live`): OpenAI- and Anthropic-compatible calls are served",
|
|
3274
|
+
"// from the recording, and one that matches no recording fails the test. Other providers",
|
|
3275
|
+
"// are not blocked: run this test without their credentials. The drafted contracts are",
|
|
3276
|
+
"// evaluated on the NEW run's trace, not on the recording.",
|
|
3277
|
+
"//",
|
|
3278
|
+
"// COMMAND is what this test runs and is reviewed with the test: if the fixture's",
|
|
3279
|
+
"// source.command stops matching it, the test fails instead of running the fixture's.",
|
|
3280
|
+
...meaning,
|
|
3281
|
+
"//",
|
|
3282
|
+
"// A pass means the new run is consistent with the recording and satisfies the drafted",
|
|
3283
|
+
"// contracts; it does not prove the agent correct. Identities are compared by role",
|
|
3284
|
+
"// (relative identity): a run that swaps who plays which role misses, while the absolute",
|
|
3285
|
+
"// identity is not checked. Review and edit before committing. Re-capturing does not",
|
|
3286
|
+
"// overwrite this file.",
|
|
3287
|
+
`import { attestExpect, replayRun, type ExpectChain } from "@attest-ai/core";`,
|
|
3288
|
+
`import type { Trace } from "@attest-ai/core/proto";`,
|
|
3289
|
+
`import { evaluate } from "@attest-ai/vitest";`,
|
|
3290
|
+
`import { describe, it } from "vitest";`,
|
|
3291
|
+
"",
|
|
3292
|
+
`const FIXTURE = ${relUrl(fixturePath, testPath)};`,
|
|
3293
|
+
"// The directory `attest capture` ran in: the source command's relative paths start here.",
|
|
3294
|
+
`const RUN_DIR = ${relUrl(rerun.cwd, testPath)};`,
|
|
3295
|
+
`const WORLD = ${world};`,
|
|
3296
|
+
"// Reviewed argv: the only command this test runs.",
|
|
3297
|
+
`const COMMAND = ${ts(fixture.source.command ?? [])};`,
|
|
3298
|
+
...skippedLines(skipped),
|
|
3299
|
+
"",
|
|
3300
|
+
...chainLines(calls, "trace", "trace: Trace"),
|
|
3301
|
+
"",
|
|
3302
|
+
`describe(${ts(fixture.name)}, () => {`,
|
|
3303
|
+
` it("new run is consistent with the recording", async () => {`,
|
|
3304
|
+
" const trace = await replayRun(FIXTURE, { command: COMMAND, cwd: RUN_DIR, world: WORLD });",
|
|
3305
|
+
"",
|
|
3306
|
+
" await evaluate(contracts(trace));",
|
|
3307
|
+
" });",
|
|
3308
|
+
"});"
|
|
3309
|
+
];
|
|
3310
|
+
return `${lines.join("\n")}
|
|
3311
|
+
`;
|
|
3312
|
+
}
|
|
3313
|
+
function renderVitest(fixture, fixturePath, testPath, rerun) {
|
|
3056
3314
|
const calls = [];
|
|
3057
3315
|
const skipped = [];
|
|
3058
3316
|
for (const c of fixture.contracts) {
|
|
@@ -3062,7 +3320,9 @@ function renderVitest(fixture, fixturePath, testPath) {
|
|
|
3062
3320
|
skipped.push(err.message);
|
|
3063
3321
|
}
|
|
3064
3322
|
}
|
|
3065
|
-
|
|
3323
|
+
if (rerun !== void 0 && fixture.source.kind === "command" && fixture.source.replay_capable) {
|
|
3324
|
+
return renderReplay(fixture, calls, skipped, fixturePath, testPath, rerun);
|
|
3325
|
+
}
|
|
3066
3326
|
const status = new Map(fixture.verification.results.map((r) => [r.contract, r.status]));
|
|
3067
3327
|
const expectFail = fixture.verification.expect_fail;
|
|
3068
3328
|
const lines = [
|
|
@@ -3074,18 +3334,11 @@ function renderVitest(fixture, fixturePath, testPath) {
|
|
|
3074
3334
|
`import { ${expectFail ? "inspect" : "evaluate"} } from "@attest-ai/vitest";`,
|
|
3075
3335
|
`import { describe, ${expectFail ? "expect, " : ""}it } from "vitest";`,
|
|
3076
3336
|
"",
|
|
3077
|
-
`const FIXTURE = loadFixture(
|
|
3337
|
+
`const FIXTURE = loadFixture(${relUrl(fixturePath, testPath)});`,
|
|
3338
|
+
...skippedLines(skipped),
|
|
3339
|
+
"",
|
|
3340
|
+
...chainLines(calls, "FIXTURE.trace", "")
|
|
3078
3341
|
];
|
|
3079
|
-
if (skipped.length > 0) {
|
|
3080
|
-
lines.push("", "// Drafted contracts without a TypeScript DSL call (left out):");
|
|
3081
|
-
for (const s of skipped) lines.push(`// - ${comment(s)}`);
|
|
3082
|
-
}
|
|
3083
|
-
lines.push("", "function contracts(): ExpectChain {", " return attestExpect(FIXTURE.trace)");
|
|
3084
|
-
for (const [c, dsl] of calls) {
|
|
3085
|
-
lines.push(` // ${comment(`${c.id}: ${c.description}`)}`, ` ${dsl}`);
|
|
3086
|
-
}
|
|
3087
|
-
lines[lines.length - 1] += ";";
|
|
3088
|
-
lines.push("}");
|
|
3089
3342
|
if (expectFail) {
|
|
3090
3343
|
lines.push("", "// Status of each contract on the captured incident, in contracts() order.");
|
|
3091
3344
|
lines.push("const EXPECTED: Array<[string, string]> = [");
|
|
@@ -3114,14 +3367,14 @@ function renderVitest(fixture, fixturePath, testPath) {
|
|
|
3114
3367
|
return `${lines.join("\n")}
|
|
3115
3368
|
`;
|
|
3116
3369
|
}
|
|
3117
|
-
function emitVitest(fixturePath) {
|
|
3370
|
+
function emitVitest(fixturePath, rerun) {
|
|
3118
3371
|
const testPath = testPathFor(fixturePath);
|
|
3119
3372
|
const existing = fs2.lstatSync(testPath, { throwIfNoEntry: false });
|
|
3120
3373
|
if (existing?.isSymbolicLink()) {
|
|
3121
3374
|
throw new CaptureError(`${testPath} is a symbolic link; refusing to draft a test through it`);
|
|
3122
3375
|
}
|
|
3123
3376
|
if (existing !== void 0) return null;
|
|
3124
|
-
const source = renderVitest(loadFixture(fixturePath), fixturePath, testPath);
|
|
3377
|
+
const source = renderVitest(loadFixture(fixturePath), fixturePath, testPath, rerun);
|
|
3125
3378
|
fs2.mkdirSync(path2.dirname(testPath), { recursive: true });
|
|
3126
3379
|
try {
|
|
3127
3380
|
fs2.writeFileSync(testPath, source, { encoding: "utf8", flag: "wx" });
|
|
@@ -3133,6 +3386,108 @@ function emitVitest(fixturePath) {
|
|
|
3133
3386
|
}
|
|
3134
3387
|
return testPath;
|
|
3135
3388
|
}
|
|
3389
|
+
var OTHER_PROVIDER_KEYS = ["ANTHROPIC_API_KEY", "GOOGLE_API_KEY", "GEMINI_API_KEY"];
|
|
3390
|
+
var REPLAY_TIMEOUT_MS = 3e5;
|
|
3391
|
+
var REDACTED_MARKER = "[redacted]";
|
|
3392
|
+
var PLACEHOLDER = /\{\{(?:id|email|timestamp)_\d+\}\}/;
|
|
3393
|
+
var STDERR_TAIL = 4e3;
|
|
3394
|
+
async function replayRun(fixture, options2) {
|
|
3395
|
+
const fp = path2.resolve(toPath(fixture));
|
|
3396
|
+
const doc = loadFixture(fp);
|
|
3397
|
+
const recorded = doc.source.command;
|
|
3398
|
+
if (doc.source.kind !== "command" || !doc.source.replay_capable || !recorded || recorded.length === 0) {
|
|
3399
|
+
throw new CaptureError(`${fp}: not a command capture with recorded exchanges; cannot replay`);
|
|
3400
|
+
}
|
|
3401
|
+
const command = [...options2.command];
|
|
3402
|
+
if (command.length !== recorded.length || command.some((a, i) => a !== recorded[i])) {
|
|
3403
|
+
throw new CaptureError(
|
|
3404
|
+
`${fp}: refusing to run: the fixture's source.command ${JSON.stringify(recorded)} differs from the test's COMMAND ${JSON.stringify(command)}; review the change, then update the test`
|
|
3405
|
+
);
|
|
3406
|
+
}
|
|
3407
|
+
if (command.some((a) => a.includes(REDACTED_MARKER) || PLACEHOLDER.test(a))) {
|
|
3408
|
+
throw new CaptureError(
|
|
3409
|
+
`${fp}: refusing to run: the recorded command has redacted or templated arguments; re-run \`attest capture\` with the real command`
|
|
3410
|
+
);
|
|
3411
|
+
}
|
|
3412
|
+
if (doc.world !== null && options2.world === void 0) {
|
|
3413
|
+
throw new CaptureError(`${fp}: captured with a world; pass the world file the capture ran with`);
|
|
3414
|
+
}
|
|
3415
|
+
const env = { ...process.env, OPENAI_API_KEY: "replay-needs-no-key", ATTEST_EGRESS: "block" };
|
|
3416
|
+
for (const key of OTHER_PROVIDER_KEYS) delete env[key];
|
|
3417
|
+
const engine = await resolveEngine(options2.enginePath);
|
|
3418
|
+
const tmp = fs2.mkdtempSync(path2.join(os2.tmpdir(), "attest-replay-"));
|
|
3419
|
+
try {
|
|
3420
|
+
const traceFile = path2.join(tmp, "trace.json");
|
|
3421
|
+
const argv = ["replay", "--fixture", fp, "--mode", "strict", "--no-live", "--trace-out", traceFile, "--json"];
|
|
3422
|
+
if (options2.world !== void 0) argv.push("--world", path2.resolve(toPath(options2.world)));
|
|
3423
|
+
argv.push("--", ...command);
|
|
3424
|
+
const timeoutMs = options2.timeoutMs ?? REPLAY_TIMEOUT_MS;
|
|
3425
|
+
const run = await runEngine(engine, argv, {
|
|
3426
|
+
cwd: options2.cwd === void 0 ? void 0 : toPath(options2.cwd),
|
|
3427
|
+
env,
|
|
3428
|
+
timeoutMs
|
|
3429
|
+
});
|
|
3430
|
+
if (run.error !== null) throw new CaptureError(`${fp}: ${run.error}`);
|
|
3431
|
+
if (run.timedOut) throw new CaptureError(`${fp}: replay run exceeded ${timeoutMs}ms`);
|
|
3432
|
+
checkReplay(fp, doc, run);
|
|
3433
|
+
if (!fs2.existsSync(traceFile)) {
|
|
3434
|
+
throw new CaptureError(`${fp}: the replay run exported no trace
|
|
3435
|
+
${tail(run.stderr)}`);
|
|
3436
|
+
}
|
|
3437
|
+
return JSON.parse(fs2.readFileSync(traceFile, "utf8"));
|
|
3438
|
+
} finally {
|
|
3439
|
+
fs2.rmSync(tmp, { recursive: true, force: true });
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
function tail(stderr) {
|
|
3443
|
+
return stderr.slice(-STDERR_TAIL);
|
|
3444
|
+
}
|
|
3445
|
+
function checkReplay(fp, fixture, run) {
|
|
3446
|
+
let misses;
|
|
3447
|
+
let exitCode;
|
|
3448
|
+
try {
|
|
3449
|
+
const result = JSON.parse(run.stdout);
|
|
3450
|
+
misses = Number(result.summary.misses);
|
|
3451
|
+
exitCode = Number(result.exit_code);
|
|
3452
|
+
if (!Number.isInteger(misses) || !Number.isInteger(exitCode)) throw new TypeError("not integers");
|
|
3453
|
+
} catch {
|
|
3454
|
+
throw new CaptureError(`${fp}: replay run exited ${run.code ?? run.signal}
|
|
3455
|
+
${tail(run.stderr)}`);
|
|
3456
|
+
}
|
|
3457
|
+
if (misses > 0) throw new CaptureError(`${fp}: ${misses} request(s) matched no recording
|
|
3458
|
+
${tail(run.stderr)}`);
|
|
3459
|
+
const recorded = fixture.source.exit_code ?? 0;
|
|
3460
|
+
if (exitCode !== recorded) {
|
|
3461
|
+
throw new CaptureError(`${fp}: the command exited ${exitCode}; the recording exited ${recorded}
|
|
3462
|
+
${tail(run.stderr)}`);
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
function runEngine(engine, argv, opts) {
|
|
3466
|
+
return new Promise((resolve5) => {
|
|
3467
|
+
const child = (0, import_node_child_process2.spawn)(engine, argv, { cwd: opts.cwd, env: opts.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
3468
|
+
let stdout = "";
|
|
3469
|
+
let stderr = "";
|
|
3470
|
+
let timedOut = false;
|
|
3471
|
+
child.stdout.setEncoding("utf8").on("data", (chunk) => {
|
|
3472
|
+
stdout += chunk;
|
|
3473
|
+
});
|
|
3474
|
+
child.stderr.setEncoding("utf8").on("data", (chunk) => {
|
|
3475
|
+
stderr += chunk;
|
|
3476
|
+
});
|
|
3477
|
+
const timer = setTimeout(() => {
|
|
3478
|
+
timedOut = true;
|
|
3479
|
+
child.kill("SIGKILL");
|
|
3480
|
+
}, opts.timeoutMs);
|
|
3481
|
+
child.on("error", (err) => {
|
|
3482
|
+
clearTimeout(timer);
|
|
3483
|
+
resolve5({ code: null, signal: null, stdout, stderr, timedOut, error: `could not start the replay run: ${err.message}` });
|
|
3484
|
+
});
|
|
3485
|
+
child.on("close", (code, signal) => {
|
|
3486
|
+
clearTimeout(timer);
|
|
3487
|
+
resolve5({ code, signal, stdout, stderr, timedOut, error: null });
|
|
3488
|
+
});
|
|
3489
|
+
});
|
|
3490
|
+
}
|
|
3136
3491
|
function serve(engine, args) {
|
|
3137
3492
|
const child = (0, import_node_child_process2.spawn)(engine, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
3138
3493
|
let stdout = "";
|
|
@@ -3727,7 +4082,7 @@ function tier(level) {
|
|
|
3727
4082
|
// src/plugins.ts
|
|
3728
4083
|
var import_node_fs2 = require("fs");
|
|
3729
4084
|
var import_node_path2 = require("path");
|
|
3730
|
-
var
|
|
4085
|
+
var import_node_url3 = require("url");
|
|
3731
4086
|
var PluginRegistry = class {
|
|
3732
4087
|
plugins = /* @__PURE__ */ new Map();
|
|
3733
4088
|
register(name, plugin) {
|
|
@@ -3781,6 +4136,21 @@ async function executePluginAssertion(plugin, trace, spec, client, traceId2, ass
|
|
|
3781
4136
|
);
|
|
3782
4137
|
return result;
|
|
3783
4138
|
}
|
|
4139
|
+
async function submitPluginResults(registry, client, trace, assertions) {
|
|
4140
|
+
for (const assertion of assertions) {
|
|
4141
|
+
if (assertion.type !== TYPE_PLUGIN) continue;
|
|
4142
|
+
const pluginId = assertion.spec["plugin_id"];
|
|
4143
|
+
if (typeof pluginId !== "string" || pluginId === "") continue;
|
|
4144
|
+
const plugin = registry.get("assertion", pluginId);
|
|
4145
|
+
if (plugin === void 0) continue;
|
|
4146
|
+
if (plugin.name !== pluginId) {
|
|
4147
|
+
throw new Error(
|
|
4148
|
+
`Plugin registered for "${pluginId}" is named "${plugin.name}"; the engine matches submitted results by plugin name, so the two must be equal`
|
|
4149
|
+
);
|
|
4150
|
+
}
|
|
4151
|
+
await executePluginAssertion(plugin, trace, assertion.spec, client, trace.trace_id, assertion.assertion_id);
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
3784
4154
|
function registerPlugin(registry, name, plugin) {
|
|
3785
4155
|
registry.register(name, plugin);
|
|
3786
4156
|
}
|
|
@@ -3859,7 +4229,7 @@ async function loadPackagePlugins(registry, projectRoot = process.cwd()) {
|
|
|
3859
4229
|
if (!isInside(pkgDir, file)) {
|
|
3860
4230
|
throw new Error(`plugin path '${entry}' escapes package '${dep}'`);
|
|
3861
4231
|
}
|
|
3862
|
-
const mod = await import((0,
|
|
4232
|
+
const mod = await import((0, import_node_url3.pathToFileURL)(file).href);
|
|
3863
4233
|
const plugins = pluginsOf(mod);
|
|
3864
4234
|
if (plugins.length === 0) {
|
|
3865
4235
|
throw new Error(`'${entry}' exports no plugin (name, pluginType, execute)`);
|
|
@@ -3879,6 +4249,11 @@ async function loadPackagePlugins(registry, projectRoot = process.cwd()) {
|
|
|
3879
4249
|
// src/continuous.ts
|
|
3880
4250
|
var import_node_events = require("events");
|
|
3881
4251
|
var DEFAULT_QUEUE_SIZE = 1e3;
|
|
4252
|
+
var DEFAULT_DISPATCH_TIMEOUT_MS = 1e4;
|
|
4253
|
+
var DEFAULT_DRAIN_TIMEOUT_MS = 15e3;
|
|
4254
|
+
var DEFAULT_STATIC_THRESHOLD = 0.8;
|
|
4255
|
+
var STATUS_HARD_FAIL2 = "hard_fail";
|
|
4256
|
+
var STATUS_DRIFT_DETECTED = "drift_detected";
|
|
3882
4257
|
function resolveQueueSize() {
|
|
3883
4258
|
const raw = process.env["ATTEST_CONTINUOUS_QUEUE_SIZE"];
|
|
3884
4259
|
if (raw !== void 0 && raw !== "") {
|
|
@@ -3899,13 +4274,18 @@ var Sampler = class {
|
|
|
3899
4274
|
return Math.random() < this.rate;
|
|
3900
4275
|
}
|
|
3901
4276
|
};
|
|
4277
|
+
function slackText(alert) {
|
|
4278
|
+
return `[attest] drift alert \u2014 runner=${alert.runner} assertion=${alert.assertion_id} status=${alert.status} score=${alert.score} baseline_mean=${alert.baseline_mean} baseline_count=${alert.baseline_count} static_threshold=${alert.static_threshold} trace_id=${alert.trace_id}`;
|
|
4279
|
+
}
|
|
3902
4280
|
var AlertDispatcher = class extends import_node_events.EventEmitter {
|
|
3903
4281
|
webhookUrl;
|
|
3904
4282
|
slackUrl;
|
|
3905
|
-
|
|
4283
|
+
timeoutMs;
|
|
4284
|
+
constructor(webhookUrl, slackUrl, timeoutMs = DEFAULT_DISPATCH_TIMEOUT_MS) {
|
|
3906
4285
|
super();
|
|
3907
4286
|
this.webhookUrl = webhookUrl;
|
|
3908
4287
|
this.slackUrl = slackUrl;
|
|
4288
|
+
this.timeoutMs = timeoutMs;
|
|
3909
4289
|
}
|
|
3910
4290
|
async dispatch(alert) {
|
|
3911
4291
|
this.emit("alert", alert);
|
|
@@ -3914,8 +4294,7 @@ var AlertDispatcher = class extends import_node_events.EventEmitter {
|
|
|
3914
4294
|
tasks.push(this.postJson(this.webhookUrl, alert));
|
|
3915
4295
|
}
|
|
3916
4296
|
if (this.slackUrl) {
|
|
3917
|
-
|
|
3918
|
-
tasks.push(this.postJson(this.slackUrl, { text }));
|
|
4297
|
+
tasks.push(this.postJson(this.slackUrl, { text: slackText(alert) }));
|
|
3919
4298
|
}
|
|
3920
4299
|
const results = await Promise.allSettled(tasks);
|
|
3921
4300
|
for (const result of results) {
|
|
@@ -3929,7 +4308,7 @@ var AlertDispatcher = class extends import_node_events.EventEmitter {
|
|
|
3929
4308
|
method: "POST",
|
|
3930
4309
|
headers: { "Content-Type": "application/json" },
|
|
3931
4310
|
body: JSON.stringify(payload),
|
|
3932
|
-
signal: AbortSignal.timeout(
|
|
4311
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
3933
4312
|
});
|
|
3934
4313
|
if (!response.ok) {
|
|
3935
4314
|
throw new Error(`POST to ${url} returned ${response.status}`);
|
|
@@ -3943,21 +4322,124 @@ var ContinuousEvalRunner = class {
|
|
|
3943
4322
|
dispatcher;
|
|
3944
4323
|
queue = [];
|
|
3945
4324
|
maxSize;
|
|
4325
|
+
name;
|
|
4326
|
+
drainTimeoutMs;
|
|
4327
|
+
onError;
|
|
4328
|
+
inFlight = /* @__PURE__ */ new Set();
|
|
4329
|
+
chain = Promise.resolve();
|
|
3946
4330
|
running = false;
|
|
3947
4331
|
loopTimer = null;
|
|
3948
4332
|
constructor(client, assertions, options2) {
|
|
3949
4333
|
this.client = client;
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
4334
|
+
if (typeof options2?.name !== "string" || options2.name === "") {
|
|
4335
|
+
throw new Error("ContinuousEvalRunner requires options.name: it is the scope of the assertion ids");
|
|
4336
|
+
}
|
|
4337
|
+
const ids = new AssertionIdAssigner(options2.name);
|
|
4338
|
+
this.assertions = assertions.map((a) => ({
|
|
4339
|
+
...a,
|
|
4340
|
+
assertion_id: ids.next(a.type, a.spec),
|
|
4341
|
+
scope_id: options2.name
|
|
4342
|
+
}));
|
|
4343
|
+
this.sampler = new Sampler(options2.sampleRate ?? 1);
|
|
4344
|
+
this.name = options2.name;
|
|
4345
|
+
this.onError = options2.onError;
|
|
4346
|
+
this.drainTimeoutMs = options2.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;
|
|
4347
|
+
this.dispatcher = new AlertDispatcher(
|
|
4348
|
+
options2.alertWebhook,
|
|
4349
|
+
options2.alertSlackUrl,
|
|
4350
|
+
options2.dispatchTimeoutMs ?? DEFAULT_DISPATCH_TIMEOUT_MS
|
|
4351
|
+
);
|
|
4352
|
+
this.dispatcher.on("alertError", (error) => this.reportError(error));
|
|
4353
|
+
this.maxSize = options2.maxSize ?? resolveQueueSize();
|
|
3954
4354
|
}
|
|
3955
4355
|
get alertEmitter() {
|
|
3956
4356
|
return this.dispatcher;
|
|
3957
4357
|
}
|
|
4358
|
+
/** Runs `fn` after every previously queued evaluation, so evaluate → query_drift pairs never interleave. */
|
|
4359
|
+
serialize(fn) {
|
|
4360
|
+
const run = this.chain.then(fn, fn);
|
|
4361
|
+
this.chain = run.catch(() => void 0);
|
|
4362
|
+
return run;
|
|
4363
|
+
}
|
|
4364
|
+
/** Surfaces an error to the log and `onError` without ever throwing or leaving a rejected promise. */
|
|
4365
|
+
reportError(error) {
|
|
4366
|
+
try {
|
|
4367
|
+
console.error(`attest continuous-eval runner ${JSON.stringify(this.name)}:`, error);
|
|
4368
|
+
} catch {
|
|
4369
|
+
}
|
|
4370
|
+
if (this.onError === void 0) return;
|
|
4371
|
+
try {
|
|
4372
|
+
const returned = this.onError(error);
|
|
4373
|
+
if (returned instanceof Promise) {
|
|
4374
|
+
returned.catch((observerError) => this.logObserverFailure(observerError));
|
|
4375
|
+
}
|
|
4376
|
+
} catch (observerError) {
|
|
4377
|
+
this.logObserverFailure(observerError);
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4380
|
+
logObserverFailure(observerError) {
|
|
4381
|
+
try {
|
|
4382
|
+
console.error(`attest continuous-eval runner ${JSON.stringify(this.name)}: onError callback failed:`, observerError);
|
|
4383
|
+
} catch {
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4386
|
+
emitError(error) {
|
|
4387
|
+
try {
|
|
4388
|
+
this.dispatcher.emit("alertError", error);
|
|
4389
|
+
} catch (listenerError) {
|
|
4390
|
+
this.logObserverFailure(listenerError);
|
|
4391
|
+
}
|
|
4392
|
+
}
|
|
3958
4393
|
async evaluateTrace(trace) {
|
|
3959
4394
|
if (!this.sampler.shouldSample()) return null;
|
|
3960
|
-
|
|
4395
|
+
const work = this.serialize(() => this.evaluateAndAlert(trace));
|
|
4396
|
+
this.inFlight.add(work);
|
|
4397
|
+
try {
|
|
4398
|
+
return await work;
|
|
4399
|
+
} finally {
|
|
4400
|
+
this.inFlight.delete(work);
|
|
4401
|
+
}
|
|
4402
|
+
}
|
|
4403
|
+
async evaluateAndAlert(trace) {
|
|
4404
|
+
const result = await this.client.evaluateBatch(trace, this.assertions);
|
|
4405
|
+
for (const r of result.results) {
|
|
4406
|
+
await this.alertIfNeeded(trace, r);
|
|
4407
|
+
}
|
|
4408
|
+
return result;
|
|
4409
|
+
}
|
|
4410
|
+
staticThresholdFor(assertionId2) {
|
|
4411
|
+
const spec = this.assertions.find((a) => a.assertion_id === assertionId2)?.spec;
|
|
4412
|
+
const threshold = spec?.["threshold"];
|
|
4413
|
+
return typeof threshold === "number" && threshold > 0 ? threshold : DEFAULT_STATIC_THRESHOLD;
|
|
4414
|
+
}
|
|
4415
|
+
async alertIfNeeded(trace, result) {
|
|
4416
|
+
if (result.status === "not_evaluated") return;
|
|
4417
|
+
const staticThreshold = this.staticThresholdFor(result.assertion_id);
|
|
4418
|
+
let report;
|
|
4419
|
+
try {
|
|
4420
|
+
report = await this.client.queryDrift(result.assertion_id, void 0, { staticThreshold });
|
|
4421
|
+
} catch (error) {
|
|
4422
|
+
this.emitError(error);
|
|
4423
|
+
}
|
|
4424
|
+
const hardFailed = result.status === STATUS_HARD_FAIL2;
|
|
4425
|
+
const drifted = report?.status === STATUS_DRIFT_DETECTED;
|
|
4426
|
+
if (!drifted && !hardFailed) return;
|
|
4427
|
+
const alert = {
|
|
4428
|
+
runner: this.name,
|
|
4429
|
+
assertion_id: result.assertion_id,
|
|
4430
|
+
status: hardFailed ? STATUS_HARD_FAIL2 : STATUS_DRIFT_DETECTED,
|
|
4431
|
+
result_status: result.status,
|
|
4432
|
+
score: typeof result.score === "number" ? result.score : null,
|
|
4433
|
+
baseline_mean: report?.mean ?? null,
|
|
4434
|
+
baseline_count: report?.count ?? null,
|
|
4435
|
+
static_threshold: staticThreshold,
|
|
4436
|
+
trace_id: trace.trace_id ?? null
|
|
4437
|
+
};
|
|
4438
|
+
try {
|
|
4439
|
+
await this.dispatcher.dispatch(alert);
|
|
4440
|
+
} catch (error) {
|
|
4441
|
+
this.emitError(error);
|
|
4442
|
+
}
|
|
3961
4443
|
}
|
|
3962
4444
|
submit(trace) {
|
|
3963
4445
|
if (this.queue.length >= this.maxSize) {
|
|
@@ -3971,12 +4453,52 @@ var ContinuousEvalRunner = class {
|
|
|
3971
4453
|
this.running = true;
|
|
3972
4454
|
this.scheduleNext();
|
|
3973
4455
|
}
|
|
3974
|
-
|
|
4456
|
+
/**
|
|
4457
|
+
* Stops the loop, then processes the traces already queued and waits for in-flight evaluations
|
|
4458
|
+
* and dispatches, all bounded by `drainTimeoutMs`. At the bound the whole remaining queue is
|
|
4459
|
+
* discarded (nothing is evaluated after the timeout and nothing is left for a restart), the
|
|
4460
|
+
* evaluation already in flight is left to finish, and the timeout with the number of discarded
|
|
4461
|
+
* traces is reported through `onError`.
|
|
4462
|
+
*/
|
|
4463
|
+
async stop() {
|
|
3975
4464
|
this.running = false;
|
|
3976
4465
|
if (this.loopTimer !== null) {
|
|
3977
4466
|
clearTimeout(this.loopTimer);
|
|
3978
4467
|
this.loopTimer = null;
|
|
3979
4468
|
}
|
|
4469
|
+
if (this.inFlight.size === 0 && this.queue.length === 0) return;
|
|
4470
|
+
let expired = false;
|
|
4471
|
+
let discarded = 0;
|
|
4472
|
+
let timer;
|
|
4473
|
+
const bound = new Promise((resolve5) => {
|
|
4474
|
+
timer = setTimeout(() => {
|
|
4475
|
+
expired = true;
|
|
4476
|
+
discarded = this.queue.splice(0).length;
|
|
4477
|
+
resolve5("timeout");
|
|
4478
|
+
}, this.drainTimeoutMs);
|
|
4479
|
+
});
|
|
4480
|
+
const drained = this.drainQueue(() => expired).then(() => "drained");
|
|
4481
|
+
const outcome = await Promise.race([drained, bound]);
|
|
4482
|
+
clearTimeout(timer);
|
|
4483
|
+
if (outcome === "timeout") {
|
|
4484
|
+
this.emitError(
|
|
4485
|
+
new Error(
|
|
4486
|
+
`stop() gave up after ${this.drainTimeoutMs} ms with ${this.inFlight.size} evaluation(s) in flight; discarded ${discarded} queued trace(s) not evaluated`
|
|
4487
|
+
)
|
|
4488
|
+
);
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
async drainQueue(expired) {
|
|
4492
|
+
while (!expired()) {
|
|
4493
|
+
const trace = this.queue.shift();
|
|
4494
|
+
if (trace === void 0) break;
|
|
4495
|
+
try {
|
|
4496
|
+
await this.evaluateTrace(trace);
|
|
4497
|
+
} catch (error) {
|
|
4498
|
+
this.reportError(error);
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
await Promise.allSettled([...this.inFlight]);
|
|
3980
4502
|
}
|
|
3981
4503
|
get queueLength() {
|
|
3982
4504
|
return this.queue.length;
|
|
@@ -3986,22 +4508,29 @@ var ContinuousEvalRunner = class {
|
|
|
3986
4508
|
}
|
|
3987
4509
|
scheduleNext() {
|
|
3988
4510
|
if (!this.running) return;
|
|
3989
|
-
this.loopTimer = setTimeout(
|
|
4511
|
+
this.loopTimer = setTimeout(() => {
|
|
4512
|
+
void this.tick();
|
|
4513
|
+
}, this.queue.length > 0 ? 0 : 100);
|
|
4514
|
+
}
|
|
4515
|
+
async tick() {
|
|
4516
|
+
try {
|
|
3990
4517
|
if (!this.running) return;
|
|
3991
4518
|
const trace = this.queue.shift();
|
|
3992
4519
|
if (trace !== void 0) {
|
|
3993
4520
|
try {
|
|
3994
4521
|
await this.evaluateTrace(trace);
|
|
3995
|
-
} catch {
|
|
4522
|
+
} catch (error) {
|
|
4523
|
+
this.reportError(error);
|
|
3996
4524
|
}
|
|
3997
4525
|
}
|
|
4526
|
+
} finally {
|
|
3998
4527
|
this.scheduleNext();
|
|
3999
|
-
}
|
|
4528
|
+
}
|
|
4000
4529
|
}
|
|
4001
4530
|
};
|
|
4002
4531
|
|
|
4003
4532
|
// src/calibration.ts
|
|
4004
|
-
var
|
|
4533
|
+
var import_node_crypto4 = require("crypto");
|
|
4005
4534
|
function computeAgreement(pairs, threshold) {
|
|
4006
4535
|
if (pairs.length === 0) {
|
|
4007
4536
|
throw new Error("no labeled pairs available");
|
|
@@ -4146,7 +4675,7 @@ function loadLabelsJSONL(text) {
|
|
|
4146
4675
|
return out;
|
|
4147
4676
|
}
|
|
4148
4677
|
function promptHash(text) {
|
|
4149
|
-
return (0,
|
|
4678
|
+
return (0, import_node_crypto4.createHash)("sha256").update(text).digest("hex").slice(0, 16);
|
|
4150
4679
|
}
|
|
4151
4680
|
function parseFloatStrict(raw) {
|
|
4152
4681
|
const trimmed = raw.trim();
|
|
@@ -4436,44 +4965,116 @@ var ManualAdapter = class {
|
|
|
4436
4965
|
}
|
|
4437
4966
|
};
|
|
4438
4967
|
|
|
4968
|
+
// src/adapters/provider-trace.ts
|
|
4969
|
+
function latency(options2) {
|
|
4970
|
+
if (options2?.latencyMs !== void 0) return options2.latencyMs;
|
|
4971
|
+
if (options2?.startedAtMs !== void 0 && options2.endedAtMs !== void 0) {
|
|
4972
|
+
return options2.endedAtMs - options2.startedAtMs;
|
|
4973
|
+
}
|
|
4974
|
+
return void 0;
|
|
4975
|
+
}
|
|
4976
|
+
function buildProviderTrace(agentId2, call2, options2, input, output) {
|
|
4977
|
+
const builder = new TraceBuilder(agentId2);
|
|
4978
|
+
if (input !== void 0) builder.setInput(input);
|
|
4979
|
+
const now = Date.now();
|
|
4980
|
+
const args = {};
|
|
4981
|
+
if (call2.model !== void 0) args.model = call2.model;
|
|
4982
|
+
const result = { completion: call2.completion };
|
|
4983
|
+
if (call2.refusal) result.refusal = call2.refusal;
|
|
4984
|
+
if (call2.totalTokens !== void 0) result.tokens = call2.totalTokens;
|
|
4985
|
+
if (call2.inputTokens !== void 0) result.input_tokens = call2.inputTokens;
|
|
4986
|
+
if (call2.outputTokens !== void 0) result.output_tokens = call2.outputTokens;
|
|
4987
|
+
builder.addStep({
|
|
4988
|
+
type: STEP_LLM_CALL,
|
|
4989
|
+
name: "completion",
|
|
4990
|
+
args,
|
|
4991
|
+
result,
|
|
4992
|
+
started_at_ms: options2?.startedAtMs ?? now,
|
|
4993
|
+
ended_at_ms: options2?.endedAtMs ?? now
|
|
4994
|
+
});
|
|
4995
|
+
for (const tc of call2.toolCalls) {
|
|
4996
|
+
builder.addToolCall(tc.name, { args: tc.args });
|
|
4997
|
+
}
|
|
4998
|
+
builder.setOutput(output);
|
|
4999
|
+
builder.setMetadata({
|
|
5000
|
+
total_tokens: call2.totalTokens,
|
|
5001
|
+
cost_usd: options2?.costUsd,
|
|
5002
|
+
latency_ms: latency(options2),
|
|
5003
|
+
model: call2.model
|
|
5004
|
+
});
|
|
5005
|
+
return builder.build();
|
|
5006
|
+
}
|
|
5007
|
+
|
|
4439
5008
|
// src/adapters/openai.ts
|
|
5009
|
+
function parseArguments(raw) {
|
|
5010
|
+
let parsed;
|
|
5011
|
+
try {
|
|
5012
|
+
parsed = JSON.parse(raw);
|
|
5013
|
+
} catch {
|
|
5014
|
+
return { raw_arguments: raw };
|
|
5015
|
+
}
|
|
5016
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
5017
|
+
return parsed;
|
|
5018
|
+
}
|
|
5019
|
+
return { raw_arguments: raw };
|
|
5020
|
+
}
|
|
5021
|
+
function isResponses(response) {
|
|
5022
|
+
return response.object === "response";
|
|
5023
|
+
}
|
|
5024
|
+
function fromResponses(response) {
|
|
5025
|
+
const texts = [];
|
|
5026
|
+
const refusals = [];
|
|
5027
|
+
const toolCalls = [];
|
|
5028
|
+
for (const item of response.output) {
|
|
5029
|
+
if (item.type === "message") {
|
|
5030
|
+
for (const part of item.content) {
|
|
5031
|
+
if (part.type === "output_text") texts.push(part.text);
|
|
5032
|
+
else if (part.type === "refusal") refusals.push(part.refusal);
|
|
5033
|
+
}
|
|
5034
|
+
} else if (item.type === "function_call") {
|
|
5035
|
+
const call2 = item;
|
|
5036
|
+
toolCalls.push({ name: call2.name, args: parseArguments(call2.arguments) });
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5039
|
+
const refusal = refusals.join("") || void 0;
|
|
5040
|
+
return {
|
|
5041
|
+
model: response.model,
|
|
5042
|
+
completion: texts.join("") || refusal || "",
|
|
5043
|
+
refusal,
|
|
5044
|
+
totalTokens: response.usage?.total_tokens,
|
|
5045
|
+
inputTokens: response.usage?.input_tokens,
|
|
5046
|
+
outputTokens: response.usage?.output_tokens,
|
|
5047
|
+
toolCalls
|
|
5048
|
+
};
|
|
5049
|
+
}
|
|
5050
|
+
function fromChat(response) {
|
|
5051
|
+
const message = response.choices[0].message;
|
|
5052
|
+
const refusal = message.refusal || void 0;
|
|
5053
|
+
return {
|
|
5054
|
+
model: response.model,
|
|
5055
|
+
completion: message.content || refusal || "",
|
|
5056
|
+
refusal,
|
|
5057
|
+
totalTokens: response.usage?.total_tokens,
|
|
5058
|
+
inputTokens: response.usage?.prompt_tokens,
|
|
5059
|
+
outputTokens: response.usage?.completion_tokens,
|
|
5060
|
+
toolCalls: (message.tool_calls ?? []).map((tc) => ({
|
|
5061
|
+
name: tc.function.name,
|
|
5062
|
+
args: parseArguments(tc.function.arguments)
|
|
5063
|
+
}))
|
|
5064
|
+
};
|
|
5065
|
+
}
|
|
4440
5066
|
var OpenAIAdapter = class {
|
|
4441
5067
|
agentId;
|
|
4442
5068
|
constructor(agentId2) {
|
|
4443
5069
|
this.agentId = agentId2;
|
|
4444
5070
|
}
|
|
4445
5071
|
traceFromResponse(response, options2) {
|
|
4446
|
-
const
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
const message = response.choices[0].message;
|
|
4451
|
-
const completionText = message.content ?? "";
|
|
4452
|
-
const stepArgs = {};
|
|
4453
|
-
if (response.model !== void 0) stepArgs.model = response.model;
|
|
4454
|
-
const stepResult = { completion: completionText };
|
|
4455
|
-
if (response.usage?.total_tokens !== void 0) {
|
|
4456
|
-
stepResult.tokens = response.usage.total_tokens;
|
|
4457
|
-
}
|
|
4458
|
-
builder.addLlmCall("completion", { args: stepArgs, result: stepResult });
|
|
4459
|
-
if (message.tool_calls) {
|
|
4460
|
-
for (const tc of message.tool_calls) {
|
|
4461
|
-
builder.addToolCall(tc.function.name, {
|
|
4462
|
-
args: { arguments: tc.function.arguments }
|
|
4463
|
-
});
|
|
4464
|
-
}
|
|
4465
|
-
}
|
|
4466
|
-
builder.setOutput({
|
|
4467
|
-
message: completionText,
|
|
5072
|
+
const call2 = isResponses(response) ? fromResponses(response) : fromChat(response);
|
|
5073
|
+
const input = options2?.inputMessages ? { messages: options2.inputMessages } : void 0;
|
|
5074
|
+
return buildProviderTrace(this.agentId, call2, options2, input, {
|
|
5075
|
+
message: call2.completion,
|
|
4468
5076
|
structured: options2?.structuredOutput ?? {}
|
|
4469
5077
|
});
|
|
4470
|
-
builder.setMetadata({
|
|
4471
|
-
total_tokens: response.usage?.total_tokens,
|
|
4472
|
-
cost_usd: options2?.costUsd,
|
|
4473
|
-
latency_ms: options2?.latencyMs,
|
|
4474
|
-
model: response.model
|
|
4475
|
-
});
|
|
4476
|
-
return builder.build();
|
|
4477
5078
|
}
|
|
4478
5079
|
};
|
|
4479
5080
|
|
|
@@ -4484,73 +5085,79 @@ var AnthropicAdapter = class {
|
|
|
4484
5085
|
this.agentId = agentId2;
|
|
4485
5086
|
}
|
|
4486
5087
|
traceFromResponse(response, options2) {
|
|
4487
|
-
const
|
|
4488
|
-
|
|
4489
|
-
builder.setInput({ messages: options2.inputMessages });
|
|
4490
|
-
}
|
|
4491
|
-
const completionParts = [];
|
|
5088
|
+
const texts = [];
|
|
5089
|
+
const toolCalls = [];
|
|
4492
5090
|
for (const block of response.content) {
|
|
4493
5091
|
if (block.type === "text") {
|
|
4494
|
-
|
|
4495
|
-
} else if (block.type === "tool_use") {
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
});
|
|
5092
|
+
texts.push(block.text);
|
|
5093
|
+
} else if (block.type === "tool_use" || block.type === "server_tool_use") {
|
|
5094
|
+
const use = block;
|
|
5095
|
+
toolCalls.push({ name: use.name, args: use.input });
|
|
4499
5096
|
}
|
|
4500
5097
|
}
|
|
4501
|
-
const
|
|
4502
|
-
const
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
5098
|
+
const completion = texts.join("\n");
|
|
5099
|
+
const usage = response.usage;
|
|
5100
|
+
const input = options2?.inputMessages ? { messages: options2.inputMessages } : void 0;
|
|
5101
|
+
return buildProviderTrace(
|
|
5102
|
+
this.agentId,
|
|
5103
|
+
{
|
|
5104
|
+
model: response.model,
|
|
5105
|
+
completion,
|
|
5106
|
+
totalTokens: usage ? usage.input_tokens + usage.output_tokens : void 0,
|
|
5107
|
+
inputTokens: usage?.input_tokens,
|
|
5108
|
+
outputTokens: usage?.output_tokens,
|
|
5109
|
+
toolCalls
|
|
5110
|
+
},
|
|
5111
|
+
options2,
|
|
5112
|
+
input,
|
|
5113
|
+
{ message: completion }
|
|
5114
|
+
);
|
|
4517
5115
|
}
|
|
4518
5116
|
};
|
|
4519
5117
|
|
|
4520
5118
|
// src/adapters/gemini.ts
|
|
5119
|
+
function totalTokens(usage) {
|
|
5120
|
+
if (usage === void 0) return void 0;
|
|
5121
|
+
if (usage.totalTokenCount !== void 0) return usage.totalTokenCount;
|
|
5122
|
+
if (usage.promptTokenCount !== void 0 && usage.candidatesTokenCount !== void 0) {
|
|
5123
|
+
return usage.promptTokenCount + usage.candidatesTokenCount;
|
|
5124
|
+
}
|
|
5125
|
+
return usage.promptTokenCount;
|
|
5126
|
+
}
|
|
5127
|
+
function geminiInput(options2) {
|
|
5128
|
+
const messages = options2?.inputMessages;
|
|
5129
|
+
if (messages && messages.length > 0) {
|
|
5130
|
+
const first = messages[0];
|
|
5131
|
+
if (messages.length === 1 && typeof first.content === "string") return { text: first.content };
|
|
5132
|
+
return { messages };
|
|
5133
|
+
}
|
|
5134
|
+
if (options2?.inputText) return { text: options2.inputText };
|
|
5135
|
+
return void 0;
|
|
5136
|
+
}
|
|
4521
5137
|
var GeminiAdapter = class {
|
|
4522
5138
|
agentId;
|
|
4523
5139
|
constructor(agentId2) {
|
|
4524
5140
|
this.agentId = agentId2;
|
|
4525
5141
|
}
|
|
4526
5142
|
traceFromResponse(response, options2) {
|
|
4527
|
-
const
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
}
|
|
4546
|
-
builder.addLlmCall("completion", { result: { completion: completionText } });
|
|
4547
|
-
builder.setOutput({ message: completionText });
|
|
4548
|
-
builder.setMetadata({
|
|
4549
|
-
cost_usd: options2?.costUsd,
|
|
4550
|
-
latency_ms: options2?.latencyMs,
|
|
4551
|
-
model: options2?.model
|
|
4552
|
-
});
|
|
4553
|
-
return builder.build();
|
|
5143
|
+
const parts = response.candidates?.[0]?.content.parts ?? [];
|
|
5144
|
+
const completion = response.text ?? parts.filter((p) => p.text && !p.thought).map((p) => p.text).join("");
|
|
5145
|
+
const toolCalls = parts.filter((p) => p.functionCall).map((p) => ({ name: p.functionCall.name, args: p.functionCall.args ?? {} }));
|
|
5146
|
+
const usage = response.usageMetadata;
|
|
5147
|
+
return buildProviderTrace(
|
|
5148
|
+
this.agentId,
|
|
5149
|
+
{
|
|
5150
|
+
model: options2?.model ?? response.modelVersion,
|
|
5151
|
+
completion,
|
|
5152
|
+
totalTokens: totalTokens(usage),
|
|
5153
|
+
inputTokens: usage?.promptTokenCount,
|
|
5154
|
+
outputTokens: usage?.candidatesTokenCount,
|
|
5155
|
+
toolCalls
|
|
5156
|
+
},
|
|
5157
|
+
options2,
|
|
5158
|
+
geminiInput(options2),
|
|
5159
|
+
{ message: completion }
|
|
5160
|
+
);
|
|
4554
5161
|
}
|
|
4555
5162
|
};
|
|
4556
5163
|
|
|
@@ -4561,26 +5168,26 @@ var OllamaAdapter = class {
|
|
|
4561
5168
|
this.agentId = agentId2;
|
|
4562
5169
|
}
|
|
4563
5170
|
traceFromResponse(response, options2) {
|
|
4564
|
-
const
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
5171
|
+
const completion = response.message?.content ?? "";
|
|
5172
|
+
const counted = response.eval_count !== void 0 && response.prompt_eval_count !== void 0;
|
|
5173
|
+
const input = options2?.inputMessages ? { messages: options2.inputMessages } : void 0;
|
|
5174
|
+
return buildProviderTrace(
|
|
5175
|
+
this.agentId,
|
|
5176
|
+
{
|
|
5177
|
+
model: response.model,
|
|
5178
|
+
completion,
|
|
5179
|
+
totalTokens: counted ? response.eval_count + response.prompt_eval_count : void 0,
|
|
5180
|
+
inputTokens: counted ? response.prompt_eval_count : void 0,
|
|
5181
|
+
outputTokens: counted ? response.eval_count : void 0,
|
|
5182
|
+
toolCalls: (response.message?.tool_calls ?? []).map((tc) => ({
|
|
5183
|
+
name: tc.function?.name ?? "",
|
|
5184
|
+
args: tc.function?.arguments ?? {}
|
|
5185
|
+
}))
|
|
5186
|
+
},
|
|
5187
|
+
options2,
|
|
5188
|
+
input,
|
|
5189
|
+
{ message: completion }
|
|
5190
|
+
);
|
|
4584
5191
|
}
|
|
4585
5192
|
};
|
|
4586
5193
|
|
|
@@ -4729,97 +5336,240 @@ function spansToOtlp(spans) {
|
|
|
4729
5336
|
}
|
|
4730
5337
|
|
|
4731
5338
|
// src/adapters/langchain.ts
|
|
4732
|
-
var
|
|
5339
|
+
var import_node_module = require("module");
|
|
5340
|
+
var import_meta2 = {};
|
|
5341
|
+
function resolveBaseCallbackHandler() {
|
|
5342
|
+
const load = (0, import_node_module.createRequire)(typeof __filename === "string" ? __filename : import_meta2.url);
|
|
5343
|
+
try {
|
|
5344
|
+
const mod = load("@langchain/core/callbacks/base");
|
|
5345
|
+
return mod.BaseCallbackHandler;
|
|
5346
|
+
} catch (cause) {
|
|
5347
|
+
if (cause.code !== "MODULE_NOT_FOUND") throw cause;
|
|
5348
|
+
return class MissingLangChain {
|
|
5349
|
+
constructor() {
|
|
5350
|
+
throw new Error(
|
|
5351
|
+
"LangChainAdapter requires @langchain/core: install it with `npm install @langchain/core`."
|
|
5352
|
+
);
|
|
5353
|
+
}
|
|
5354
|
+
};
|
|
5355
|
+
}
|
|
5356
|
+
}
|
|
5357
|
+
var BaseCallbackHandler = resolveBaseCallbackHandler();
|
|
5358
|
+
function isValues(value) {
|
|
5359
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5360
|
+
}
|
|
5361
|
+
function hasContent(value) {
|
|
5362
|
+
return isValues(value) && "content" in value;
|
|
5363
|
+
}
|
|
5364
|
+
function messageText(content) {
|
|
5365
|
+
if (typeof content === "string") return content;
|
|
5366
|
+
if (Array.isArray(content)) {
|
|
5367
|
+
return content.map((block) => {
|
|
5368
|
+
if (typeof block === "string") return block;
|
|
5369
|
+
if (isValues(block) && block.type === "text") return String(block.text ?? "");
|
|
5370
|
+
return "";
|
|
5371
|
+
}).join("");
|
|
5372
|
+
}
|
|
5373
|
+
return String(content);
|
|
5374
|
+
}
|
|
5375
|
+
function firstTruthy(values, keys) {
|
|
5376
|
+
for (const key of keys) if (values[key]) return values[key];
|
|
5377
|
+
return void 0;
|
|
5378
|
+
}
|
|
5379
|
+
function textOf(value) {
|
|
5380
|
+
if (typeof value === "string") return value;
|
|
5381
|
+
if (hasContent(value)) return messageText(value.content);
|
|
5382
|
+
return value ? String(value) : "";
|
|
5383
|
+
}
|
|
5384
|
+
function modelNameOf(extraParams) {
|
|
5385
|
+
if (extraParams === void 0) return void 0;
|
|
5386
|
+
const params = isValues(extraParams.invocation_params) ? extraParams.invocation_params : extraParams;
|
|
5387
|
+
const name = params.model_name ?? params.model ?? params.modelName;
|
|
5388
|
+
return typeof name === "string" && name ? name : void 0;
|
|
5389
|
+
}
|
|
5390
|
+
function toolNameOf(serialized, runName) {
|
|
5391
|
+
if (runName) return runName;
|
|
5392
|
+
if (isValues(serialized)) {
|
|
5393
|
+
if (typeof serialized.name === "string" && serialized.name) return serialized.name;
|
|
5394
|
+
if (Array.isArray(serialized.id) && serialized.id.length > 0) return String(serialized.id.at(-1));
|
|
5395
|
+
}
|
|
5396
|
+
return "unknown_tool";
|
|
5397
|
+
}
|
|
5398
|
+
function toolInputOf(input) {
|
|
5399
|
+
try {
|
|
5400
|
+
const parsed = JSON.parse(input);
|
|
5401
|
+
if (isValues(parsed)) return parsed;
|
|
5402
|
+
} catch {
|
|
5403
|
+
}
|
|
5404
|
+
return { input };
|
|
5405
|
+
}
|
|
5406
|
+
var LangChainAdapter = class extends BaseCallbackHandler {
|
|
5407
|
+
name = "attest_langchain_adapter";
|
|
4733
5408
|
agentId;
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
5409
|
+
input;
|
|
5410
|
+
output;
|
|
5411
|
+
steps = [];
|
|
5412
|
+
llmStarts = /* @__PURE__ */ new Map();
|
|
5413
|
+
toolStarts = /* @__PURE__ */ new Map();
|
|
5414
|
+
totalTokens = 0;
|
|
5415
|
+
model;
|
|
5416
|
+
startedAtMs;
|
|
5417
|
+
endedAtMs;
|
|
4738
5418
|
constructor(agentId2) {
|
|
5419
|
+
super();
|
|
4739
5420
|
this.agentId = agentId2;
|
|
5421
|
+
this.awaitHandlers = true;
|
|
5422
|
+
this.raiseError = false;
|
|
4740
5423
|
}
|
|
4741
|
-
/**
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
reset() {
|
|
4745
|
-
this.llmCalls.length = 0;
|
|
4746
|
-
this.toolCalls.length = 0;
|
|
4747
|
-
this.chainOutput = "";
|
|
4748
|
-
this.startTime = 0;
|
|
5424
|
+
/** Handlers are shared by the callback manager, never cloned: a clone would lose the capture. */
|
|
5425
|
+
copy() {
|
|
5426
|
+
return this;
|
|
4749
5427
|
}
|
|
4750
|
-
/**
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
5428
|
+
/** Reset accumulated state for a new capture session. */
|
|
5429
|
+
reset() {
|
|
5430
|
+
this.input = void 0;
|
|
5431
|
+
this.output = void 0;
|
|
5432
|
+
this.steps = [];
|
|
5433
|
+
this.llmStarts.clear();
|
|
5434
|
+
this.toolStarts.clear();
|
|
5435
|
+
this.totalTokens = 0;
|
|
5436
|
+
this.model = void 0;
|
|
5437
|
+
this.startedAtMs = void 0;
|
|
5438
|
+
this.endedAtMs = void 0;
|
|
5439
|
+
}
|
|
5440
|
+
now() {
|
|
5441
|
+
return Date.now();
|
|
5442
|
+
}
|
|
5443
|
+
handleChainStart(_chain, inputs, _runId, parentRunId) {
|
|
5444
|
+
if (parentRunId !== void 0) return;
|
|
5445
|
+
this.startedAtMs = this.now();
|
|
5446
|
+
if (!isValues(inputs)) return;
|
|
5447
|
+
const message = firstTruthy(inputs, ["input", "messages", "query"]) ?? "";
|
|
5448
|
+
if (typeof message === "string") {
|
|
5449
|
+
this.input = message;
|
|
5450
|
+
} else if (Array.isArray(message) && message.length > 0) {
|
|
5451
|
+
const first = message[0];
|
|
5452
|
+
this.input = hasContent(first) ? messageText(first.content) : String(first);
|
|
5453
|
+
} else {
|
|
5454
|
+
this.input = message ? String(message) : "";
|
|
4756
5455
|
}
|
|
4757
5456
|
}
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
const
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
5457
|
+
handleChainEnd(outputs, _runId, parentRunId) {
|
|
5458
|
+
if (parentRunId !== void 0) return;
|
|
5459
|
+
this.endedAtMs = this.now();
|
|
5460
|
+
if (!isValues(outputs)) return;
|
|
5461
|
+
const messages = outputs.messages;
|
|
5462
|
+
if (Array.isArray(messages) && messages.length > 0) {
|
|
5463
|
+
const last = messages[messages.length - 1];
|
|
5464
|
+
if (hasContent(last)) {
|
|
5465
|
+
this.output = messageText(last.content);
|
|
5466
|
+
return;
|
|
5467
|
+
}
|
|
5468
|
+
}
|
|
5469
|
+
this.output = textOf(firstTruthy(outputs, ["output", "result", "text"]));
|
|
5470
|
+
}
|
|
5471
|
+
handleChatModelStart(_llm, _messages, runId, _parentRunId, extraParams) {
|
|
5472
|
+
this.llmStarts.set(runId, { modelName: modelNameOf(extraParams), startedAtMs: this.now() });
|
|
5473
|
+
}
|
|
5474
|
+
handleLLMStart(_llm, _prompts, runId, _parentRunId, extraParams) {
|
|
5475
|
+
this.llmStarts.set(runId, { modelName: modelNameOf(extraParams), startedAtMs: this.now() });
|
|
5476
|
+
}
|
|
5477
|
+
handleLLMEnd(output, runId) {
|
|
5478
|
+
const start3 = this.llmStarts.get(runId);
|
|
5479
|
+
this.llmStarts.delete(runId);
|
|
5480
|
+
const endedAtMs = this.now();
|
|
5481
|
+
let modelName = start3?.modelName;
|
|
5482
|
+
let inputTokens = 0;
|
|
5483
|
+
let outputTokens = 0;
|
|
5484
|
+
let total = 0;
|
|
5485
|
+
const usage = output.generations.flat().find((g) => g.message?.usage_metadata)?.message?.usage_metadata;
|
|
5486
|
+
if (usage) {
|
|
5487
|
+
inputTokens = usage.input_tokens || 0;
|
|
5488
|
+
outputTokens = usage.output_tokens || 0;
|
|
5489
|
+
total = usage.total_tokens || inputTokens + outputTokens;
|
|
5490
|
+
}
|
|
5491
|
+
const llmOutput = output.llmOutput;
|
|
5492
|
+
if (llmOutput) {
|
|
5493
|
+
if (!usage) {
|
|
5494
|
+
const tokenUsage = llmOutput.tokenUsage ?? llmOutput.token_usage ?? {};
|
|
5495
|
+
inputTokens = Number(tokenUsage.promptTokens ?? tokenUsage.prompt_tokens ?? 0);
|
|
5496
|
+
outputTokens = Number(tokenUsage.completionTokens ?? tokenUsage.completion_tokens ?? 0);
|
|
5497
|
+
total = inputTokens + outputTokens;
|
|
5498
|
+
}
|
|
5499
|
+
const reported = llmOutput.model_name ?? llmOutput.modelName;
|
|
5500
|
+
if (modelName === void 0 && typeof reported === "string") modelName = reported;
|
|
5501
|
+
}
|
|
5502
|
+
this.totalTokens += total;
|
|
5503
|
+
if (modelName && this.model === void 0) this.model = modelName;
|
|
5504
|
+
const args = {};
|
|
5505
|
+
if (modelName) args.model = modelName;
|
|
5506
|
+
const result = {};
|
|
5507
|
+
const completion = output.generations[0]?.[0]?.text;
|
|
5508
|
+
if (completion) result.completion = completion;
|
|
5509
|
+
if (total) result.tokens = total;
|
|
5510
|
+
if (inputTokens) result.input_tokens = inputTokens;
|
|
5511
|
+
if (outputTokens) result.output_tokens = outputTokens;
|
|
5512
|
+
this.steps.push(this.step(STEP_LLM_CALL, modelName || "llm", args, result, start3?.startedAtMs, endedAtMs));
|
|
5513
|
+
}
|
|
5514
|
+
handleToolStart(tool, input, runId, _parentRunId, _tags, _metadata, runName) {
|
|
5515
|
+
this.toolStarts.set(runId, {
|
|
5516
|
+
name: toolNameOf(tool, runName),
|
|
5517
|
+
input: toolInputOf(input),
|
|
5518
|
+
startedAtMs: this.now()
|
|
4770
5519
|
});
|
|
4771
5520
|
}
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
this.
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
5521
|
+
handleToolEnd(output, runId) {
|
|
5522
|
+
const text = hasContent(output) ? messageText(output.content) : String(output);
|
|
5523
|
+
const isToolMessage = isValues(output) && typeof output.tool_call_id === "string";
|
|
5524
|
+
const toolName = isToolMessage && typeof output.name === "string" && output.name ? output.name : void 0;
|
|
5525
|
+
this.finishTool(runId, { output: text }, toolName);
|
|
5526
|
+
}
|
|
5527
|
+
handleToolError(error, runId) {
|
|
5528
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5529
|
+
this.finishTool(runId, { error: message }, void 0);
|
|
5530
|
+
}
|
|
5531
|
+
finishTool(runId, result, endName) {
|
|
5532
|
+
const start3 = this.toolStarts.get(runId);
|
|
5533
|
+
this.toolStarts.delete(runId);
|
|
5534
|
+
this.steps.push(
|
|
5535
|
+
this.step(
|
|
5536
|
+
STEP_TOOL_CALL,
|
|
5537
|
+
endName ?? start3?.name ?? "unknown_tool",
|
|
5538
|
+
start3?.input ?? {},
|
|
5539
|
+
result,
|
|
5540
|
+
start3?.startedAtMs,
|
|
5541
|
+
this.now()
|
|
5542
|
+
)
|
|
5543
|
+
);
|
|
4780
5544
|
}
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
5545
|
+
step(type, name, args, result, startedAtMs, endedAtMs) {
|
|
5546
|
+
return {
|
|
5547
|
+
type,
|
|
5548
|
+
name,
|
|
5549
|
+
args,
|
|
5550
|
+
result,
|
|
5551
|
+
metadata: startedAtMs === void 0 ? {} : { duration_ms: endedAtMs - startedAtMs },
|
|
5552
|
+
started_at_ms: startedAtMs,
|
|
5553
|
+
ended_at_ms: endedAtMs,
|
|
5554
|
+
agent_id: this.agentId
|
|
5555
|
+
};
|
|
4789
5556
|
}
|
|
4790
5557
|
/**
|
|
4791
|
-
* Build a Trace from
|
|
5558
|
+
* Build a Trace from the captured callback events.
|
|
4792
5559
|
*
|
|
4793
|
-
* @param options.costUsd - Total cost
|
|
5560
|
+
* @param options.costUsd - Total cost.
|
|
4794
5561
|
* @param options.model - Model name override.
|
|
4795
|
-
* @returns A finalized Trace object.
|
|
4796
5562
|
*/
|
|
4797
5563
|
buildTrace(options2) {
|
|
4798
5564
|
const builder = new TraceBuilder(this.agentId);
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
const args = {};
|
|
4803
|
-
if (llm.model ?? options2?.model) args.model = llm.model ?? options2?.model;
|
|
4804
|
-
const result = { completion: llm.text };
|
|
4805
|
-
if (llm.tokens !== void 0) {
|
|
4806
|
-
result.tokens = llm.tokens;
|
|
4807
|
-
totalTokens = (totalTokens ?? 0) + llm.tokens;
|
|
4808
|
-
}
|
|
4809
|
-
builder.addLlmCall(llm.name, { args, result });
|
|
4810
|
-
}
|
|
4811
|
-
for (const tool of this.toolCalls) {
|
|
4812
|
-
builder.addToolCall(tool.name, {
|
|
4813
|
-
result: { output: tool.output }
|
|
4814
|
-
});
|
|
4815
|
-
}
|
|
4816
|
-
const outputMessage = this.chainOutput || (this.llmCalls.length > 0 ? this.llmCalls[this.llmCalls.length - 1].text : "");
|
|
4817
|
-
builder.setOutput({ message: outputMessage });
|
|
5565
|
+
if (this.input) builder.setInput({ message: this.input });
|
|
5566
|
+
for (const step of this.steps) builder.addStep(step);
|
|
5567
|
+
builder.setOutput({ message: this.output ?? "" });
|
|
4818
5568
|
builder.setMetadata({
|
|
4819
|
-
total_tokens: totalTokens,
|
|
5569
|
+
total_tokens: this.totalTokens > 0 ? this.totalTokens : void 0,
|
|
4820
5570
|
cost_usd: options2?.costUsd,
|
|
4821
|
-
latency_ms:
|
|
4822
|
-
model: options2?.model ?? this.
|
|
5571
|
+
latency_ms: this.startedAtMs !== void 0 && this.endedAtMs !== void 0 ? this.endedAtMs - this.startedAtMs : void 0,
|
|
5572
|
+
model: options2?.model ?? this.model
|
|
4823
5573
|
});
|
|
4824
5574
|
return builder.build();
|
|
4825
5575
|
}
|
|
@@ -4831,6 +5581,7 @@ var LangChainAdapter = class {
|
|
|
4831
5581
|
AgentResult,
|
|
4832
5582
|
AlertDispatcher,
|
|
4833
5583
|
AnthropicAdapter,
|
|
5584
|
+
AssertionIdAssigner,
|
|
4834
5585
|
AttestClient,
|
|
4835
5586
|
CONFUSED_USER,
|
|
4836
5587
|
COOPERATIVE_USER,
|
|
@@ -4850,6 +5601,7 @@ var LangChainAdapter = class {
|
|
|
4850
5601
|
FRIENDLY_USER,
|
|
4851
5602
|
FailureClass,
|
|
4852
5603
|
GeminiAdapter,
|
|
5604
|
+
ID_SCHEME,
|
|
4853
5605
|
LangChainAdapter,
|
|
4854
5606
|
ManualAdapter,
|
|
4855
5607
|
MockToolRegistry,
|
|
@@ -4866,7 +5618,9 @@ var LangChainAdapter = class {
|
|
|
4866
5618
|
ProtocolError,
|
|
4867
5619
|
REASON_BUDGET_EXHAUSTED,
|
|
4868
5620
|
REASON_JUDGE_ABSTAINED,
|
|
5621
|
+
REASON_JUDGE_MALFORMED,
|
|
4869
5622
|
REASON_MISSING_EVIDENCE,
|
|
5623
|
+
REASON_PROVIDER_AUTH,
|
|
4870
5624
|
REASON_PROVIDER_NOT_CONFIGURED,
|
|
4871
5625
|
REASON_PROVIDER_UNAVAILABLE,
|
|
4872
5626
|
REASON_REPLAY_MISS,
|
|
@@ -4908,7 +5662,10 @@ var LangChainAdapter = class {
|
|
|
4908
5662
|
agent,
|
|
4909
5663
|
agentId,
|
|
4910
5664
|
assertionId,
|
|
5665
|
+
assertionIdOf,
|
|
4911
5666
|
attestExpect,
|
|
5667
|
+
canonicalIdInput,
|
|
5668
|
+
canonicalSpec,
|
|
4912
5669
|
computeAgreement,
|
|
4913
5670
|
config,
|
|
4914
5671
|
decodeResponse,
|
|
@@ -4945,13 +5702,16 @@ var LangChainAdapter = class {
|
|
|
4945
5702
|
renderVerdictDiagnostics,
|
|
4946
5703
|
renderVitest,
|
|
4947
5704
|
repeat,
|
|
5705
|
+
replayRun,
|
|
4948
5706
|
requireSimulationCapability,
|
|
4949
5707
|
resetConfig,
|
|
4950
5708
|
resolveMode,
|
|
4951
5709
|
scenario,
|
|
5710
|
+
setChainObserver,
|
|
4952
5711
|
simulationEnvEnabled,
|
|
4953
5712
|
startReplay,
|
|
4954
5713
|
startWorld,
|
|
5714
|
+
submitPluginResults,
|
|
4955
5715
|
sumUsage,
|
|
4956
5716
|
testPathFor,
|
|
4957
5717
|
tier,
|