@tangle-network/agent-eval 0.23.1 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +80 -0
- package/README.md +141 -79
- package/dist/baseline-4R5deP0N.d.ts +108 -0
- package/dist/benchmarks/index.d.ts +3 -2
- package/dist/benchmarks/index.js +1 -1
- package/dist/builder-eval/index.d.ts +249 -0
- package/dist/builder-eval/index.js +391 -0
- package/dist/builder-eval/index.js.map +1 -0
- package/dist/{chunk-IOXMGMHQ.js → chunk-2A5XJB43.js} +142 -318
- package/dist/chunk-2A5XJB43.js.map +1 -0
- package/dist/chunk-47X6LRCE.js +76 -0
- package/dist/chunk-47X6LRCE.js.map +1 -0
- package/dist/{chunk-6M774GY6.js → chunk-4F5DQN55.js} +1 -1
- package/dist/chunk-4F5DQN55.js.map +1 -0
- package/dist/{chunk-KAO3Q65R.js → chunk-4S4BM3QQ.js} +15 -13
- package/dist/chunk-4S4BM3QQ.js.map +1 -0
- package/dist/chunk-5BKGXME7.js +65 -0
- package/dist/chunk-5BKGXME7.js.map +1 -0
- package/dist/{chunk-42I2QC2L.js → chunk-6QDKWHLS.js} +18 -14
- package/dist/chunk-6QDKWHLS.js.map +1 -0
- package/dist/chunk-I4MBDTY5.js +272 -0
- package/dist/chunk-I4MBDTY5.js.map +1 -0
- package/dist/chunk-K2TPS5LB.js +569 -0
- package/dist/chunk-K2TPS5LB.js.map +1 -0
- package/dist/chunk-KKHDIONI.js +414 -0
- package/dist/chunk-KKHDIONI.js.map +1 -0
- package/dist/chunk-KMPRBJK4.js +74 -0
- package/dist/chunk-KMPRBJK4.js.map +1 -0
- package/dist/{chunk-QUKKGHTZ.js → chunk-KTGTIOFD.js} +6 -3
- package/dist/chunk-KTGTIOFD.js.map +1 -0
- package/dist/chunk-LSH4MMOZ.js +838 -0
- package/dist/chunk-LSH4MMOZ.js.map +1 -0
- package/dist/chunk-NG236HPC.js +57 -0
- package/dist/chunk-NG236HPC.js.map +1 -0
- package/dist/{chunk-QBW3YBTR.js → chunk-NLMNWKVM.js} +14 -6
- package/dist/chunk-NLMNWKVM.js.map +1 -0
- package/dist/chunk-NU65VQ7M.js +99 -0
- package/dist/chunk-NU65VQ7M.js.map +1 -0
- package/dist/chunk-OHEPNJQN.js +554 -0
- package/dist/chunk-OHEPNJQN.js.map +1 -0
- package/dist/chunk-OWLAAMME.js +250 -0
- package/dist/chunk-OWLAAMME.js.map +1 -0
- package/dist/{chunk-SQQLHODJ.js → chunk-PC4UYEBM.js} +7 -4
- package/dist/chunk-PC4UYEBM.js.map +1 -0
- package/dist/{chunk-7EAUOUQS.js → chunk-RAF443UI.js} +213 -115
- package/dist/chunk-RAF443UI.js.map +1 -0
- package/dist/chunk-RZTMDUO7.js +49 -0
- package/dist/chunk-RZTMDUO7.js.map +1 -0
- package/dist/{chunk-EXGR4XEM.js → chunk-SESZDQPX.js} +23 -19
- package/dist/chunk-SESZDQPX.js.map +1 -0
- package/dist/{chunk-6KQG5HAH.js → chunk-SY6WAAAD.js} +84 -71
- package/dist/chunk-SY6WAAAD.js.map +1 -0
- package/dist/{chunk-5IIQKMD5.js → chunk-TVVP3ZZQ.js} +14 -4
- package/dist/chunk-TVVP3ZZQ.js.map +1 -0
- package/dist/{chunk-VQQSPGSM.js → chunk-VRJVTXRV.js} +169 -111
- package/dist/chunk-VRJVTXRV.js.map +1 -0
- package/dist/chunk-WWYCWKUM.js +196 -0
- package/dist/chunk-WWYCWKUM.js.map +1 -0
- package/dist/{chunk-AXHNWLIX.js → chunk-YRZ4M5GS.js} +2 -90
- package/dist/chunk-YRZ4M5GS.js.map +1 -0
- package/dist/chunk-ZN274SWR.js +613 -0
- package/dist/chunk-ZN274SWR.js.map +1 -0
- package/dist/cli.js +10 -6
- package/dist/cli.js.map +1 -1
- package/dist/{control-DvkH87qJ.d.ts → control-CBShYYA6.d.ts} +32 -33
- package/dist/control-runtime-BuJHoLg0.d.ts +180 -0
- package/dist/control.d.ts +8 -6
- package/dist/control.js +10 -7
- package/dist/{dataset-B9qvlm_o.d.ts → dataset-CiK_3LDr.d.ts} +5 -2
- package/dist/{emitter-B2XqDKFU.d.ts → emitter-DP_cSSiw.d.ts} +1 -1
- package/dist/errors-BZ9sTdz7.d.ts +70 -0
- package/dist/failure-cluster-C2EGSDiT.d.ts +76 -0
- package/dist/feedback-trajectory-DfFdrraJ.d.ts +169 -0
- package/dist/governance/index.d.ts +5 -0
- package/dist/governance/index.js +18 -0
- package/dist/governance/index.js.map +1 -0
- package/dist/{index-DDTlbHEK.d.ts → index--fVrWDiR.d.ts} +1 -1
- package/dist/index-Oj9fAPPN.d.ts +270 -0
- package/dist/index.d.ts +1866 -3151
- package/dist/index.js +5457 -7809
- package/dist/index.js.map +1 -1
- package/dist/{integrity-Cr5YodSY.d.ts → integrity-DK2EBVZC.d.ts} +4 -3
- package/dist/knowledge/index.d.ts +102 -0
- package/dist/knowledge/index.js +18 -0
- package/dist/knowledge/index.js.map +1 -0
- package/dist/meta-eval/index.d.ts +99 -0
- package/dist/meta-eval/index.js +324 -0
- package/dist/meta-eval/index.js.map +1 -0
- package/dist/multi-layer-verifier-LkP3LVKj.d.ts +141 -0
- package/dist/openapi.json +1 -1
- package/dist/optimization.d.ts +11 -8
- package/dist/optimization.js +11 -9
- package/dist/outcome-store-D6KWmYvj.d.ts +63 -0
- package/dist/pipelines/index.d.ts +172 -0
- package/dist/pipelines/index.js +409 -0
- package/dist/pipelines/index.js.map +1 -0
- package/dist/prm/index.d.ts +99 -0
- package/dist/prm/index.js +222 -0
- package/dist/prm/index.js.map +1 -0
- package/dist/query-DODUYdPg.d.ts +30 -0
- package/dist/release-report-TDPn1cxq.d.ts +292 -0
- package/dist/replay-BL96gCEP.d.ts +226 -0
- package/dist/reporting.d.ts +10 -295
- package/dist/reporting.js +10 -6
- package/dist/{eval-campaign-Ds5QljIh.d.ts → researcher-CUOiGcGv.d.ts} +148 -146
- package/dist/rl.d.ts +1762 -8
- package/dist/rl.js +2035 -58
- package/dist/rl.js.map +1 -1
- package/dist/rubric-D5tjHNJQ.d.ts +72 -0
- package/dist/rubric-predictive-validity-C0uDYwG6.d.ts +105 -0
- package/dist/{run-record-DNiOMBrZ.d.ts → run-record-CqzahIbx.d.ts} +4 -1
- package/dist/sequential-Dgz1n51-.d.ts +139 -0
- package/dist/{store-u47QaJ9G.d.ts → store-Db2Bv8Cf.d.ts} +1 -1
- package/dist/{summary-report-Ce1r4EYo.d.ts → summary-report-BXGs_9V0.d.ts} +3 -76
- package/dist/telemetry/file.js +4 -1
- package/dist/telemetry/file.js.map +1 -1
- package/dist/telemetry/index.js +57 -57
- package/dist/telemetry/index.js.map +1 -1
- package/dist/test-graded-scenario-B2kWEdh9.d.ts +146 -0
- package/dist/traces.d.ts +142 -387
- package/dist/traces.js +1302 -40
- package/dist/traces.js.map +1 -1
- package/dist/trajectory-CnoBo-JY.d.ts +32 -0
- package/dist/wire/index.d.ts +22 -22
- package/dist/wire/index.js +4 -3
- package/package.json +44 -18
- package/dist/chunk-42I2QC2L.js.map +0 -1
- package/dist/chunk-5IIQKMD5.js.map +0 -1
- package/dist/chunk-6KQG5HAH.js.map +0 -1
- package/dist/chunk-6M774GY6.js.map +0 -1
- package/dist/chunk-7EAUOUQS.js.map +0 -1
- package/dist/chunk-AXHNWLIX.js.map +0 -1
- package/dist/chunk-EXGR4XEM.js.map +0 -1
- package/dist/chunk-IOXMGMHQ.js.map +0 -1
- package/dist/chunk-KAO3Q65R.js.map +0 -1
- package/dist/chunk-LZKIOBG2.js +0 -2026
- package/dist/chunk-LZKIOBG2.js.map +0 -1
- package/dist/chunk-QBW3YBTR.js.map +0 -1
- package/dist/chunk-QUKKGHTZ.js.map +0 -1
- package/dist/chunk-SQQLHODJ.js.map +0 -1
- package/dist/chunk-V5QSWN7L.js +0 -1310
- package/dist/chunk-V5QSWN7L.js.map +0 -1
- package/dist/chunk-VQQSPGSM.js.map +0 -1
- package/dist/chunk-XPHOZPOM.js +0 -1947
- package/dist/chunk-XPHOZPOM.js.map +0 -1
- package/dist/feedback-trajectory-c43WGtTX.d.ts +0 -346
- package/dist/index-ekBXweiQ.d.ts +0 -1894
- package/dist/sequential-DgU2mFsE.d.ts +0 -304
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TraceEmitter
|
|
3
|
+
} from "./chunk-TVVP3ZZQ.js";
|
|
4
|
+
import {
|
|
5
|
+
ConfigError
|
|
6
|
+
} from "./chunk-NG236HPC.js";
|
|
7
|
+
|
|
8
|
+
// src/sandbox-harness.ts
|
|
9
|
+
var vitestTestParser = {
|
|
10
|
+
id: "vitest",
|
|
11
|
+
parse(stdout) {
|
|
12
|
+
const m = stdout.match(/Tests\s+(\d+)\s+(passed|failed)(?:\s*\|\s*(\d+)\s+(passed|failed))?/i);
|
|
13
|
+
if (!m) return void 0;
|
|
14
|
+
let passed = 0;
|
|
15
|
+
let failed = 0;
|
|
16
|
+
const a = parseInt(m[1], 10);
|
|
17
|
+
const aLabel = m[2].toLowerCase();
|
|
18
|
+
if (aLabel === "passed") passed += a;
|
|
19
|
+
else failed += a;
|
|
20
|
+
if (m[3] && m[4]) {
|
|
21
|
+
const b = parseInt(m[3], 10);
|
|
22
|
+
if (m[4].toLowerCase() === "passed") passed += b;
|
|
23
|
+
else failed += b;
|
|
24
|
+
}
|
|
25
|
+
return { testsTotal: passed + failed, testsPassed: passed };
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var pytestTestParser = {
|
|
29
|
+
id: "pytest",
|
|
30
|
+
parse(stdout) {
|
|
31
|
+
const total = stdout.match(/collected\s+(\d+)\s+items?/i);
|
|
32
|
+
const passed = stdout.match(/(\d+)\s+passed/);
|
|
33
|
+
if (!total || !passed) return void 0;
|
|
34
|
+
return { testsTotal: parseInt(total[1], 10), testsPassed: parseInt(passed[1], 10) };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var jestTestParser = {
|
|
38
|
+
id: "jest",
|
|
39
|
+
parse(stdout) {
|
|
40
|
+
const m = stdout.match(/Tests:\s+(?:(\d+)\s+failed[^,]*,\s*)?(\d+)\s+passed,\s+(\d+)\s+total/i);
|
|
41
|
+
if (!m) return void 0;
|
|
42
|
+
return { testsTotal: parseInt(m[3], 10), testsPassed: parseInt(m[2], 10) };
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
function composeParsers(...parsers) {
|
|
46
|
+
return {
|
|
47
|
+
id: parsers.map((p) => p.id).join("|"),
|
|
48
|
+
parse(stdout, stderr, exitCode) {
|
|
49
|
+
for (const p of parsers) {
|
|
50
|
+
const res = p.parse(stdout, stderr, exitCode);
|
|
51
|
+
if (res) return res;
|
|
52
|
+
}
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
var SubprocessSandboxDriver = class {
|
|
58
|
+
id = "subprocess";
|
|
59
|
+
defaultCwd;
|
|
60
|
+
defaultEnv;
|
|
61
|
+
constructor(options = {}) {
|
|
62
|
+
this.defaultCwd = options.cwd;
|
|
63
|
+
this.defaultEnv = options.env;
|
|
64
|
+
}
|
|
65
|
+
async exec(phase, command, config) {
|
|
66
|
+
const { spawn } = await import("child_process");
|
|
67
|
+
const start = Date.now();
|
|
68
|
+
const effectiveCwd = config.cwd ?? this.defaultCwd;
|
|
69
|
+
const effectiveEnv = { ...process.env, ...this.defaultEnv ?? {}, ...config.env ?? {} };
|
|
70
|
+
return await new Promise((resolve) => {
|
|
71
|
+
const child = spawn(command, {
|
|
72
|
+
shell: true,
|
|
73
|
+
cwd: effectiveCwd,
|
|
74
|
+
env: effectiveEnv
|
|
75
|
+
});
|
|
76
|
+
let stdout = "";
|
|
77
|
+
let stderr = "";
|
|
78
|
+
child.stdout?.on("data", (d) => {
|
|
79
|
+
stdout += String(d);
|
|
80
|
+
});
|
|
81
|
+
child.stderr?.on("data", (d) => {
|
|
82
|
+
stderr += String(d);
|
|
83
|
+
});
|
|
84
|
+
const timeout = setTimeout(
|
|
85
|
+
() => {
|
|
86
|
+
try {
|
|
87
|
+
child.kill("SIGKILL");
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
config.timeoutMs ?? 10 * 6e4
|
|
92
|
+
);
|
|
93
|
+
child.on("close", (code) => {
|
|
94
|
+
clearTimeout(timeout);
|
|
95
|
+
const wallMs = Date.now() - start;
|
|
96
|
+
const parsed = phase === "test" && config.testParser ? config.testParser.parse(stdout, stderr, code ?? 1) : void 0;
|
|
97
|
+
resolve({
|
|
98
|
+
phase,
|
|
99
|
+
exitCode: code ?? 1,
|
|
100
|
+
stdout,
|
|
101
|
+
stderr,
|
|
102
|
+
wallMs,
|
|
103
|
+
testsTotal: parsed?.testsTotal,
|
|
104
|
+
testsPassed: parsed?.testsPassed
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
child.on("error", (err) => {
|
|
108
|
+
clearTimeout(timeout);
|
|
109
|
+
const wallMs = Date.now() - start;
|
|
110
|
+
resolve({ phase, exitCode: 127, stdout, stderr: stderr + String(err), wallMs });
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var DockerSandboxDriver = class {
|
|
116
|
+
id = "docker";
|
|
117
|
+
async exec(phase, command, config) {
|
|
118
|
+
if (!config.image) throw new ConfigError("DockerSandboxDriver requires config.image");
|
|
119
|
+
const sub = new SubprocessSandboxDriver();
|
|
120
|
+
const envArgs = Object.entries(config.env ?? {}).map(([k, v]) => `-e ${shellQuote(k)}=${shellQuote(v)}`).join(" ");
|
|
121
|
+
const wrapped = `docker run --rm ${envArgs} ${shellQuote(config.image)} sh -c ${shellQuote(command)}`;
|
|
122
|
+
return sub.exec(phase, wrapped, { ...config, env: void 0 });
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
function shellQuote(v) {
|
|
126
|
+
if (/^[A-Za-z0-9_\-/.@:=]+$/.test(v)) return v;
|
|
127
|
+
return `'${v.replace(/'/g, `'\\''`)}'`;
|
|
128
|
+
}
|
|
129
|
+
var SandboxHarness = class {
|
|
130
|
+
driver;
|
|
131
|
+
constructor(driver = new SubprocessSandboxDriver()) {
|
|
132
|
+
this.driver = driver;
|
|
133
|
+
}
|
|
134
|
+
async run(config, emitter) {
|
|
135
|
+
const handle = await emitter.sandbox({
|
|
136
|
+
name: `sandbox(${this.driver.id})`,
|
|
137
|
+
image: config.image,
|
|
138
|
+
command: [config.setupCommand, config.runCommand, config.testCommand].filter(Boolean).join(" && ")
|
|
139
|
+
});
|
|
140
|
+
const result = { passed: false, totalWallMs: 0, score: 0 };
|
|
141
|
+
try {
|
|
142
|
+
if (config.setupCommand) {
|
|
143
|
+
result.setup = await this.driver.exec("setup", config.setupCommand, config);
|
|
144
|
+
result.totalWallMs += result.setup.wallMs;
|
|
145
|
+
if (result.setup.exitCode !== 0) {
|
|
146
|
+
await handle.fail(`setup failed (exit ${result.setup.exitCode})`, {
|
|
147
|
+
exitCode: result.setup.exitCode,
|
|
148
|
+
wallMs: result.totalWallMs
|
|
149
|
+
});
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (config.runCommand) {
|
|
154
|
+
result.run = await this.driver.exec("run", config.runCommand, config);
|
|
155
|
+
result.totalWallMs += result.run.wallMs;
|
|
156
|
+
if (result.run.exitCode !== 0) {
|
|
157
|
+
await handle.fail(`run failed (exit ${result.run.exitCode})`, {
|
|
158
|
+
exitCode: result.run.exitCode,
|
|
159
|
+
wallMs: result.totalWallMs
|
|
160
|
+
});
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (config.testCommand) {
|
|
165
|
+
result.test = await this.driver.exec("test", config.testCommand, config);
|
|
166
|
+
result.totalWallMs += result.test.wallMs;
|
|
167
|
+
const passed = result.test.exitCode === 0;
|
|
168
|
+
result.passed = passed;
|
|
169
|
+
if (result.test.testsTotal !== void 0 && result.test.testsTotal > 0) {
|
|
170
|
+
result.score = (result.test.testsPassed ?? 0) / result.test.testsTotal;
|
|
171
|
+
} else {
|
|
172
|
+
result.score = passed ? 1 : 0;
|
|
173
|
+
}
|
|
174
|
+
await handle.end({
|
|
175
|
+
exitCode: result.test.exitCode,
|
|
176
|
+
testsTotal: result.test.testsTotal,
|
|
177
|
+
testsPassed: result.test.testsPassed,
|
|
178
|
+
wallMs: result.totalWallMs,
|
|
179
|
+
status: passed ? "ok" : "error"
|
|
180
|
+
});
|
|
181
|
+
} else {
|
|
182
|
+
result.passed = true;
|
|
183
|
+
result.score = 1;
|
|
184
|
+
await handle.end({ wallMs: result.totalWallMs });
|
|
185
|
+
}
|
|
186
|
+
} catch (err) {
|
|
187
|
+
await handle.fail(err instanceof Error ? err : String(err));
|
|
188
|
+
throw err;
|
|
189
|
+
}
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// src/test-graded-scenario.ts
|
|
195
|
+
async function runTestGradedScenario(scenario, store, options = {}) {
|
|
196
|
+
const emitter = new TraceEmitter(store);
|
|
197
|
+
await emitter.startRun({
|
|
198
|
+
scenarioId: scenario.id,
|
|
199
|
+
variantId: options.variantId,
|
|
200
|
+
datasetVersion: scenario.datasetVersion,
|
|
201
|
+
tags: scenario.tags,
|
|
202
|
+
...options.provenance
|
|
203
|
+
});
|
|
204
|
+
const harness = new SandboxHarness(options.driver);
|
|
205
|
+
const result = await harness.run(scenario.harness, emitter);
|
|
206
|
+
const threshold = scenario.passThreshold ?? 1;
|
|
207
|
+
const pass = result.passed && result.score >= threshold;
|
|
208
|
+
const setupFailed = result.setup !== void 0 && result.setup.exitCode !== 0;
|
|
209
|
+
const runFailed = result.run !== void 0 && result.run.exitCode !== 0;
|
|
210
|
+
const testFailed = result.test !== void 0 && result.test.exitCode !== 0;
|
|
211
|
+
const failureClass = pass ? "success" : setupFailed || runFailed ? "sandbox_failure" : testFailed ? "format_drift" : "unknown";
|
|
212
|
+
await emitter.endRun({
|
|
213
|
+
pass,
|
|
214
|
+
score: result.score,
|
|
215
|
+
failureClass,
|
|
216
|
+
notes: pass ? void 0 : reasonForFailure(result)
|
|
217
|
+
});
|
|
218
|
+
return {
|
|
219
|
+
runId: emitter.runId,
|
|
220
|
+
scenario,
|
|
221
|
+
harness: result,
|
|
222
|
+
pass,
|
|
223
|
+
score: result.score,
|
|
224
|
+
failureClass
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function reasonForFailure(result) {
|
|
228
|
+
if (result.setup && result.setup.exitCode !== 0)
|
|
229
|
+
return `setup failed: exit ${result.setup.exitCode}`;
|
|
230
|
+
if (result.run && result.run.exitCode !== 0) return `run failed: exit ${result.run.exitCode}`;
|
|
231
|
+
if (result.test) {
|
|
232
|
+
if (result.test.testsTotal !== void 0) {
|
|
233
|
+
return `tests: ${result.test.testsPassed ?? 0}/${result.test.testsTotal}`;
|
|
234
|
+
}
|
|
235
|
+
return `test exit ${result.test.exitCode}`;
|
|
236
|
+
}
|
|
237
|
+
return "no test command";
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export {
|
|
241
|
+
vitestTestParser,
|
|
242
|
+
pytestTestParser,
|
|
243
|
+
jestTestParser,
|
|
244
|
+
composeParsers,
|
|
245
|
+
SubprocessSandboxDriver,
|
|
246
|
+
DockerSandboxDriver,
|
|
247
|
+
SandboxHarness,
|
|
248
|
+
runTestGradedScenario
|
|
249
|
+
};
|
|
250
|
+
//# sourceMappingURL=chunk-OWLAAMME.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sandbox-harness.ts","../src/test-graded-scenario.ts"],"sourcesContent":["/**\n * SandboxHarness — executes a scenario in an isolated environment and\n * emits a rich SandboxSpan into the trace.\n *\n * Two built-in drivers:\n * - `SubprocessSandboxDriver` — spawn in a local cwd with env vars.\n * Fast, no dependencies, fine for unit tests and most CI gates.\n * - `DockerSandboxDriver` — lifted from tangle-router's sandbox path;\n * shells out to `docker run`. Stronger isolation, slower startup.\n *\n * Consumers implement `SandboxDriver` for custom backends (Firecracker,\n * Cloudflare sandbox product, etc.). The harness doesn't care which.\n */\n\nimport { ConfigError } from './errors'\nimport type { TraceEmitter } from './trace/emitter'\nimport type { SandboxSpan } from './trace/schema'\n\nexport interface HarnessConfig {\n /** Setup command (e.g. \"pnpm install\"). Non-zero exit fails the run. */\n setupCommand?: string\n /** Run command (e.g. \"pnpm build\"). */\n runCommand?: string\n /** Test command (e.g. \"pnpm test --run\"). Drives the test count + pass count. */\n testCommand?: string\n /** Absolute cwd for the subprocess driver. Ignored by docker driver. */\n cwd?: string\n /** Max wall-clock per phase in ms. Default 10 minutes. */\n timeoutMs?: number\n /** Image for the docker driver. */\n image?: string\n /** Extra env vars (validated; shell-escaped). */\n env?: Record<string, string>\n /** Parser for the test output — maps stdout/stderr/exit code → pass count. */\n testParser?: TestOutputParser\n}\n\nexport interface TestOutputParser {\n id: string\n parse(\n stdout: string,\n stderr: string,\n exitCode: number,\n ): { testsTotal: number; testsPassed: number } | undefined\n}\n\nexport interface SandboxResult {\n phase: 'setup' | 'run' | 'test'\n exitCode: number\n stdout: string\n stderr: string\n wallMs: number\n testsTotal?: number\n testsPassed?: number\n}\n\nexport interface SandboxDriver {\n id: string\n exec(\n phase: SandboxResult['phase'],\n command: string,\n config: HarnessConfig,\n ): Promise<SandboxResult>\n}\n\n// ── Parsers ──────────────────────────────────────────────────────────\n\n/** Vitest default summary line: \"Tests X passed | Y failed\". */\nexport const vitestTestParser: TestOutputParser = {\n id: 'vitest',\n parse(stdout) {\n const m = stdout.match(/Tests\\s+(\\d+)\\s+(passed|failed)(?:\\s*\\|\\s*(\\d+)\\s+(passed|failed))?/i)\n if (!m) return undefined\n let passed = 0\n let failed = 0\n const a = parseInt(m[1]!, 10)\n const aLabel = m[2]!.toLowerCase()\n if (aLabel === 'passed') passed += a\n else failed += a\n if (m[3] && m[4]) {\n const b = parseInt(m[3], 10)\n if (m[4].toLowerCase() === 'passed') passed += b\n else failed += b\n }\n return { testsTotal: passed + failed, testsPassed: passed }\n },\n}\n\n/** Pytest default: \"collected N items\" + \" X passed, Y failed\". */\nexport const pytestTestParser: TestOutputParser = {\n id: 'pytest',\n parse(stdout) {\n const total = stdout.match(/collected\\s+(\\d+)\\s+items?/i)\n const passed = stdout.match(/(\\d+)\\s+passed/)\n if (!total || !passed) return undefined\n return { testsTotal: parseInt(total[1]!, 10), testsPassed: parseInt(passed[1]!, 10) }\n },\n}\n\n/** Jest: \"Tests: X passed, Y total\" (and optional failed). */\nexport const jestTestParser: TestOutputParser = {\n id: 'jest',\n parse(stdout) {\n const m = stdout.match(/Tests:\\s+(?:(\\d+)\\s+failed[^,]*,\\s*)?(\\d+)\\s+passed,\\s+(\\d+)\\s+total/i)\n if (!m) return undefined\n return { testsTotal: parseInt(m[3]!, 10), testsPassed: parseInt(m[2]!, 10) }\n },\n}\n\n/** Composite parser — tries a list of parsers in order. */\nexport function composeParsers(...parsers: TestOutputParser[]): TestOutputParser {\n return {\n id: parsers.map((p) => p.id).join('|'),\n parse(stdout, stderr, exitCode) {\n for (const p of parsers) {\n const res = p.parse(stdout, stderr, exitCode)\n if (res) return res\n }\n return undefined\n },\n }\n}\n\n// ── Drivers ──────────────────────────────────────────────────────────\n\nexport interface SubprocessSandboxDriverOptions {\n /**\n * Default cwd for all `exec` calls. Used when the per-call `HarnessConfig`\n * does not set its own `cwd`. Lets callers bind the driver to a working\n * directory once instead of spreading cwd into every harness config —\n * useful when the harness config is constructed far from the call site\n * (e.g. starter-foundry's promoter passes a static HarnessConfig per\n * family taxonomy but needs a per-run composed-scaffold cwd).\n */\n cwd?: string\n /**\n * Default env merged into every `exec` call's env (per-call `HarnessConfig.env`\n * still wins on key collision). Same ergonomic rationale as `cwd` above.\n */\n env?: Record<string, string>\n}\n\nexport class SubprocessSandboxDriver implements SandboxDriver {\n id = 'subprocess'\n private defaultCwd?: string\n private defaultEnv?: Record<string, string>\n\n constructor(options: SubprocessSandboxDriverOptions = {}) {\n this.defaultCwd = options.cwd\n this.defaultEnv = options.env\n }\n\n async exec(\n phase: SandboxResult['phase'],\n command: string,\n config: HarnessConfig,\n ): Promise<SandboxResult> {\n const { spawn } = await import('node:child_process')\n const start = Date.now()\n // Per-call config wins; fall back to constructor defaults. Historically\n // `config.cwd` was the only path, which silently dropped the constructor\n // arg when callers passed `new SubprocessSandboxDriver({ cwd })` — the\n // subprocess then inherited Node's cwd and e.g. ran `tsc --noEmit`\n // against the wrong repo. Honoring the constructor `cwd` restores the\n // invariant implied by the constructor shape.\n const effectiveCwd = config.cwd ?? this.defaultCwd\n const effectiveEnv = { ...process.env, ...(this.defaultEnv ?? {}), ...(config.env ?? {}) }\n return await new Promise<SandboxResult>((resolve) => {\n const child = spawn(command, {\n shell: true,\n cwd: effectiveCwd,\n env: effectiveEnv,\n })\n let stdout = ''\n let stderr = ''\n child.stdout?.on('data', (d) => {\n stdout += String(d)\n })\n child.stderr?.on('data', (d) => {\n stderr += String(d)\n })\n const timeout = setTimeout(\n () => {\n try {\n child.kill('SIGKILL')\n } catch {}\n },\n config.timeoutMs ?? 10 * 60_000,\n )\n child.on('close', (code) => {\n clearTimeout(timeout)\n const wallMs = Date.now() - start\n const parsed =\n phase === 'test' && config.testParser\n ? config.testParser.parse(stdout, stderr, code ?? 1)\n : undefined\n resolve({\n phase,\n exitCode: code ?? 1,\n stdout,\n stderr,\n wallMs,\n testsTotal: parsed?.testsTotal,\n testsPassed: parsed?.testsPassed,\n })\n })\n child.on('error', (err) => {\n clearTimeout(timeout)\n const wallMs = Date.now() - start\n resolve({ phase, exitCode: 127, stdout, stderr: stderr + String(err), wallMs })\n })\n })\n }\n}\n\nexport class DockerSandboxDriver implements SandboxDriver {\n id = 'docker'\n\n async exec(\n phase: SandboxResult['phase'],\n command: string,\n config: HarnessConfig,\n ): Promise<SandboxResult> {\n if (!config.image) throw new ConfigError('DockerSandboxDriver requires config.image')\n const sub = new SubprocessSandboxDriver()\n const envArgs = Object.entries(config.env ?? {})\n .map(([k, v]) => `-e ${shellQuote(k)}=${shellQuote(v)}`)\n .join(' ')\n const wrapped = `docker run --rm ${envArgs} ${shellQuote(config.image)} sh -c ${shellQuote(command)}`\n return sub.exec(phase, wrapped, { ...config, env: undefined })\n }\n}\n\nfunction shellQuote(v: string): string {\n if (/^[A-Za-z0-9_\\-/.@:=]+$/.test(v)) return v\n return `'${v.replace(/'/g, `'\\\\''`)}'`\n}\n\n// ── Harness orchestration ────────────────────────────────────────────\n\nexport interface SandboxHarnessResult {\n passed: boolean\n setup?: SandboxResult\n run?: SandboxResult\n test?: SandboxResult\n totalWallMs: number\n /** Final score — 0 when no tests; otherwise testsPassed/testsTotal. */\n score: number\n}\n\nexport class SandboxHarness {\n private driver: SandboxDriver\n constructor(driver: SandboxDriver = new SubprocessSandboxDriver()) {\n this.driver = driver\n }\n\n async run(config: HarnessConfig, emitter: TraceEmitter): Promise<SandboxHarnessResult> {\n const handle = await emitter.sandbox({\n name: `sandbox(${this.driver.id})`,\n image: config.image,\n command: [config.setupCommand, config.runCommand, config.testCommand]\n .filter(Boolean)\n .join(' && '),\n })\n const result: SandboxHarnessResult = { passed: false, totalWallMs: 0, score: 0 }\n try {\n if (config.setupCommand) {\n result.setup = await this.driver.exec('setup', config.setupCommand, config)\n result.totalWallMs += result.setup.wallMs\n if (result.setup.exitCode !== 0) {\n await handle.fail(`setup failed (exit ${result.setup.exitCode})`, {\n exitCode: result.setup.exitCode,\n wallMs: result.totalWallMs,\n } as Partial<SandboxSpan>)\n return result\n }\n }\n if (config.runCommand) {\n result.run = await this.driver.exec('run', config.runCommand, config)\n result.totalWallMs += result.run.wallMs\n if (result.run.exitCode !== 0) {\n await handle.fail(`run failed (exit ${result.run.exitCode})`, {\n exitCode: result.run.exitCode,\n wallMs: result.totalWallMs,\n } as Partial<SandboxSpan>)\n return result\n }\n }\n if (config.testCommand) {\n result.test = await this.driver.exec('test', config.testCommand, config)\n result.totalWallMs += result.test.wallMs\n const passed = result.test.exitCode === 0\n result.passed = passed\n if (result.test.testsTotal !== undefined && result.test.testsTotal > 0) {\n result.score = (result.test.testsPassed ?? 0) / result.test.testsTotal\n } else {\n result.score = passed ? 1 : 0\n }\n await handle.end({\n exitCode: result.test.exitCode,\n testsTotal: result.test.testsTotal,\n testsPassed: result.test.testsPassed,\n wallMs: result.totalWallMs,\n status: passed ? 'ok' : 'error',\n } as Partial<SandboxSpan>)\n } else {\n result.passed = true\n result.score = 1\n await handle.end({ wallMs: result.totalWallMs } as Partial<SandboxSpan>)\n }\n } catch (err) {\n await handle.fail(err instanceof Error ? err : String(err))\n throw err\n }\n return result\n }\n}\n","/**\n * TestGradedScenario — a scenario whose score comes from a test suite.\n *\n * This is the SWE-bench pattern generalized. The scenario ships:\n * - fixture data (setup instructions)\n * - a test command the harness runs\n * - optional assertion overrides\n *\n * The runner emits a run, delegates to SandboxHarness, records the\n * outcome, and returns a structured verdict. Consumers bind their own\n * agent execution to this contract.\n */\n\nimport type { HarnessConfig, SandboxDriver, SandboxHarnessResult } from './sandbox-harness'\nimport { SandboxHarness } from './sandbox-harness'\nimport { TraceEmitter } from './trace/emitter'\nimport type { FailureClass, Run } from './trace/schema'\nimport type { TraceStore } from './trace/store'\n\nexport interface TestGradedScenario {\n id: string\n description?: string\n harness: HarnessConfig\n /** Optional pass threshold in 0..1 (default 1.0 = all tests must pass). */\n passThreshold?: number\n /** Provenance for dataset tracking. */\n datasetVersion?: string\n /** Free-form tags (difficulty, category, etc.). */\n tags?: Record<string, string>\n}\n\nexport interface TestGradedRunOptions {\n variantId?: string\n driver?: SandboxDriver\n /** Metadata recorded on the Run (codeSha, promptSha, modelFingerprint, seed). */\n provenance?: Pick<Run, 'codeSha' | 'promptSha' | 'modelFingerprint' | 'seed' | 'envFingerprint'>\n}\n\nexport interface TestGradedRunResult {\n runId: string\n scenario: TestGradedScenario\n harness: SandboxHarnessResult\n pass: boolean\n score: number\n failureClass?: FailureClass\n}\n\nexport async function runTestGradedScenario(\n scenario: TestGradedScenario,\n store: TraceStore,\n options: TestGradedRunOptions = {},\n): Promise<TestGradedRunResult> {\n const emitter = new TraceEmitter(store)\n await emitter.startRun({\n scenarioId: scenario.id,\n variantId: options.variantId,\n datasetVersion: scenario.datasetVersion,\n tags: scenario.tags,\n ...options.provenance,\n })\n const harness = new SandboxHarness(options.driver)\n const result = await harness.run(scenario.harness, emitter)\n const threshold = scenario.passThreshold ?? 1.0\n const pass = result.passed && result.score >= threshold\n const setupFailed = result.setup !== undefined && result.setup.exitCode !== 0\n const runFailed = result.run !== undefined && result.run.exitCode !== 0\n const testFailed = result.test !== undefined && result.test.exitCode !== 0\n const failureClass: FailureClass | undefined = pass\n ? 'success'\n : setupFailed || runFailed\n ? 'sandbox_failure'\n : testFailed\n ? 'format_drift'\n : 'unknown'\n await emitter.endRun({\n pass,\n score: result.score,\n failureClass,\n notes: pass ? undefined : reasonForFailure(result),\n })\n return {\n runId: emitter.runId,\n scenario,\n harness: result,\n pass,\n score: result.score,\n failureClass,\n }\n}\n\nfunction reasonForFailure(result: SandboxHarnessResult): string {\n if (result.setup && result.setup.exitCode !== 0)\n return `setup failed: exit ${result.setup.exitCode}`\n if (result.run && result.run.exitCode !== 0) return `run failed: exit ${result.run.exitCode}`\n if (result.test) {\n if (result.test.testsTotal !== undefined) {\n return `tests: ${result.test.testsPassed ?? 0}/${result.test.testsTotal}`\n }\n return `test exit ${result.test.exitCode}`\n }\n return 'no test command'\n}\n"],"mappings":"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","names":[]}
|
|
@@ -32,7 +32,8 @@ function redactHeaders(headers, prefix, redactedFields) {
|
|
|
32
32
|
}
|
|
33
33
|
function redactBody(value, pathStr, redactedFields) {
|
|
34
34
|
if (value == null) return value;
|
|
35
|
-
if (Array.isArray(value))
|
|
35
|
+
if (Array.isArray(value))
|
|
36
|
+
return value.map((v, i) => redactBody(v, `${pathStr}[${i}]`, redactedFields));
|
|
36
37
|
if (typeof value === "object") {
|
|
37
38
|
const out = {};
|
|
38
39
|
for (const [k, v] of Object.entries(value)) {
|
|
@@ -104,7 +105,8 @@ var FileSystemRawProviderSink = class {
|
|
|
104
105
|
async record(event) {
|
|
105
106
|
await this.ensureInit();
|
|
106
107
|
const redacted = this.redactor({ ...event, redactedFields: event.redactedFields ?? [] });
|
|
107
|
-
const line = JSON.stringify(redacted)
|
|
108
|
+
const line = `${JSON.stringify(redacted)}
|
|
109
|
+
`;
|
|
108
110
|
if (this.bytesWritten + line.length > this.rollAtBytes && this.bytesWritten > 0) {
|
|
109
111
|
this.rollIndex += 1;
|
|
110
112
|
this.bytesWritten = 0;
|
|
@@ -130,7 +132,8 @@ var FileSystemRawProviderSink = class {
|
|
|
130
132
|
if (filter.runId !== void 0 && event.runId !== filter.runId) continue;
|
|
131
133
|
if (filter.spanId !== void 0 && event.spanId !== filter.spanId) continue;
|
|
132
134
|
if (filter.direction !== void 0 && event.direction !== filter.direction) continue;
|
|
133
|
-
if (filter.attemptIndex !== void 0 && event.attemptIndex !== filter.attemptIndex)
|
|
135
|
+
if (filter.attemptIndex !== void 0 && event.attemptIndex !== filter.attemptIndex)
|
|
136
|
+
continue;
|
|
134
137
|
out.push(event);
|
|
135
138
|
}
|
|
136
139
|
}
|
|
@@ -160,4 +163,4 @@ export {
|
|
|
160
163
|
FileSystemRawProviderSink,
|
|
161
164
|
providerFromBaseUrl
|
|
162
165
|
};
|
|
163
|
-
//# sourceMappingURL=chunk-
|
|
166
|
+
//# sourceMappingURL=chunk-PC4UYEBM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/trace/raw-provider-sink.ts"],"sourcesContent":["/**\n * RawProviderSink — first-class persistence for the actual HTTP-level\n * request/response bodies of every LLM provider call.\n *\n * Why this is a separate sink from the structured `LlmSpan`:\n *\n * - `LlmSpan` records the *intent* — model name, messages, output text,\n * usage. It's what dashboards read; it's NOT enough for forensics.\n * - When a downstream consumer reports \"the verifier used the wrong route\"\n * or \"tokens look right but reasoning was missing,\" the only way to\n * answer is the raw HTTP body. Span fields can lie (a proxy can echo\n * a different `model` value than what actually answered); the raw\n * response is ground truth.\n *\n * Default behaviour: opt-in. Pass `rawSink` to `LlmClientOptions` (or the\n * matrix runner / BuilderSession sets it up automatically) and every\n * request, response, and error is recorded — including retries, with the\n * attempt index attached so a flaky call's full event chain is recoverable.\n *\n * Redaction is enforced at sink time. The default redactor strips\n * `Authorization`, `X-Api-Key`, `X-Auth-Token`, `Cookie` headers and any\n * payload field whose key matches `apiKey | api_key | bearer | password |\n * secret | token` (case-insensitive). Override via the sink constructor or\n * the per-call `redactor`. The `redactedFields` array on the persisted\n * event lets a reviewer see what was stripped without exposing the values.\n */\n\nimport { promises as fs } from 'node:fs'\nimport * as path from 'node:path'\n\nexport type RawProviderDirection = 'request' | 'response' | 'error'\n\nexport interface RawProviderEvent {\n /** Stable id. Generated by the sink if omitted. */\n eventId: string\n /** Trace context populated by `LlmClient` when the call is wrapped in a span. */\n runId?: string\n spanId?: string\n /**\n * Logical provider name. Free-form so callers can use whatever id matches\n * their topology (`'openai'`, `'anthropic'`, `'tangle-router'`, …). When\n * omitted, derived from `baseUrl` in `LlmClientOptions`.\n */\n provider: string\n model: string\n /** Endpoint path, e.g. `'/v1/chat/completions'`. */\n endpoint: string\n /** Base URL used for the call (already-normalised — no trailing slash). */\n baseUrl: string\n /** 0-indexed retry attempt. The first attempt is 0; a retried call gets 1, 2, … */\n attemptIndex: number\n direction: RawProviderDirection\n /** Unix ms. */\n timestamp: number\n /** Wall-clock duration of the call leg. Set on `response` and `error` events; null on `request`. */\n durationMs?: number\n statusCode?: number\n requestHeaders?: Record<string, string>\n requestBody?: unknown\n responseHeaders?: Record<string, string>\n responseBody?: unknown\n /** Set on `direction: 'error'` events. */\n errorMessage?: string\n /** Field paths the redactor stripped from this event ('header:Authorization', 'body.apiKey', …). */\n redactedFields: string[]\n}\n\nexport interface RawProviderSinkFilter {\n runId?: string\n spanId?: string\n direction?: RawProviderDirection\n attemptIndex?: number\n}\n\nexport interface RawProviderSink {\n record(event: RawProviderEvent): Promise<void>\n /** Optional listing — implementations that durably persist (file, db) should support this. */\n list?(filter?: RawProviderSinkFilter): Promise<RawProviderEvent[]>\n /** Optional teardown for backed implementations. */\n close?(): Promise<void>\n}\n\nexport type ProviderRedactor = (event: RawProviderEvent) => RawProviderEvent\n\nconst REDACTED_HEADER_NAMES = new Set([\n 'authorization',\n 'x-api-key',\n 'x-auth-token',\n 'cookie',\n 'set-cookie',\n 'proxy-authorization',\n])\n\nconst REDACTED_BODY_KEY =\n /^(api[_-]?key|bearer|password|secret|token|access[_-]?token|refresh[_-]?token)$/i\n\n/**\n * Default redactor — strips well-known auth headers and any body field whose\n * key matches the credential pattern. Records every redacted path on\n * `event.redactedFields` so a downstream reviewer can see what was removed.\n */\nexport function defaultProviderRedactor(event: RawProviderEvent): RawProviderEvent {\n const redactedFields: string[] = [...(event.redactedFields ?? [])]\n const requestHeaders = redactHeaders(event.requestHeaders, 'request', redactedFields)\n const responseHeaders = redactHeaders(event.responseHeaders, 'response', redactedFields)\n const requestBody = redactBody(event.requestBody, 'requestBody', redactedFields)\n const responseBody = redactBody(event.responseBody, 'responseBody', redactedFields)\n return { ...event, requestHeaders, responseHeaders, requestBody, responseBody, redactedFields }\n}\n\nfunction redactHeaders(\n headers: Record<string, string> | undefined,\n prefix: 'request' | 'response',\n redactedFields: string[],\n): Record<string, string> | undefined {\n if (!headers) return headers\n const out: Record<string, string> = {}\n for (const [k, v] of Object.entries(headers)) {\n if (REDACTED_HEADER_NAMES.has(k.toLowerCase())) {\n redactedFields.push(`${prefix}Headers.${k}`)\n continue\n }\n out[k] = v\n }\n return out\n}\n\nfunction redactBody(value: unknown, pathStr: string, redactedFields: string[]): unknown {\n if (value == null) return value\n if (Array.isArray(value))\n return value.map((v, i) => redactBody(v, `${pathStr}[${i}]`, redactedFields))\n if (typeof value === 'object') {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (REDACTED_BODY_KEY.test(k)) {\n redactedFields.push(`${pathStr}.${k}`)\n continue\n }\n out[k] = redactBody(v, `${pathStr}.${k}`, redactedFields)\n }\n return out\n }\n return value\n}\n\n// ── In-memory ────────────────────────────────────────────────────────────\n\nexport interface InMemoryRawProviderSinkOptions {\n redactor?: ProviderRedactor\n}\n\nexport class InMemoryRawProviderSink implements RawProviderSink {\n private events: RawProviderEvent[] = []\n private redactor: ProviderRedactor\n\n constructor(opts: InMemoryRawProviderSinkOptions = {}) {\n this.redactor = opts.redactor ?? defaultProviderRedactor\n }\n\n async record(event: RawProviderEvent): Promise<void> {\n this.events.push(this.redactor({ ...event, redactedFields: event.redactedFields ?? [] }))\n }\n\n async list(filter: RawProviderSinkFilter = {}): Promise<RawProviderEvent[]> {\n return this.events.filter(\n (e) =>\n (filter.runId === undefined || e.runId === filter.runId) &&\n (filter.spanId === undefined || e.spanId === filter.spanId) &&\n (filter.direction === undefined || e.direction === filter.direction) &&\n (filter.attemptIndex === undefined || e.attemptIndex === filter.attemptIndex),\n )\n }\n\n size(): number {\n return this.events.length\n }\n}\n\nexport class NoopRawProviderSink implements RawProviderSink {\n async record(): Promise<void> {\n /* no-op */\n }\n /**\n * Returns an empty array. Implemented so `assertRunCaptured` does not\n * trip the `no_raw_sink` issue when a caller explicitly opts out of\n * capture by passing this sink — opt-out is a deliberate choice, not a\n * misconfiguration.\n */\n async list(): Promise<RawProviderEvent[]> {\n return []\n }\n}\n\n// ── Filesystem (NDJSON) ──────────────────────────────────────────────────\n\nexport interface FileSystemRawProviderSinkOptions {\n /** Directory the NDJSON file is written into. Created if missing. */\n dir: string\n /** File name; default `'raw-provider-events.ndjson'`. */\n fileName?: string\n /** Bytes after which the writer rolls over to a new file (default 32 MiB). */\n rollAtBytes?: number\n redactor?: ProviderRedactor\n}\n\nexport class FileSystemRawProviderSink implements RawProviderSink {\n private dir: string\n private fileName: string\n private rollAtBytes: number\n private redactor: ProviderRedactor\n private bytesWritten = 0\n private rollIndex = 0\n private initPromise: Promise<void> | null = null\n\n constructor(opts: FileSystemRawProviderSinkOptions) {\n this.dir = opts.dir\n this.fileName = opts.fileName ?? 'raw-provider-events.ndjson'\n this.rollAtBytes = opts.rollAtBytes ?? 32 * 1024 * 1024\n this.redactor = opts.redactor ?? defaultProviderRedactor\n }\n\n private async ensureInit(): Promise<void> {\n if (!this.initPromise) {\n this.initPromise = fs.mkdir(this.dir, { recursive: true }).then(() => undefined)\n }\n await this.initPromise\n }\n\n private currentPath(): string {\n if (this.rollIndex === 0) return path.join(this.dir, this.fileName)\n return path.join(this.dir, `${this.fileName}.${this.rollIndex}`)\n }\n\n async record(event: RawProviderEvent): Promise<void> {\n await this.ensureInit()\n const redacted = this.redactor({ ...event, redactedFields: event.redactedFields ?? [] })\n const line = `${JSON.stringify(redacted)}\\n`\n if (this.bytesWritten + line.length > this.rollAtBytes && this.bytesWritten > 0) {\n this.rollIndex += 1\n this.bytesWritten = 0\n }\n await fs.appendFile(this.currentPath(), line, 'utf8')\n this.bytesWritten += line.length\n }\n\n async list(filter: RawProviderSinkFilter = {}): Promise<RawProviderEvent[]> {\n await this.ensureInit()\n const out: RawProviderEvent[] = []\n for (let i = 0; i <= this.rollIndex; i++) {\n const file =\n i === 0 ? path.join(this.dir, this.fileName) : path.join(this.dir, `${this.fileName}.${i}`)\n let body: string\n try {\n body = await fs.readFile(file, 'utf8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') continue\n throw err\n }\n for (const line of body.split('\\n')) {\n if (!line) continue\n const event = JSON.parse(line) as RawProviderEvent\n if (filter.runId !== undefined && event.runId !== filter.runId) continue\n if (filter.spanId !== undefined && event.spanId !== filter.spanId) continue\n if (filter.direction !== undefined && event.direction !== filter.direction) continue\n if (filter.attemptIndex !== undefined && event.attemptIndex !== filter.attemptIndex)\n continue\n out.push(event)\n }\n }\n return out\n }\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────\n\n/**\n * Best-effort provider id from a base URL. Falls back to the URL host when\n * none of the well-known patterns match.\n */\nexport function providerFromBaseUrl(baseUrl: string): string {\n const lower = baseUrl.toLowerCase()\n if (lower.includes('api.openai.com')) return 'openai'\n if (lower.includes('api.anthropic.com')) return 'anthropic'\n if (lower.includes('generativelanguage.googleapis.com')) return 'google'\n if (lower.includes('api.together.ai') || lower.includes('api.together.xyz')) return 'together'\n if (lower.includes('api.deepseek.com')) return 'deepseek'\n if (lower.includes('router.tangle.tools')) return 'tangle-router'\n if (lower.includes('api.litellm') || lower.includes('litellm')) return 'litellm'\n try {\n return new URL(baseUrl).host\n } catch {\n return baseUrl\n }\n}\n"],"mappings":"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","names":[]}
|