@bitfab/sdk 0.65.4 → 0.65.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/autoTrace-CQw0qMBt.cjs +396 -0
- package/dist/autoTrace-CQw0qMBt.cjs.map +1 -0
- package/dist/autoTrace-O40k8f5M.js +271 -0
- package/dist/autoTrace-O40k8f5M.js.map +1 -0
- package/dist/autoTrace.cjs +15 -353
- package/dist/autoTrace.d.cts +48 -44
- package/dist/autoTrace.d.ts +48 -44
- package/dist/autoTrace.js +1 -28
- package/dist/babelPlugin-C70OxrqA.js +209 -0
- package/dist/babelPlugin-C70OxrqA.js.map +1 -0
- package/dist/babelPlugin-Nta4-VcL.cjs +232 -0
- package/dist/babelPlugin-Nta4-VcL.cjs.map +1 -0
- package/dist/http-lnqCEnB1.js +2154 -0
- package/dist/http-lnqCEnB1.js.map +1 -0
- package/dist/index-CFOLQ1IF.d.cts +4410 -0
- package/dist/index-CFOLQ1IF.d.ts +4410 -0
- package/dist/index.cjs +48 -12504
- package/dist/index.d.cts +2 -4443
- package/dist/index.d.ts +2 -4443
- package/dist/index.js +3 -96
- package/dist/loaderRuntime-0huddOOP.d.ts +12 -0
- package/dist/loaderRuntime-CslfDFO7.d.cts +12 -0
- package/dist/node.cjs +56 -12520
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +2 -1
- package/dist/node.d.ts +2 -1
- package/dist/node.js +10 -105
- package/dist/node.js.map +1 -1
- package/dist/registryCommand-Di8x0YoJ.js +1111 -0
- package/dist/registryCommand-Di8x0YoJ.js.map +1 -0
- package/dist/replayCli.js +49 -87
- package/dist/replayCli.js.map +1 -1
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/seedCli.js +10 -24
- package/dist/seedCli.js.map +1 -1
- package/dist/src-BungStQw.js +6015 -0
- package/dist/src-BungStQw.js.map +1 -0
- package/dist/src-FFSCRXpz.cjs +8438 -0
- package/dist/src-FFSCRXpz.cjs.map +1 -0
- package/dist/transform/babel.cjs +5 -422
- package/dist/transform/babel.d.cts +16 -16
- package/dist/transform/babel.d.ts +16 -16
- package/dist/transform/babel.js +1 -7
- package/dist/transform/bun.cjs +9 -478
- package/dist/transform/bun.cjs.map +1 -1
- package/dist/transform/bun.d.cts +5 -7
- package/dist/transform/bun.d.ts +5 -7
- package/dist/transform/bun.js +6 -11
- package/dist/transform/bun.js.map +1 -1
- package/dist/transform/esbuild.cjs +9 -478
- package/dist/transform/esbuild.cjs.map +1 -1
- package/dist/transform/esbuild.d.cts +5 -7
- package/dist/transform/esbuild.d.ts +5 -7
- package/dist/transform/esbuild.js +6 -11
- package/dist/transform/esbuild.js.map +1 -1
- package/dist/transform/loaderRuntime.cjs +85 -532
- package/dist/transform/loaderRuntime.cjs.map +1 -1
- package/dist/transform/loaderRuntime.d.cts +2 -12
- package/dist/transform/loaderRuntime.d.ts +2 -12
- package/dist/transform/loaderRuntime.js +56 -98
- package/dist/transform/loaderRuntime.js.map +1 -1
- package/dist/transform/nest.cjs +12 -481
- package/dist/transform/nest.cjs.map +1 -1
- package/dist/transform/nest.d.cts +8 -9
- package/dist/transform/nest.d.ts +8 -9
- package/dist/transform/nest.js +9 -14
- package/dist/transform/nest.js.map +1 -1
- package/dist/transform/next.cjs +56 -528
- package/dist/transform/next.cjs.map +1 -1
- package/dist/transform/next.d.cts +25 -25
- package/dist/transform/next.d.ts +24 -24
- package/dist/transform/next.js +52 -60
- package/dist/transform/next.js.map +1 -1
- package/dist/transform/nodeLoader.js +260 -506
- package/dist/transform/nodeLoader.js.map +1 -1
- package/dist/transform/register.js +4 -1
- package/dist/transform/register.js.map +1 -1
- package/dist/transform/rolldown.cjs +9 -478
- package/dist/transform/rolldown.cjs.map +1 -1
- package/dist/transform/rolldown.d.cts +5 -7
- package/dist/transform/rolldown.d.ts +5 -7
- package/dist/transform/rolldown.js +6 -11
- package/dist/transform/rolldown.js.map +1 -1
- package/dist/transform/rollup.cjs +9 -478
- package/dist/transform/rollup.cjs.map +1 -1
- package/dist/transform/rollup.d.cts +5 -7
- package/dist/transform/rollup.d.ts +5 -7
- package/dist/transform/rollup.js +6 -11
- package/dist/transform/rollup.js.map +1 -1
- package/dist/transform/rsbuild.cjs +9 -478
- package/dist/transform/rsbuild.cjs.map +1 -1
- package/dist/transform/rsbuild.d.cts +5 -7
- package/dist/transform/rsbuild.d.ts +5 -7
- package/dist/transform/rsbuild.js +6 -11
- package/dist/transform/rsbuild.js.map +1 -1
- package/dist/transform/rspack.cjs +9 -478
- package/dist/transform/rspack.cjs.map +1 -1
- package/dist/transform/rspack.d.cts +5 -7
- package/dist/transform/rspack.d.ts +5 -7
- package/dist/transform/rspack.js +6 -11
- package/dist/transform/rspack.js.map +1 -1
- package/dist/transform/swc.cjs +18 -486
- package/dist/transform/swc.cjs.map +1 -1
- package/dist/transform/swc.d.cts +15 -14
- package/dist/transform/swc.d.ts +15 -14
- package/dist/transform/swc.js +14 -32
- package/dist/transform/swc.js.map +1 -1
- package/dist/transform/unplugin.cjs +20 -476
- package/dist/transform/unplugin.cjs.map +1 -1
- package/dist/transform/unplugin.d.cts +4 -6
- package/dist/transform/unplugin.d.ts +4 -6
- package/dist/transform/unplugin.js +19 -8
- package/dist/transform/unplugin.js.map +1 -1
- package/dist/transform/vite.cjs +9 -478
- package/dist/transform/vite.cjs.map +1 -1
- package/dist/transform/vite.d.cts +5 -7
- package/dist/transform/vite.d.ts +5 -7
- package/dist/transform/vite.js +6 -11
- package/dist/transform/vite.js.map +1 -1
- package/dist/transform/webpack.cjs +9 -478
- package/dist/transform/webpack.cjs.map +1 -1
- package/dist/transform/webpack.d.cts +5 -7
- package/dist/transform/webpack.d.ts +5 -7
- package/dist/transform/webpack.js +6 -11
- package/dist/transform/webpack.js.map +1 -1
- package/dist/transform-BcyUocwm.js +36 -0
- package/dist/transform-BcyUocwm.js.map +1 -0
- package/dist/transform-BjxHGGKc.cjs +47 -0
- package/dist/transform-BjxHGGKc.cjs.map +1 -0
- package/dist/transformOptions-BsjCELkh.d.cts +11 -0
- package/dist/transformOptions-BsjCELkh.d.ts +11 -0
- package/package.json +5 -5
- package/dist/autoTrace.cjs.map +0 -1
- package/dist/autoTrace.js.map +0 -1
- package/dist/chunk-27B3UBSS.js +0 -2547
- package/dist/chunk-27B3UBSS.js.map +0 -1
- package/dist/chunk-B2M4KELA.js +0 -1912
- package/dist/chunk-B2M4KELA.js.map +0 -1
- package/dist/chunk-I233ERUH.js +0 -2979
- package/dist/chunk-I233ERUH.js.map +0 -1
- package/dist/chunk-ISU6FQKY.js +0 -6877
- package/dist/chunk-ISU6FQKY.js.map +0 -1
- package/dist/chunk-MT36GJMW.js +0 -106
- package/dist/chunk-MT36GJMW.js.map +0 -1
- package/dist/chunk-PTCLRG4E.js +0 -28
- package/dist/chunk-PTCLRG4E.js.map +0 -1
- package/dist/chunk-QRNHRY7F.js +0 -270
- package/dist/chunk-QRNHRY7F.js.map +0 -1
- package/dist/chunk-R6M54SPO.js +0 -1176
- package/dist/chunk-R6M54SPO.js.map +0 -1
- package/dist/chunk-SOP5AFMH.js +0 -2060
- package/dist/chunk-SOP5AFMH.js.map +0 -1
- package/dist/chunk-YVJAOGHE.js +0 -45
- package/dist/chunk-YVJAOGHE.js.map +0 -1
- package/dist/chunk-ZG6SHKEF.js +0 -404
- package/dist/chunk-ZG6SHKEF.js.map +0 -1
- package/dist/http-LL3JD2JE.js +0 -24
- package/dist/http-LL3JD2JE.js.map +0 -1
- package/dist/http-QZDTQC3C.js +0 -23
- package/dist/http-QZDTQC3C.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/replay-636XZIS2.js +0 -29
- package/dist/replay-636XZIS2.js.map +0 -1
- package/dist/replay-S4DNF4VR.js +0 -28
- package/dist/replay-S4DNF4VR.js.map +0 -1
- package/dist/transform/babel.cjs.map +0 -1
- package/dist/transform/babel.js.map +0 -1
- package/dist/transformOptions-Df2_Chtc.d.cts +0 -10
- package/dist/transformOptions-Df2_Chtc.d.ts +0 -10
|
@@ -0,0 +1,1111 @@
|
|
|
1
|
+
import { register } from "tsx/esm/api";
|
|
2
|
+
import "@opentelemetry/api";
|
|
3
|
+
import "@opentelemetry/core";
|
|
4
|
+
import "@opentelemetry/resources";
|
|
5
|
+
import "@opentelemetry/sdk-trace-base";
|
|
6
|
+
import "superjson";
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __exportAll = (all, no_symbols) => {
|
|
10
|
+
let target = {};
|
|
11
|
+
for (var name in all) __defProp(target, name, {
|
|
12
|
+
get: all[name],
|
|
13
|
+
enumerable: true
|
|
14
|
+
});
|
|
15
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
16
|
+
return target;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/errors.ts
|
|
20
|
+
var BitfabError = class extends Error {
|
|
21
|
+
constructor(message, url, status, retryAfterMs) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.url = url;
|
|
24
|
+
this.status = status;
|
|
25
|
+
this.retryAfterMs = retryAfterMs;
|
|
26
|
+
this.name = "BitfabError";
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
(typeof process !== "undefined" && process.versions?.node ? import(
|
|
30
|
+
/* webpackIgnore: true */
|
|
31
|
+
["node", "zlib"].join(":")
|
|
32
|
+
).then(({ gzip }) => {}).catch(() => {}) : Promise.resolve()).then(() => {});
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/asyncStorage.ts
|
|
35
|
+
let AsyncLocalStorageClass = null;
|
|
36
|
+
function registerAsyncLocalStorageClass(cls) {
|
|
37
|
+
if (!AsyncLocalStorageClass) AsyncLocalStorageClass = cls;
|
|
38
|
+
}
|
|
39
|
+
const asyncStorageReady = (typeof process !== "undefined" && process.versions?.node ?
|
|
40
|
+
// webpackIgnore tells webpack/turbopack to emit a native import()
|
|
41
|
+
import(
|
|
42
|
+
/* webpackIgnore: true */
|
|
43
|
+
["node", "async_hooks"].join(":")
|
|
44
|
+
).then((mod) => {
|
|
45
|
+
registerAsyncLocalStorageClass(mod.AsyncLocalStorage);
|
|
46
|
+
}).catch(() => {}) : Promise.resolve()).then(() => {});
|
|
47
|
+
function createAsyncLocalStorage() {
|
|
48
|
+
return AsyncLocalStorageClass ? new AsyncLocalStorageClass() : null;
|
|
49
|
+
}
|
|
50
|
+
const REPLAY_CONTEXT_STORAGE_SYMBOL = Symbol.for("bitfab.replayContextStorage");
|
|
51
|
+
const replayContextReady = asyncStorageReady.then(() => {
|
|
52
|
+
const shared = globalThis;
|
|
53
|
+
if (shared[REPLAY_CONTEXT_STORAGE_SYMBOL]) return;
|
|
54
|
+
const created = createAsyncLocalStorage();
|
|
55
|
+
if (created) shared[REPLAY_CONTEXT_STORAGE_SYMBOL] = created;
|
|
56
|
+
});
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/captureSuppression.ts
|
|
59
|
+
const suppressionGlobal = globalThis;
|
|
60
|
+
suppressionGlobal.__bitfabCaptureSuppressionV1 = suppressionGlobal.__bitfabCaptureSuppressionV1 ?? { depth: 0 };
|
|
61
|
+
typeof TextEncoder !== "undefined" && new TextEncoder();
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/warnOnce.ts
|
|
64
|
+
const warned = /* @__PURE__ */ new Set();
|
|
65
|
+
function warn(message) {
|
|
66
|
+
try {
|
|
67
|
+
console.warn(`[bitfab] ${message}`);
|
|
68
|
+
} catch {}
|
|
69
|
+
}
|
|
70
|
+
function warnOnce(key, message) {
|
|
71
|
+
if (warned.has(key)) return;
|
|
72
|
+
warned.add(key);
|
|
73
|
+
warn(message);
|
|
74
|
+
}
|
|
75
|
+
function writeOnce(key, line, write = console.warn) {
|
|
76
|
+
if (warned.has(key)) return;
|
|
77
|
+
warned.add(key);
|
|
78
|
+
try {
|
|
79
|
+
write(line);
|
|
80
|
+
} catch {}
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/simulationPlan.ts
|
|
84
|
+
const SIMULATION_PLAN_READ_TIMEOUT_MS = 5e3;
|
|
85
|
+
function planWaitSliceMs(timeoutMs) {
|
|
86
|
+
return Math.min(SIMULATION_PLAN_READ_TIMEOUT_MS, Math.max(timeoutMs, 0) / 2);
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/unrefTimer.ts
|
|
90
|
+
function unrefTimer(timer) {
|
|
91
|
+
const handle = timer;
|
|
92
|
+
if (typeof handle.unref === "function") handle.unref();
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/otel.ts
|
|
96
|
+
const DEFAULT_LIFECYCLE_TIMEOUT_MS$1 = 3e4;
|
|
97
|
+
const liveTransports = /* @__PURE__ */ new Set();
|
|
98
|
+
async function forEachLiveTransport(timeoutMs, run) {
|
|
99
|
+
const deadline = Date.now() + Math.max(timeoutMs, 0);
|
|
100
|
+
let succeeded = true;
|
|
101
|
+
for (const transport of [...liveTransports]) succeeded = await run(transport, Math.max(0, deadline - Date.now())) && succeeded;
|
|
102
|
+
return succeeded;
|
|
103
|
+
}
|
|
104
|
+
function flushOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS$1) {
|
|
105
|
+
return forEachLiveTransport(timeoutMs, (transport, remaining) => transport.flush(remaining));
|
|
106
|
+
}
|
|
107
|
+
function shutdownOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS$1) {
|
|
108
|
+
return forEachLiveTransport(timeoutMs, (transport, remaining) => transport.shutdown(remaining));
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/transport.ts
|
|
112
|
+
function flushTraceTransports(timeoutMs) {
|
|
113
|
+
return flushOtelTransports(timeoutMs);
|
|
114
|
+
}
|
|
115
|
+
function shutdownTraceTransports(timeoutMs) {
|
|
116
|
+
return shutdownOtelTransports(timeoutMs);
|
|
117
|
+
}
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/http.ts
|
|
120
|
+
var http_exports = /* @__PURE__ */ __exportAll({
|
|
121
|
+
awaitPendingRequests: () => awaitPendingRequests,
|
|
122
|
+
flushTraces: () => flushTraces
|
|
123
|
+
});
|
|
124
|
+
const EXIT_FLUSH_TIMEOUT_MS = 5e3;
|
|
125
|
+
const DEFAULT_LIFECYCLE_TIMEOUT_MS = 3e4;
|
|
126
|
+
const pendingTracePromises = /* @__PURE__ */ new Set();
|
|
127
|
+
const liveHttpClients = /* @__PURE__ */ new Set();
|
|
128
|
+
async function releaseHeldExternalSpans(timeoutMs) {
|
|
129
|
+
const deadline = Date.now() + Math.max(timeoutMs, 0);
|
|
130
|
+
let released = true;
|
|
131
|
+
for (const client of [...liveHttpClients]) released = await client.releaseHeldExternalSpans?.(Math.max(0, deadline - Date.now())) !== false && released;
|
|
132
|
+
return released;
|
|
133
|
+
}
|
|
134
|
+
function stopSimulationPlans() {
|
|
135
|
+
for (const client of [...liveHttpClients]) client.stopSimulationPlan?.();
|
|
136
|
+
}
|
|
137
|
+
async function flushTraces(timeoutMs = 5e3) {
|
|
138
|
+
const deadline = Date.now() + Math.max(timeoutMs, 0);
|
|
139
|
+
const released = await releaseHeldExternalSpans(planWaitSliceMs(timeoutMs));
|
|
140
|
+
const requestsFlushed = await awaitPendingRequests(Math.max(0, deadline - Date.now()));
|
|
141
|
+
const releasedAfterDeferred = await releaseHeldExternalSpans(0);
|
|
142
|
+
const transportsFlushed = await flushTraceTransports(Math.max(0, deadline - Date.now()));
|
|
143
|
+
return released && releasedAfterDeferred && requestsFlushed && transportsFlushed;
|
|
144
|
+
}
|
|
145
|
+
async function awaitPendingRequests(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS) {
|
|
146
|
+
await replayContextReady.catch(() => {});
|
|
147
|
+
return waitForPromises(Array.from(pendingTracePromises), timeoutMs);
|
|
148
|
+
}
|
|
149
|
+
async function waitForPromises(promises, timeoutMs) {
|
|
150
|
+
if (promises.length === 0) return true;
|
|
151
|
+
let timer;
|
|
152
|
+
try {
|
|
153
|
+
return await Promise.race([Promise.allSettled(promises).then(() => true), new Promise((resolve) => {
|
|
154
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
155
|
+
unrefTimer(timer);
|
|
156
|
+
})]);
|
|
157
|
+
} finally {
|
|
158
|
+
if (timer) clearTimeout(timer);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) {
|
|
162
|
+
let isFlushing = false;
|
|
163
|
+
process.on("beforeExit", () => {
|
|
164
|
+
if (isFlushing) return;
|
|
165
|
+
isFlushing = true;
|
|
166
|
+
const planWaitMs = SIMULATION_PLAN_READ_TIMEOUT_MS;
|
|
167
|
+
const stopFlushing = () => {
|
|
168
|
+
isFlushing = false;
|
|
169
|
+
};
|
|
170
|
+
const deadline = setTimeout(stopFlushing, 1e4);
|
|
171
|
+
unrefTimer(deadline);
|
|
172
|
+
releaseHeldExternalSpans(planWaitMs).catch(() => {}).then(() => {
|
|
173
|
+
stopSimulationPlans();
|
|
174
|
+
}).then(() => Promise.allSettled([...Array.from(pendingTracePromises).map((p) => p.catch(() => {})), shutdownTraceTransports(EXIT_FLUSH_TIMEOUT_MS).catch(() => false)])).then(() => {
|
|
175
|
+
clearTimeout(deadline);
|
|
176
|
+
stopFlushing();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/replayInterrupt.ts
|
|
182
|
+
function describeReplayInterrupt(experimentId, markedInterrupted) {
|
|
183
|
+
if (markedInterrupted) return `[replay] Experiment ${experimentId} interrupted.`;
|
|
184
|
+
return `[replay] Experiment ${experimentId} stopped, but Bitfab could not mark it interrupted. It will be marked interrupted within 15 minutes. A resume in the next two minutes may be refused as still running.`;
|
|
185
|
+
}
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/replayInvocation.ts
|
|
188
|
+
const CLI_FLAGS = Symbol("bitfab.replayCliFlags");
|
|
189
|
+
const CLI_FLAG_ALIASES = {
|
|
190
|
+
"max-concurrency": "concurrency",
|
|
191
|
+
"dataset-id": "dataset-ids"
|
|
192
|
+
};
|
|
193
|
+
function canonicalCliFlag(name) {
|
|
194
|
+
return CLI_FLAG_ALIASES[name] ?? name;
|
|
195
|
+
}
|
|
196
|
+
function withCliFlags(options, flags) {
|
|
197
|
+
Object.defineProperty(options, CLI_FLAGS, {
|
|
198
|
+
value: flags,
|
|
199
|
+
enumerable: false
|
|
200
|
+
});
|
|
201
|
+
return options;
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/replay.ts
|
|
205
|
+
const BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
|
|
206
|
+
function reportReplayProgress(progress) {
|
|
207
|
+
const stderr = typeof process !== "undefined" ? process.stderr : void 0;
|
|
208
|
+
if (!stderr) return;
|
|
209
|
+
try {
|
|
210
|
+
stderr.write(`${BITFAB_PROGRESS_PREFIX}${JSON.stringify(progress, replayJsonReplacer)}\n`);
|
|
211
|
+
} catch {}
|
|
212
|
+
}
|
|
213
|
+
var ReplayError = class extends BitfabError {
|
|
214
|
+
constructor(message, items, experimentId, experimentUrl, cause) {
|
|
215
|
+
super(message, experimentUrl);
|
|
216
|
+
this.items = items;
|
|
217
|
+
this.experimentId = experimentId;
|
|
218
|
+
this.experimentUrl = experimentUrl;
|
|
219
|
+
this.cause = cause;
|
|
220
|
+
this.name = "ReplayError";
|
|
221
|
+
this.testRunId = experimentId;
|
|
222
|
+
this.testRunUrl = experimentUrl;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
var DbBranchReplayError = class extends BitfabError {
|
|
226
|
+
constructor(code, message, originalTraceId, cause) {
|
|
227
|
+
super(message);
|
|
228
|
+
this.code = code;
|
|
229
|
+
this.originalTraceId = originalTraceId;
|
|
230
|
+
this.cause = cause;
|
|
231
|
+
this.name = "DbBranchReplayError";
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
function replayJsonReplacer(_key, value) {
|
|
235
|
+
if (value instanceof Error) {
|
|
236
|
+
const serialized = {
|
|
237
|
+
name: value.name,
|
|
238
|
+
message: value.message,
|
|
239
|
+
stack: value.stack
|
|
240
|
+
};
|
|
241
|
+
if (value instanceof DbBranchReplayError) {
|
|
242
|
+
serialized.code = value.code;
|
|
243
|
+
serialized.originalTraceId = value.originalTraceId;
|
|
244
|
+
if (value.cause !== void 0) serialized.cause = value.cause;
|
|
245
|
+
}
|
|
246
|
+
return serialized;
|
|
247
|
+
}
|
|
248
|
+
return value;
|
|
249
|
+
}
|
|
250
|
+
function serializeReplayResult(result) {
|
|
251
|
+
return JSON.stringify(result, replayJsonReplacer, 2);
|
|
252
|
+
}
|
|
253
|
+
const JUDGE_ASSERTIONS_DEPRECATION_WARNING = "Bitfab: judgeAssertions is deprecated. Assertion judging is on by default; pass skipAssertionJudging: true (--skip-assertion-judging) to turn it off.";
|
|
254
|
+
function warnJudgeAssertionsDeprecated(write = console.warn) {
|
|
255
|
+
writeOnce("judgeAssertions-deprecated", JUDGE_ASSERTIONS_DEPRECATION_WARNING, write);
|
|
256
|
+
}
|
|
257
|
+
//#endregion
|
|
258
|
+
//#region src/autoTrace.ts
|
|
259
|
+
const autoTraceGlobal = globalThis;
|
|
260
|
+
autoTraceGlobal.__bitfabAutoTraceStateV4 = autoTraceGlobal.__bitfabAutoTraceStateV4 ?? {
|
|
261
|
+
storage: null,
|
|
262
|
+
browserScope: void 0,
|
|
263
|
+
capturePolicies: /* @__PURE__ */ new WeakMap(),
|
|
264
|
+
activeRoots: 0
|
|
265
|
+
};
|
|
266
|
+
const VERCEL_PROVIDER_HOSTS = {
|
|
267
|
+
github: "github.com",
|
|
268
|
+
gitlab: "gitlab.com",
|
|
269
|
+
bitbucket: "bitbucket.org"
|
|
270
|
+
};
|
|
271
|
+
function read(env, name) {
|
|
272
|
+
if (name === null) return null;
|
|
273
|
+
const value = env(name)?.trim();
|
|
274
|
+
return value ? value : null;
|
|
275
|
+
}
|
|
276
|
+
function remoteFrom(...names) {
|
|
277
|
+
return (env) => {
|
|
278
|
+
const parts = names.map((name) => read(env, name));
|
|
279
|
+
if (parts.some((part) => part === null)) return null;
|
|
280
|
+
return normalizeRemote(parts.join("/"));
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function vercelRemote(env) {
|
|
284
|
+
const provider = read(env, "VERCEL_GIT_PROVIDER")?.toLowerCase() ?? "";
|
|
285
|
+
const host = VERCEL_PROVIDER_HOSTS[provider];
|
|
286
|
+
const owner = read(env, "VERCEL_GIT_REPO_OWNER");
|
|
287
|
+
const slug = read(env, "VERCEL_GIT_REPO_SLUG");
|
|
288
|
+
if (!host || !owner || !slug) return null;
|
|
289
|
+
return `${host}/${owner}/${slug}`;
|
|
290
|
+
}
|
|
291
|
+
function noRemote() {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
[...[
|
|
295
|
+
[
|
|
296
|
+
"VERCEL_GIT_COMMIT_SHA",
|
|
297
|
+
"VERCEL_GIT_COMMIT_REF",
|
|
298
|
+
vercelRemote
|
|
299
|
+
],
|
|
300
|
+
[
|
|
301
|
+
"GITHUB_SHA",
|
|
302
|
+
"GITHUB_REF_NAME",
|
|
303
|
+
remoteFrom("GITHUB_SERVER_URL", "GITHUB_REPOSITORY")
|
|
304
|
+
],
|
|
305
|
+
[
|
|
306
|
+
"RAILWAY_GIT_COMMIT_SHA",
|
|
307
|
+
"RAILWAY_GIT_BRANCH",
|
|
308
|
+
noRemote
|
|
309
|
+
],
|
|
310
|
+
[
|
|
311
|
+
"RENDER_GIT_COMMIT",
|
|
312
|
+
"RENDER_GIT_BRANCH",
|
|
313
|
+
noRemote
|
|
314
|
+
],
|
|
315
|
+
[
|
|
316
|
+
"SOURCE_VERSION",
|
|
317
|
+
null,
|
|
318
|
+
noRemote
|
|
319
|
+
],
|
|
320
|
+
[
|
|
321
|
+
"CF_PAGES_COMMIT_SHA",
|
|
322
|
+
"CF_PAGES_BRANCH",
|
|
323
|
+
noRemote
|
|
324
|
+
],
|
|
325
|
+
[
|
|
326
|
+
"CI_COMMIT_SHA",
|
|
327
|
+
"CI_COMMIT_REF_NAME",
|
|
328
|
+
remoteFrom("CI_REPOSITORY_URL")
|
|
329
|
+
],
|
|
330
|
+
[
|
|
331
|
+
"BUILD_SOURCEVERSION",
|
|
332
|
+
"BUILD_SOURCEBRANCHNAME",
|
|
333
|
+
remoteFrom("BUILD_REPOSITORY_URI")
|
|
334
|
+
],
|
|
335
|
+
[
|
|
336
|
+
"CIRCLE_SHA1",
|
|
337
|
+
"CIRCLE_BRANCH",
|
|
338
|
+
remoteFrom("CIRCLE_REPOSITORY_URL")
|
|
339
|
+
]
|
|
340
|
+
].flatMap(([sha, branch]) => branch === null ? [sha] : [sha, branch])];
|
|
341
|
+
function normalizeRemote(raw) {
|
|
342
|
+
const value = raw?.trim();
|
|
343
|
+
if (!value) return null;
|
|
344
|
+
let host = "";
|
|
345
|
+
let path = value;
|
|
346
|
+
if (value.includes("://")) try {
|
|
347
|
+
const parsed = new URL(value);
|
|
348
|
+
host = parsed.hostname;
|
|
349
|
+
path = parsed.pathname;
|
|
350
|
+
} catch {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
const separator = value.indexOf(":");
|
|
355
|
+
if (separator !== -1) {
|
|
356
|
+
const head = value.slice(0, separator);
|
|
357
|
+
host = head.slice(head.lastIndexOf("@") + 1);
|
|
358
|
+
path = value.slice(separator + 1);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
path = path.replace(/^\/+|\/+$/g, "");
|
|
362
|
+
if (path.endsWith(".git")) path = path.slice(0, -4).replace(/\/+$/, "");
|
|
363
|
+
if (!host) return path || null;
|
|
364
|
+
return path ? `${host}/${path}` : host;
|
|
365
|
+
}
|
|
366
|
+
Promise.resolve();
|
|
367
|
+
//#endregion
|
|
368
|
+
//#region src/openaiAgentsSpanContext.ts
|
|
369
|
+
const spanGlobal = globalThis;
|
|
370
|
+
spanGlobal.__bitfabOpenAiAgentsSpansV1 = spanGlobal.__bitfabOpenAiAgentsSpansV1 ?? {
|
|
371
|
+
bitfabTraceIds: /* @__PURE__ */ new Map(),
|
|
372
|
+
endedSpanIds: /* @__PURE__ */ new Map()
|
|
373
|
+
};
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region src/rivetContext.ts
|
|
376
|
+
let storage = null;
|
|
377
|
+
const STORAGE_SYMBOL = Symbol.for("bitfab.rivetContextStorage");
|
|
378
|
+
function initializeStorage() {
|
|
379
|
+
if (storage) return;
|
|
380
|
+
const shared = globalThis;
|
|
381
|
+
const existing = shared[STORAGE_SYMBOL];
|
|
382
|
+
if (existing) {
|
|
383
|
+
storage = existing;
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const created = createAsyncLocalStorage();
|
|
387
|
+
if (created) {
|
|
388
|
+
shared[STORAGE_SYMBOL] = created;
|
|
389
|
+
storage = created;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
asyncStorageReady.then(() => {
|
|
393
|
+
initializeStorage();
|
|
394
|
+
});
|
|
395
|
+
const SEED_CONTEXT_STORAGE_SYMBOL = Symbol.for("bitfab.seedContextStorage");
|
|
396
|
+
asyncStorageReady.then(() => {
|
|
397
|
+
const shared = globalThis;
|
|
398
|
+
if (shared[SEED_CONTEXT_STORAGE_SYMBOL]) return;
|
|
399
|
+
const created = createAsyncLocalStorage();
|
|
400
|
+
if (created) shared[SEED_CONTEXT_STORAGE_SYMBOL] = created;
|
|
401
|
+
});
|
|
402
|
+
//#endregion
|
|
403
|
+
//#region src/client.ts
|
|
404
|
+
let asyncLocalStorage = null;
|
|
405
|
+
const SPAN_CONTEXT_STORAGE_SYMBOL = Symbol.for("bitfab.spanContextStorage");
|
|
406
|
+
const initializeAsyncContext = () => {
|
|
407
|
+
if (asyncLocalStorage) return;
|
|
408
|
+
const shared = globalThis;
|
|
409
|
+
const existing = shared[SPAN_CONTEXT_STORAGE_SYMBOL];
|
|
410
|
+
if (existing) {
|
|
411
|
+
asyncLocalStorage = existing;
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const created = createAsyncLocalStorage();
|
|
415
|
+
if (created) {
|
|
416
|
+
shared[SPAN_CONTEXT_STORAGE_SYMBOL] = created;
|
|
417
|
+
asyncLocalStorage = created;
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
asyncStorageReady.then(() => {
|
|
421
|
+
initializeAsyncContext();
|
|
422
|
+
});
|
|
423
|
+
function _resolvedApiKey(client) {
|
|
424
|
+
return client.findApiKey(true) ?? null;
|
|
425
|
+
}
|
|
426
|
+
function _serviceUrl(client) {
|
|
427
|
+
return client.serviceUrl;
|
|
428
|
+
}
|
|
429
|
+
//#endregion
|
|
430
|
+
//#region src/replayRegistry.ts
|
|
431
|
+
const REGISTRATION_FIELDS = /* @__PURE__ */ new Set([
|
|
432
|
+
"client",
|
|
433
|
+
"fn",
|
|
434
|
+
"traceFunctionKey",
|
|
435
|
+
"options",
|
|
436
|
+
"optionsFactory"
|
|
437
|
+
]);
|
|
438
|
+
function validateReplayRegistry(registry) {
|
|
439
|
+
for (const [pipeline, registration] of Object.entries(registry)) {
|
|
440
|
+
if (typeof registration !== "object" || registration === null) throw new BitfabError(`Replay registry entry '${pipeline}' must be an object with client and fn.`);
|
|
441
|
+
const unknownFields = Object.keys(registration).filter((field) => !REGISTRATION_FIELDS.has(field));
|
|
442
|
+
if (unknownFields.length > 0) warnOnce(`replay-registry-unknown-fields:${pipeline}:${unknownFields.join(",")}`, `Replay registry entry '${pipeline}' has unknown field(s): ${unknownFields.join(", ")}. They are ignored. An entry takes client, fn, traceFunctionKey, options, and optionsFactory. Put replay options such as adaptInputs, mock, and mockOverride inside options. A later SDK release will reject unknown fields.`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
446
|
+
"--limit",
|
|
447
|
+
"--attempts",
|
|
448
|
+
"--trace-ids",
|
|
449
|
+
"--name",
|
|
450
|
+
"--notes",
|
|
451
|
+
"--metadata",
|
|
452
|
+
"--concurrency",
|
|
453
|
+
"--max-concurrency",
|
|
454
|
+
"--code-change",
|
|
455
|
+
"--experiment-group-id",
|
|
456
|
+
"--experiment-group-name",
|
|
457
|
+
"--dataset-id",
|
|
458
|
+
"--dataset-ids",
|
|
459
|
+
"--grader-ids",
|
|
460
|
+
"--mock",
|
|
461
|
+
"--param",
|
|
462
|
+
"--params",
|
|
463
|
+
"--resume"
|
|
464
|
+
]);
|
|
465
|
+
const BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
466
|
+
"--only-with-assertions",
|
|
467
|
+
"--skip-assertion-judging",
|
|
468
|
+
"--judge-assertions",
|
|
469
|
+
"--db-branch",
|
|
470
|
+
"--no-db-branch",
|
|
471
|
+
"--no-code-change",
|
|
472
|
+
"--dry-run",
|
|
473
|
+
"--fail-on-error",
|
|
474
|
+
"--force"
|
|
475
|
+
]);
|
|
476
|
+
const HELP_FLAGS = /* @__PURE__ */ new Set(["--help", "-h"]);
|
|
477
|
+
const REPEATABLE_FLAGS = /* @__PURE__ */ new Set(["--metadata", "--param"]);
|
|
478
|
+
function recordFlag(flags, flag, raw) {
|
|
479
|
+
const name = canonicalCliFlag(flag.slice(2));
|
|
480
|
+
if (raw === void 0) {
|
|
481
|
+
flags[name] = true;
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const value = flag === "--param" ? raw.split("=")[0] : raw;
|
|
485
|
+
if (REPEATABLE_FLAGS.has(flag)) {
|
|
486
|
+
const previous = flags[name];
|
|
487
|
+
flags[name] = [...Array.isArray(previous) ? previous : [], value];
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
flags[name] = value;
|
|
491
|
+
}
|
|
492
|
+
const MAX_ATTEMPTS = 100;
|
|
493
|
+
const MAX_PINNED_TRACE_IDS = 100;
|
|
494
|
+
const INTERRUPT_EXIT_CODES = {
|
|
495
|
+
SIGINT: 130,
|
|
496
|
+
SIGTERM: 143
|
|
497
|
+
};
|
|
498
|
+
const RESUME_CONFLICTING_FLAGS = [
|
|
499
|
+
["traceIds", "--trace-ids"],
|
|
500
|
+
["datasetIds", "--dataset-ids"],
|
|
501
|
+
["limit", "--limit"],
|
|
502
|
+
["attempts", "--attempts"],
|
|
503
|
+
["graderIds", "--grader-ids"],
|
|
504
|
+
["onlyWithAssertions", "--only-with-assertions"],
|
|
505
|
+
["dryRun", "--dry-run"]
|
|
506
|
+
];
|
|
507
|
+
const RESUME_CLEARED_OPTIONS = {
|
|
508
|
+
traceIds: void 0,
|
|
509
|
+
limit: void 0,
|
|
510
|
+
datasetId: void 0,
|
|
511
|
+
datasetIds: void 0,
|
|
512
|
+
attempts: void 0,
|
|
513
|
+
graderIds: void 0,
|
|
514
|
+
onlyWithAssertions: void 0,
|
|
515
|
+
dryRun: void 0
|
|
516
|
+
};
|
|
517
|
+
function hasApprovedAssertion(assertions) {
|
|
518
|
+
return assertions.some((assertion) => assertion.approvalState === "approved");
|
|
519
|
+
}
|
|
520
|
+
async function boundTraceIds(client, traceIds, limit, onlyWithAssertions) {
|
|
521
|
+
if (!Number.isInteger(limit) || limit < 1) throw new BitfabError("Replay limit must be a positive integer.");
|
|
522
|
+
if (!onlyWithAssertions || limit >= traceIds.length) return traceIds.slice(0, limit);
|
|
523
|
+
const selected = [];
|
|
524
|
+
for (const traceId of traceIds) {
|
|
525
|
+
const { assertions, inheritedFrom } = await client.traces.getAssertions(traceId);
|
|
526
|
+
if (inheritedFrom === null && hasApprovedAssertion(assertions)) {
|
|
527
|
+
selected.push(traceId);
|
|
528
|
+
if (selected.length === limit) break;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (selected.length === 0) throw new BitfabError("No traces with approved assertions matched this replay selection.");
|
|
532
|
+
return selected;
|
|
533
|
+
}
|
|
534
|
+
async function pinnedDatasetMembers(client, datasetIds, limit, onlyWithAssertions = false) {
|
|
535
|
+
const members = /* @__PURE__ */ new Set();
|
|
536
|
+
for (const datasetId of datasetIds) {
|
|
537
|
+
const { traceIds } = await client.datasets.listTraces(datasetId);
|
|
538
|
+
for (const traceId of traceIds) members.add(traceId);
|
|
539
|
+
}
|
|
540
|
+
const ordered = [...members].sort();
|
|
541
|
+
if (limit >= ordered.length) return;
|
|
542
|
+
if (limit > MAX_PINNED_TRACE_IDS) throw new BitfabError(`Bounding a dataset replay to ${limit} names one trace ID per replayed item, and a replay accepts at most ${MAX_PINNED_TRACE_IDS}. Drop the limit to replay every selected dataset in full.`);
|
|
543
|
+
return boundTraceIds(client, ordered, limit, onlyWithAssertions);
|
|
544
|
+
}
|
|
545
|
+
function usage(registry) {
|
|
546
|
+
return `Usage: bitfab-replay --registry <path> <${registry === void 0 ? "pipeline" : Object.keys(registry).join("|")}> [options]\n\nOptions:\n --limit N (bounds a trace ID or dataset selection instead of replacing it)\n --attempts N (replay each trace N times in this run, max ${MAX_ATTEMPTS})\n --trace-ids id1,id2\n --name NAME (what this run is testing, such as "baseline"; Bitfab already records the commit, branch, and datasets)\n --notes TEXT (run conditions Bitfab cannot see, such as a forced feature flag)\n --metadata key=value (repeatable; tags kept on the experiment, such as which schedule launched it, and filterable through client.experiments.list)\n --concurrency N, --max-concurrency N\n --code-change PATH, --no-code-change\n --experiment-group-id UUID (join this experiment group; a new UUID creates the group)\n --experiment-group-name NAME (name the group when this run creates it or it has no name yet; requires --experiment-group-id)\n --dataset-ids uuid1,uuid2 (--dataset-id is the same flag)\n --grader-ids id1,id2\n --only-with-assertions (replay only traces carrying an approved assertion)\n --skip-assertion-judging (don't judge approved assertions on each replay; judging is on by default and costs model calls)\n --judge-assertions (deprecated: judging is on by default)\n --mock none|all|marked\n --db-branch, --no-db-branch\n --dry-run (resolve inputs, run nothing)\n --fail-on-error (exit 1 after printing the result when any replayed item errored; under --dry-run, items whose inputs failed to resolve count)\n --resume EXPERIMENT_ID (finish an experiment that stopped partway, running only the items it did not finish, with the traces, attempts, and graders it started with)\n --force (with --resume, resume even when the experiment may still be running or the code changed since it started)\n --params PATH\n --param name=value (repeatable)\n -h, --help`;
|
|
547
|
+
}
|
|
548
|
+
var ReplayCliHelp = class extends Error {};
|
|
549
|
+
async function reseedFromRegistry(registry, pipeline, traceIds) {
|
|
550
|
+
const registration = registry[pipeline];
|
|
551
|
+
if (registration === void 0) throw new BitfabError(`Unknown pipeline '${pipeline}'. Registered: ${Object.keys(registry).join(", ")}`);
|
|
552
|
+
const traceFunctionKey = resolveTraceFunctionKey(registration);
|
|
553
|
+
const reseeded = [];
|
|
554
|
+
for (const traceId of traceIds) reseeded.push(await registration.client.reseedTrace(traceFunctionKey, registration.fn, { traceId }));
|
|
555
|
+
return {
|
|
556
|
+
pipeline,
|
|
557
|
+
traceFunctionKey,
|
|
558
|
+
reseeded
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
function parseSeedCases(raw, path, run) {
|
|
562
|
+
const trimmed = raw.trim();
|
|
563
|
+
if (trimmed.length === 0) throw new BitfabError(`Seed file '${path}' is empty.`);
|
|
564
|
+
return (trimmed.startsWith("[") ? JSON.parse(trimmed) : trimmed.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => JSON.parse(line))).map((value, index) => {
|
|
565
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new BitfabError(`Seed case ${index} in '${path}' must be an object with an "input" array.`);
|
|
566
|
+
const { input } = value;
|
|
567
|
+
if (!Array.isArray(input)) throw new BitfabError(`Seed case ${index} in '${path}' is missing an "input" array. Wrap a single argument as [arg].`);
|
|
568
|
+
if (run && "expected" in value) throw new BitfabError(`Seed case ${index} in '${path}' carries "expected", which --run does not record: the case is run once and its output is what the run produced. Remove the field, or drop --run to record the case without running it.`);
|
|
569
|
+
return value;
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
async function seedFromRegistry(registry, pipeline, cases, options = {}) {
|
|
573
|
+
const registration = registry[pipeline];
|
|
574
|
+
if (registration === void 0) throw new BitfabError(`Unknown pipeline '${pipeline}'. Registered: ${Object.keys(registry).join(", ")}`);
|
|
575
|
+
const traceFunctionKey = resolveTraceFunctionKey(registration);
|
|
576
|
+
if (options.run === true) {
|
|
577
|
+
const traceIds = [];
|
|
578
|
+
for (const seedCase of cases) traceIds.push(await registration.client.seedTrace(traceFunctionKey, registration.fn, {
|
|
579
|
+
args: seedCase.input,
|
|
580
|
+
metadata: seedCase.metadata,
|
|
581
|
+
sessionId: seedCase.sessionId
|
|
582
|
+
}));
|
|
583
|
+
return {
|
|
584
|
+
pipeline,
|
|
585
|
+
traceFunctionKey,
|
|
586
|
+
traceIds
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const traceIds = cases.map((seedCase) => registration.client.seedTrace(traceFunctionKey, {
|
|
590
|
+
input: seedCase.input,
|
|
591
|
+
expected: seedCase.expected,
|
|
592
|
+
fn: registration.fn,
|
|
593
|
+
metadata: seedCase.metadata,
|
|
594
|
+
sessionId: seedCase.sessionId
|
|
595
|
+
}));
|
|
596
|
+
const { flushTraces } = await Promise.resolve().then(() => http_exports);
|
|
597
|
+
await flushTraces(3e4);
|
|
598
|
+
return {
|
|
599
|
+
pipeline,
|
|
600
|
+
traceFunctionKey,
|
|
601
|
+
traceIds
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
const SEED_USAGE = "Usage: bitfab-seed --registry <path> <pipeline> --from-trace <id>[,<id>...]\n bitfab-seed --registry <path> <pipeline> --cases <cases.jsonl> [--run]\n\n--from-trace re-seeds each trace: the registered function runs once on the trace's recorded inputs and the result is recorded under the same trace id, with the previous run kept as history under a new id. Nothing is mocked and no experiment is created. Labels, assertions, and dataset membership stay on the trace.\n\n--cases seeds new cases. Each case is a JSON object with an \"input\" array, plus optional \"expected\", \"metadata\", and \"sessionId\"; a JSON array of those objects works too. Without --run, each case is written as a trace directly (\"input\" becomes the root span input and \"expected\" its output) and nothing executes. With --run, each case runs once through the registered function with capture off and the execution is recorded, so the output is what the run produced; cases carrying \"expected\" are rejected.\n\nbitfab-replay --registry <path> <pipeline> --seed <cases.jsonl> [--run] is the older spelling of --cases.";
|
|
605
|
+
async function runSeedCli(registry, argv, io = {}) {
|
|
606
|
+
const stdout = io.stdout ?? console.log;
|
|
607
|
+
const stderr = io.stderr ?? console.error;
|
|
608
|
+
if (argv.some((value) => HELP_FLAGS.has(value))) throw new ReplayCliHelp(SEED_USAGE);
|
|
609
|
+
const pipeline = argv[0];
|
|
610
|
+
if (pipeline === void 0 || registry[pipeline] === void 0) throw new BitfabError(SEED_USAGE);
|
|
611
|
+
const fromTraceIndex = argv.indexOf("--from-trace");
|
|
612
|
+
if (fromTraceIndex !== -1) {
|
|
613
|
+
const raw = argv[fromTraceIndex + 1];
|
|
614
|
+
if (raw === void 0 || raw.startsWith("--")) throw new BitfabError("--from-trace requires one or more trace ids.");
|
|
615
|
+
const traceIds = commaSeparated("--from-trace", raw);
|
|
616
|
+
stderr(`[seed] Re-seeding ${traceIds.length} trace(s) through "${pipeline}"...`);
|
|
617
|
+
const result = await reseedFromRegistry(registry, pipeline, traceIds);
|
|
618
|
+
for (const entry of result.reseeded) stderr(`[seed] ${entry.traceId} now holds the fresh run; the previous run is kept as ${entry.previousRunTraceId}`);
|
|
619
|
+
stdout(JSON.stringify(result, null, 2));
|
|
620
|
+
return result;
|
|
621
|
+
}
|
|
622
|
+
const casesFlagIndex = argv.indexOf("--cases");
|
|
623
|
+
const seedIndex = casesFlagIndex === -1 ? argv.indexOf("--seed") : casesFlagIndex;
|
|
624
|
+
if (seedIndex === -1) throw new BitfabError(SEED_USAGE);
|
|
625
|
+
const casesPath = argv[seedIndex + 1];
|
|
626
|
+
if (casesPath === void 0 || casesPath.startsWith("--")) throw new BitfabError(`${argv[seedIndex]} requires a path to a cases file.`);
|
|
627
|
+
const run = argv.includes("--run");
|
|
628
|
+
const cases = parseSeedCases(await (io.readFile ?? defaultReadFile)(casesPath), casesPath, run);
|
|
629
|
+
stderr(run ? `[seed] Running ${cases.length} case(s) through "${pipeline}"...` : `[seed] Seeding ${cases.length} case(s) into "${pipeline}"...`);
|
|
630
|
+
const result = await seedFromRegistry(registry, pipeline, cases, { run });
|
|
631
|
+
stderr(`[seed] ${run ? "Recorded" : "Wrote"} ${result.traceIds.length} trace(s) for "${result.traceFunctionKey}". Replay them with --trace-ids ${result.traceIds.slice(0, 3).join(",")}${result.traceIds.length > 3 ? ",..." : ""}`);
|
|
632
|
+
stdout(JSON.stringify(result, null, 2));
|
|
633
|
+
return result;
|
|
634
|
+
}
|
|
635
|
+
function requirePositiveInteger(flag, raw, max) {
|
|
636
|
+
const value = Number(raw);
|
|
637
|
+
if (!Number.isInteger(value) || value < 1) throw new BitfabError(`${flag} must be a positive integer (received '${raw}').`);
|
|
638
|
+
if (max !== void 0 && value > max) throw new BitfabError(`${flag} must be at most ${max} (received '${raw}').`);
|
|
639
|
+
return value;
|
|
640
|
+
}
|
|
641
|
+
function commaSeparated(flag, raw) {
|
|
642
|
+
const values = raw.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
|
|
643
|
+
if (values.length === 0) throw new BitfabError(`${flag} must contain at least one value.`);
|
|
644
|
+
return values;
|
|
645
|
+
}
|
|
646
|
+
function checkReplayCliArgs(argv) {
|
|
647
|
+
parseReplayCliArgs(void 0, argv);
|
|
648
|
+
}
|
|
649
|
+
function parseReplayCliArgs(registry, argv) {
|
|
650
|
+
if (argv.some((value) => HELP_FLAGS.has(value))) throw new ReplayCliHelp(usage(registry));
|
|
651
|
+
const pipeline = argv[0];
|
|
652
|
+
if (pipeline === void 0 || registry !== void 0 && registry[pipeline] === void 0) throw new BitfabError(usage(registry));
|
|
653
|
+
const parsed = {
|
|
654
|
+
pipeline,
|
|
655
|
+
params: [],
|
|
656
|
+
flags: {}
|
|
657
|
+
};
|
|
658
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
659
|
+
const flag = argv[index];
|
|
660
|
+
if (BOOLEAN_FLAGS.has(flag)) {
|
|
661
|
+
recordFlag(parsed.flags, flag);
|
|
662
|
+
switch (flag) {
|
|
663
|
+
case "--db-branch":
|
|
664
|
+
if (parsed.dbBranch === false) throw new BitfabError("--db-branch and --no-db-branch cannot be used together.");
|
|
665
|
+
parsed.dbBranch = true;
|
|
666
|
+
break;
|
|
667
|
+
case "--no-db-branch":
|
|
668
|
+
if (parsed.dbBranch === true) throw new BitfabError("--db-branch and --no-db-branch cannot be used together.");
|
|
669
|
+
parsed.dbBranch = false;
|
|
670
|
+
break;
|
|
671
|
+
case "--only-with-assertions":
|
|
672
|
+
parsed.onlyWithAssertions = true;
|
|
673
|
+
break;
|
|
674
|
+
case "--skip-assertion-judging":
|
|
675
|
+
parsed.skipAssertionJudging = true;
|
|
676
|
+
break;
|
|
677
|
+
case "--judge-assertions":
|
|
678
|
+
parsed.judgeAssertionsFlag = true;
|
|
679
|
+
break;
|
|
680
|
+
case "--no-code-change":
|
|
681
|
+
parsed.noCodeChange = true;
|
|
682
|
+
break;
|
|
683
|
+
case "--dry-run":
|
|
684
|
+
parsed.dryRun = true;
|
|
685
|
+
break;
|
|
686
|
+
case "--fail-on-error":
|
|
687
|
+
parsed.failOnError = true;
|
|
688
|
+
break;
|
|
689
|
+
case "--force": parsed.force = true;
|
|
690
|
+
}
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
if (!VALUE_FLAGS.has(flag)) throw new BitfabError(`Unknown replay option '${flag}'.\n${usage(registry)}`);
|
|
694
|
+
const raw = argv[index + 1];
|
|
695
|
+
if (raw === void 0 || raw.startsWith("--")) throw new BitfabError(`${flag} requires a value.`);
|
|
696
|
+
index += 1;
|
|
697
|
+
recordFlag(parsed.flags, flag, raw);
|
|
698
|
+
switch (flag) {
|
|
699
|
+
case "--limit":
|
|
700
|
+
parsed.limit = requirePositiveInteger(flag, raw);
|
|
701
|
+
break;
|
|
702
|
+
case "--attempts":
|
|
703
|
+
parsed.attempts = requirePositiveInteger(flag, raw, MAX_ATTEMPTS);
|
|
704
|
+
break;
|
|
705
|
+
case "--trace-ids":
|
|
706
|
+
parsed.traceIds = commaSeparated(flag, raw);
|
|
707
|
+
break;
|
|
708
|
+
case "--name":
|
|
709
|
+
parsed.name = raw;
|
|
710
|
+
break;
|
|
711
|
+
case "--notes":
|
|
712
|
+
parsed.notes = raw;
|
|
713
|
+
break;
|
|
714
|
+
case "--metadata": {
|
|
715
|
+
const [key, value] = parseMetadataTag(raw);
|
|
716
|
+
parsed.metadata = {
|
|
717
|
+
...parsed.metadata,
|
|
718
|
+
[key]: value
|
|
719
|
+
};
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
case "--concurrency":
|
|
723
|
+
case "--max-concurrency":
|
|
724
|
+
parsed.maxConcurrency = requirePositiveInteger(flag, raw);
|
|
725
|
+
break;
|
|
726
|
+
case "--code-change":
|
|
727
|
+
parsed.codeChangePath = raw;
|
|
728
|
+
break;
|
|
729
|
+
case "--experiment-group-id":
|
|
730
|
+
parsed.experimentGroupId = raw;
|
|
731
|
+
break;
|
|
732
|
+
case "--experiment-group-name":
|
|
733
|
+
parsed.experimentGroupName = raw;
|
|
734
|
+
break;
|
|
735
|
+
case "--dataset-id":
|
|
736
|
+
case "--dataset-ids":
|
|
737
|
+
parsed.datasetIds = commaSeparated(flag, raw);
|
|
738
|
+
break;
|
|
739
|
+
case "--grader-ids":
|
|
740
|
+
parsed.graderIds = commaSeparated(flag, raw);
|
|
741
|
+
break;
|
|
742
|
+
case "--mock":
|
|
743
|
+
if (raw !== "none" && raw !== "all" && raw !== "marked") throw new BitfabError(`--mock must be one of: none, all, marked (received '${raw}').`);
|
|
744
|
+
parsed.mock = raw;
|
|
745
|
+
break;
|
|
746
|
+
case "--params":
|
|
747
|
+
parsed.paramsPath = raw;
|
|
748
|
+
break;
|
|
749
|
+
case "--param":
|
|
750
|
+
parsed.params.push(raw);
|
|
751
|
+
break;
|
|
752
|
+
case "--resume": parsed.resume = raw;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
if (parsed.force === true && parsed.resume === void 0) throw new BitfabError("--force only applies with --resume <experimentId>.");
|
|
756
|
+
if (parsed.resume !== void 0) {
|
|
757
|
+
const conflicting = RESUME_CONFLICTING_FLAGS.filter(([key]) => parsed[key] !== void 0).map(([, flag]) => flag);
|
|
758
|
+
if (conflicting.length > 0) throw new BitfabError(`--resume reuses the traces, attempts, and graders experiment ${parsed.resume} started with, so it cannot be combined with ${conflicting.join(", ")}.`);
|
|
759
|
+
}
|
|
760
|
+
if (parsed.traceIds !== void 0 && parsed.datasetIds !== void 0) throw new BitfabError("--trace-ids and --dataset-ids select different replay sources and cannot be used together.");
|
|
761
|
+
if (parsed.codeChangePath !== void 0 && parsed.noCodeChange === true) throw new BitfabError("--code-change and --no-code-change cannot be used together.");
|
|
762
|
+
return parsed;
|
|
763
|
+
}
|
|
764
|
+
function resolveRegistrationDatasetIds(options) {
|
|
765
|
+
if (options.datasetId !== void 0 && options.datasetIds !== void 0) throw new BitfabError("Replay registry options datasetId and datasetIds are the same selector: set one of them.");
|
|
766
|
+
if (options.datasetId !== void 0) return [options.datasetId];
|
|
767
|
+
return options.datasetIds;
|
|
768
|
+
}
|
|
769
|
+
function describeSelection(options) {
|
|
770
|
+
if (options.traceIds !== void 0) return `${options.traceIds.length} traces`;
|
|
771
|
+
if (options.limit !== void 0) return `${options.limit} traces`;
|
|
772
|
+
const datasetCount = options.datasetIds?.length ?? 1;
|
|
773
|
+
return datasetCount > 1 ? `${datasetCount} datasets' traces` : "dataset traces";
|
|
774
|
+
}
|
|
775
|
+
function resolveTraceFunctionKey(registration) {
|
|
776
|
+
const wrappedKey = registration.fn._bitfabTraceFunctionKey;
|
|
777
|
+
const key = registration.traceFunctionKey ?? wrappedKey;
|
|
778
|
+
if (key === void 0) throw new BitfabError("Replay registry entry uses a plain function. Set traceFunctionKey to the key its production handler records.");
|
|
779
|
+
return key;
|
|
780
|
+
}
|
|
781
|
+
async function defaultReadFile(path) {
|
|
782
|
+
const fs = await import("fs/promises").catch(() => null);
|
|
783
|
+
if (fs === null) throw new BitfabError("--code-change requires a runtime that can read local files.");
|
|
784
|
+
return fs.readFile(path, "utf8");
|
|
785
|
+
}
|
|
786
|
+
async function loadCodeChange(path, readFile) {
|
|
787
|
+
if (path === void 0) return;
|
|
788
|
+
const value = JSON.parse(await readFile(path));
|
|
789
|
+
if (typeof value.description !== "string" || !Array.isArray(value.files)) throw new BitfabError(`Invalid --code-change file '${path}': expected { description, files }.`);
|
|
790
|
+
return {
|
|
791
|
+
description: value.description,
|
|
792
|
+
files: value.files
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function parseMetadataTag(raw) {
|
|
796
|
+
const separator = raw.indexOf("=");
|
|
797
|
+
const key = raw.slice(0, separator).trim();
|
|
798
|
+
if (separator < 1 || key.length === 0) throw new BitfabError(`Invalid --metadata '${raw}': expected a non-empty key=value pair.`);
|
|
799
|
+
return [key, raw.slice(separator + 1)];
|
|
800
|
+
}
|
|
801
|
+
function parseParameter(raw) {
|
|
802
|
+
const separator = raw.indexOf("=");
|
|
803
|
+
const key = raw.slice(0, separator).trim();
|
|
804
|
+
if (separator < 1 || key.length === 0) throw new BitfabError(`Invalid --param '${raw}': expected a non-empty name=value pair.`);
|
|
805
|
+
const value = raw.slice(separator + 1);
|
|
806
|
+
try {
|
|
807
|
+
return [key, JSON.parse(value)];
|
|
808
|
+
} catch {
|
|
809
|
+
return [key, value];
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
async function loadParameters(path, rawParameters, readFile) {
|
|
813
|
+
let params = {};
|
|
814
|
+
if (path !== void 0) {
|
|
815
|
+
const value = JSON.parse(await readFile(path));
|
|
816
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new BitfabError(`Invalid --params file '${path}': expected a JSON object.`);
|
|
817
|
+
params = { ...value };
|
|
818
|
+
}
|
|
819
|
+
for (const raw of rawParameters) {
|
|
820
|
+
const [key, value] = parseParameter(raw);
|
|
821
|
+
params[key] = value;
|
|
822
|
+
}
|
|
823
|
+
return params;
|
|
824
|
+
}
|
|
825
|
+
function valuesEqual(left, right) {
|
|
826
|
+
try {
|
|
827
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
828
|
+
} catch {
|
|
829
|
+
return Object.is(left, right);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
function renderSummary(pipeline, result, stderr) {
|
|
833
|
+
let same = 0;
|
|
834
|
+
let changed = 0;
|
|
835
|
+
let matched = 0;
|
|
836
|
+
let missed = 0;
|
|
837
|
+
let errors = 0;
|
|
838
|
+
let carriedOver = 0;
|
|
839
|
+
for (const item of result.items) {
|
|
840
|
+
if (item.carriedOver === true) {
|
|
841
|
+
carriedOver += 1;
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
if (item.error !== null) {
|
|
845
|
+
errors += 1;
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
const equal = valuesEqual(item.result, item.originalOutput);
|
|
849
|
+
if (item.ingestionType === "seeded") {
|
|
850
|
+
if (equal) matched += 1;
|
|
851
|
+
else missed += 1;
|
|
852
|
+
} else if (equal) same += 1;
|
|
853
|
+
else changed += 1;
|
|
854
|
+
}
|
|
855
|
+
stderr("\n─── Summary ───");
|
|
856
|
+
stderr(` Pipeline: ${pipeline}`);
|
|
857
|
+
stderr(` Replayed: ${result.items.length - carriedOver}`);
|
|
858
|
+
if (carriedOver > 0) stderr(` Carried over from the earlier run: ${carriedOver}`);
|
|
859
|
+
if (result.attempts > 1) stderr(` Attempts: ${result.attempts}`);
|
|
860
|
+
if (same > 0 || changed > 0 || matched + missed === 0) {
|
|
861
|
+
stderr(` Same: ${same}`);
|
|
862
|
+
stderr(` Changed: ${changed}`);
|
|
863
|
+
}
|
|
864
|
+
if (matched > 0 || missed > 0) {
|
|
865
|
+
stderr(` Matched expected: ${matched}`);
|
|
866
|
+
stderr(` Missed expected: ${missed}`);
|
|
867
|
+
}
|
|
868
|
+
if (errors > 0) stderr(` Errors: ${errors}`);
|
|
869
|
+
stderr(`\n ${result.experimentUrl}`);
|
|
870
|
+
}
|
|
871
|
+
function countErroredItems(result) {
|
|
872
|
+
const replayed = result.items.filter((item) => item.carriedOver !== true);
|
|
873
|
+
return {
|
|
874
|
+
errored: replayed.filter((item) => item.error !== null).length,
|
|
875
|
+
replayed: replayed.length
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function renderDryRun(pipeline, result, stderr) {
|
|
879
|
+
stderr("\n─── Dry run ───");
|
|
880
|
+
stderr(` Pipeline: ${pipeline}`);
|
|
881
|
+
stderr(` Resolved: ${result.items.length} (nothing was executed)`);
|
|
882
|
+
for (const item of result.items) {
|
|
883
|
+
stderr(`\n ${item.originalTraceId}`);
|
|
884
|
+
stderr(` args: ${safeJson(item.input)}`);
|
|
885
|
+
}
|
|
886
|
+
if (result.experimentUrl !== null) stderr(`\n ${result.experimentUrl}`);
|
|
887
|
+
}
|
|
888
|
+
function safeJson(value) {
|
|
889
|
+
try {
|
|
890
|
+
return JSON.stringify(value) ?? String(value);
|
|
891
|
+
} catch {
|
|
892
|
+
return String(value);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
function shellQuote(value) {
|
|
896
|
+
return /^[\w@%+=:,./-]+$/.test(value) ? value : `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
897
|
+
}
|
|
898
|
+
function resumeCommand(registryPath, args, experimentId) {
|
|
899
|
+
const carried = [
|
|
900
|
+
...args.mock === void 0 ? [] : ["--mock", args.mock],
|
|
901
|
+
...args.paramsPath === void 0 ? [] : ["--params", args.paramsPath],
|
|
902
|
+
...args.params.flatMap((param) => ["--param", param])
|
|
903
|
+
];
|
|
904
|
+
return [
|
|
905
|
+
"Resume with: bitfab-replay --registry",
|
|
906
|
+
registryPath === void 0 ? "<path>" : shellQuote(registryPath),
|
|
907
|
+
...[
|
|
908
|
+
args.pipeline,
|
|
909
|
+
...carried,
|
|
910
|
+
"--resume",
|
|
911
|
+
experimentId
|
|
912
|
+
].map(shellQuote)
|
|
913
|
+
].join(" ");
|
|
914
|
+
}
|
|
915
|
+
function reportReplayError(error, stderr) {
|
|
916
|
+
for (const item of error.items) stderr(`${item.originalTraceId}: ${String(item.traceError ?? item.replayError ?? item.error)}`);
|
|
917
|
+
}
|
|
918
|
+
const API_KEY_LINE_PREFIX = "@@bitfab:api-key ";
|
|
919
|
+
const PRINT_API_KEY_FLAG = "--print-api-key";
|
|
920
|
+
async function runReplayCli(registry, argv = typeof process === "undefined" ? [] : process.argv.slice(2), io = {}, registryPath) {
|
|
921
|
+
const stdout = io.stdout ?? console.log;
|
|
922
|
+
const stderr = io.stderr ?? console.error;
|
|
923
|
+
const printApiKey = argv.includes(PRINT_API_KEY_FLAG);
|
|
924
|
+
const args = parseReplayCliArgs(registry, argv.filter((arg) => arg !== PRINT_API_KEY_FLAG));
|
|
925
|
+
if (printApiKey) {
|
|
926
|
+
const client = registry[args.pipeline].client;
|
|
927
|
+
const apiKey = _resolvedApiKey(client);
|
|
928
|
+
const serviceUrl = _serviceUrl(client);
|
|
929
|
+
stdout(`${API_KEY_LINE_PREFIX}${JSON.stringify({
|
|
930
|
+
apiKey,
|
|
931
|
+
serviceUrl
|
|
932
|
+
})}`);
|
|
933
|
+
return null;
|
|
934
|
+
}
|
|
935
|
+
if (args.judgeAssertionsFlag) warnJudgeAssertionsDeprecated(stderr);
|
|
936
|
+
const registration = registry[args.pipeline];
|
|
937
|
+
const traceFunctionKey = resolveTraceFunctionKey(registration);
|
|
938
|
+
const readFile = io.readFile ?? defaultReadFile;
|
|
939
|
+
const params = await loadParameters(args.paramsPath, args.params, readFile);
|
|
940
|
+
const dynamicOptions = await registration.optionsFactory?.({ params });
|
|
941
|
+
const registrationOptions = {
|
|
942
|
+
...registration.options,
|
|
943
|
+
...dynamicOptions
|
|
944
|
+
};
|
|
945
|
+
const registrationDatasetIds = resolveRegistrationDatasetIds(registrationOptions);
|
|
946
|
+
if (registrationOptions.traceIds !== void 0 && registrationDatasetIds !== void 0) throw new BitfabError("Replay registry options traceIds and datasetIds select different sources and cannot be used together.");
|
|
947
|
+
const selectedDatasetIds = args.datasetIds ?? registrationDatasetIds;
|
|
948
|
+
const codeChange = await loadCodeChange(args.codeChangePath, readFile);
|
|
949
|
+
let startedExperimentId;
|
|
950
|
+
const options = {
|
|
951
|
+
...registrationOptions,
|
|
952
|
+
...args.name === void 0 ? {} : { name: args.name },
|
|
953
|
+
...args.notes === void 0 ? {} : { notes: args.notes },
|
|
954
|
+
...args.metadata === void 0 ? {} : { metadata: args.metadata },
|
|
955
|
+
...args.attempts === void 0 ? {} : { attempts: args.attempts },
|
|
956
|
+
...args.maxConcurrency === void 0 ? {} : { maxConcurrency: args.maxConcurrency },
|
|
957
|
+
...args.experimentGroupId === void 0 ? {} : { experimentGroupId: args.experimentGroupId },
|
|
958
|
+
...args.experimentGroupName === void 0 ? {} : { experimentGroupName: args.experimentGroupName },
|
|
959
|
+
datasetId: void 0,
|
|
960
|
+
datasetIds: selectedDatasetIds,
|
|
961
|
+
...args.graderIds === void 0 ? {} : { graderIds: args.graderIds },
|
|
962
|
+
...args.onlyWithAssertions === void 0 ? {} : { onlyWithAssertions: args.onlyWithAssertions },
|
|
963
|
+
...args.skipAssertionJudging === void 0 ? {} : { skipAssertionJudging: args.skipAssertionJudging },
|
|
964
|
+
...args.mock === void 0 ? {} : { mock: args.mock },
|
|
965
|
+
...args.dbBranch === void 0 ? {} : { dbBranch: args.dbBranch && registrationOptions.dbBranch !== void 0 && registrationOptions.dbBranch !== false ? registrationOptions.dbBranch : args.dbBranch },
|
|
966
|
+
...args.noCodeChange === true ? {
|
|
967
|
+
codeChangeDescription: null,
|
|
968
|
+
codeChangeFiles: null
|
|
969
|
+
} : {},
|
|
970
|
+
...args.dryRun === true ? { dryRun: true } : {},
|
|
971
|
+
...codeChange === void 0 ? {} : {
|
|
972
|
+
codeChangeDescription: codeChange.description,
|
|
973
|
+
codeChangeFiles: codeChange.files
|
|
974
|
+
},
|
|
975
|
+
onItemStart: reportReplayProgress,
|
|
976
|
+
onItemFinish: reportReplayProgress,
|
|
977
|
+
onExperimentStart: ({ experimentId, experimentUrl }) => {
|
|
978
|
+
startedExperimentId = experimentId;
|
|
979
|
+
stderr(`[replay] Experiment ${experimentId}: ${experimentUrl}`);
|
|
980
|
+
},
|
|
981
|
+
onInterrupt: ({ experimentId, signal, markedInterrupted }) => {
|
|
982
|
+
stderr(`${describeReplayInterrupt(experimentId, markedInterrupted)} ${resumeCommand(registryPath, args, experimentId)}`);
|
|
983
|
+
process.exit(INTERRUPT_EXIT_CODES[signal]);
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
withCliFlags(options, args.flags);
|
|
987
|
+
if (args.resume !== void 0) {
|
|
988
|
+
Object.assign(options, RESUME_CLEARED_OPTIONS, {
|
|
989
|
+
resume: args.resume,
|
|
990
|
+
...args.force === true ? { force: true } : {}
|
|
991
|
+
});
|
|
992
|
+
stderr(`[replay] Resuming experiment ${args.resume} of "${traceFunctionKey}"...`);
|
|
993
|
+
} else {
|
|
994
|
+
if (args.traceIds !== void 0) {
|
|
995
|
+
options.traceIds = args.traceIds;
|
|
996
|
+
options.datasetIds = void 0;
|
|
997
|
+
} else if (args.datasetIds !== void 0) options.traceIds = void 0;
|
|
998
|
+
const bound = args.limit ?? registrationOptions.limit;
|
|
999
|
+
options.limit = void 0;
|
|
1000
|
+
if (options.traceIds !== void 0) {
|
|
1001
|
+
if (bound !== void 0) options.traceIds = await boundTraceIds(registration.client, options.traceIds, bound, options.onlyWithAssertions === true);
|
|
1002
|
+
} else if (options.datasetIds !== void 0) {
|
|
1003
|
+
if (bound !== void 0) {
|
|
1004
|
+
const pinned = await pinnedDatasetMembers(registration.client, options.datasetIds, bound, options.onlyWithAssertions === true);
|
|
1005
|
+
if (pinned !== void 0) options.traceIds = pinned;
|
|
1006
|
+
}
|
|
1007
|
+
} else options.limit = bound ?? 10;
|
|
1008
|
+
const attemptsSuffix = options.attempts !== void 0 && options.attempts > 1 ? ` (${options.attempts} attempts each)` : "";
|
|
1009
|
+
stderr(`[replay] ${args.dryRun === true ? "Resolving inputs for" : "Replaying"} ${describeSelection(options)} from "${traceFunctionKey}"${attemptsSuffix}...`);
|
|
1010
|
+
}
|
|
1011
|
+
let result;
|
|
1012
|
+
try {
|
|
1013
|
+
result = await registration.client.replay(traceFunctionKey, registration.fn, options);
|
|
1014
|
+
} catch (error) {
|
|
1015
|
+
if (error instanceof ReplayError) reportReplayError(error, stderr);
|
|
1016
|
+
const experimentId = error instanceof ReplayError ? error.experimentId : startedExperimentId;
|
|
1017
|
+
if (experimentId !== void 0) stderr(resumeCommand(registryPath, args, experimentId));
|
|
1018
|
+
throw error;
|
|
1019
|
+
}
|
|
1020
|
+
if (args.dryRun === true) renderDryRun(args.pipeline, result, stderr);
|
|
1021
|
+
else renderSummary(args.pipeline, result, stderr);
|
|
1022
|
+
stdout(serializeReplayResult(result));
|
|
1023
|
+
if (result.items.length === 0) throw new BitfabError(`No traces matched "${traceFunctionKey}", so nothing was replayed. Seed cases with --seed, or capture a trace first.`);
|
|
1024
|
+
if (args.failOnError === true) {
|
|
1025
|
+
const { errored, replayed } = countErroredItems(result);
|
|
1026
|
+
if (errored > 0) throw new BitfabError(`[replay] ${errored} of ${replayed} ${args.dryRun === true ? "resolved" : "replayed"} items errored; exiting 1 because of --fail-on-error`);
|
|
1027
|
+
}
|
|
1028
|
+
return result;
|
|
1029
|
+
}
|
|
1030
|
+
//#endregion
|
|
1031
|
+
//#region src/registryCommand.ts
|
|
1032
|
+
let isTypeScriptLoaderRegistered = false;
|
|
1033
|
+
async function parseRegistryCommandArgs(argv, usage) {
|
|
1034
|
+
if (argv.some((value) => value === "--help" || value === "-h") && !argv.includes("--registry")) throw new ReplayCliHelp(usage);
|
|
1035
|
+
const registryFlagIndex = argv.indexOf("--registry");
|
|
1036
|
+
const registryPath = argv[registryFlagIndex + 1];
|
|
1037
|
+
if (registryFlagIndex === -1 || registryPath === void 0 || registryPath.startsWith("--")) throw new BitfabError(usage);
|
|
1038
|
+
const commandArgs = argv.filter((_value, index) => index !== registryFlagIndex && index !== registryFlagIndex + 1);
|
|
1039
|
+
if (commandArgs.length === 0) throw new BitfabError(usage);
|
|
1040
|
+
const { resolve } = await import("path");
|
|
1041
|
+
return {
|
|
1042
|
+
registryPath: resolve(registryPath),
|
|
1043
|
+
commandArgs
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
async function importRegistry(path) {
|
|
1047
|
+
const { pathToFileURL } = await import("url");
|
|
1048
|
+
if (!isTypeScriptLoaderRegistered) {
|
|
1049
|
+
register();
|
|
1050
|
+
isTypeScriptLoaderRegistered = true;
|
|
1051
|
+
}
|
|
1052
|
+
try {
|
|
1053
|
+
return await import(pathToFileURL(path).href);
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
if (isModuleNotFoundError(error)) throw new BitfabError(`${error.message}\n\n${registryImportHint(path)}`);
|
|
1056
|
+
throw error;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
function isModuleNotFoundError(error) {
|
|
1060
|
+
const code = error?.code;
|
|
1061
|
+
return error instanceof Error && (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND");
|
|
1062
|
+
}
|
|
1063
|
+
function registryImportHint(path) {
|
|
1064
|
+
return [
|
|
1065
|
+
`The replay registry '${path}' could not load one of its imports.`,
|
|
1066
|
+
"A registry belongs in the package that builds the traced function's call and already depends on everything it imports (its tools, clients, and config), and bitfab-replay runs from that package's directory.",
|
|
1067
|
+
"When the traced function lives in a lower-level library that an app package calls, put the registry in the app package and add @bitfab/sdk to it. Copying the app's dependencies into the library is the wrong fix.",
|
|
1068
|
+
`See "When the traced function needs the app's dependencies" at https://docs.bitfab.ai/typescript-sdk.`
|
|
1069
|
+
].join("\n");
|
|
1070
|
+
}
|
|
1071
|
+
function resolveRegistry(moduleValue, path) {
|
|
1072
|
+
if (typeof moduleValue !== "object" || moduleValue === null) throw new BitfabError(`Replay registry '${path}' must export a default registry.`);
|
|
1073
|
+
const registry = moduleValue.default;
|
|
1074
|
+
if (typeof registry !== "object" || registry === null) throw new BitfabError(`Replay registry '${path}' must export a default registry.`);
|
|
1075
|
+
validateReplayRegistry(registry);
|
|
1076
|
+
return registry;
|
|
1077
|
+
}
|
|
1078
|
+
async function runRegistryMain(run, io = {}) {
|
|
1079
|
+
const stdout = io.stdout ?? console.log;
|
|
1080
|
+
const stderr = io.stderr ?? console.error;
|
|
1081
|
+
try {
|
|
1082
|
+
await run();
|
|
1083
|
+
return 0;
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1086
|
+
if (error instanceof ReplayCliHelp) {
|
|
1087
|
+
stdout(message);
|
|
1088
|
+
return 0;
|
|
1089
|
+
}
|
|
1090
|
+
if (error instanceof ReplayError) stderr(`Experiment ${error.experimentId}: ${error.experimentUrl}`);
|
|
1091
|
+
stderr(message);
|
|
1092
|
+
return 1;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
async function isEntrypoint(moduleUrl) {
|
|
1096
|
+
const executablePath = process.argv[1];
|
|
1097
|
+
if (executablePath === void 0) return false;
|
|
1098
|
+
const [{ realpath }, { fileURLToPath }] = await Promise.all([import("fs/promises"), import("url")]);
|
|
1099
|
+
return await realpath(executablePath) === await realpath(fileURLToPath(moduleUrl));
|
|
1100
|
+
}
|
|
1101
|
+
function runIfEntrypoint(moduleUrl, main) {
|
|
1102
|
+
isEntrypoint(moduleUrl).then((entrypoint) => {
|
|
1103
|
+
if (entrypoint) main().then((exitCode) => {
|
|
1104
|
+
process.exitCode = exitCode;
|
|
1105
|
+
});
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
//#endregion
|
|
1109
|
+
export { runRegistryMain as a, runSeedCli as c, runIfEntrypoint as i, parseRegistryCommandArgs as n, checkReplayCliArgs as o, resolveRegistry as r, runReplayCli as s, importRegistry as t };
|
|
1110
|
+
|
|
1111
|
+
//# sourceMappingURL=registryCommand-Di8x0YoJ.js.map
|