margin-meter 0.2.0 → 0.4.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/README.md +292 -2
- package/dist/batching.d.ts +94 -0
- package/dist/batching.d.ts.map +1 -0
- package/dist/batching.js +181 -0
- package/dist/batching.js.map +1 -0
- package/dist/braintrust.d.ts +153 -0
- package/dist/braintrust.d.ts.map +1 -0
- package/dist/braintrust.js +424 -0
- package/dist/braintrust.js.map +1 -0
- package/dist/capture.d.ts +28 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +177 -0
- package/dist/capture.js.map +1 -0
- package/dist/index.d.ts +334 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +697 -23
- package/dist/index.js.map +1 -1
- package/dist/instrument.d.ts +8 -7
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +8 -7
- package/dist/instrument.js.map +1 -1
- package/dist/langsmith.d.ts +150 -0
- package/dist/langsmith.d.ts.map +1 -0
- package/dist/langsmith.js +415 -0
- package/dist/langsmith.js.map +1 -0
- package/dist/promptfoo.d.ts +161 -0
- package/dist/promptfoo.d.ts.map +1 -0
- package/dist/promptfoo.js +446 -0
- package/dist/promptfoo.js.map +1 -0
- package/dist/providers.d.ts +36 -0
- package/dist/providers.d.ts.map +1 -1
- package/dist/providers.js +229 -3
- package/dist/providers.js.map +1 -1
- package/dist/vitest.d.ts +232 -0
- package/dist/vitest.d.ts.map +1 -0
- package/dist/vitest.js +502 -0
- package/dist/vitest.js.map +1 -0
- package/package.json +24 -6
package/dist/vitest.js
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* margin-meter/vitest — turn a vitest/jest JSON result file into outcomes.
|
|
3
|
+
*
|
|
4
|
+
* The TypeScript test-runner adapter, the analog of the Python
|
|
5
|
+
* `margin_meter.pytest_plugin`. A unit-test suite is already an eval harness:
|
|
6
|
+
* each test is a pass/fail verdict graded by code assertions against a
|
|
7
|
+
* known-correct expectation — a *ground-truth*, *code* grade, which is exactly
|
|
8
|
+
* the honest denominator Margin's outcome intake wants
|
|
9
|
+
* (`docs/INTEGRATION_LADDER.md` rung 2, and rung 6's grading provenance). So the
|
|
10
|
+
* whole of rung 2 — "their evals report pass/fail" — is available for the most
|
|
11
|
+
* common JS/TS grading path with no hand-written `POST`.
|
|
12
|
+
*
|
|
13
|
+
* ## One adapter, two frameworks
|
|
14
|
+
*
|
|
15
|
+
* This is a JSON-results CONSUMER, not a runtime reporter, because that reads a
|
|
16
|
+
* file the customer already produces and drags in no test-framework dependency —
|
|
17
|
+
* the same design as the promptfoo/braintrust/langsmith adapters. Both dominant
|
|
18
|
+
* JS test runners emit the SAME jest-compatible JSON, so one adapter covers
|
|
19
|
+
* both:
|
|
20
|
+
*
|
|
21
|
+
* vitest run --reporter=json --outputFile=results.json # vitest
|
|
22
|
+
* jest --json --outputFile=results.json # jest
|
|
23
|
+
*
|
|
24
|
+
* import { MarginMeter, vitest } from "margin-meter";
|
|
25
|
+
* const meter = new MarginMeter(); // MARGIN_INGEST_URL/KEY from env
|
|
26
|
+
* const data = JSON.parse(await readFile("results.json", "utf8"));
|
|
27
|
+
* await vitest.recordResults(meter, data, "my-test-suite");
|
|
28
|
+
*
|
|
29
|
+
* The mapping mirrors the pytest plugin's, on purpose — the SDKs must not drift:
|
|
30
|
+
*
|
|
31
|
+
* - `passed` ← the assertion `status`: `passed` → true, `failed` →
|
|
32
|
+
* false. Any other status (`skipped`/`todo`/`pending`/`disabled`) is NOT a
|
|
33
|
+
* verdict and is SKIPPED — recording a skip as a pass or a fail would
|
|
34
|
+
* fabricate an outcome the suite never produced.
|
|
35
|
+
* - `qualityScore` ← `1.0` / `0.0` (a binary ground-truth grade). Always
|
|
36
|
+
* supplied: the ingest layer discards `quality_method`/`grader_kind` on a
|
|
37
|
+
* scoreless outcome, so the score is what lights the grading provenance.
|
|
38
|
+
* - `qualityMethod`/`graderKind` ← `ground_truth`/`code`, ASSERTED, never
|
|
39
|
+
* inferred — a code assertion IS a ground-truth code grade, exactly as the
|
|
40
|
+
* pytest plugin asserts. (Unlike promptfoo/braintrust, which grade several
|
|
41
|
+
* ways and must derive the signature; a unit test is unambiguously code.)
|
|
42
|
+
* - `link` ← the assertion `fullName`, else its ancestor titles + its
|
|
43
|
+
* title, else the file name + row index — for traceability back to the exact
|
|
44
|
+
* test. Truncated to 512 (a lossy link beats a lost outcome).
|
|
45
|
+
* - a test FILE that crashed on load/collection (a failing file status with no
|
|
46
|
+
* per-test results) records no outcome — there is no honest count of the
|
|
47
|
+
* tests it would have produced — but it WARNS, so a fully broken run is not
|
|
48
|
+
* silently read as a clean one.
|
|
49
|
+
*
|
|
50
|
+
* ## The honest limit — rung 8 is not reachable here, and that is stated, not defaulted
|
|
51
|
+
*
|
|
52
|
+
* The jest/vitest JSON format carries no per-test value field, so a
|
|
53
|
+
* value-per-outcome numerator (rung 8) cannot ride this FILE path. Absent is
|
|
54
|
+
* the honest cost-per-outcome default — never coerced to `0`. The route to
|
|
55
|
+
* rung 8 from a test suite is the RUNTIME path (`@pytest.mark.margin_value` in
|
|
56
|
+
* Python), and it lives in this same module: {@link MarginVitestReporter} runs
|
|
57
|
+
* inside the suite and reads `task.meta.marginValue`. Use the file consumer
|
|
58
|
+
* when you already have a results file; use the reporter when you want value.
|
|
59
|
+
*
|
|
60
|
+
* Two design promises, same as the sibling adapters: it consumes the file the
|
|
61
|
+
* customer already produces (no new harness), and it is fail-safe — a delivery
|
|
62
|
+
* fault on one row warns and is counted, never aborting the batch.
|
|
63
|
+
*/
|
|
64
|
+
// Grading provenance strings — mirror `src/margin/grading.py`, kept as literals
|
|
65
|
+
// so the SDK has no dependency on the server package. A unit test is always a
|
|
66
|
+
// code-graded ground-truth verdict, so unlike the model-graded adapters there is
|
|
67
|
+
// no signature to derive.
|
|
68
|
+
const GROUND_TRUTH = "ground_truth";
|
|
69
|
+
const GRADER_CODE = "code";
|
|
70
|
+
// `Outcome.link` is a `String(512)`; a longer link is truncated, not dropped (a
|
|
71
|
+
// lossy link beats a lost outcome) — same rule as the pytest adapter.
|
|
72
|
+
const MAX_LINK_LEN = 512;
|
|
73
|
+
const defaultWarn = (message) => {
|
|
74
|
+
// `process` may be absent in a non-Node runtime; fall back to console.
|
|
75
|
+
const p = globalThis
|
|
76
|
+
.process;
|
|
77
|
+
if (p && typeof p.emitWarning === "function")
|
|
78
|
+
p.emitWarning(message);
|
|
79
|
+
else
|
|
80
|
+
console.warn(message);
|
|
81
|
+
};
|
|
82
|
+
function isObject(x) {
|
|
83
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
84
|
+
}
|
|
85
|
+
/** Map a jest/vitest assertion `status` to a pass/fail verdict, or `undefined`
|
|
86
|
+
* when the status is not a verdict at all (`skipped`/`todo`/`pending`/
|
|
87
|
+
* `disabled`/unknown). A skip is neither a pass nor a fail, so it is dropped
|
|
88
|
+
* rather than fabricated. */
|
|
89
|
+
export function verdictFromStatus(status) {
|
|
90
|
+
if (status === "passed")
|
|
91
|
+
return true;
|
|
92
|
+
if (status === "failed")
|
|
93
|
+
return false;
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
/** A traceable link back to the exact test: the assertion's `fullName` if the
|
|
97
|
+
* runner wrote one, else its ancestor titles joined with its title, else the
|
|
98
|
+
* test file name plus the row index. */
|
|
99
|
+
export function linkFromAssertion(assertion, fileName, index) {
|
|
100
|
+
if (isObject(assertion)) {
|
|
101
|
+
const full = assertion.fullName;
|
|
102
|
+
if (typeof full === "string" && full.trim()) {
|
|
103
|
+
return full.trim().slice(0, MAX_LINK_LEN);
|
|
104
|
+
}
|
|
105
|
+
const parts = [];
|
|
106
|
+
const ancestors = assertion.ancestorTitles;
|
|
107
|
+
if (Array.isArray(ancestors)) {
|
|
108
|
+
for (const t of ancestors)
|
|
109
|
+
if (typeof t === "string" && t)
|
|
110
|
+
parts.push(t);
|
|
111
|
+
}
|
|
112
|
+
const title = assertion.title;
|
|
113
|
+
if (typeof title === "string" && title.trim())
|
|
114
|
+
parts.push(title.trim());
|
|
115
|
+
if (parts.length > 0)
|
|
116
|
+
return parts.join(" > ").slice(0, MAX_LINK_LEN);
|
|
117
|
+
}
|
|
118
|
+
const base = typeof fileName === "string" && fileName ? fileName : "vitest";
|
|
119
|
+
return `${base}:${index}`.slice(0, MAX_LINK_LEN);
|
|
120
|
+
}
|
|
121
|
+
/** Flatten a parsed vitest/jest JSON output to a per-test list. The canonical
|
|
122
|
+
* shape is `{ testResults: [{ name, assertionResults: [...] }] }` (jest's
|
|
123
|
+
* `--json`, vitest's `--reporter=json`); a bare array is treated as an
|
|
124
|
+
* already-flattened list of assertion results, for convenience and testing. */
|
|
125
|
+
export function extractAssertions(data) {
|
|
126
|
+
const out = [];
|
|
127
|
+
if (Array.isArray(data)) {
|
|
128
|
+
for (const a of data)
|
|
129
|
+
if (isObject(a))
|
|
130
|
+
out.push({ assertion: a });
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
if (!isObject(data))
|
|
134
|
+
return out;
|
|
135
|
+
const files = data.testResults;
|
|
136
|
+
if (!Array.isArray(files))
|
|
137
|
+
return out;
|
|
138
|
+
for (const file of files) {
|
|
139
|
+
if (!isObject(file))
|
|
140
|
+
continue;
|
|
141
|
+
const fileName = typeof file.name === "string" ? file.name : undefined;
|
|
142
|
+
const asserts = file.assertionResults;
|
|
143
|
+
if (!Array.isArray(asserts))
|
|
144
|
+
continue;
|
|
145
|
+
for (const a of asserts) {
|
|
146
|
+
if (isObject(a))
|
|
147
|
+
out.push({ assertion: a, fileName });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
/** File-status values jest/vitest use to mark a suite that FAILED rather than a
|
|
153
|
+
* normal per-test failure. A file that crashed on load/collection emits a
|
|
154
|
+
* `testResults` entry with a failing status and NO `assertionResults` — the
|
|
155
|
+
* error lands in `message`/`failureMessage`, not a per-test verdict. */
|
|
156
|
+
const FAILED_FILE_STATUSES = new Set(["failed", "error"]);
|
|
157
|
+
/** Names of test files that failed with no per-test results — a suite that
|
|
158
|
+
* crashed on load/collection. These carry no recordable verdict (there is no
|
|
159
|
+
* honest count of tests a crashed suite "would have" produced), so they are NOT
|
|
160
|
+
* turned into outcomes; {@link recordResults} warns for each so a fully broken
|
|
161
|
+
* run is not silently read as a clean one — the whole point of an honest
|
|
162
|
+
* denominator. */
|
|
163
|
+
export function failedEmptySuites(data) {
|
|
164
|
+
const out = [];
|
|
165
|
+
if (!isObject(data))
|
|
166
|
+
return out;
|
|
167
|
+
const files = data.testResults;
|
|
168
|
+
if (!Array.isArray(files))
|
|
169
|
+
return out;
|
|
170
|
+
for (const file of files) {
|
|
171
|
+
if (!isObject(file))
|
|
172
|
+
continue;
|
|
173
|
+
const asserts = file.assertionResults;
|
|
174
|
+
const hasRows = Array.isArray(asserts) && asserts.some(isObject);
|
|
175
|
+
if (hasRows)
|
|
176
|
+
continue;
|
|
177
|
+
const status = typeof file.status === "string" ? file.status : "";
|
|
178
|
+
const msg = file.message ?? file.failureMessage;
|
|
179
|
+
const failed = FAILED_FILE_STATUSES.has(status) ||
|
|
180
|
+
(typeof msg === "string" && msg.trim() !== "");
|
|
181
|
+
if (failed) {
|
|
182
|
+
const name = typeof file.name === "string" && file.name ? file.name : "<unnamed suite>";
|
|
183
|
+
out.push(name);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
/** Translate one code-graded test verdict into `MarginMeter.recordOutcome`
|
|
189
|
+
* input. The grader signature is asserted (`ground_truth`/`code`) because a
|
|
190
|
+
* unit test is unambiguously a code grade. `valueUsd` rides only when supplied
|
|
191
|
+
* (never defaulted to 0). */
|
|
192
|
+
export function outcomeInput(args) {
|
|
193
|
+
const input = {
|
|
194
|
+
workflowId: args.workflowId,
|
|
195
|
+
passed: Boolean(args.passed),
|
|
196
|
+
qualityScore: args.passed ? 1.0 : 0.0,
|
|
197
|
+
qualityMethod: GROUND_TRUTH,
|
|
198
|
+
graderKind: GRADER_CODE,
|
|
199
|
+
link: String(args.link).slice(0, MAX_LINK_LEN),
|
|
200
|
+
isSimulated: Boolean(args.isSimulated),
|
|
201
|
+
};
|
|
202
|
+
if (args.valueUsd !== undefined)
|
|
203
|
+
input.valueUsd = Number(args.valueUsd);
|
|
204
|
+
return input;
|
|
205
|
+
}
|
|
206
|
+
/** Full translation of one flattened assertion, or `undefined` if its status is
|
|
207
|
+
* not a real verdict (a skip/todo/pending is dropped, never fabricated). */
|
|
208
|
+
export function assertionToOutcomeInput(item, opts) {
|
|
209
|
+
const index = opts.index ?? 0;
|
|
210
|
+
const passed = verdictFromStatus(item.assertion.status);
|
|
211
|
+
if (passed === undefined)
|
|
212
|
+
return undefined;
|
|
213
|
+
return outcomeInput({
|
|
214
|
+
workflowId: opts.workflowId,
|
|
215
|
+
passed,
|
|
216
|
+
link: linkFromAssertion(item.assertion, item.fileName, index),
|
|
217
|
+
isSimulated: opts.isSimulated,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/** Yield `recordOutcome` inputs for every real verdict in a parsed vitest/jest
|
|
221
|
+
* output (any envelope shape {@link extractAssertions} understands). */
|
|
222
|
+
export function iterOutcomes(data, opts) {
|
|
223
|
+
const out = [];
|
|
224
|
+
const items = extractAssertions(data);
|
|
225
|
+
for (let i = 0; i < items.length; i++) {
|
|
226
|
+
const input = assertionToOutcomeInput(items[i], {
|
|
227
|
+
workflowId: opts.workflowId,
|
|
228
|
+
index: i,
|
|
229
|
+
isSimulated: opts.isSimulated,
|
|
230
|
+
});
|
|
231
|
+
if (input !== undefined)
|
|
232
|
+
out.push(input);
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
}
|
|
236
|
+
/** Emit an outcome per verdict in a parsed vitest/jest output through `meter`.
|
|
237
|
+
* Fail-safe: a delivery fault on one row warns and counts an error; it never
|
|
238
|
+
* aborts the batch. Non-verdict rows (skips/todos/pending) are counted as
|
|
239
|
+
* skipped. */
|
|
240
|
+
export async function recordResults(meter, data, workflowId, opts = {}) {
|
|
241
|
+
const onWarn = opts.onWarn ?? defaultWarn;
|
|
242
|
+
// A suite that crashed on load emits a failing file entry with no per-test
|
|
243
|
+
// rows; those produce nothing below, so warn here or a broken run reads clean.
|
|
244
|
+
for (const name of failedEmptySuites(data)) {
|
|
245
|
+
onWarn(`test file "${name}" failed with no test results (suite load/collection ` +
|
|
246
|
+
"error?) — its failure is NOT recorded as an outcome");
|
|
247
|
+
}
|
|
248
|
+
const items = extractAssertions(data);
|
|
249
|
+
let recorded = 0;
|
|
250
|
+
let errors = 0;
|
|
251
|
+
let skipped = 0;
|
|
252
|
+
for (let i = 0; i < items.length; i++) {
|
|
253
|
+
const input = assertionToOutcomeInput(items[i], {
|
|
254
|
+
workflowId,
|
|
255
|
+
index: i,
|
|
256
|
+
isSimulated: opts.isSimulated,
|
|
257
|
+
});
|
|
258
|
+
if (input === undefined) {
|
|
259
|
+
skipped++;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
await meter.recordOutcome(input);
|
|
264
|
+
recorded++;
|
|
265
|
+
}
|
|
266
|
+
catch (exc) {
|
|
267
|
+
// Never let a delivery fault abort the batch. (The meter is fail-safe by
|
|
268
|
+
// default and resolves rather than rejects; this guards a strict meter.)
|
|
269
|
+
errors++;
|
|
270
|
+
onWarn(`failed to record outcome for ${input.link}: ${exc.message}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
await meter.flush();
|
|
275
|
+
}
|
|
276
|
+
catch (exc) {
|
|
277
|
+
onWarn(`flush failed: ${exc.message}`);
|
|
278
|
+
}
|
|
279
|
+
return { recorded, errors, skipped };
|
|
280
|
+
}
|
|
281
|
+
// =========================================================================== //
|
|
282
|
+
// The RUNTIME reporter — the route to rung 8 (value-per-outcome) from a test
|
|
283
|
+
// suite, the TS analog of pytest's `@pytest.mark.margin_value(<usd>)`. The
|
|
284
|
+
// JSON-file consumer above cannot reach it: the results file carries no per-test
|
|
285
|
+
// value. A vitest reporter runs INSIDE the suite, so it can read a value a test
|
|
286
|
+
// attached to `task.meta`.
|
|
287
|
+
//
|
|
288
|
+
// // vitest.config.ts
|
|
289
|
+
// import { vitest as m } from "margin-meter";
|
|
290
|
+
// export default {
|
|
291
|
+
// test: { reporters: ["default", new m.MarginVitestReporter({ workflowId: "my-suite" })] },
|
|
292
|
+
// };
|
|
293
|
+
//
|
|
294
|
+
// // in a test — tag its business value (optional; absent = cost-per-outcome)
|
|
295
|
+
// import { getCurrentTest } from "vitest";
|
|
296
|
+
// test("resolves the ticket", () => { getCurrentTest().meta.marginValue = 120; /* ... */ });
|
|
297
|
+
// =========================================================================== //
|
|
298
|
+
// Value coercion — mirrors promptfoo.ts / braintrust.ts EXACTLY so a stringly-
|
|
299
|
+
// typed tag is treated the same across every adapter and neither SDK lets a
|
|
300
|
+
// non-number, non-finite, or implausibly large value through (a loss is kept, MAR-1198). A
|
|
301
|
+
// finite DECIMAL only: deliberately narrower than JS `Number()`, which also
|
|
302
|
+
// parses `0x10`/`Infinity`/`""` — a `marginValue` typo must be a DROPPED value,
|
|
303
|
+
// never a silent $16.
|
|
304
|
+
const VALUE_KEY = "marginValue";
|
|
305
|
+
const DECIMAL_RE = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
|
|
306
|
+
// Mirror `src/margin/ingest.MAX_VALUE_USD` — a value above this is rejected by
|
|
307
|
+
// the server, so reject it here rather than emitting a doomed row.
|
|
308
|
+
const MAX_VALUE_USD = 10_000_000.0;
|
|
309
|
+
function coerceValue(raw) {
|
|
310
|
+
if (typeof raw === "boolean")
|
|
311
|
+
throw new Error(`${VALUE_KEY} must be a number, got ${JSON.stringify(raw)}`);
|
|
312
|
+
let val;
|
|
313
|
+
if (typeof raw === "number") {
|
|
314
|
+
val = raw;
|
|
315
|
+
}
|
|
316
|
+
else if (typeof raw === "string") {
|
|
317
|
+
const s = raw.trim();
|
|
318
|
+
if (!DECIMAL_RE.test(s))
|
|
319
|
+
throw new Error(`${VALUE_KEY} must be a number, got ${JSON.stringify(raw)}`);
|
|
320
|
+
val = Number(s);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
throw new Error(`${VALUE_KEY} must be a number, got ${JSON.stringify(raw)}`);
|
|
324
|
+
}
|
|
325
|
+
if (!Number.isFinite(val))
|
|
326
|
+
throw new Error(`${VALUE_KEY} must be a finite number, got ${JSON.stringify(raw)}`);
|
|
327
|
+
// A loss is a real value (MAR-1198): ingest takes any finite amount within +/-MAX.
|
|
328
|
+
if (Math.abs(val) > MAX_VALUE_USD)
|
|
329
|
+
throw new Error(`${VALUE_KEY} implausible (|value| > ${MAX_VALUE_USD}), got ${val}`);
|
|
330
|
+
return val;
|
|
331
|
+
}
|
|
332
|
+
/** Read an opt-in `marginValue` off a task's `meta` — the value-per-outcome
|
|
333
|
+
* numerator (rung 8). Returns the USD amount, or `undefined` when the key is
|
|
334
|
+
* absent (the normal cost-per-outcome case). Throws on a malformed value
|
|
335
|
+
* (including an explicit `null`, i.e. present-but-malformed) so the caller can
|
|
336
|
+
* warn and drop it while keeping the pass/fail.
|
|
337
|
+
*
|
|
338
|
+
* An explicit `undefined` (`meta.marginValue = undefined`) is treated as absent
|
|
339
|
+
* — setting a meta key to `undefined` is not a value a customer meant to
|
|
340
|
+
* supply. This is the one intentional divergence from the file-adapters'
|
|
341
|
+
* `valueFrom*`, and it only ever drops-silently rather than fabricating. */
|
|
342
|
+
export function valueFromTaskMeta(meta) {
|
|
343
|
+
if (!isObject(meta))
|
|
344
|
+
return undefined;
|
|
345
|
+
if (!(VALUE_KEY in meta))
|
|
346
|
+
return undefined;
|
|
347
|
+
const raw = meta[VALUE_KEY];
|
|
348
|
+
if (raw === undefined)
|
|
349
|
+
return undefined;
|
|
350
|
+
return coerceValue(raw);
|
|
351
|
+
}
|
|
352
|
+
/** Map a vitest task result state to a pass/fail verdict, or `undefined` when it
|
|
353
|
+
* is not a verdict (`skip`/`todo`/`queued`/absent — a skipped or not-yet-run
|
|
354
|
+
* test is neither a pass nor a fail, so it is dropped, never fabricated). */
|
|
355
|
+
export function verdictFromState(state) {
|
|
356
|
+
if (state === "pass")
|
|
357
|
+
return true;
|
|
358
|
+
if (state === "fail")
|
|
359
|
+
return false;
|
|
360
|
+
return undefined;
|
|
361
|
+
}
|
|
362
|
+
/** Depth-first walk of a vitest task tree, yielding every LEAF test together
|
|
363
|
+
* with its ancestor suite names. A node is a CONTAINER (a suite, or a file
|
|
364
|
+
* whose `tasks` are its top-level tests) exactly when it has a `tasks` array —
|
|
365
|
+
* regardless of whether that array is empty — and is recursed into, never
|
|
366
|
+
* recorded. Everything else is a leaf test.
|
|
367
|
+
*
|
|
368
|
+
* ⛔ The container test is the PRESENCE of `tasks`, not its length. vitest emits
|
|
369
|
+
* a file that CRASHED on collection as a suite node with `tasks: []` AND
|
|
370
|
+
* `result.state: "fail"`; a length check would misread that as a failed leaf
|
|
371
|
+
* test and fabricate a `passed:false` code-graded outcome for a file that ran
|
|
372
|
+
* zero tests — the exact over-claim the file-consumer path refuses. A real test
|
|
373
|
+
* never carries a `tasks` property, so this classification keeps every genuine
|
|
374
|
+
* verdict while dropping the crashed/empty suite. */
|
|
375
|
+
export function walkTasks(nodes, ancestors = []) {
|
|
376
|
+
const out = [];
|
|
377
|
+
if (!Array.isArray(nodes))
|
|
378
|
+
return out;
|
|
379
|
+
for (const node of nodes) {
|
|
380
|
+
if (!isObject(node))
|
|
381
|
+
continue;
|
|
382
|
+
const name = typeof node.name === "string" ? node.name : "";
|
|
383
|
+
const children = node.tasks;
|
|
384
|
+
if (Array.isArray(children)) {
|
|
385
|
+
// A container (suite or file) — recurse; an empty one yields nothing.
|
|
386
|
+
out.push(...walkTasks(children, name ? [...ancestors, name] : ancestors));
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
// A leaf test (no `tasks` array). Its verdict is decided by verdictFromState.
|
|
390
|
+
out.push({ task: node, path: name ? [...ancestors, name] : ancestors });
|
|
391
|
+
}
|
|
392
|
+
return out;
|
|
393
|
+
}
|
|
394
|
+
/** Yield `recordOutcome` inputs for every real verdict in a vitest `files` tree
|
|
395
|
+
* (the argument vitest passes a reporter's `onFinished`). A test's value rides
|
|
396
|
+
* from `task.meta.marginValue`; a malformed value warns and drops the value,
|
|
397
|
+
* keeping the pass/fail. */
|
|
398
|
+
export function iterTaskOutcomes(files, opts) {
|
|
399
|
+
const onWarn = opts.onWarn ?? defaultWarn;
|
|
400
|
+
const out = [];
|
|
401
|
+
const leaves = walkTasks(files);
|
|
402
|
+
for (const { task, path } of leaves) {
|
|
403
|
+
const result = task.result;
|
|
404
|
+
const state = isObject(result) ? result.state : undefined;
|
|
405
|
+
const passed = verdictFromState(state);
|
|
406
|
+
if (passed === undefined)
|
|
407
|
+
continue;
|
|
408
|
+
const link = path.length > 0 ? path.join(" > ") : "vitest";
|
|
409
|
+
let value;
|
|
410
|
+
try {
|
|
411
|
+
value = valueFromTaskMeta(task.meta);
|
|
412
|
+
}
|
|
413
|
+
catch (exc) {
|
|
414
|
+
value = undefined;
|
|
415
|
+
onWarn(`${link}: ${exc.message}`);
|
|
416
|
+
}
|
|
417
|
+
out.push(outcomeInput({
|
|
418
|
+
workflowId: opts.workflowId,
|
|
419
|
+
passed,
|
|
420
|
+
link,
|
|
421
|
+
isSimulated: opts.isSimulated,
|
|
422
|
+
valueUsd: value,
|
|
423
|
+
}));
|
|
424
|
+
}
|
|
425
|
+
return out;
|
|
426
|
+
}
|
|
427
|
+
/** A vitest reporter that records every test as a code-graded outcome and reads
|
|
428
|
+
* an opt-in per-test value off `task.meta.marginValue` (rung 8). It implements
|
|
429
|
+
* only the `onFinished(files)` hook — the one vitest calls with the completed
|
|
430
|
+
* task tree — and is fail-safe: a delivery or config fault warns, and NOTHING
|
|
431
|
+
* it does throws into the test run. */
|
|
432
|
+
export class MarginVitestReporter {
|
|
433
|
+
opts;
|
|
434
|
+
/** The last batch's summary — handy for a customer's own assertions and for
|
|
435
|
+
* the hermetic tests. */
|
|
436
|
+
summary = { recorded: 0, errors: 0, skipped: 0 };
|
|
437
|
+
constructor(opts = {}) {
|
|
438
|
+
this.opts = opts;
|
|
439
|
+
}
|
|
440
|
+
resolveWorkflow() {
|
|
441
|
+
if (this.opts.workflowId)
|
|
442
|
+
return this.opts.workflowId;
|
|
443
|
+
const p = globalThis
|
|
444
|
+
.process;
|
|
445
|
+
const env = p?.env?.MARGIN_VITEST_WORKFLOW;
|
|
446
|
+
return env && env.trim() ? env.trim() : undefined;
|
|
447
|
+
}
|
|
448
|
+
async resolveMeter() {
|
|
449
|
+
if (this.opts.meter)
|
|
450
|
+
return this.opts.meter;
|
|
451
|
+
// Lazy dynamic import so this module has NO static runtime dependency on
|
|
452
|
+
// index.ts (avoids the barrel re-export cycle) and a customer only pays for
|
|
453
|
+
// the client when the reporter actually runs.
|
|
454
|
+
const mod = await import("./index.js");
|
|
455
|
+
return new mod.MarginMeter();
|
|
456
|
+
}
|
|
457
|
+
/** vitest's end-of-run hook. `files` is the completed task tree. */
|
|
458
|
+
async onFinished(files) {
|
|
459
|
+
const onWarn = this.opts.onWarn ?? defaultWarn;
|
|
460
|
+
// Reset first, so a no-op run or a caught fault reports THIS run's counts
|
|
461
|
+
// rather than leaving the previous batch's summary in place.
|
|
462
|
+
this.summary = { recorded: 0, errors: 0, skipped: 0 };
|
|
463
|
+
try {
|
|
464
|
+
const workflowId = this.resolveWorkflow();
|
|
465
|
+
if (!workflowId) {
|
|
466
|
+
onWarn("MarginVitestReporter: no workflowId (constructor option or " +
|
|
467
|
+
"MARGIN_VITEST_WORKFLOW) — recording nothing this run");
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const inputs = iterTaskOutcomes(files, {
|
|
471
|
+
workflowId,
|
|
472
|
+
isSimulated: this.opts.isSimulated,
|
|
473
|
+
onWarn,
|
|
474
|
+
});
|
|
475
|
+
const meter = await this.resolveMeter();
|
|
476
|
+
let recorded = 0;
|
|
477
|
+
let errors = 0;
|
|
478
|
+
for (const input of inputs) {
|
|
479
|
+
try {
|
|
480
|
+
await meter.recordOutcome(input);
|
|
481
|
+
recorded++;
|
|
482
|
+
}
|
|
483
|
+
catch (exc) {
|
|
484
|
+
errors++;
|
|
485
|
+
onWarn(`failed to record outcome for ${input.link}: ${exc.message}`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
await meter.flush();
|
|
490
|
+
}
|
|
491
|
+
catch (exc) {
|
|
492
|
+
onWarn(`flush failed: ${exc.message}`);
|
|
493
|
+
}
|
|
494
|
+
this.summary = { recorded, errors, skipped: 0 };
|
|
495
|
+
}
|
|
496
|
+
catch (exc) {
|
|
497
|
+
// A reporter must never break the test run it observes.
|
|
498
|
+
onWarn(`MarginVitestReporter.onFinished failed: ${exc.message}`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
//# sourceMappingURL=vitest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitest.js","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "margin-meter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Tiny dependency-free client that auto-instruments OpenAI/Anthropic/Gemini and emits LLM call + outcome economics to a Margin ingest API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,16 +13,34 @@
|
|
|
13
13
|
"default": "./dist/index.js"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
|
-
"files": [
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
17
20
|
"scripts": {
|
|
18
21
|
"build": "tsc -p tsconfig.build.json",
|
|
19
22
|
"prepublishOnly": "npm run build",
|
|
20
23
|
"test": "node --experimental-strip-types --test test/*.test.mjs",
|
|
21
24
|
"typecheck": "tsc --noEmit"
|
|
22
25
|
},
|
|
23
|
-
"keywords": [
|
|
26
|
+
"keywords": [
|
|
27
|
+
"llm",
|
|
28
|
+
"cost",
|
|
29
|
+
"observability",
|
|
30
|
+
"finops",
|
|
31
|
+
"margin"
|
|
32
|
+
],
|
|
24
33
|
"license": "MIT",
|
|
25
|
-
"repository": {
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/subhsubh24/Margin.ai.git",
|
|
37
|
+
"directory": "sdk/ts"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^20.0.0",
|
|
44
|
+
"typescript": "^5.4.0"
|
|
45
|
+
}
|
|
28
46
|
}
|