@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/esm/index.js
CHANGED
|
@@ -14,6 +14,8 @@ var REASON_MISSING_EVIDENCE = "missing_evidence";
|
|
|
14
14
|
var REASON_SHORT_CIRCUITED = "short_circuited";
|
|
15
15
|
var REASON_BUDGET_EXHAUSTED = "budget_exhausted";
|
|
16
16
|
var REASON_JUDGE_ABSTAINED = "judge_abstained";
|
|
17
|
+
var REASON_JUDGE_MALFORMED = "judge_malformed";
|
|
18
|
+
var REASON_PROVIDER_AUTH = "provider_auth";
|
|
17
19
|
var REASON_REPLAY_MISS = "replay_miss";
|
|
18
20
|
var OUTCOME_PASS = "pass";
|
|
19
21
|
var OUTCOME_FAIL = "fail";
|
|
@@ -49,7 +51,8 @@ var FailureClass = {
|
|
|
49
51
|
FlakyJudge: "flaky_judge",
|
|
50
52
|
BadRubric: "bad_rubric",
|
|
51
53
|
MissingTraceData: "missing_trace_data",
|
|
52
|
-
StochasticVariance: "stochastic_variance"
|
|
54
|
+
StochasticVariance: "stochastic_variance",
|
|
55
|
+
SpecError: "spec_error"
|
|
53
56
|
};
|
|
54
57
|
function layerForType(assertionType) {
|
|
55
58
|
switch (assertionType) {
|
|
@@ -219,13 +222,13 @@ function extractId(response) {
|
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
// src/version.ts
|
|
222
|
-
var VERSION = "1.0.1";
|
|
223
|
-
var ENGINE_VERSION = "1.0.1";
|
|
225
|
+
var VERSION = "1.1.0-rc.1";
|
|
226
|
+
var ENGINE_VERSION = "1.1.0-rc.1";
|
|
224
227
|
|
|
225
228
|
// src/engine-manager.ts
|
|
226
229
|
import { spawn, spawnSync } from "child_process";
|
|
227
230
|
import { createInterface } from "readline";
|
|
228
|
-
import { existsSync, accessSync, statSync as statSync2, constants as fsConstants } from "fs";
|
|
231
|
+
import { existsSync as existsSync2, accessSync, statSync as statSync2, constants as fsConstants } from "fs";
|
|
229
232
|
import { delimiter, join as join2 } from "path";
|
|
230
233
|
|
|
231
234
|
// src/config.ts
|
|
@@ -345,6 +348,7 @@ var DEFAULT_TIMEOUT_MS = 3e4;
|
|
|
345
348
|
var DEFAULT_DIAGNOSTIC_BUFFER = 32;
|
|
346
349
|
var DEFAULT_DESYNC_THRESHOLD = 3;
|
|
347
350
|
var DEFAULT_DESYNC_WINDOW_MS = 1e3;
|
|
351
|
+
var MAX_TIMEOUT_ENV_SECONDS = 3600;
|
|
348
352
|
function resolveTimeoutMs(override) {
|
|
349
353
|
if (override !== void 0) return override;
|
|
350
354
|
const env = process.env["ATTEST_ENGINE_TIMEOUT"];
|
|
@@ -352,10 +356,15 @@ function resolveTimeoutMs(override) {
|
|
|
352
356
|
const parsed = Number(env);
|
|
353
357
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
354
358
|
throw new Error(
|
|
355
|
-
`ATTEST_ENGINE_TIMEOUT must be a positive number of
|
|
359
|
+
`ATTEST_ENGINE_TIMEOUT must be a positive number of seconds, got: '${env}'`
|
|
356
360
|
);
|
|
357
361
|
}
|
|
358
|
-
|
|
362
|
+
if (parsed > MAX_TIMEOUT_ENV_SECONDS) {
|
|
363
|
+
throw new Error(
|
|
364
|
+
`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.`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
return parsed * 1e3;
|
|
359
368
|
}
|
|
360
369
|
return DEFAULT_TIMEOUT_MS;
|
|
361
370
|
}
|
|
@@ -430,10 +439,10 @@ var AttestClient = class {
|
|
|
430
439
|
rl.on("line", this.lineHandler);
|
|
431
440
|
rl.on("close", () => {
|
|
432
441
|
if (this.attachedReader !== rl) return;
|
|
433
|
-
const
|
|
442
|
+
const tail2 = this.engine.stderrTail ?? "";
|
|
434
443
|
this.failAll(
|
|
435
|
-
new Error(
|
|
436
|
-
${
|
|
444
|
+
new Error(tail2 === "" ? "Engine closed stdout." : `Engine closed stdout. stderr:
|
|
445
|
+
${tail2}`)
|
|
437
446
|
);
|
|
438
447
|
this.readerActive = false;
|
|
439
448
|
this.attachedReader = null;
|
|
@@ -583,9 +592,9 @@ ${tail}`)
|
|
|
583
592
|
}
|
|
584
593
|
const resolvedTimeout = resolveTimeoutMs(timeoutMs);
|
|
585
594
|
if (this.engine.isRunning === false) {
|
|
586
|
-
const
|
|
587
|
-
throw new Error(`Engine process is not running.${
|
|
588
|
-
${
|
|
595
|
+
const tail2 = this.engine.stderrTail ?? "";
|
|
596
|
+
throw new Error(`Engine process is not running.${tail2 === "" ? "" : ` stderr:
|
|
597
|
+
${tail2}`}`);
|
|
589
598
|
}
|
|
590
599
|
const cp = this.engine.childProcess;
|
|
591
600
|
if (cp === null || cp.stdin === null) {
|
|
@@ -627,10 +636,10 @@ ${tail}`}`);
|
|
|
627
636
|
}
|
|
628
637
|
/** A failed pipe write names the engine's stderr tail; the original error stays as `cause` and `code`. */
|
|
629
638
|
describeWriteError(err) {
|
|
630
|
-
const
|
|
639
|
+
const tail2 = this.engine.stderrTail ?? "";
|
|
631
640
|
const wrapped = new Error(
|
|
632
|
-
`Failed to write to the engine: ${err.message}.${
|
|
633
|
-
${
|
|
641
|
+
`Failed to write to the engine: ${err.message}.${tail2 === "" ? "" : ` stderr:
|
|
642
|
+
${tail2}`}`,
|
|
634
643
|
{ cause: err }
|
|
635
644
|
);
|
|
636
645
|
return Object.assign(wrapped, { code: err.code });
|
|
@@ -726,7 +735,11 @@ ${tail}`}`,
|
|
|
726
735
|
}
|
|
727
736
|
const raw = await this.sendRequest(
|
|
728
737
|
"query_drift",
|
|
729
|
-
{
|
|
738
|
+
{
|
|
739
|
+
assertion_id: assertionId2,
|
|
740
|
+
window_size: windowSize,
|
|
741
|
+
...options2?.staticThreshold !== void 0 ? { static_threshold: options2.staticThreshold } : {}
|
|
742
|
+
},
|
|
730
743
|
options2?.timeout
|
|
731
744
|
);
|
|
732
745
|
return raw.report;
|
|
@@ -789,14 +802,18 @@ function requireSimulationCapability(init) {
|
|
|
789
802
|
|
|
790
803
|
// src/engine-downloader.ts
|
|
791
804
|
import * as https from "https";
|
|
792
|
-
import * as http from "http";
|
|
793
805
|
import * as fs from "fs";
|
|
794
806
|
import * as path from "path";
|
|
795
807
|
import * as os from "os";
|
|
796
808
|
import * as crypto from "crypto";
|
|
809
|
+
import { fileURLToPath } from "url";
|
|
797
810
|
var RELEASE_BASE = `https://github.com/attest-framework/attest/releases/download/v${ENGINE_VERSION}`;
|
|
798
|
-
var
|
|
811
|
+
var TABLE_NAME = "engine-checksums.json";
|
|
799
812
|
var MAX_REDIRECTS = 5;
|
|
813
|
+
var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
814
|
+
var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
|
|
815
|
+
var DEFAULT_TOTAL_TIMEOUT_MS = 12e4;
|
|
816
|
+
var SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
800
817
|
function versionedBinDir() {
|
|
801
818
|
return path.join(os.homedir(), ".attest", "bin", ENGINE_VERSION);
|
|
802
819
|
}
|
|
@@ -829,73 +846,131 @@ function cachedEnginePath() {
|
|
|
829
846
|
const binPath = path.join(versionedBinDir(), binaryFilename());
|
|
830
847
|
return fs.statSync(binPath, { throwIfNoEntry: false })?.isFile() ? binPath : null;
|
|
831
848
|
}
|
|
832
|
-
function
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
const
|
|
836
|
-
if (
|
|
837
|
-
|
|
838
|
-
if (match) {
|
|
839
|
-
checksums.set(match[2], match[1]);
|
|
840
|
-
}
|
|
849
|
+
function packageRoot() {
|
|
850
|
+
let dir = typeof __dirname === "string" ? __dirname : path.dirname(fileURLToPath(import.meta.url));
|
|
851
|
+
while (!fs.existsSync(path.join(dir, "package.json"))) {
|
|
852
|
+
const parent = path.dirname(dir);
|
|
853
|
+
if (parent === dir) return dir;
|
|
854
|
+
dir = parent;
|
|
841
855
|
}
|
|
842
|
-
return
|
|
856
|
+
return dir;
|
|
843
857
|
}
|
|
844
|
-
function
|
|
845
|
-
if (
|
|
846
|
-
|
|
847
|
-
|
|
858
|
+
function embeddedHash(key, tablePath) {
|
|
859
|
+
if (!fs.existsSync(tablePath)) {
|
|
860
|
+
throw new Error(
|
|
861
|
+
`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.`
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
let hash;
|
|
865
|
+
try {
|
|
866
|
+
const table = JSON.parse(fs.readFileSync(tablePath, "utf-8"));
|
|
867
|
+
hash = table[ENGINE_VERSION]?.[key];
|
|
868
|
+
} catch (err) {
|
|
869
|
+
throw new Error(
|
|
870
|
+
`@attest-ai/core ${VERSION} ships an unreadable engine hash table at ${tablePath}: ${String(err)}`
|
|
848
871
|
);
|
|
849
872
|
}
|
|
873
|
+
if (typeof hash !== "string" || !SHA256_HEX.test(hash)) {
|
|
874
|
+
throw new Error(
|
|
875
|
+
`@attest-ai/core ${VERSION} ships no valid engine hash for v${ENGINE_VERSION} on ${key} in ${tablePath}.`
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
return hash;
|
|
879
|
+
}
|
|
880
|
+
function httpsGet(url, o) {
|
|
850
881
|
return new Promise((resolve5, reject) => {
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
if (
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
resolve5(
|
|
882
|
+
let current;
|
|
883
|
+
let done = false;
|
|
884
|
+
const finish = (err, data) => {
|
|
885
|
+
if (done) return;
|
|
886
|
+
done = true;
|
|
887
|
+
clearTimeout(deadline);
|
|
888
|
+
if (err) {
|
|
889
|
+
current?.destroy();
|
|
890
|
+
reject(err);
|
|
891
|
+
} else {
|
|
892
|
+
resolve5(data);
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
const deadline = setTimeout(
|
|
896
|
+
() => finish(new Error(`Timed out after ${o.totalTimeoutMs}ms fetching ${url}`)),
|
|
897
|
+
o.totalTimeoutMs
|
|
898
|
+
);
|
|
899
|
+
const hop = (target, redirects) => {
|
|
900
|
+
if (redirects > MAX_REDIRECTS) {
|
|
901
|
+
finish(new Error(`Too many redirects (>${MAX_REDIRECTS}) following ${url}`));
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
let parsed;
|
|
905
|
+
try {
|
|
906
|
+
parsed = new URL(target);
|
|
907
|
+
} catch {
|
|
908
|
+
finish(new Error(`Invalid URL: ${target}`));
|
|
862
909
|
return;
|
|
863
910
|
}
|
|
864
|
-
if (
|
|
865
|
-
|
|
866
|
-
reject(new Error(`HTTP ${status} fetching ${url}`));
|
|
911
|
+
if (parsed.protocol !== "https:") {
|
|
912
|
+
finish(new Error(`Refusing to download over a non-https URL: ${target}`));
|
|
867
913
|
return;
|
|
868
914
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
915
|
+
current = https.get(parsed, { ca: o.ca, timeout: o.connectTimeoutMs }, (res) => {
|
|
916
|
+
const status = res.statusCode ?? 0;
|
|
917
|
+
if (REDIRECT_STATUSES.has(status)) {
|
|
918
|
+
res.resume();
|
|
919
|
+
const location = res.headers.location;
|
|
920
|
+
if (!location) {
|
|
921
|
+
finish(new Error(`Redirect ${status} without Location header from ${target}`));
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
let next;
|
|
925
|
+
try {
|
|
926
|
+
next = new URL(location, parsed).toString();
|
|
927
|
+
} catch {
|
|
928
|
+
finish(new Error(`Invalid redirect Location ${JSON.stringify(location)} from ${target}`));
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
hop(next, redirects + 1);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
if (status !== 200) {
|
|
935
|
+
res.resume();
|
|
936
|
+
finish(new Error(`HTTP ${status} fetching ${target}`));
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
const chunks = [];
|
|
940
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
941
|
+
res.on("end", () => finish(null, Buffer.concat(chunks)));
|
|
942
|
+
res.on("error", (err) => finish(err));
|
|
943
|
+
});
|
|
944
|
+
current.on(
|
|
945
|
+
"timeout",
|
|
946
|
+
() => finish(new Error(`No response within ${o.connectTimeoutMs}ms from ${target}`))
|
|
947
|
+
);
|
|
948
|
+
current.on("error", (err) => finish(err));
|
|
949
|
+
};
|
|
950
|
+
hop(url, 0);
|
|
874
951
|
});
|
|
875
952
|
}
|
|
876
|
-
async function downloadEngine() {
|
|
953
|
+
async function downloadEngine(options2 = {}) {
|
|
877
954
|
const key = platformKey();
|
|
878
955
|
const filename = `attest-engine-${key}${os.platform() === "win32" ? ".exe" : ""}`;
|
|
879
|
-
const
|
|
956
|
+
const expectedHash = embeddedHash(key, options2.tablePath ?? path.join(packageRoot(), TABLE_NAME));
|
|
957
|
+
const binaryUrl = `${options2.releaseBase ?? RELEASE_BASE}/${filename}`;
|
|
880
958
|
process.stderr.write(`[attest] Downloading engine v${ENGINE_VERSION} for ${key}...
|
|
881
959
|
`);
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
);
|
|
960
|
+
let data;
|
|
961
|
+
try {
|
|
962
|
+
data = await httpsGet(binaryUrl, {
|
|
963
|
+
ca: options2.ca,
|
|
964
|
+
connectTimeoutMs: options2.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS,
|
|
965
|
+
totalTimeoutMs: options2.totalTimeoutMs ?? DEFAULT_TOTAL_TIMEOUT_MS
|
|
966
|
+
});
|
|
967
|
+
} catch (err) {
|
|
968
|
+
throw new Error(`Failed to download engine from ${binaryUrl}: ${err.message}`);
|
|
891
969
|
}
|
|
892
|
-
process.stderr.write(`[attest] Downloading ${binaryUrl}...
|
|
893
|
-
`);
|
|
894
|
-
const data = await httpsGet(binaryUrl);
|
|
895
970
|
const actualHash = crypto.createHash("sha256").update(data).digest("hex");
|
|
896
971
|
if (actualHash !== expectedHash) {
|
|
897
972
|
throw new Error(
|
|
898
|
-
|
|
973
|
+
`${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.`
|
|
899
974
|
);
|
|
900
975
|
}
|
|
901
976
|
process.stderr.write(`[attest] Checksum verified.
|
|
@@ -976,7 +1051,7 @@ function onPath() {
|
|
|
976
1051
|
return null;
|
|
977
1052
|
}
|
|
978
1053
|
function requireFile(enginePath, label) {
|
|
979
|
-
if (!
|
|
1054
|
+
if (!existsSync2(enginePath) || !statSync2(enginePath).isFile()) {
|
|
980
1055
|
throw new Error(`${label}=${enginePath} does not exist or is not a file.`);
|
|
981
1056
|
}
|
|
982
1057
|
}
|
|
@@ -1800,12 +1875,26 @@ var WorldChecks = class {
|
|
|
1800
1875
|
);
|
|
1801
1876
|
}
|
|
1802
1877
|
};
|
|
1878
|
+
var chainObserver;
|
|
1879
|
+
function setChainObserver(observer) {
|
|
1880
|
+
chainObserver = observer;
|
|
1881
|
+
}
|
|
1803
1882
|
var ExpectChain = class {
|
|
1804
1883
|
_result;
|
|
1805
1884
|
_assertions = [];
|
|
1806
1885
|
_worldBytes = 0;
|
|
1886
|
+
_evaluated = false;
|
|
1807
1887
|
constructor(result) {
|
|
1808
1888
|
this._result = result;
|
|
1889
|
+
chainObserver?.(this);
|
|
1890
|
+
}
|
|
1891
|
+
/** True once the chain was handed to an evaluation. */
|
|
1892
|
+
get evaluated() {
|
|
1893
|
+
return this._evaluated;
|
|
1894
|
+
}
|
|
1895
|
+
/** Record that the chain was handed to an evaluation. */
|
|
1896
|
+
markEvaluated() {
|
|
1897
|
+
this._evaluated = true;
|
|
1809
1898
|
}
|
|
1810
1899
|
get assertions() {
|
|
1811
1900
|
return [...this._assertions];
|
|
@@ -2743,11 +2832,101 @@ function attestExpect(result) {
|
|
|
2743
2832
|
return new ExpectChain(new AgentResult(result));
|
|
2744
2833
|
}
|
|
2745
2834
|
|
|
2835
|
+
// src/assertion-id.ts
|
|
2836
|
+
import { createHash as createHash2 } from "crypto";
|
|
2837
|
+
var ID_SCHEME = 2;
|
|
2838
|
+
function lonelySurrogateAt(text) {
|
|
2839
|
+
for (let i = 0; i < text.length; i++) {
|
|
2840
|
+
const unit = text.charCodeAt(i);
|
|
2841
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
2842
|
+
const next = text.charCodeAt(i + 1);
|
|
2843
|
+
if (next >= 56320 && next <= 57343) {
|
|
2844
|
+
i++;
|
|
2845
|
+
continue;
|
|
2846
|
+
}
|
|
2847
|
+
return i;
|
|
2848
|
+
}
|
|
2849
|
+
if (unit >= 56320 && unit <= 57343) return i;
|
|
2850
|
+
}
|
|
2851
|
+
return -1;
|
|
2852
|
+
}
|
|
2853
|
+
function describe(identity) {
|
|
2854
|
+
if (identity === void 0) return "";
|
|
2855
|
+
return ` (assertion type ${JSON.stringify(identity.type)}, scope ${JSON.stringify(identity.scopeId)})`;
|
|
2856
|
+
}
|
|
2857
|
+
function jcsString(text, where, identity) {
|
|
2858
|
+
const at = lonelySurrogateAt(text);
|
|
2859
|
+
if (at >= 0) {
|
|
2860
|
+
throw new Error(
|
|
2861
|
+
`assertion id: ${where} contains an unpaired surrogate at UTF-16 index ${at}; it has no UTF-8 form${describe(identity)}`
|
|
2862
|
+
);
|
|
2863
|
+
}
|
|
2864
|
+
return JSON.stringify(text);
|
|
2865
|
+
}
|
|
2866
|
+
function jcs(value, where, identity) {
|
|
2867
|
+
if (value === null) return "null";
|
|
2868
|
+
switch (typeof value) {
|
|
2869
|
+
case "string":
|
|
2870
|
+
return jcsString(value, where, identity);
|
|
2871
|
+
case "boolean":
|
|
2872
|
+
return value ? "true" : "false";
|
|
2873
|
+
case "number":
|
|
2874
|
+
if (!Number.isFinite(value)) {
|
|
2875
|
+
throw new Error(`assertion id: ${where} is ${String(value)}, which JSON cannot represent${describe(identity)}`);
|
|
2876
|
+
}
|
|
2877
|
+
return String(value);
|
|
2878
|
+
case "object": {
|
|
2879
|
+
if (Array.isArray(value)) {
|
|
2880
|
+
return `[${value.map((item, i) => jcs(item === void 0 ? null : item, `${where}[${i}]`, identity)).join(",")}]`;
|
|
2881
|
+
}
|
|
2882
|
+
const record2 = value;
|
|
2883
|
+
const keys = Object.keys(record2).filter((k) => record2[k] !== void 0).sort();
|
|
2884
|
+
const members = keys.map((key) => {
|
|
2885
|
+
const quoted = jcsString(key, `${where} key ${JSON.stringify(key)}`, identity);
|
|
2886
|
+
return `${quoted}:${jcs(record2[key], `${where}.${key}`, identity)}`;
|
|
2887
|
+
});
|
|
2888
|
+
return `{${members.join(",")}}`;
|
|
2889
|
+
}
|
|
2890
|
+
default:
|
|
2891
|
+
throw new Error(`assertion id: ${where} has non-JSON type ${typeof value}${describe(identity)}`);
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
function canonicalSpec(spec, identity) {
|
|
2895
|
+
return jcs(spec ?? {}, "spec", identity);
|
|
2896
|
+
}
|
|
2897
|
+
function canonicalIdInput(scopeId, type, spec, occurrenceIndex) {
|
|
2898
|
+
const identity = { scopeId, type, spec };
|
|
2899
|
+
if (!Number.isInteger(occurrenceIndex) || occurrenceIndex < 0) {
|
|
2900
|
+
throw new Error(`assertion id: occurrence index must be an integer >= 0, got ${String(occurrenceIndex)}`);
|
|
2901
|
+
}
|
|
2902
|
+
return `[${jcsString(scopeId, "scope_id", identity)},${jcsString(type, "assertion_type", identity)},${canonicalSpec(spec, identity)},${occurrenceIndex}]`;
|
|
2903
|
+
}
|
|
2904
|
+
function assertionIdOf(scopeId, type, spec, occurrenceIndex) {
|
|
2905
|
+
const input = canonicalIdInput(scopeId, type, spec, occurrenceIndex);
|
|
2906
|
+
return `a2_${createHash2("sha256").update(input, "utf8").digest("hex").slice(0, 32)}`;
|
|
2907
|
+
}
|
|
2908
|
+
var AssertionIdAssigner = class {
|
|
2909
|
+
constructor(scopeId) {
|
|
2910
|
+
this.scopeId = scopeId;
|
|
2911
|
+
if (scopeId === "") throw new Error("assertion id: scope id must not be empty");
|
|
2912
|
+
}
|
|
2913
|
+
scopeId;
|
|
2914
|
+
seen = /* @__PURE__ */ new Map();
|
|
2915
|
+
next(type, spec) {
|
|
2916
|
+
const key = `${jcsString(type, "assertion_type", { scopeId: this.scopeId, type, spec })}\0${canonicalSpec(spec, { scopeId: this.scopeId, type, spec })}`;
|
|
2917
|
+
const occurrence = this.seen.get(key) ?? 0;
|
|
2918
|
+
const id = assertionIdOf(this.scopeId, type, spec, occurrence);
|
|
2919
|
+
this.seen.set(key, occurrence + 1);
|
|
2920
|
+
return id;
|
|
2921
|
+
}
|
|
2922
|
+
};
|
|
2923
|
+
|
|
2746
2924
|
// src/capture.ts
|
|
2747
2925
|
import { spawn as spawn2 } from "child_process";
|
|
2748
2926
|
import * as fs2 from "fs";
|
|
2927
|
+
import * as os2 from "os";
|
|
2749
2928
|
import * as path2 from "path";
|
|
2750
|
-
import { fileURLToPath } from "url";
|
|
2929
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2751
2930
|
var FIXTURE_VERSION = 1;
|
|
2752
2931
|
var DEFAULT_FIXTURE_DIR = path2.join("tests", "attest", "fixtures");
|
|
2753
2932
|
var CaptureError = class extends Error {
|
|
@@ -2760,7 +2939,7 @@ var CaptureError = class extends Error {
|
|
|
2760
2939
|
}
|
|
2761
2940
|
};
|
|
2762
2941
|
function toPath(p) {
|
|
2763
|
-
return p instanceof URL ?
|
|
2942
|
+
return p instanceof URL ? fileURLToPath2(p) : p;
|
|
2764
2943
|
}
|
|
2765
2944
|
function loadFixture(file) {
|
|
2766
2945
|
const p = toPath(file);
|
|
@@ -2884,7 +3063,75 @@ function testPathFor(fixturePath) {
|
|
|
2884
3063
|
const name = path2.basename(fixturePath).replace(/\.attest\.json$/, "");
|
|
2885
3064
|
return path2.join(path2.dirname(path2.dirname(fixturePath)), `${name}.test.ts`);
|
|
2886
3065
|
}
|
|
2887
|
-
function
|
|
3066
|
+
function relUrl(target, testPath) {
|
|
3067
|
+
const rel = path2.relative(path2.dirname(testPath), target).split(path2.sep).join("/");
|
|
3068
|
+
const url = rel === "" ? "." : rel;
|
|
3069
|
+
return `new URL(${ts(url.startsWith(".") ? url : `./${url}`)}, import.meta.url)`;
|
|
3070
|
+
}
|
|
3071
|
+
function chainLines(calls, subject, param) {
|
|
3072
|
+
const lines = [`function contracts(${param}): ExpectChain {`, ` return attestExpect(${subject})`];
|
|
3073
|
+
for (const [c, dsl] of calls) {
|
|
3074
|
+
lines.push(` // ${comment(`${c.id}: ${c.description}`)}`, ` ${dsl}`);
|
|
3075
|
+
}
|
|
3076
|
+
lines[lines.length - 1] += ";";
|
|
3077
|
+
lines.push("}");
|
|
3078
|
+
return lines;
|
|
3079
|
+
}
|
|
3080
|
+
function skippedLines(skipped) {
|
|
3081
|
+
if (skipped.length === 0) return [];
|
|
3082
|
+
return ["", "// Drafted contracts without a TypeScript DSL call (left out):", ...skipped.map((s) => `// - ${comment(s)}`)];
|
|
3083
|
+
}
|
|
3084
|
+
function renderReplay(fixture, calls, skipped, fixturePath, testPath, rerun) {
|
|
3085
|
+
const meaning = fixture.verification.expect_fail ? [
|
|
3086
|
+
"// The recording is an incident: these contracts failed on it. The test passes once",
|
|
3087
|
+
"// the new run satisfies them, so it fails again if the agent regresses."
|
|
3088
|
+
] : ["// The test fails when the agent's behavior stops satisfying them."];
|
|
3089
|
+
const world = rerun.world === void 0 ? "undefined" : relUrl(rerun.world, testPath);
|
|
3090
|
+
const lines = [
|
|
3091
|
+
`// Replay test drafted by \`attest capture\` from fixture ${comment(ts(fixture.name))}.`,
|
|
3092
|
+
"//",
|
|
3093
|
+
"// Re-runs COMMAND, the source command of the fixture, under strict replay (the engine's",
|
|
3094
|
+
"// `replay --mode strict --no-live`): OpenAI- and Anthropic-compatible calls are served",
|
|
3095
|
+
"// from the recording, and one that matches no recording fails the test. Other providers",
|
|
3096
|
+
"// are not blocked: run this test without their credentials. The drafted contracts are",
|
|
3097
|
+
"// evaluated on the NEW run's trace, not on the recording.",
|
|
3098
|
+
"//",
|
|
3099
|
+
"// COMMAND is what this test runs and is reviewed with the test: if the fixture's",
|
|
3100
|
+
"// source.command stops matching it, the test fails instead of running the fixture's.",
|
|
3101
|
+
...meaning,
|
|
3102
|
+
"//",
|
|
3103
|
+
"// A pass means the new run is consistent with the recording and satisfies the drafted",
|
|
3104
|
+
"// contracts; it does not prove the agent correct. Identities are compared by role",
|
|
3105
|
+
"// (relative identity): a run that swaps who plays which role misses, while the absolute",
|
|
3106
|
+
"// identity is not checked. Review and edit before committing. Re-capturing does not",
|
|
3107
|
+
"// overwrite this file.",
|
|
3108
|
+
`import { attestExpect, replayRun, type ExpectChain } from "@attest-ai/core";`,
|
|
3109
|
+
`import type { Trace } from "@attest-ai/core/proto";`,
|
|
3110
|
+
`import { evaluate } from "@attest-ai/vitest";`,
|
|
3111
|
+
`import { describe, it } from "vitest";`,
|
|
3112
|
+
"",
|
|
3113
|
+
`const FIXTURE = ${relUrl(fixturePath, testPath)};`,
|
|
3114
|
+
"// The directory `attest capture` ran in: the source command's relative paths start here.",
|
|
3115
|
+
`const RUN_DIR = ${relUrl(rerun.cwd, testPath)};`,
|
|
3116
|
+
`const WORLD = ${world};`,
|
|
3117
|
+
"// Reviewed argv: the only command this test runs.",
|
|
3118
|
+
`const COMMAND = ${ts(fixture.source.command ?? [])};`,
|
|
3119
|
+
...skippedLines(skipped),
|
|
3120
|
+
"",
|
|
3121
|
+
...chainLines(calls, "trace", "trace: Trace"),
|
|
3122
|
+
"",
|
|
3123
|
+
`describe(${ts(fixture.name)}, () => {`,
|
|
3124
|
+
` it("new run is consistent with the recording", async () => {`,
|
|
3125
|
+
" const trace = await replayRun(FIXTURE, { command: COMMAND, cwd: RUN_DIR, world: WORLD });",
|
|
3126
|
+
"",
|
|
3127
|
+
" await evaluate(contracts(trace));",
|
|
3128
|
+
" });",
|
|
3129
|
+
"});"
|
|
3130
|
+
];
|
|
3131
|
+
return `${lines.join("\n")}
|
|
3132
|
+
`;
|
|
3133
|
+
}
|
|
3134
|
+
function renderVitest(fixture, fixturePath, testPath, rerun) {
|
|
2888
3135
|
const calls = [];
|
|
2889
3136
|
const skipped = [];
|
|
2890
3137
|
for (const c of fixture.contracts) {
|
|
@@ -2894,7 +3141,9 @@ function renderVitest(fixture, fixturePath, testPath) {
|
|
|
2894
3141
|
skipped.push(err.message);
|
|
2895
3142
|
}
|
|
2896
3143
|
}
|
|
2897
|
-
|
|
3144
|
+
if (rerun !== void 0 && fixture.source.kind === "command" && fixture.source.replay_capable) {
|
|
3145
|
+
return renderReplay(fixture, calls, skipped, fixturePath, testPath, rerun);
|
|
3146
|
+
}
|
|
2898
3147
|
const status = new Map(fixture.verification.results.map((r) => [r.contract, r.status]));
|
|
2899
3148
|
const expectFail = fixture.verification.expect_fail;
|
|
2900
3149
|
const lines = [
|
|
@@ -2906,18 +3155,11 @@ function renderVitest(fixture, fixturePath, testPath) {
|
|
|
2906
3155
|
`import { ${expectFail ? "inspect" : "evaluate"} } from "@attest-ai/vitest";`,
|
|
2907
3156
|
`import { describe, ${expectFail ? "expect, " : ""}it } from "vitest";`,
|
|
2908
3157
|
"",
|
|
2909
|
-
`const FIXTURE = loadFixture(
|
|
3158
|
+
`const FIXTURE = loadFixture(${relUrl(fixturePath, testPath)});`,
|
|
3159
|
+
...skippedLines(skipped),
|
|
3160
|
+
"",
|
|
3161
|
+
...chainLines(calls, "FIXTURE.trace", "")
|
|
2910
3162
|
];
|
|
2911
|
-
if (skipped.length > 0) {
|
|
2912
|
-
lines.push("", "// Drafted contracts without a TypeScript DSL call (left out):");
|
|
2913
|
-
for (const s of skipped) lines.push(`// - ${comment(s)}`);
|
|
2914
|
-
}
|
|
2915
|
-
lines.push("", "function contracts(): ExpectChain {", " return attestExpect(FIXTURE.trace)");
|
|
2916
|
-
for (const [c, dsl] of calls) {
|
|
2917
|
-
lines.push(` // ${comment(`${c.id}: ${c.description}`)}`, ` ${dsl}`);
|
|
2918
|
-
}
|
|
2919
|
-
lines[lines.length - 1] += ";";
|
|
2920
|
-
lines.push("}");
|
|
2921
3163
|
if (expectFail) {
|
|
2922
3164
|
lines.push("", "// Status of each contract on the captured incident, in contracts() order.");
|
|
2923
3165
|
lines.push("const EXPECTED: Array<[string, string]> = [");
|
|
@@ -2946,14 +3188,14 @@ function renderVitest(fixture, fixturePath, testPath) {
|
|
|
2946
3188
|
return `${lines.join("\n")}
|
|
2947
3189
|
`;
|
|
2948
3190
|
}
|
|
2949
|
-
function emitVitest(fixturePath) {
|
|
3191
|
+
function emitVitest(fixturePath, rerun) {
|
|
2950
3192
|
const testPath = testPathFor(fixturePath);
|
|
2951
3193
|
const existing = fs2.lstatSync(testPath, { throwIfNoEntry: false });
|
|
2952
3194
|
if (existing?.isSymbolicLink()) {
|
|
2953
3195
|
throw new CaptureError(`${testPath} is a symbolic link; refusing to draft a test through it`);
|
|
2954
3196
|
}
|
|
2955
3197
|
if (existing !== void 0) return null;
|
|
2956
|
-
const source = renderVitest(loadFixture(fixturePath), fixturePath, testPath);
|
|
3198
|
+
const source = renderVitest(loadFixture(fixturePath), fixturePath, testPath, rerun);
|
|
2957
3199
|
fs2.mkdirSync(path2.dirname(testPath), { recursive: true });
|
|
2958
3200
|
try {
|
|
2959
3201
|
fs2.writeFileSync(testPath, source, { encoding: "utf8", flag: "wx" });
|
|
@@ -2965,6 +3207,108 @@ function emitVitest(fixturePath) {
|
|
|
2965
3207
|
}
|
|
2966
3208
|
return testPath;
|
|
2967
3209
|
}
|
|
3210
|
+
var OTHER_PROVIDER_KEYS = ["ANTHROPIC_API_KEY", "GOOGLE_API_KEY", "GEMINI_API_KEY"];
|
|
3211
|
+
var REPLAY_TIMEOUT_MS = 3e5;
|
|
3212
|
+
var REDACTED_MARKER = "[redacted]";
|
|
3213
|
+
var PLACEHOLDER = /\{\{(?:id|email|timestamp)_\d+\}\}/;
|
|
3214
|
+
var STDERR_TAIL = 4e3;
|
|
3215
|
+
async function replayRun(fixture, options2) {
|
|
3216
|
+
const fp = path2.resolve(toPath(fixture));
|
|
3217
|
+
const doc = loadFixture(fp);
|
|
3218
|
+
const recorded = doc.source.command;
|
|
3219
|
+
if (doc.source.kind !== "command" || !doc.source.replay_capable || !recorded || recorded.length === 0) {
|
|
3220
|
+
throw new CaptureError(`${fp}: not a command capture with recorded exchanges; cannot replay`);
|
|
3221
|
+
}
|
|
3222
|
+
const command = [...options2.command];
|
|
3223
|
+
if (command.length !== recorded.length || command.some((a, i) => a !== recorded[i])) {
|
|
3224
|
+
throw new CaptureError(
|
|
3225
|
+
`${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`
|
|
3226
|
+
);
|
|
3227
|
+
}
|
|
3228
|
+
if (command.some((a) => a.includes(REDACTED_MARKER) || PLACEHOLDER.test(a))) {
|
|
3229
|
+
throw new CaptureError(
|
|
3230
|
+
`${fp}: refusing to run: the recorded command has redacted or templated arguments; re-run \`attest capture\` with the real command`
|
|
3231
|
+
);
|
|
3232
|
+
}
|
|
3233
|
+
if (doc.world !== null && options2.world === void 0) {
|
|
3234
|
+
throw new CaptureError(`${fp}: captured with a world; pass the world file the capture ran with`);
|
|
3235
|
+
}
|
|
3236
|
+
const env = { ...process.env, OPENAI_API_KEY: "replay-needs-no-key", ATTEST_EGRESS: "block" };
|
|
3237
|
+
for (const key of OTHER_PROVIDER_KEYS) delete env[key];
|
|
3238
|
+
const engine = await resolveEngine(options2.enginePath);
|
|
3239
|
+
const tmp = fs2.mkdtempSync(path2.join(os2.tmpdir(), "attest-replay-"));
|
|
3240
|
+
try {
|
|
3241
|
+
const traceFile = path2.join(tmp, "trace.json");
|
|
3242
|
+
const argv = ["replay", "--fixture", fp, "--mode", "strict", "--no-live", "--trace-out", traceFile, "--json"];
|
|
3243
|
+
if (options2.world !== void 0) argv.push("--world", path2.resolve(toPath(options2.world)));
|
|
3244
|
+
argv.push("--", ...command);
|
|
3245
|
+
const timeoutMs = options2.timeoutMs ?? REPLAY_TIMEOUT_MS;
|
|
3246
|
+
const run = await runEngine(engine, argv, {
|
|
3247
|
+
cwd: options2.cwd === void 0 ? void 0 : toPath(options2.cwd),
|
|
3248
|
+
env,
|
|
3249
|
+
timeoutMs
|
|
3250
|
+
});
|
|
3251
|
+
if (run.error !== null) throw new CaptureError(`${fp}: ${run.error}`);
|
|
3252
|
+
if (run.timedOut) throw new CaptureError(`${fp}: replay run exceeded ${timeoutMs}ms`);
|
|
3253
|
+
checkReplay(fp, doc, run);
|
|
3254
|
+
if (!fs2.existsSync(traceFile)) {
|
|
3255
|
+
throw new CaptureError(`${fp}: the replay run exported no trace
|
|
3256
|
+
${tail(run.stderr)}`);
|
|
3257
|
+
}
|
|
3258
|
+
return JSON.parse(fs2.readFileSync(traceFile, "utf8"));
|
|
3259
|
+
} finally {
|
|
3260
|
+
fs2.rmSync(tmp, { recursive: true, force: true });
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
function tail(stderr) {
|
|
3264
|
+
return stderr.slice(-STDERR_TAIL);
|
|
3265
|
+
}
|
|
3266
|
+
function checkReplay(fp, fixture, run) {
|
|
3267
|
+
let misses;
|
|
3268
|
+
let exitCode;
|
|
3269
|
+
try {
|
|
3270
|
+
const result = JSON.parse(run.stdout);
|
|
3271
|
+
misses = Number(result.summary.misses);
|
|
3272
|
+
exitCode = Number(result.exit_code);
|
|
3273
|
+
if (!Number.isInteger(misses) || !Number.isInteger(exitCode)) throw new TypeError("not integers");
|
|
3274
|
+
} catch {
|
|
3275
|
+
throw new CaptureError(`${fp}: replay run exited ${run.code ?? run.signal}
|
|
3276
|
+
${tail(run.stderr)}`);
|
|
3277
|
+
}
|
|
3278
|
+
if (misses > 0) throw new CaptureError(`${fp}: ${misses} request(s) matched no recording
|
|
3279
|
+
${tail(run.stderr)}`);
|
|
3280
|
+
const recorded = fixture.source.exit_code ?? 0;
|
|
3281
|
+
if (exitCode !== recorded) {
|
|
3282
|
+
throw new CaptureError(`${fp}: the command exited ${exitCode}; the recording exited ${recorded}
|
|
3283
|
+
${tail(run.stderr)}`);
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
function runEngine(engine, argv, opts) {
|
|
3287
|
+
return new Promise((resolve5) => {
|
|
3288
|
+
const child = spawn2(engine, argv, { cwd: opts.cwd, env: opts.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
3289
|
+
let stdout = "";
|
|
3290
|
+
let stderr = "";
|
|
3291
|
+
let timedOut = false;
|
|
3292
|
+
child.stdout.setEncoding("utf8").on("data", (chunk) => {
|
|
3293
|
+
stdout += chunk;
|
|
3294
|
+
});
|
|
3295
|
+
child.stderr.setEncoding("utf8").on("data", (chunk) => {
|
|
3296
|
+
stderr += chunk;
|
|
3297
|
+
});
|
|
3298
|
+
const timer = setTimeout(() => {
|
|
3299
|
+
timedOut = true;
|
|
3300
|
+
child.kill("SIGKILL");
|
|
3301
|
+
}, opts.timeoutMs);
|
|
3302
|
+
child.on("error", (err) => {
|
|
3303
|
+
clearTimeout(timer);
|
|
3304
|
+
resolve5({ code: null, signal: null, stdout, stderr, timedOut, error: `could not start the replay run: ${err.message}` });
|
|
3305
|
+
});
|
|
3306
|
+
child.on("close", (code, signal) => {
|
|
3307
|
+
clearTimeout(timer);
|
|
3308
|
+
resolve5({ code, signal, stdout, stderr, timedOut, error: null });
|
|
3309
|
+
});
|
|
3310
|
+
});
|
|
3311
|
+
}
|
|
2968
3312
|
function serve(engine, args) {
|
|
2969
3313
|
const child = spawn2(engine, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
2970
3314
|
let stdout = "";
|
|
@@ -3557,8 +3901,8 @@ function tier(level) {
|
|
|
3557
3901
|
}
|
|
3558
3902
|
|
|
3559
3903
|
// src/plugins.ts
|
|
3560
|
-
import { existsSync as
|
|
3561
|
-
import { dirname as
|
|
3904
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, realpathSync } from "fs";
|
|
3905
|
+
import { dirname as dirname3, join as join4, relative as relative2, resolve as resolve4, isAbsolute, sep as sep2 } from "path";
|
|
3562
3906
|
import { pathToFileURL } from "url";
|
|
3563
3907
|
var PluginRegistry = class {
|
|
3564
3908
|
plugins = /* @__PURE__ */ new Map();
|
|
@@ -3613,6 +3957,21 @@ async function executePluginAssertion(plugin, trace, spec, client, traceId2, ass
|
|
|
3613
3957
|
);
|
|
3614
3958
|
return result;
|
|
3615
3959
|
}
|
|
3960
|
+
async function submitPluginResults(registry, client, trace, assertions) {
|
|
3961
|
+
for (const assertion of assertions) {
|
|
3962
|
+
if (assertion.type !== TYPE_PLUGIN) continue;
|
|
3963
|
+
const pluginId = assertion.spec["plugin_id"];
|
|
3964
|
+
if (typeof pluginId !== "string" || pluginId === "") continue;
|
|
3965
|
+
const plugin = registry.get("assertion", pluginId);
|
|
3966
|
+
if (plugin === void 0) continue;
|
|
3967
|
+
if (plugin.name !== pluginId) {
|
|
3968
|
+
throw new Error(
|
|
3969
|
+
`Plugin registered for "${pluginId}" is named "${plugin.name}"; the engine matches submitted results by plugin name, so the two must be equal`
|
|
3970
|
+
);
|
|
3971
|
+
}
|
|
3972
|
+
await executePluginAssertion(plugin, trace, assertion.spec, client, trace.trace_id, assertion.assertion_id);
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3616
3975
|
function registerPlugin(registry, name, plugin) {
|
|
3617
3976
|
registry.register(name, plugin);
|
|
3618
3977
|
}
|
|
@@ -3622,14 +3981,14 @@ function isAttestPlugin(value) {
|
|
|
3622
3981
|
return typeof v["name"] === "string" && typeof v["pluginType"] === "string" && typeof v["execute"] === "function";
|
|
3623
3982
|
}
|
|
3624
3983
|
function readJson(path5) {
|
|
3625
|
-
return JSON.parse(
|
|
3984
|
+
return JSON.parse(readFileSync3(path5, "utf8"));
|
|
3626
3985
|
}
|
|
3627
3986
|
function findInstalledPackage(projectRoot, name) {
|
|
3628
3987
|
let dir = resolve4(projectRoot);
|
|
3629
3988
|
for (; ; ) {
|
|
3630
3989
|
const candidate = join4(dir, "node_modules", name);
|
|
3631
|
-
if (
|
|
3632
|
-
const parent =
|
|
3990
|
+
if (existsSync4(join4(candidate, "package.json"))) return candidate;
|
|
3991
|
+
const parent = dirname3(dir);
|
|
3633
3992
|
if (parent === dir) return void 0;
|
|
3634
3993
|
dir = parent;
|
|
3635
3994
|
}
|
|
@@ -3711,6 +4070,11 @@ async function loadPackagePlugins(registry, projectRoot = process.cwd()) {
|
|
|
3711
4070
|
// src/continuous.ts
|
|
3712
4071
|
import { EventEmitter } from "events";
|
|
3713
4072
|
var DEFAULT_QUEUE_SIZE = 1e3;
|
|
4073
|
+
var DEFAULT_DISPATCH_TIMEOUT_MS = 1e4;
|
|
4074
|
+
var DEFAULT_DRAIN_TIMEOUT_MS = 15e3;
|
|
4075
|
+
var DEFAULT_STATIC_THRESHOLD = 0.8;
|
|
4076
|
+
var STATUS_HARD_FAIL2 = "hard_fail";
|
|
4077
|
+
var STATUS_DRIFT_DETECTED = "drift_detected";
|
|
3714
4078
|
function resolveQueueSize() {
|
|
3715
4079
|
const raw = process.env["ATTEST_CONTINUOUS_QUEUE_SIZE"];
|
|
3716
4080
|
if (raw !== void 0 && raw !== "") {
|
|
@@ -3731,13 +4095,18 @@ var Sampler = class {
|
|
|
3731
4095
|
return Math.random() < this.rate;
|
|
3732
4096
|
}
|
|
3733
4097
|
};
|
|
4098
|
+
function slackText(alert) {
|
|
4099
|
+
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}`;
|
|
4100
|
+
}
|
|
3734
4101
|
var AlertDispatcher = class extends EventEmitter {
|
|
3735
4102
|
webhookUrl;
|
|
3736
4103
|
slackUrl;
|
|
3737
|
-
|
|
4104
|
+
timeoutMs;
|
|
4105
|
+
constructor(webhookUrl, slackUrl, timeoutMs = DEFAULT_DISPATCH_TIMEOUT_MS) {
|
|
3738
4106
|
super();
|
|
3739
4107
|
this.webhookUrl = webhookUrl;
|
|
3740
4108
|
this.slackUrl = slackUrl;
|
|
4109
|
+
this.timeoutMs = timeoutMs;
|
|
3741
4110
|
}
|
|
3742
4111
|
async dispatch(alert) {
|
|
3743
4112
|
this.emit("alert", alert);
|
|
@@ -3746,8 +4115,7 @@ var AlertDispatcher = class extends EventEmitter {
|
|
|
3746
4115
|
tasks.push(this.postJson(this.webhookUrl, alert));
|
|
3747
4116
|
}
|
|
3748
4117
|
if (this.slackUrl) {
|
|
3749
|
-
|
|
3750
|
-
tasks.push(this.postJson(this.slackUrl, { text }));
|
|
4118
|
+
tasks.push(this.postJson(this.slackUrl, { text: slackText(alert) }));
|
|
3751
4119
|
}
|
|
3752
4120
|
const results = await Promise.allSettled(tasks);
|
|
3753
4121
|
for (const result of results) {
|
|
@@ -3761,7 +4129,7 @@ var AlertDispatcher = class extends EventEmitter {
|
|
|
3761
4129
|
method: "POST",
|
|
3762
4130
|
headers: { "Content-Type": "application/json" },
|
|
3763
4131
|
body: JSON.stringify(payload),
|
|
3764
|
-
signal: AbortSignal.timeout(
|
|
4132
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
3765
4133
|
});
|
|
3766
4134
|
if (!response.ok) {
|
|
3767
4135
|
throw new Error(`POST to ${url} returned ${response.status}`);
|
|
@@ -3775,21 +4143,124 @@ var ContinuousEvalRunner = class {
|
|
|
3775
4143
|
dispatcher;
|
|
3776
4144
|
queue = [];
|
|
3777
4145
|
maxSize;
|
|
4146
|
+
name;
|
|
4147
|
+
drainTimeoutMs;
|
|
4148
|
+
onError;
|
|
4149
|
+
inFlight = /* @__PURE__ */ new Set();
|
|
4150
|
+
chain = Promise.resolve();
|
|
3778
4151
|
running = false;
|
|
3779
4152
|
loopTimer = null;
|
|
3780
4153
|
constructor(client, assertions, options2) {
|
|
3781
4154
|
this.client = client;
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
4155
|
+
if (typeof options2?.name !== "string" || options2.name === "") {
|
|
4156
|
+
throw new Error("ContinuousEvalRunner requires options.name: it is the scope of the assertion ids");
|
|
4157
|
+
}
|
|
4158
|
+
const ids = new AssertionIdAssigner(options2.name);
|
|
4159
|
+
this.assertions = assertions.map((a) => ({
|
|
4160
|
+
...a,
|
|
4161
|
+
assertion_id: ids.next(a.type, a.spec),
|
|
4162
|
+
scope_id: options2.name
|
|
4163
|
+
}));
|
|
4164
|
+
this.sampler = new Sampler(options2.sampleRate ?? 1);
|
|
4165
|
+
this.name = options2.name;
|
|
4166
|
+
this.onError = options2.onError;
|
|
4167
|
+
this.drainTimeoutMs = options2.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;
|
|
4168
|
+
this.dispatcher = new AlertDispatcher(
|
|
4169
|
+
options2.alertWebhook,
|
|
4170
|
+
options2.alertSlackUrl,
|
|
4171
|
+
options2.dispatchTimeoutMs ?? DEFAULT_DISPATCH_TIMEOUT_MS
|
|
4172
|
+
);
|
|
4173
|
+
this.dispatcher.on("alertError", (error) => this.reportError(error));
|
|
4174
|
+
this.maxSize = options2.maxSize ?? resolveQueueSize();
|
|
3786
4175
|
}
|
|
3787
4176
|
get alertEmitter() {
|
|
3788
4177
|
return this.dispatcher;
|
|
3789
4178
|
}
|
|
4179
|
+
/** Runs `fn` after every previously queued evaluation, so evaluate → query_drift pairs never interleave. */
|
|
4180
|
+
serialize(fn) {
|
|
4181
|
+
const run = this.chain.then(fn, fn);
|
|
4182
|
+
this.chain = run.catch(() => void 0);
|
|
4183
|
+
return run;
|
|
4184
|
+
}
|
|
4185
|
+
/** Surfaces an error to the log and `onError` without ever throwing or leaving a rejected promise. */
|
|
4186
|
+
reportError(error) {
|
|
4187
|
+
try {
|
|
4188
|
+
console.error(`attest continuous-eval runner ${JSON.stringify(this.name)}:`, error);
|
|
4189
|
+
} catch {
|
|
4190
|
+
}
|
|
4191
|
+
if (this.onError === void 0) return;
|
|
4192
|
+
try {
|
|
4193
|
+
const returned = this.onError(error);
|
|
4194
|
+
if (returned instanceof Promise) {
|
|
4195
|
+
returned.catch((observerError) => this.logObserverFailure(observerError));
|
|
4196
|
+
}
|
|
4197
|
+
} catch (observerError) {
|
|
4198
|
+
this.logObserverFailure(observerError);
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
logObserverFailure(observerError) {
|
|
4202
|
+
try {
|
|
4203
|
+
console.error(`attest continuous-eval runner ${JSON.stringify(this.name)}: onError callback failed:`, observerError);
|
|
4204
|
+
} catch {
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
emitError(error) {
|
|
4208
|
+
try {
|
|
4209
|
+
this.dispatcher.emit("alertError", error);
|
|
4210
|
+
} catch (listenerError) {
|
|
4211
|
+
this.logObserverFailure(listenerError);
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
3790
4214
|
async evaluateTrace(trace) {
|
|
3791
4215
|
if (!this.sampler.shouldSample()) return null;
|
|
3792
|
-
|
|
4216
|
+
const work = this.serialize(() => this.evaluateAndAlert(trace));
|
|
4217
|
+
this.inFlight.add(work);
|
|
4218
|
+
try {
|
|
4219
|
+
return await work;
|
|
4220
|
+
} finally {
|
|
4221
|
+
this.inFlight.delete(work);
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
4224
|
+
async evaluateAndAlert(trace) {
|
|
4225
|
+
const result = await this.client.evaluateBatch(trace, this.assertions);
|
|
4226
|
+
for (const r of result.results) {
|
|
4227
|
+
await this.alertIfNeeded(trace, r);
|
|
4228
|
+
}
|
|
4229
|
+
return result;
|
|
4230
|
+
}
|
|
4231
|
+
staticThresholdFor(assertionId2) {
|
|
4232
|
+
const spec = this.assertions.find((a) => a.assertion_id === assertionId2)?.spec;
|
|
4233
|
+
const threshold = spec?.["threshold"];
|
|
4234
|
+
return typeof threshold === "number" && threshold > 0 ? threshold : DEFAULT_STATIC_THRESHOLD;
|
|
4235
|
+
}
|
|
4236
|
+
async alertIfNeeded(trace, result) {
|
|
4237
|
+
if (result.status === "not_evaluated") return;
|
|
4238
|
+
const staticThreshold = this.staticThresholdFor(result.assertion_id);
|
|
4239
|
+
let report;
|
|
4240
|
+
try {
|
|
4241
|
+
report = await this.client.queryDrift(result.assertion_id, void 0, { staticThreshold });
|
|
4242
|
+
} catch (error) {
|
|
4243
|
+
this.emitError(error);
|
|
4244
|
+
}
|
|
4245
|
+
const hardFailed = result.status === STATUS_HARD_FAIL2;
|
|
4246
|
+
const drifted = report?.status === STATUS_DRIFT_DETECTED;
|
|
4247
|
+
if (!drifted && !hardFailed) return;
|
|
4248
|
+
const alert = {
|
|
4249
|
+
runner: this.name,
|
|
4250
|
+
assertion_id: result.assertion_id,
|
|
4251
|
+
status: hardFailed ? STATUS_HARD_FAIL2 : STATUS_DRIFT_DETECTED,
|
|
4252
|
+
result_status: result.status,
|
|
4253
|
+
score: typeof result.score === "number" ? result.score : null,
|
|
4254
|
+
baseline_mean: report?.mean ?? null,
|
|
4255
|
+
baseline_count: report?.count ?? null,
|
|
4256
|
+
static_threshold: staticThreshold,
|
|
4257
|
+
trace_id: trace.trace_id ?? null
|
|
4258
|
+
};
|
|
4259
|
+
try {
|
|
4260
|
+
await this.dispatcher.dispatch(alert);
|
|
4261
|
+
} catch (error) {
|
|
4262
|
+
this.emitError(error);
|
|
4263
|
+
}
|
|
3793
4264
|
}
|
|
3794
4265
|
submit(trace) {
|
|
3795
4266
|
if (this.queue.length >= this.maxSize) {
|
|
@@ -3803,12 +4274,52 @@ var ContinuousEvalRunner = class {
|
|
|
3803
4274
|
this.running = true;
|
|
3804
4275
|
this.scheduleNext();
|
|
3805
4276
|
}
|
|
3806
|
-
|
|
4277
|
+
/**
|
|
4278
|
+
* Stops the loop, then processes the traces already queued and waits for in-flight evaluations
|
|
4279
|
+
* and dispatches, all bounded by `drainTimeoutMs`. At the bound the whole remaining queue is
|
|
4280
|
+
* discarded (nothing is evaluated after the timeout and nothing is left for a restart), the
|
|
4281
|
+
* evaluation already in flight is left to finish, and the timeout with the number of discarded
|
|
4282
|
+
* traces is reported through `onError`.
|
|
4283
|
+
*/
|
|
4284
|
+
async stop() {
|
|
3807
4285
|
this.running = false;
|
|
3808
4286
|
if (this.loopTimer !== null) {
|
|
3809
4287
|
clearTimeout(this.loopTimer);
|
|
3810
4288
|
this.loopTimer = null;
|
|
3811
4289
|
}
|
|
4290
|
+
if (this.inFlight.size === 0 && this.queue.length === 0) return;
|
|
4291
|
+
let expired = false;
|
|
4292
|
+
let discarded = 0;
|
|
4293
|
+
let timer;
|
|
4294
|
+
const bound = new Promise((resolve5) => {
|
|
4295
|
+
timer = setTimeout(() => {
|
|
4296
|
+
expired = true;
|
|
4297
|
+
discarded = this.queue.splice(0).length;
|
|
4298
|
+
resolve5("timeout");
|
|
4299
|
+
}, this.drainTimeoutMs);
|
|
4300
|
+
});
|
|
4301
|
+
const drained = this.drainQueue(() => expired).then(() => "drained");
|
|
4302
|
+
const outcome = await Promise.race([drained, bound]);
|
|
4303
|
+
clearTimeout(timer);
|
|
4304
|
+
if (outcome === "timeout") {
|
|
4305
|
+
this.emitError(
|
|
4306
|
+
new Error(
|
|
4307
|
+
`stop() gave up after ${this.drainTimeoutMs} ms with ${this.inFlight.size} evaluation(s) in flight; discarded ${discarded} queued trace(s) not evaluated`
|
|
4308
|
+
)
|
|
4309
|
+
);
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
async drainQueue(expired) {
|
|
4313
|
+
while (!expired()) {
|
|
4314
|
+
const trace = this.queue.shift();
|
|
4315
|
+
if (trace === void 0) break;
|
|
4316
|
+
try {
|
|
4317
|
+
await this.evaluateTrace(trace);
|
|
4318
|
+
} catch (error) {
|
|
4319
|
+
this.reportError(error);
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
await Promise.allSettled([...this.inFlight]);
|
|
3812
4323
|
}
|
|
3813
4324
|
get queueLength() {
|
|
3814
4325
|
return this.queue.length;
|
|
@@ -3818,22 +4329,29 @@ var ContinuousEvalRunner = class {
|
|
|
3818
4329
|
}
|
|
3819
4330
|
scheduleNext() {
|
|
3820
4331
|
if (!this.running) return;
|
|
3821
|
-
this.loopTimer = setTimeout(
|
|
4332
|
+
this.loopTimer = setTimeout(() => {
|
|
4333
|
+
void this.tick();
|
|
4334
|
+
}, this.queue.length > 0 ? 0 : 100);
|
|
4335
|
+
}
|
|
4336
|
+
async tick() {
|
|
4337
|
+
try {
|
|
3822
4338
|
if (!this.running) return;
|
|
3823
4339
|
const trace = this.queue.shift();
|
|
3824
4340
|
if (trace !== void 0) {
|
|
3825
4341
|
try {
|
|
3826
4342
|
await this.evaluateTrace(trace);
|
|
3827
|
-
} catch {
|
|
4343
|
+
} catch (error) {
|
|
4344
|
+
this.reportError(error);
|
|
3828
4345
|
}
|
|
3829
4346
|
}
|
|
4347
|
+
} finally {
|
|
3830
4348
|
this.scheduleNext();
|
|
3831
|
-
}
|
|
4349
|
+
}
|
|
3832
4350
|
}
|
|
3833
4351
|
};
|
|
3834
4352
|
|
|
3835
4353
|
// src/calibration.ts
|
|
3836
|
-
import { createHash as
|
|
4354
|
+
import { createHash as createHash3 } from "crypto";
|
|
3837
4355
|
function computeAgreement(pairs, threshold) {
|
|
3838
4356
|
if (pairs.length === 0) {
|
|
3839
4357
|
throw new Error("no labeled pairs available");
|
|
@@ -3978,7 +4496,7 @@ function loadLabelsJSONL(text) {
|
|
|
3978
4496
|
return out;
|
|
3979
4497
|
}
|
|
3980
4498
|
function promptHash(text) {
|
|
3981
|
-
return
|
|
4499
|
+
return createHash3("sha256").update(text).digest("hex").slice(0, 16);
|
|
3982
4500
|
}
|
|
3983
4501
|
function parseFloatStrict(raw) {
|
|
3984
4502
|
const trimmed = raw.trim();
|
|
@@ -4268,44 +4786,116 @@ var ManualAdapter = class {
|
|
|
4268
4786
|
}
|
|
4269
4787
|
};
|
|
4270
4788
|
|
|
4789
|
+
// src/adapters/provider-trace.ts
|
|
4790
|
+
function latency(options2) {
|
|
4791
|
+
if (options2?.latencyMs !== void 0) return options2.latencyMs;
|
|
4792
|
+
if (options2?.startedAtMs !== void 0 && options2.endedAtMs !== void 0) {
|
|
4793
|
+
return options2.endedAtMs - options2.startedAtMs;
|
|
4794
|
+
}
|
|
4795
|
+
return void 0;
|
|
4796
|
+
}
|
|
4797
|
+
function buildProviderTrace(agentId2, call2, options2, input, output) {
|
|
4798
|
+
const builder = new TraceBuilder(agentId2);
|
|
4799
|
+
if (input !== void 0) builder.setInput(input);
|
|
4800
|
+
const now = Date.now();
|
|
4801
|
+
const args = {};
|
|
4802
|
+
if (call2.model !== void 0) args.model = call2.model;
|
|
4803
|
+
const result = { completion: call2.completion };
|
|
4804
|
+
if (call2.refusal) result.refusal = call2.refusal;
|
|
4805
|
+
if (call2.totalTokens !== void 0) result.tokens = call2.totalTokens;
|
|
4806
|
+
if (call2.inputTokens !== void 0) result.input_tokens = call2.inputTokens;
|
|
4807
|
+
if (call2.outputTokens !== void 0) result.output_tokens = call2.outputTokens;
|
|
4808
|
+
builder.addStep({
|
|
4809
|
+
type: STEP_LLM_CALL,
|
|
4810
|
+
name: "completion",
|
|
4811
|
+
args,
|
|
4812
|
+
result,
|
|
4813
|
+
started_at_ms: options2?.startedAtMs ?? now,
|
|
4814
|
+
ended_at_ms: options2?.endedAtMs ?? now
|
|
4815
|
+
});
|
|
4816
|
+
for (const tc of call2.toolCalls) {
|
|
4817
|
+
builder.addToolCall(tc.name, { args: tc.args });
|
|
4818
|
+
}
|
|
4819
|
+
builder.setOutput(output);
|
|
4820
|
+
builder.setMetadata({
|
|
4821
|
+
total_tokens: call2.totalTokens,
|
|
4822
|
+
cost_usd: options2?.costUsd,
|
|
4823
|
+
latency_ms: latency(options2),
|
|
4824
|
+
model: call2.model
|
|
4825
|
+
});
|
|
4826
|
+
return builder.build();
|
|
4827
|
+
}
|
|
4828
|
+
|
|
4271
4829
|
// src/adapters/openai.ts
|
|
4830
|
+
function parseArguments(raw) {
|
|
4831
|
+
let parsed;
|
|
4832
|
+
try {
|
|
4833
|
+
parsed = JSON.parse(raw);
|
|
4834
|
+
} catch {
|
|
4835
|
+
return { raw_arguments: raw };
|
|
4836
|
+
}
|
|
4837
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
4838
|
+
return parsed;
|
|
4839
|
+
}
|
|
4840
|
+
return { raw_arguments: raw };
|
|
4841
|
+
}
|
|
4842
|
+
function isResponses(response) {
|
|
4843
|
+
return response.object === "response";
|
|
4844
|
+
}
|
|
4845
|
+
function fromResponses(response) {
|
|
4846
|
+
const texts = [];
|
|
4847
|
+
const refusals = [];
|
|
4848
|
+
const toolCalls = [];
|
|
4849
|
+
for (const item of response.output) {
|
|
4850
|
+
if (item.type === "message") {
|
|
4851
|
+
for (const part of item.content) {
|
|
4852
|
+
if (part.type === "output_text") texts.push(part.text);
|
|
4853
|
+
else if (part.type === "refusal") refusals.push(part.refusal);
|
|
4854
|
+
}
|
|
4855
|
+
} else if (item.type === "function_call") {
|
|
4856
|
+
const call2 = item;
|
|
4857
|
+
toolCalls.push({ name: call2.name, args: parseArguments(call2.arguments) });
|
|
4858
|
+
}
|
|
4859
|
+
}
|
|
4860
|
+
const refusal = refusals.join("") || void 0;
|
|
4861
|
+
return {
|
|
4862
|
+
model: response.model,
|
|
4863
|
+
completion: texts.join("") || refusal || "",
|
|
4864
|
+
refusal,
|
|
4865
|
+
totalTokens: response.usage?.total_tokens,
|
|
4866
|
+
inputTokens: response.usage?.input_tokens,
|
|
4867
|
+
outputTokens: response.usage?.output_tokens,
|
|
4868
|
+
toolCalls
|
|
4869
|
+
};
|
|
4870
|
+
}
|
|
4871
|
+
function fromChat(response) {
|
|
4872
|
+
const message = response.choices[0].message;
|
|
4873
|
+
const refusal = message.refusal || void 0;
|
|
4874
|
+
return {
|
|
4875
|
+
model: response.model,
|
|
4876
|
+
completion: message.content || refusal || "",
|
|
4877
|
+
refusal,
|
|
4878
|
+
totalTokens: response.usage?.total_tokens,
|
|
4879
|
+
inputTokens: response.usage?.prompt_tokens,
|
|
4880
|
+
outputTokens: response.usage?.completion_tokens,
|
|
4881
|
+
toolCalls: (message.tool_calls ?? []).map((tc) => ({
|
|
4882
|
+
name: tc.function.name,
|
|
4883
|
+
args: parseArguments(tc.function.arguments)
|
|
4884
|
+
}))
|
|
4885
|
+
};
|
|
4886
|
+
}
|
|
4272
4887
|
var OpenAIAdapter = class {
|
|
4273
4888
|
agentId;
|
|
4274
4889
|
constructor(agentId2) {
|
|
4275
4890
|
this.agentId = agentId2;
|
|
4276
4891
|
}
|
|
4277
4892
|
traceFromResponse(response, options2) {
|
|
4278
|
-
const
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
const message = response.choices[0].message;
|
|
4283
|
-
const completionText = message.content ?? "";
|
|
4284
|
-
const stepArgs = {};
|
|
4285
|
-
if (response.model !== void 0) stepArgs.model = response.model;
|
|
4286
|
-
const stepResult = { completion: completionText };
|
|
4287
|
-
if (response.usage?.total_tokens !== void 0) {
|
|
4288
|
-
stepResult.tokens = response.usage.total_tokens;
|
|
4289
|
-
}
|
|
4290
|
-
builder.addLlmCall("completion", { args: stepArgs, result: stepResult });
|
|
4291
|
-
if (message.tool_calls) {
|
|
4292
|
-
for (const tc of message.tool_calls) {
|
|
4293
|
-
builder.addToolCall(tc.function.name, {
|
|
4294
|
-
args: { arguments: tc.function.arguments }
|
|
4295
|
-
});
|
|
4296
|
-
}
|
|
4297
|
-
}
|
|
4298
|
-
builder.setOutput({
|
|
4299
|
-
message: completionText,
|
|
4893
|
+
const call2 = isResponses(response) ? fromResponses(response) : fromChat(response);
|
|
4894
|
+
const input = options2?.inputMessages ? { messages: options2.inputMessages } : void 0;
|
|
4895
|
+
return buildProviderTrace(this.agentId, call2, options2, input, {
|
|
4896
|
+
message: call2.completion,
|
|
4300
4897
|
structured: options2?.structuredOutput ?? {}
|
|
4301
4898
|
});
|
|
4302
|
-
builder.setMetadata({
|
|
4303
|
-
total_tokens: response.usage?.total_tokens,
|
|
4304
|
-
cost_usd: options2?.costUsd,
|
|
4305
|
-
latency_ms: options2?.latencyMs,
|
|
4306
|
-
model: response.model
|
|
4307
|
-
});
|
|
4308
|
-
return builder.build();
|
|
4309
4899
|
}
|
|
4310
4900
|
};
|
|
4311
4901
|
|
|
@@ -4316,73 +4906,79 @@ var AnthropicAdapter = class {
|
|
|
4316
4906
|
this.agentId = agentId2;
|
|
4317
4907
|
}
|
|
4318
4908
|
traceFromResponse(response, options2) {
|
|
4319
|
-
const
|
|
4320
|
-
|
|
4321
|
-
builder.setInput({ messages: options2.inputMessages });
|
|
4322
|
-
}
|
|
4323
|
-
const completionParts = [];
|
|
4909
|
+
const texts = [];
|
|
4910
|
+
const toolCalls = [];
|
|
4324
4911
|
for (const block of response.content) {
|
|
4325
4912
|
if (block.type === "text") {
|
|
4326
|
-
|
|
4327
|
-
} else if (block.type === "tool_use") {
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
});
|
|
4913
|
+
texts.push(block.text);
|
|
4914
|
+
} else if (block.type === "tool_use" || block.type === "server_tool_use") {
|
|
4915
|
+
const use = block;
|
|
4916
|
+
toolCalls.push({ name: use.name, args: use.input });
|
|
4331
4917
|
}
|
|
4332
4918
|
}
|
|
4333
|
-
const
|
|
4334
|
-
const
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4919
|
+
const completion = texts.join("\n");
|
|
4920
|
+
const usage = response.usage;
|
|
4921
|
+
const input = options2?.inputMessages ? { messages: options2.inputMessages } : void 0;
|
|
4922
|
+
return buildProviderTrace(
|
|
4923
|
+
this.agentId,
|
|
4924
|
+
{
|
|
4925
|
+
model: response.model,
|
|
4926
|
+
completion,
|
|
4927
|
+
totalTokens: usage ? usage.input_tokens + usage.output_tokens : void 0,
|
|
4928
|
+
inputTokens: usage?.input_tokens,
|
|
4929
|
+
outputTokens: usage?.output_tokens,
|
|
4930
|
+
toolCalls
|
|
4931
|
+
},
|
|
4932
|
+
options2,
|
|
4933
|
+
input,
|
|
4934
|
+
{ message: completion }
|
|
4935
|
+
);
|
|
4349
4936
|
}
|
|
4350
4937
|
};
|
|
4351
4938
|
|
|
4352
4939
|
// src/adapters/gemini.ts
|
|
4940
|
+
function totalTokens(usage) {
|
|
4941
|
+
if (usage === void 0) return void 0;
|
|
4942
|
+
if (usage.totalTokenCount !== void 0) return usage.totalTokenCount;
|
|
4943
|
+
if (usage.promptTokenCount !== void 0 && usage.candidatesTokenCount !== void 0) {
|
|
4944
|
+
return usage.promptTokenCount + usage.candidatesTokenCount;
|
|
4945
|
+
}
|
|
4946
|
+
return usage.promptTokenCount;
|
|
4947
|
+
}
|
|
4948
|
+
function geminiInput(options2) {
|
|
4949
|
+
const messages = options2?.inputMessages;
|
|
4950
|
+
if (messages && messages.length > 0) {
|
|
4951
|
+
const first = messages[0];
|
|
4952
|
+
if (messages.length === 1 && typeof first.content === "string") return { text: first.content };
|
|
4953
|
+
return { messages };
|
|
4954
|
+
}
|
|
4955
|
+
if (options2?.inputText) return { text: options2.inputText };
|
|
4956
|
+
return void 0;
|
|
4957
|
+
}
|
|
4353
4958
|
var GeminiAdapter = class {
|
|
4354
4959
|
agentId;
|
|
4355
4960
|
constructor(agentId2) {
|
|
4356
4961
|
this.agentId = agentId2;
|
|
4357
4962
|
}
|
|
4358
4963
|
traceFromResponse(response, options2) {
|
|
4359
|
-
const
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
}
|
|
4378
|
-
builder.addLlmCall("completion", { result: { completion: completionText } });
|
|
4379
|
-
builder.setOutput({ message: completionText });
|
|
4380
|
-
builder.setMetadata({
|
|
4381
|
-
cost_usd: options2?.costUsd,
|
|
4382
|
-
latency_ms: options2?.latencyMs,
|
|
4383
|
-
model: options2?.model
|
|
4384
|
-
});
|
|
4385
|
-
return builder.build();
|
|
4964
|
+
const parts = response.candidates?.[0]?.content.parts ?? [];
|
|
4965
|
+
const completion = response.text ?? parts.filter((p) => p.text && !p.thought).map((p) => p.text).join("");
|
|
4966
|
+
const toolCalls = parts.filter((p) => p.functionCall).map((p) => ({ name: p.functionCall.name, args: p.functionCall.args ?? {} }));
|
|
4967
|
+
const usage = response.usageMetadata;
|
|
4968
|
+
return buildProviderTrace(
|
|
4969
|
+
this.agentId,
|
|
4970
|
+
{
|
|
4971
|
+
model: options2?.model ?? response.modelVersion,
|
|
4972
|
+
completion,
|
|
4973
|
+
totalTokens: totalTokens(usage),
|
|
4974
|
+
inputTokens: usage?.promptTokenCount,
|
|
4975
|
+
outputTokens: usage?.candidatesTokenCount,
|
|
4976
|
+
toolCalls
|
|
4977
|
+
},
|
|
4978
|
+
options2,
|
|
4979
|
+
geminiInput(options2),
|
|
4980
|
+
{ message: completion }
|
|
4981
|
+
);
|
|
4386
4982
|
}
|
|
4387
4983
|
};
|
|
4388
4984
|
|
|
@@ -4393,26 +4989,26 @@ var OllamaAdapter = class {
|
|
|
4393
4989
|
this.agentId = agentId2;
|
|
4394
4990
|
}
|
|
4395
4991
|
traceFromResponse(response, options2) {
|
|
4396
|
-
const
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4992
|
+
const completion = response.message?.content ?? "";
|
|
4993
|
+
const counted = response.eval_count !== void 0 && response.prompt_eval_count !== void 0;
|
|
4994
|
+
const input = options2?.inputMessages ? { messages: options2.inputMessages } : void 0;
|
|
4995
|
+
return buildProviderTrace(
|
|
4996
|
+
this.agentId,
|
|
4997
|
+
{
|
|
4998
|
+
model: response.model,
|
|
4999
|
+
completion,
|
|
5000
|
+
totalTokens: counted ? response.eval_count + response.prompt_eval_count : void 0,
|
|
5001
|
+
inputTokens: counted ? response.prompt_eval_count : void 0,
|
|
5002
|
+
outputTokens: counted ? response.eval_count : void 0,
|
|
5003
|
+
toolCalls: (response.message?.tool_calls ?? []).map((tc) => ({
|
|
5004
|
+
name: tc.function?.name ?? "",
|
|
5005
|
+
args: tc.function?.arguments ?? {}
|
|
5006
|
+
}))
|
|
5007
|
+
},
|
|
5008
|
+
options2,
|
|
5009
|
+
input,
|
|
5010
|
+
{ message: completion }
|
|
5011
|
+
);
|
|
4416
5012
|
}
|
|
4417
5013
|
};
|
|
4418
5014
|
|
|
@@ -4561,97 +5157,239 @@ function spansToOtlp(spans) {
|
|
|
4561
5157
|
}
|
|
4562
5158
|
|
|
4563
5159
|
// src/adapters/langchain.ts
|
|
4564
|
-
|
|
5160
|
+
import { createRequire } from "module";
|
|
5161
|
+
function resolveBaseCallbackHandler() {
|
|
5162
|
+
const load = createRequire(typeof __filename === "string" ? __filename : import.meta.url);
|
|
5163
|
+
try {
|
|
5164
|
+
const mod = load("@langchain/core/callbacks/base");
|
|
5165
|
+
return mod.BaseCallbackHandler;
|
|
5166
|
+
} catch (cause) {
|
|
5167
|
+
if (cause.code !== "MODULE_NOT_FOUND") throw cause;
|
|
5168
|
+
return class MissingLangChain {
|
|
5169
|
+
constructor() {
|
|
5170
|
+
throw new Error(
|
|
5171
|
+
"LangChainAdapter requires @langchain/core: install it with `npm install @langchain/core`."
|
|
5172
|
+
);
|
|
5173
|
+
}
|
|
5174
|
+
};
|
|
5175
|
+
}
|
|
5176
|
+
}
|
|
5177
|
+
var BaseCallbackHandler = resolveBaseCallbackHandler();
|
|
5178
|
+
function isValues(value) {
|
|
5179
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5180
|
+
}
|
|
5181
|
+
function hasContent(value) {
|
|
5182
|
+
return isValues(value) && "content" in value;
|
|
5183
|
+
}
|
|
5184
|
+
function messageText(content) {
|
|
5185
|
+
if (typeof content === "string") return content;
|
|
5186
|
+
if (Array.isArray(content)) {
|
|
5187
|
+
return content.map((block) => {
|
|
5188
|
+
if (typeof block === "string") return block;
|
|
5189
|
+
if (isValues(block) && block.type === "text") return String(block.text ?? "");
|
|
5190
|
+
return "";
|
|
5191
|
+
}).join("");
|
|
5192
|
+
}
|
|
5193
|
+
return String(content);
|
|
5194
|
+
}
|
|
5195
|
+
function firstTruthy(values, keys) {
|
|
5196
|
+
for (const key of keys) if (values[key]) return values[key];
|
|
5197
|
+
return void 0;
|
|
5198
|
+
}
|
|
5199
|
+
function textOf(value) {
|
|
5200
|
+
if (typeof value === "string") return value;
|
|
5201
|
+
if (hasContent(value)) return messageText(value.content);
|
|
5202
|
+
return value ? String(value) : "";
|
|
5203
|
+
}
|
|
5204
|
+
function modelNameOf(extraParams) {
|
|
5205
|
+
if (extraParams === void 0) return void 0;
|
|
5206
|
+
const params = isValues(extraParams.invocation_params) ? extraParams.invocation_params : extraParams;
|
|
5207
|
+
const name = params.model_name ?? params.model ?? params.modelName;
|
|
5208
|
+
return typeof name === "string" && name ? name : void 0;
|
|
5209
|
+
}
|
|
5210
|
+
function toolNameOf(serialized, runName) {
|
|
5211
|
+
if (runName) return runName;
|
|
5212
|
+
if (isValues(serialized)) {
|
|
5213
|
+
if (typeof serialized.name === "string" && serialized.name) return serialized.name;
|
|
5214
|
+
if (Array.isArray(serialized.id) && serialized.id.length > 0) return String(serialized.id.at(-1));
|
|
5215
|
+
}
|
|
5216
|
+
return "unknown_tool";
|
|
5217
|
+
}
|
|
5218
|
+
function toolInputOf(input) {
|
|
5219
|
+
try {
|
|
5220
|
+
const parsed = JSON.parse(input);
|
|
5221
|
+
if (isValues(parsed)) return parsed;
|
|
5222
|
+
} catch {
|
|
5223
|
+
}
|
|
5224
|
+
return { input };
|
|
5225
|
+
}
|
|
5226
|
+
var LangChainAdapter = class extends BaseCallbackHandler {
|
|
5227
|
+
name = "attest_langchain_adapter";
|
|
4565
5228
|
agentId;
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
5229
|
+
input;
|
|
5230
|
+
output;
|
|
5231
|
+
steps = [];
|
|
5232
|
+
llmStarts = /* @__PURE__ */ new Map();
|
|
5233
|
+
toolStarts = /* @__PURE__ */ new Map();
|
|
5234
|
+
totalTokens = 0;
|
|
5235
|
+
model;
|
|
5236
|
+
startedAtMs;
|
|
5237
|
+
endedAtMs;
|
|
4570
5238
|
constructor(agentId2) {
|
|
5239
|
+
super();
|
|
4571
5240
|
this.agentId = agentId2;
|
|
5241
|
+
this.awaitHandlers = true;
|
|
5242
|
+
this.raiseError = false;
|
|
4572
5243
|
}
|
|
4573
|
-
/**
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
reset() {
|
|
4577
|
-
this.llmCalls.length = 0;
|
|
4578
|
-
this.toolCalls.length = 0;
|
|
4579
|
-
this.chainOutput = "";
|
|
4580
|
-
this.startTime = 0;
|
|
5244
|
+
/** Handlers are shared by the callback manager, never cloned: a clone would lose the capture. */
|
|
5245
|
+
copy() {
|
|
5246
|
+
return this;
|
|
4581
5247
|
}
|
|
4582
|
-
/**
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
5248
|
+
/** Reset accumulated state for a new capture session. */
|
|
5249
|
+
reset() {
|
|
5250
|
+
this.input = void 0;
|
|
5251
|
+
this.output = void 0;
|
|
5252
|
+
this.steps = [];
|
|
5253
|
+
this.llmStarts.clear();
|
|
5254
|
+
this.toolStarts.clear();
|
|
5255
|
+
this.totalTokens = 0;
|
|
5256
|
+
this.model = void 0;
|
|
5257
|
+
this.startedAtMs = void 0;
|
|
5258
|
+
this.endedAtMs = void 0;
|
|
5259
|
+
}
|
|
5260
|
+
now() {
|
|
5261
|
+
return Date.now();
|
|
5262
|
+
}
|
|
5263
|
+
handleChainStart(_chain, inputs, _runId, parentRunId) {
|
|
5264
|
+
if (parentRunId !== void 0) return;
|
|
5265
|
+
this.startedAtMs = this.now();
|
|
5266
|
+
if (!isValues(inputs)) return;
|
|
5267
|
+
const message = firstTruthy(inputs, ["input", "messages", "query"]) ?? "";
|
|
5268
|
+
if (typeof message === "string") {
|
|
5269
|
+
this.input = message;
|
|
5270
|
+
} else if (Array.isArray(message) && message.length > 0) {
|
|
5271
|
+
const first = message[0];
|
|
5272
|
+
this.input = hasContent(first) ? messageText(first.content) : String(first);
|
|
5273
|
+
} else {
|
|
5274
|
+
this.input = message ? String(message) : "";
|
|
4588
5275
|
}
|
|
4589
5276
|
}
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
const
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
5277
|
+
handleChainEnd(outputs, _runId, parentRunId) {
|
|
5278
|
+
if (parentRunId !== void 0) return;
|
|
5279
|
+
this.endedAtMs = this.now();
|
|
5280
|
+
if (!isValues(outputs)) return;
|
|
5281
|
+
const messages = outputs.messages;
|
|
5282
|
+
if (Array.isArray(messages) && messages.length > 0) {
|
|
5283
|
+
const last = messages[messages.length - 1];
|
|
5284
|
+
if (hasContent(last)) {
|
|
5285
|
+
this.output = messageText(last.content);
|
|
5286
|
+
return;
|
|
5287
|
+
}
|
|
5288
|
+
}
|
|
5289
|
+
this.output = textOf(firstTruthy(outputs, ["output", "result", "text"]));
|
|
5290
|
+
}
|
|
5291
|
+
handleChatModelStart(_llm, _messages, runId, _parentRunId, extraParams) {
|
|
5292
|
+
this.llmStarts.set(runId, { modelName: modelNameOf(extraParams), startedAtMs: this.now() });
|
|
5293
|
+
}
|
|
5294
|
+
handleLLMStart(_llm, _prompts, runId, _parentRunId, extraParams) {
|
|
5295
|
+
this.llmStarts.set(runId, { modelName: modelNameOf(extraParams), startedAtMs: this.now() });
|
|
5296
|
+
}
|
|
5297
|
+
handleLLMEnd(output, runId) {
|
|
5298
|
+
const start3 = this.llmStarts.get(runId);
|
|
5299
|
+
this.llmStarts.delete(runId);
|
|
5300
|
+
const endedAtMs = this.now();
|
|
5301
|
+
let modelName = start3?.modelName;
|
|
5302
|
+
let inputTokens = 0;
|
|
5303
|
+
let outputTokens = 0;
|
|
5304
|
+
let total = 0;
|
|
5305
|
+
const usage = output.generations.flat().find((g) => g.message?.usage_metadata)?.message?.usage_metadata;
|
|
5306
|
+
if (usage) {
|
|
5307
|
+
inputTokens = usage.input_tokens || 0;
|
|
5308
|
+
outputTokens = usage.output_tokens || 0;
|
|
5309
|
+
total = usage.total_tokens || inputTokens + outputTokens;
|
|
5310
|
+
}
|
|
5311
|
+
const llmOutput = output.llmOutput;
|
|
5312
|
+
if (llmOutput) {
|
|
5313
|
+
if (!usage) {
|
|
5314
|
+
const tokenUsage = llmOutput.tokenUsage ?? llmOutput.token_usage ?? {};
|
|
5315
|
+
inputTokens = Number(tokenUsage.promptTokens ?? tokenUsage.prompt_tokens ?? 0);
|
|
5316
|
+
outputTokens = Number(tokenUsage.completionTokens ?? tokenUsage.completion_tokens ?? 0);
|
|
5317
|
+
total = inputTokens + outputTokens;
|
|
5318
|
+
}
|
|
5319
|
+
const reported = llmOutput.model_name ?? llmOutput.modelName;
|
|
5320
|
+
if (modelName === void 0 && typeof reported === "string") modelName = reported;
|
|
5321
|
+
}
|
|
5322
|
+
this.totalTokens += total;
|
|
5323
|
+
if (modelName && this.model === void 0) this.model = modelName;
|
|
5324
|
+
const args = {};
|
|
5325
|
+
if (modelName) args.model = modelName;
|
|
5326
|
+
const result = {};
|
|
5327
|
+
const completion = output.generations[0]?.[0]?.text;
|
|
5328
|
+
if (completion) result.completion = completion;
|
|
5329
|
+
if (total) result.tokens = total;
|
|
5330
|
+
if (inputTokens) result.input_tokens = inputTokens;
|
|
5331
|
+
if (outputTokens) result.output_tokens = outputTokens;
|
|
5332
|
+
this.steps.push(this.step(STEP_LLM_CALL, modelName || "llm", args, result, start3?.startedAtMs, endedAtMs));
|
|
5333
|
+
}
|
|
5334
|
+
handleToolStart(tool, input, runId, _parentRunId, _tags, _metadata, runName) {
|
|
5335
|
+
this.toolStarts.set(runId, {
|
|
5336
|
+
name: toolNameOf(tool, runName),
|
|
5337
|
+
input: toolInputOf(input),
|
|
5338
|
+
startedAtMs: this.now()
|
|
4602
5339
|
});
|
|
4603
5340
|
}
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
this.
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
5341
|
+
handleToolEnd(output, runId) {
|
|
5342
|
+
const text = hasContent(output) ? messageText(output.content) : String(output);
|
|
5343
|
+
const isToolMessage = isValues(output) && typeof output.tool_call_id === "string";
|
|
5344
|
+
const toolName = isToolMessage && typeof output.name === "string" && output.name ? output.name : void 0;
|
|
5345
|
+
this.finishTool(runId, { output: text }, toolName);
|
|
5346
|
+
}
|
|
5347
|
+
handleToolError(error, runId) {
|
|
5348
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5349
|
+
this.finishTool(runId, { error: message }, void 0);
|
|
5350
|
+
}
|
|
5351
|
+
finishTool(runId, result, endName) {
|
|
5352
|
+
const start3 = this.toolStarts.get(runId);
|
|
5353
|
+
this.toolStarts.delete(runId);
|
|
5354
|
+
this.steps.push(
|
|
5355
|
+
this.step(
|
|
5356
|
+
STEP_TOOL_CALL,
|
|
5357
|
+
endName ?? start3?.name ?? "unknown_tool",
|
|
5358
|
+
start3?.input ?? {},
|
|
5359
|
+
result,
|
|
5360
|
+
start3?.startedAtMs,
|
|
5361
|
+
this.now()
|
|
5362
|
+
)
|
|
5363
|
+
);
|
|
4612
5364
|
}
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
5365
|
+
step(type, name, args, result, startedAtMs, endedAtMs) {
|
|
5366
|
+
return {
|
|
5367
|
+
type,
|
|
5368
|
+
name,
|
|
5369
|
+
args,
|
|
5370
|
+
result,
|
|
5371
|
+
metadata: startedAtMs === void 0 ? {} : { duration_ms: endedAtMs - startedAtMs },
|
|
5372
|
+
started_at_ms: startedAtMs,
|
|
5373
|
+
ended_at_ms: endedAtMs,
|
|
5374
|
+
agent_id: this.agentId
|
|
5375
|
+
};
|
|
4621
5376
|
}
|
|
4622
5377
|
/**
|
|
4623
|
-
* Build a Trace from
|
|
5378
|
+
* Build a Trace from the captured callback events.
|
|
4624
5379
|
*
|
|
4625
|
-
* @param options.costUsd - Total cost
|
|
5380
|
+
* @param options.costUsd - Total cost.
|
|
4626
5381
|
* @param options.model - Model name override.
|
|
4627
|
-
* @returns A finalized Trace object.
|
|
4628
5382
|
*/
|
|
4629
5383
|
buildTrace(options2) {
|
|
4630
5384
|
const builder = new TraceBuilder(this.agentId);
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
const args = {};
|
|
4635
|
-
if (llm.model ?? options2?.model) args.model = llm.model ?? options2?.model;
|
|
4636
|
-
const result = { completion: llm.text };
|
|
4637
|
-
if (llm.tokens !== void 0) {
|
|
4638
|
-
result.tokens = llm.tokens;
|
|
4639
|
-
totalTokens = (totalTokens ?? 0) + llm.tokens;
|
|
4640
|
-
}
|
|
4641
|
-
builder.addLlmCall(llm.name, { args, result });
|
|
4642
|
-
}
|
|
4643
|
-
for (const tool of this.toolCalls) {
|
|
4644
|
-
builder.addToolCall(tool.name, {
|
|
4645
|
-
result: { output: tool.output }
|
|
4646
|
-
});
|
|
4647
|
-
}
|
|
4648
|
-
const outputMessage = this.chainOutput || (this.llmCalls.length > 0 ? this.llmCalls[this.llmCalls.length - 1].text : "");
|
|
4649
|
-
builder.setOutput({ message: outputMessage });
|
|
5385
|
+
if (this.input) builder.setInput({ message: this.input });
|
|
5386
|
+
for (const step of this.steps) builder.addStep(step);
|
|
5387
|
+
builder.setOutput({ message: this.output ?? "" });
|
|
4650
5388
|
builder.setMetadata({
|
|
4651
|
-
total_tokens: totalTokens,
|
|
5389
|
+
total_tokens: this.totalTokens > 0 ? this.totalTokens : void 0,
|
|
4652
5390
|
cost_usd: options2?.costUsd,
|
|
4653
|
-
latency_ms:
|
|
4654
|
-
model: options2?.model ?? this.
|
|
5391
|
+
latency_ms: this.startedAtMs !== void 0 && this.endedAtMs !== void 0 ? this.endedAtMs - this.startedAtMs : void 0,
|
|
5392
|
+
model: options2?.model ?? this.model
|
|
4655
5393
|
});
|
|
4656
5394
|
return builder.build();
|
|
4657
5395
|
}
|
|
@@ -4662,6 +5400,7 @@ export {
|
|
|
4662
5400
|
AgentResult,
|
|
4663
5401
|
AlertDispatcher,
|
|
4664
5402
|
AnthropicAdapter,
|
|
5403
|
+
AssertionIdAssigner,
|
|
4665
5404
|
AttestClient,
|
|
4666
5405
|
CONFUSED_USER,
|
|
4667
5406
|
COOPERATIVE_USER,
|
|
@@ -4681,6 +5420,7 @@ export {
|
|
|
4681
5420
|
FRIENDLY_USER,
|
|
4682
5421
|
FailureClass,
|
|
4683
5422
|
GeminiAdapter,
|
|
5423
|
+
ID_SCHEME,
|
|
4684
5424
|
LangChainAdapter,
|
|
4685
5425
|
ManualAdapter,
|
|
4686
5426
|
MockToolRegistry,
|
|
@@ -4697,7 +5437,9 @@ export {
|
|
|
4697
5437
|
ProtocolError,
|
|
4698
5438
|
REASON_BUDGET_EXHAUSTED,
|
|
4699
5439
|
REASON_JUDGE_ABSTAINED,
|
|
5440
|
+
REASON_JUDGE_MALFORMED,
|
|
4700
5441
|
REASON_MISSING_EVIDENCE,
|
|
5442
|
+
REASON_PROVIDER_AUTH,
|
|
4701
5443
|
REASON_PROVIDER_NOT_CONFIGURED,
|
|
4702
5444
|
REASON_PROVIDER_UNAVAILABLE,
|
|
4703
5445
|
REASON_REPLAY_MISS,
|
|
@@ -4739,7 +5481,10 @@ export {
|
|
|
4739
5481
|
agent,
|
|
4740
5482
|
agentId,
|
|
4741
5483
|
assertionId,
|
|
5484
|
+
assertionIdOf,
|
|
4742
5485
|
attestExpect,
|
|
5486
|
+
canonicalIdInput,
|
|
5487
|
+
canonicalSpec,
|
|
4743
5488
|
computeAgreement,
|
|
4744
5489
|
config,
|
|
4745
5490
|
decodeResponse,
|
|
@@ -4776,13 +5521,16 @@ export {
|
|
|
4776
5521
|
renderVerdictDiagnostics,
|
|
4777
5522
|
renderVitest,
|
|
4778
5523
|
repeat,
|
|
5524
|
+
replayRun,
|
|
4779
5525
|
requireSimulationCapability,
|
|
4780
5526
|
resetConfig,
|
|
4781
5527
|
resolveMode,
|
|
4782
5528
|
scenario,
|
|
5529
|
+
setChainObserver,
|
|
4783
5530
|
simulationEnvEnabled,
|
|
4784
5531
|
startReplay,
|
|
4785
5532
|
startWorld,
|
|
5533
|
+
submitPluginResults,
|
|
4786
5534
|
sumUsage,
|
|
4787
5535
|
testPathFor,
|
|
4788
5536
|
tier,
|