@swissspidy/belay-calibrate 0.1.0 → 0.1.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.
Files changed (50) hide show
  1. package/dist/analyze.d.ts +139 -0
  2. package/dist/analyze.d.ts.map +1 -0
  3. package/dist/analyze.js +265 -0
  4. package/dist/analyze.js.map +1 -0
  5. package/dist/bin.d.ts +3 -0
  6. package/dist/bin.d.ts.map +1 -0
  7. package/dist/bin.js +7 -0
  8. package/dist/bin.js.map +1 -0
  9. package/dist/browser.d.ts +22 -0
  10. package/dist/browser.d.ts.map +1 -0
  11. package/dist/browser.js +240 -0
  12. package/dist/browser.js.map +1 -0
  13. package/dist/cache.d.ts +16 -0
  14. package/dist/cache.d.ts.map +1 -0
  15. package/dist/cache.js +60 -0
  16. package/dist/cache.js.map +1 -0
  17. package/dist/calibrate.d.ts +31 -0
  18. package/dist/calibrate.d.ts.map +1 -0
  19. package/dist/calibrate.js +98 -0
  20. package/dist/calibrate.js.map +1 -0
  21. package/dist/cli.d.ts +2 -0
  22. package/dist/cli.d.ts.map +1 -0
  23. package/dist/cli.js +240 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/config.d.ts +108 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +15 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/dataset.d.ts +19 -0
  30. package/dist/dataset.d.ts.map +1 -0
  31. package/dist/dataset.js +48 -0
  32. package/dist/dataset.js.map +1 -0
  33. package/dist/evaluate.d.ts +65 -0
  34. package/dist/evaluate.d.ts.map +1 -0
  35. package/dist/evaluate.js +191 -0
  36. package/dist/evaluate.js.map +1 -0
  37. package/dist/harness/harness.d.ts +2 -0
  38. package/dist/harness/harness.d.ts.map +1 -0
  39. package/dist/harness/harness.js +80 -0
  40. package/dist/harness/harness.js.map +1 -0
  41. package/dist/index.d.ts +17 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +10 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/report.d.ts +16 -0
  46. package/dist/report.d.ts.map +1 -0
  47. package/dist/report.js +519 -0
  48. package/dist/report.js.map +1 -0
  49. package/package.json +5 -4
  50. package/src/report.ts +13 -4
@@ -0,0 +1,139 @@
1
+ import type { CalibrationCurvePoint, CalibrationFile, CloudUsage, TargetMode } from '@swissspidy/belay-core';
2
+ /** One labeled example after both runners have seen it. */
3
+ export interface Sample {
4
+ id: string;
5
+ input: string;
6
+ /** Ground-truth label (for classifier tasks, the label string; "true"/"false" for binary). */
7
+ truth: string;
8
+ /** Local prediction, or `null` if the local runner failed / was unavailable (always escalated). */
9
+ local: {
10
+ label: string;
11
+ confidence: number;
12
+ correct: boolean; /** The parsed local answer. */
13
+ value?: unknown;
14
+ } | null;
15
+ /** Cloud prediction, or `null` if the cloud runner failed (counted as wrong). */
16
+ cloud: {
17
+ label: string;
18
+ correct: boolean;
19
+ /** The cloud model's own confidence, when its runner reports one (e.g. Jev). */
20
+ confidence?: number;
21
+ /** Measured cost of the call (from its token usage and the configured prices). */
22
+ cost?: number;
23
+ usage?: CloudUsage[];
24
+ } | null;
25
+ }
26
+ export interface AnalyzeOptions {
27
+ /**
28
+ * A cascade accuracy in (0, 1]; `'cloud'` for at least the cloud-only accuracy; `'max'` for the
29
+ * most accurate threshold. The smallest threshold that reaches it is recommended.
30
+ */
31
+ target: number | 'cloud' | 'max';
32
+ /** Labels in schema order (for per-label thresholds and the confusion matrix). */
33
+ labels?: string[];
34
+ /** Fit per-label thresholds (see ADR 0001, Decision 4). */
35
+ perLabel?: boolean;
36
+ /**
37
+ * Enables costs. Samples with a measured `cloud.cost` are priced at it; the rest (and failed
38
+ * cloud calls) at the mean measured cost, or at `cloudPerRun` when nothing was measured.
39
+ */
40
+ cost?: {
41
+ currency: string;
42
+ cloudPerRun?: number;
43
+ };
44
+ histogramBins?: number;
45
+ }
46
+ /** How the local and cloud answers compare on the runs the cascade keeps local. */
47
+ export interface HeadToHead {
48
+ kept: number;
49
+ bothRight: number;
50
+ /** Local right, cloud wrong: each one is accuracy the cascade gains over cloud only. */
51
+ localOnlyRight: number;
52
+ /** Cloud right, local wrong: each one is accuracy the cascade loses. */
53
+ cloudOnlyRight: number;
54
+ bothWrong: number;
55
+ }
56
+ export interface CostSummary {
57
+ currency: string;
58
+ basis: 'measured' | 'estimate';
59
+ /** Mean cost of one cloud call. */
60
+ cloudPerRun: number;
61
+ /** Cloud calls with measured cost. */
62
+ measuredCalls: number;
63
+ tokensPerRun?: {
64
+ input: number;
65
+ output: number;
66
+ cacheRead: number;
67
+ cacheWrite: number;
68
+ };
69
+ }
70
+ export interface OptionStats {
71
+ label: string;
72
+ /** Examples with this true label. */
73
+ count: number;
74
+ localAccuracy: number | null;
75
+ cloudAccuracy: number | null;
76
+ /** At the recommended threshold(s). */
77
+ localShare: number;
78
+ cascadeAccuracy: number;
79
+ threshold: number;
80
+ }
81
+ export interface Analysis {
82
+ threshold: number;
83
+ thresholds: Record<string, number> | undefined;
84
+ /** The target as an accuracy, and how it was set. */
85
+ target: {
86
+ value: number;
87
+ mode: TargetMode;
88
+ };
89
+ targetMet: boolean;
90
+ headToHead: HeadToHead;
91
+ cost: CostSummary | undefined;
92
+ expected: CalibrationFile['expected'];
93
+ /** 95% Wilson score interval of the expected cascade accuracy. */
94
+ accuracyInterval: [number, number];
95
+ curve: CalibrationCurvePoint[];
96
+ confidenceHistogram: CalibrationFile['confidenceHistogram'];
97
+ confusion: NonNullable<CalibrationFile['confusion']> | undefined;
98
+ perOption: OptionStats[];
99
+ counts: {
100
+ total: number;
101
+ localFailed: number;
102
+ cloudFailed: number;
103
+ };
104
+ /** Local answers that were wrong but would be accepted at the recommended threshold, most confident first. */
105
+ confidentMistakes: Sample[];
106
+ }
107
+ /**
108
+ * The shortest decimal `t` with `lo < t <= hi`, so the threshold reads well (0.82 rather than
109
+ * 0.8123456) and sits strictly between two observed confidences, which keeps it from
110
+ * over-fitting to a single sample's exact score. `lo = -Infinity` yields 0.
111
+ */
112
+ export declare function niceThreshold(lo: number, hi: number): number;
113
+ /**
114
+ * Candidate thresholds: 0, then one per gap between consecutive distinct local confidences,
115
+ * then one above the maximum, so escalating everything is always a candidate. When a confidence
116
+ * is exactly 1 (common for deterministic checks) that one is above 1. Every threshold in the same
117
+ * gap routes the samples identically, so this set is exhaustive and deterministic.
118
+ */
119
+ export declare function candidateThresholds(confidences: readonly number[]): number[];
120
+ export declare function wilsonInterval(successes: number, n: number, z?: number): [number, number];
121
+ export declare function analyze(samples: readonly Sample[], options: AnalyzeOptions): Analysis;
122
+ export interface CrossValidation {
123
+ folds: number;
124
+ /** Cascade accuracy on held-out folds, each routed by thresholds fitted on the other folds. */
125
+ accuracy: number;
126
+ localShare: number;
127
+ /** Cloud-only accuracy on the same examples, for comparison. */
128
+ cloudAccuracy: number;
129
+ /** Cloud cost per 1,000 runs on the held-out folds (with costs). */
130
+ costPer1k?: number;
131
+ }
132
+ /**
133
+ * K-fold cross-validation of the threshold choice: fits the thresholds on K-1 folds and routes
134
+ * the held-out fold with them. The recommended threshold is fitted and scored on the same data,
135
+ * which flatters it; this estimate does not. Folds interleave by position, so it is deterministic.
136
+ * Returns undefined for datasets too small to split (fewer than 10 examples per fold).
137
+ */
138
+ export declare function crossValidate(samples: readonly Sample[], options: AnalyzeOptions, folds?: number): CrossValidation | undefined;
139
+ //# sourceMappingURL=analyze.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../src/analyze.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,265 @@
1
+ const EPS = 1e-12;
2
+ /**
3
+ * The shortest decimal `t` with `lo < t <= hi`, so the threshold reads well (0.82 rather than
4
+ * 0.8123456) and sits strictly between two observed confidences, which keeps it from
5
+ * over-fitting to a single sample's exact score. `lo = -Infinity` yields 0.
6
+ */
7
+ export function niceThreshold(lo, hi) {
8
+ if (lo < 0)
9
+ return 0;
10
+ for (let d = 1; d <= 15; d++) {
11
+ const scale = 10 ** d;
12
+ let t = Math.ceil(lo * scale) / scale;
13
+ if (t <= lo)
14
+ t = (Math.round(lo * scale) + 1) / scale;
15
+ if (t > lo && t <= hi)
16
+ return t;
17
+ }
18
+ return hi;
19
+ }
20
+ const kept = (s, thresholdOf) => !!s.local && s.local.confidence >= thresholdOf(s);
21
+ function evaluate(samples, thresholdOf, costOf = () => 0) {
22
+ let correct = 0;
23
+ let local = 0;
24
+ let localCorrect = 0;
25
+ let cloudCost = 0;
26
+ for (const s of samples) {
27
+ if (kept(s, thresholdOf)) {
28
+ local++;
29
+ if (s.local.correct) {
30
+ correct++;
31
+ localCorrect++;
32
+ }
33
+ }
34
+ else {
35
+ cloudCost += costOf(s);
36
+ if (s.cloud?.correct)
37
+ correct++;
38
+ }
39
+ }
40
+ const n = samples.length;
41
+ return {
42
+ accuracy: n ? correct / n : 0,
43
+ localShare: n ? local / n : 0,
44
+ localAccuracy: local ? localCorrect / local : null,
45
+ cloudCost,
46
+ };
47
+ }
48
+ function headToHead(samples, thresholdOf) {
49
+ const h = { kept: 0, bothRight: 0, localOnlyRight: 0, cloudOnlyRight: 0, bothWrong: 0 };
50
+ for (const s of samples) {
51
+ if (!kept(s, thresholdOf))
52
+ continue;
53
+ h.kept++;
54
+ const cloudRight = !!s.cloud?.correct;
55
+ if (s.local.correct)
56
+ cloudRight ? h.bothRight++ : h.localOnlyRight++;
57
+ else
58
+ cloudRight ? h.cloudOnlyRight++ : h.bothWrong++;
59
+ }
60
+ return h;
61
+ }
62
+ function summarizeCost(samples, cost) {
63
+ if (!cost)
64
+ return undefined;
65
+ const measured = samples.flatMap((s) => (s.cloud?.cost !== undefined ? [s.cloud] : []));
66
+ if (measured.length) {
67
+ const tokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
68
+ for (const c of measured) {
69
+ for (const u of c.usage ?? []) {
70
+ tokens.input += u.inputTokens;
71
+ tokens.output += u.outputTokens;
72
+ tokens.cacheRead += u.cacheReadInputTokens ?? 0;
73
+ tokens.cacheWrite += u.cacheWriteInputTokens ?? 0;
74
+ }
75
+ }
76
+ const m = measured.length;
77
+ const round = (v) => Math.round((v / m) * 10) / 10;
78
+ return {
79
+ currency: cost.currency,
80
+ basis: 'measured',
81
+ cloudPerRun: measured.reduce((a, c) => a + c.cost, 0) / m,
82
+ measuredCalls: m,
83
+ tokensPerRun: { input: round(tokens.input), output: round(tokens.output), cacheRead: round(tokens.cacheRead), cacheWrite: round(tokens.cacheWrite) },
84
+ };
85
+ }
86
+ if (cost.cloudPerRun === undefined)
87
+ return undefined;
88
+ return { currency: cost.currency, basis: 'estimate', cloudPerRun: cost.cloudPerRun, measuredCalls: 0 };
89
+ }
90
+ /**
91
+ * Candidate thresholds: 0, then one per gap between consecutive distinct local confidences,
92
+ * then one above the maximum, so escalating everything is always a candidate. When a confidence
93
+ * is exactly 1 (common for deterministic checks) that one is above 1. Every threshold in the same
94
+ * gap routes the samples identically, so this set is exhaustive and deterministic.
95
+ */
96
+ export function candidateThresholds(confidences) {
97
+ const distinct = [...new Set(confidences)].sort((a, b) => a - b);
98
+ const out = [0];
99
+ for (let i = 0; i < distinct.length; i++) {
100
+ const t = niceThreshold(i === 0 ? -Infinity : distinct[i - 1], distinct[i]);
101
+ if (t > out[out.length - 1])
102
+ out.push(t);
103
+ }
104
+ const max = distinct[distinct.length - 1];
105
+ if (max !== undefined)
106
+ out.push(max < 1 ? niceThreshold(max, 1) : niceThreshold(max, max + 1));
107
+ return out;
108
+ }
109
+ export function wilsonInterval(successes, n, z = 1.959963984540054) {
110
+ if (n === 0)
111
+ return [0, 1];
112
+ const p = successes / n;
113
+ const denom = 1 + (z * z) / n;
114
+ const center = (p + (z * z) / (2 * n)) / denom;
115
+ const half = (z * Math.sqrt((p * (1 - p)) / n + (z * z) / (4 * n * n))) / denom;
116
+ return [Math.max(0, center - half), Math.min(1, center + half)];
117
+ }
118
+ export function analyze(samples, options) {
119
+ const n = samples.length;
120
+ const confidences = samples.flatMap((s) => (s.local ? [s.local.confidence] : []));
121
+ const candidates = candidateThresholds(confidences);
122
+ const cost = summarizeCost(samples, options.cost);
123
+ const sampleCost = (s) => s.cloud?.cost ?? cost?.cloudPerRun ?? 0;
124
+ const per1k = (sum) => (cost && n ? Math.round((sum / n) * 1000 * 1e6) / 1e6 : undefined);
125
+ const curve = candidates.map((threshold) => {
126
+ const { cloudCost, ...e } = evaluate(samples, () => threshold, sampleCost);
127
+ const c = per1k(cloudCost);
128
+ return { threshold, ...e, ...(c !== undefined ? { costPer1k: c } : {}) };
129
+ });
130
+ const cloudAccuracy = n ? samples.filter((s) => s.cloud?.correct).length / n : null;
131
+ const mode = typeof options.target === 'number' ? 'fixed' : options.target;
132
+ const target = mode === 'fixed' ? options.target : mode === 'cloud' ? (cloudAccuracy ?? 0) : Math.max(...curve.map((p) => p.accuracy));
133
+ // Smallest threshold meeting the target maximizes local share (local share is non-increasing in t).
134
+ let best = curve.find((p) => p.accuracy >= target - EPS);
135
+ const targetMet = best !== undefined;
136
+ if (!best) {
137
+ best = curve.reduce((a, b) => (b.accuracy > a.accuracy + EPS ? b : a));
138
+ }
139
+ const threshold = best.threshold;
140
+ // Per-label thresholds: greedy in label order, each lowered as far as the target allows.
141
+ let thresholds;
142
+ if (options.perLabel && targetMet) {
143
+ const current = {};
144
+ const labels = options.labels ?? [...new Set(samples.flatMap((s) => (s.local ? [s.local.label] : [])))].sort();
145
+ const thresholdOf = (s) => (s.local && Object.hasOwn(current, s.local.label) ? current[s.local.label] : threshold);
146
+ for (const label of labels) {
147
+ const own = samples.flatMap((s) => (s.local?.label === label ? [s.local.confidence] : []));
148
+ for (const t of candidateThresholds(own)) {
149
+ if (t >= threshold)
150
+ break;
151
+ current[label] = t;
152
+ if (evaluate(samples, thresholdOf).accuracy >= target - EPS)
153
+ break;
154
+ delete current[label];
155
+ }
156
+ }
157
+ if (Object.keys(current).length)
158
+ thresholds = current;
159
+ }
160
+ const thresholdOf = (s) => s.local && thresholds && Object.hasOwn(thresholds, s.local.label) ? thresholds[s.local.label] : threshold;
161
+ const final = evaluate(samples, thresholdOf, sampleCost);
162
+ const cloudAnswered = samples.filter((s) => s.cloud);
163
+ const finalCost = per1k(final.cloudCost);
164
+ const cloudOnlyCost = per1k(samples.reduce((a, s) => a + sampleCost(s), 0));
165
+ const expected = {
166
+ accuracy: final.accuracy,
167
+ localShare: final.localShare,
168
+ localAccuracy: final.localAccuracy,
169
+ cloudAccuracy,
170
+ ...(finalCost !== undefined && cloudOnlyCost !== undefined
171
+ ? { costPer1k: finalCost, cloudOnlyCostPer1k: cloudOnlyCost, savingsPer1k: Math.round((cloudOnlyCost - finalCost) * 1e6) / 1e6 }
172
+ : {}),
173
+ };
174
+ // Histogram of local confidences.
175
+ const bins = options.histogramBins ?? 10;
176
+ const edges = Array.from({ length: bins + 1 }, (_, i) => Math.round((i / bins) * 1e6) / 1e6);
177
+ const counts = new Array(bins).fill(0);
178
+ for (const c of confidences)
179
+ counts[Math.min(bins - 1, Math.floor(c * bins))]++;
180
+ // Confusion matrix (local predictions only).
181
+ let confusion;
182
+ const labels = options.labels;
183
+ if (labels?.length) {
184
+ const index = new Map(labels.map((l, i) => [l, i]));
185
+ const matrix = labels.map(() => labels.map(() => 0));
186
+ for (const s of samples) {
187
+ const i = index.get(s.truth);
188
+ const j = s.local ? index.get(s.local.label) : undefined;
189
+ if (i !== undefined && j !== undefined)
190
+ matrix[i][j]++;
191
+ }
192
+ confusion = { labels, local: matrix };
193
+ }
194
+ const perOption = (labels ?? [...new Set(samples.map((s) => s.truth))].sort()).map((label) => {
195
+ const group = samples.filter((s) => s.truth === label);
196
+ const withLocal = group.filter((s) => s.local);
197
+ const withCloud = group.filter((s) => s.cloud);
198
+ const e = evaluate(group, thresholdOf);
199
+ return {
200
+ label,
201
+ count: group.length,
202
+ localAccuracy: withLocal.length ? withLocal.filter((s) => s.local.correct).length / withLocal.length : null,
203
+ cloudAccuracy: withCloud.length ? withCloud.filter((s) => s.cloud.correct).length / group.length : null,
204
+ localShare: e.localShare,
205
+ cascadeAccuracy: e.accuracy,
206
+ threshold: thresholds?.[label] ?? threshold,
207
+ };
208
+ });
209
+ const confidentMistakes = samples
210
+ .filter((s) => s.local && !s.local.correct && s.local.confidence >= thresholdOf(s))
211
+ .sort((a, b) => b.local.confidence - a.local.confidence || (a.id < b.id ? -1 : 1));
212
+ return {
213
+ threshold,
214
+ thresholds,
215
+ target: { value: target, mode },
216
+ targetMet,
217
+ headToHead: headToHead(samples, thresholdOf),
218
+ cost,
219
+ expected,
220
+ accuracyInterval: wilsonInterval(Math.round(final.accuracy * n), n),
221
+ curve,
222
+ confidenceHistogram: { edges, counts },
223
+ confusion,
224
+ perOption,
225
+ counts: { total: n, localFailed: n - confidences.length, cloudFailed: n - cloudAnswered.length },
226
+ confidentMistakes,
227
+ };
228
+ }
229
+ /**
230
+ * K-fold cross-validation of the threshold choice: fits the thresholds on K-1 folds and routes
231
+ * the held-out fold with them. The recommended threshold is fitted and scored on the same data,
232
+ * which flatters it; this estimate does not. Folds interleave by position, so it is deterministic.
233
+ * Returns undefined for datasets too small to split (fewer than 10 examples per fold).
234
+ */
235
+ export function crossValidate(samples, options, folds = 5) {
236
+ const n = samples.length;
237
+ if (folds < 2 || n < folds * 10)
238
+ return undefined;
239
+ const cost = summarizeCost(samples, options.cost);
240
+ const sampleCost = (s) => s.cloud?.cost ?? cost?.cloudPerRun ?? 0;
241
+ let correct = 0;
242
+ let local = 0;
243
+ let cloudCorrect = 0;
244
+ let cloudCost = 0;
245
+ for (let k = 0; k < folds; k++) {
246
+ const train = samples.filter((_, i) => i % folds !== k);
247
+ const test = samples.filter((_, i) => i % folds === k);
248
+ const fit = analyze(train, options);
249
+ const thresholdOf = (s) => s.local && fit.thresholds && Object.hasOwn(fit.thresholds, s.local.label) ? fit.thresholds[s.local.label] : fit.threshold;
250
+ const e = evaluate(test, thresholdOf, sampleCost);
251
+ correct += e.accuracy * test.length;
252
+ local += e.localShare * test.length;
253
+ cloudCost += e.cloudCost;
254
+ cloudCorrect += test.filter((s) => s.cloud?.correct).length;
255
+ }
256
+ const round = (v) => Math.round(v * 1e9) / 1e9;
257
+ return {
258
+ folds,
259
+ accuracy: round(correct / n),
260
+ localShare: round(local / n),
261
+ cloudAccuracy: round(cloudCorrect / n),
262
+ ...(cost ? { costPer1k: Math.round((cloudCost / n) * 1000 * 1e6) / 1e6 } : {}),
263
+ };
264
+ }
265
+ //# sourceMappingURL=analyze.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../src/analyze.ts"],"names":[],"mappings":"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"}
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/dist/bin.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { main } from './cli.js';
3
+ main(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
4
+ process.stderr.write(`belay: ${err instanceof Error ? err.message : String(err)}\n`);
5
+ process.exit(1);
6
+ });
7
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC9B,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAC5B,CAAC,GAAY,EAAE,EAAE;IACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACrF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CACF,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { TaskSchema } from '@swissspidy/belay-core';
2
+ import type { BrowserConfig, BrowserJudgeSpec, BrowserRunnerSpec } from './config.js';
3
+ import type { LocalBackend } from './evaluate.js';
4
+ export interface BrowserBackendOptions {
5
+ task: string;
6
+ schema: TaskSchema;
7
+ context?: string;
8
+ local: BrowserRunnerSpec;
9
+ judge?: BrowserJudgeSpec;
10
+ browser?: BrowserConfig;
11
+ cacheDir: string;
12
+ log?: (message: string) => void;
13
+ /** Injected in tests: a script evaluated in the page before any other script (e.g. a stub `Classifier`). */
14
+ initScript?: string;
15
+ }
16
+ /**
17
+ * Runs a browser runner inside a real Chrome through a Playwright persistent context, optionally
18
+ * with an unpacked extension (the WebAI Studio extension polyfills the Classifier API). The
19
+ * profile persists in the cache directory, so a downloaded model is reused across runs.
20
+ */
21
+ export declare function browserBackend(options: BrowserBackendOptions): Promise<LocalBackend>;
22
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAA6B,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,iBAAiB,CAAC;IACzB,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,4GAA4G;IAC5G,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA+HD;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,YAAY,CAAC,CAqI1F"}