bitfab 0.61.2 → 0.61.4
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.cjs.map +1 -1
- package/dist/autoTrace.d.cts +1 -0
- package/dist/autoTrace.d.ts +1 -0
- package/dist/autoTrace.js +1 -1
- package/dist/{chunk-INA6W7OB.js → chunk-54EGXUZ3.js} +32 -3
- package/dist/chunk-54EGXUZ3.js.map +1 -0
- package/dist/{chunk-BMJNDIVA.js → chunk-K4XCWQBC.js} +1 -1
- package/dist/chunk-K4XCWQBC.js.map +1 -0
- package/dist/{chunk-A4UVO6BQ.js → chunk-PSAEQ4OW.js} +480 -15
- package/dist/chunk-PSAEQ4OW.js.map +1 -0
- package/dist/{chunk-ONZFMRRF.js → chunk-VWRCLZCA.js} +30 -3
- package/dist/chunk-VWRCLZCA.js.map +1 -0
- package/dist/{chunk-MTK6PZHA.js → chunk-WPYBJGTG.js} +21 -3
- package/dist/chunk-WPYBJGTG.js.map +1 -0
- package/dist/{chunk-LUOCT4HG.js → chunk-XGRQMQ2R.js} +38 -7
- package/dist/chunk-XGRQMQ2R.js.map +1 -0
- package/dist/{http-2R6MSRVA.js → http-C2TCBBQV.js} +2 -2
- package/dist/{http-KM6QWKAP.js → http-ONLJPP6W.js} +2 -2
- package/dist/index.cjs +568 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +131 -3
- package/dist/index.d.ts +131 -3
- package/dist/index.js +4 -4
- package/dist/node.cjs +568 -15
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +4 -4
- package/dist/{replay-6UUBJBLK.js → replay-RM24QKEA.js} +3 -3
- package/dist/replayCli.js +2 -2
- package/dist/seedCli.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-A4UVO6BQ.js.map +0 -1
- package/dist/chunk-BMJNDIVA.js.map +0 -1
- package/dist/chunk-INA6W7OB.js.map +0 -1
- package/dist/chunk-LUOCT4HG.js.map +0 -1
- package/dist/chunk-MTK6PZHA.js.map +0 -1
- package/dist/chunk-ONZFMRRF.js.map +0 -1
- /package/dist/{http-2R6MSRVA.js.map → http-C2TCBBQV.js.map} +0 -0
- /package/dist/{http-KM6QWKAP.js.map → http-ONLJPP6W.js.map} +0 -0
- /package/dist/{replay-6UUBJBLK.js.map → replay-RM24QKEA.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -51,7 +51,7 @@ var __version__, __packageName__;
|
|
|
51
51
|
var init_version_generated = __esm({
|
|
52
52
|
"src/version.generated.ts"() {
|
|
53
53
|
"use strict";
|
|
54
|
-
__version__ = "0.61.
|
|
54
|
+
__version__ = "0.61.4";
|
|
55
55
|
__packageName__ = "bitfab";
|
|
56
56
|
}
|
|
57
57
|
});
|
|
@@ -1077,6 +1077,7 @@ var init_simulationPlan = __esm({
|
|
|
1077
1077
|
"input_meta",
|
|
1078
1078
|
"output",
|
|
1079
1079
|
"output_meta",
|
|
1080
|
+
"replay_recording",
|
|
1080
1081
|
"prompt"
|
|
1081
1082
|
];
|
|
1082
1083
|
SimulationPlan = class {
|
|
@@ -2285,7 +2286,7 @@ function warnDroppedExternalTrace(error) {
|
|
|
2285
2286
|
function experimentIdWireFields(experimentId) {
|
|
2286
2287
|
return { experimentId, testRunId: experimentId };
|
|
2287
2288
|
}
|
|
2288
|
-
var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, REPLAY_COMPLETE_REQUEST_TIMEOUT_MS, OTLP_TRACES_ENDPOINT, RETRYABLE_STATUSES, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCLE_TIMEOUT_MS2, pendingTracePromises, liveHttpClients, carrierSeq, HttpClient;
|
|
2289
|
+
var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, REPLAY_COMPLETE_REQUEST_TIMEOUT_MS, REPLAY_INTERRUPT_REQUEST_TIMEOUT_MS, REPLAY_HEARTBEAT_REQUEST_TIMEOUT_MS, OTLP_TRACES_ENDPOINT, RETRYABLE_STATUSES, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCLE_TIMEOUT_MS2, pendingTracePromises, liveHttpClients, carrierSeq, HttpClient;
|
|
2289
2290
|
var init_http = __esm({
|
|
2290
2291
|
"src/http.ts"() {
|
|
2291
2292
|
"use strict";
|
|
@@ -2304,6 +2305,8 @@ var init_http = __esm({
|
|
|
2304
2305
|
init_warnOnce();
|
|
2305
2306
|
REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS = 3e5;
|
|
2306
2307
|
REPLAY_COMPLETE_REQUEST_TIMEOUT_MS = 12e4;
|
|
2308
|
+
REPLAY_INTERRUPT_REQUEST_TIMEOUT_MS = 5e3;
|
|
2309
|
+
REPLAY_HEARTBEAT_REQUEST_TIMEOUT_MS = 5e3;
|
|
2307
2310
|
OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
|
|
2308
2311
|
RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
2309
2312
|
EXIT_FLUSH_TIMEOUT_MS = 5e3;
|
|
@@ -2891,8 +2894,11 @@ var init_http = __esm({
|
|
|
2891
2894
|
* Start a replay session by fetching historical traces.
|
|
2892
2895
|
* Blocking call - creates an experiment and returns lightweight item references.
|
|
2893
2896
|
*/
|
|
2894
|
-
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions) {
|
|
2897
|
+
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions, experimentalSelectiveReplay) {
|
|
2895
2898
|
const payload = { traceFunctionKey };
|
|
2899
|
+
if (experimentalSelectiveReplay !== void 0) {
|
|
2900
|
+
payload.experimentalSelectiveReplay = experimentalSelectiveReplay;
|
|
2901
|
+
}
|
|
2896
2902
|
if (limit !== void 0) {
|
|
2897
2903
|
payload.limit = limit;
|
|
2898
2904
|
}
|
|
@@ -3113,6 +3119,28 @@ var init_http = __esm({
|
|
|
3113
3119
|
{ timeout: REPLAY_COMPLETE_REQUEST_TIMEOUT_MS }
|
|
3114
3120
|
);
|
|
3115
3121
|
}
|
|
3122
|
+
async interruptReplay(experimentId) {
|
|
3123
|
+
try {
|
|
3124
|
+
await this.request(
|
|
3125
|
+
"/api/sdk/replay/interrupt",
|
|
3126
|
+
experimentIdWireFields(experimentId),
|
|
3127
|
+
{ timeout: REPLAY_INTERRUPT_REQUEST_TIMEOUT_MS }
|
|
3128
|
+
);
|
|
3129
|
+
return true;
|
|
3130
|
+
} catch {
|
|
3131
|
+
return false;
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
async heartbeatReplay(experimentId) {
|
|
3135
|
+
try {
|
|
3136
|
+
await this.request(
|
|
3137
|
+
"/api/sdk/replay/heartbeat",
|
|
3138
|
+
experimentIdWireFields(experimentId),
|
|
3139
|
+
{ timeout: REPLAY_HEARTBEAT_REQUEST_TIMEOUT_MS }
|
|
3140
|
+
);
|
|
3141
|
+
} catch {
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3116
3144
|
async getReseedSource(traceId) {
|
|
3117
3145
|
return this.get(
|
|
3118
3146
|
`/api/sdk/traces/${encodeURIComponent(traceId)}/reseedSource`
|
|
@@ -3616,6 +3644,399 @@ var init_integrationFixture = __esm({
|
|
|
3616
3644
|
}
|
|
3617
3645
|
});
|
|
3618
3646
|
|
|
3647
|
+
// src/replayHeartbeat.ts
|
|
3648
|
+
function startReplayHeartbeat(send) {
|
|
3649
|
+
let stopped = false;
|
|
3650
|
+
let inFlight;
|
|
3651
|
+
const timer = setInterval(() => {
|
|
3652
|
+
if (stopped || inFlight !== void 0) {
|
|
3653
|
+
return;
|
|
3654
|
+
}
|
|
3655
|
+
inFlight = send().catch(() => void 0).finally(() => {
|
|
3656
|
+
inFlight = void 0;
|
|
3657
|
+
});
|
|
3658
|
+
}, REPLAY_HEARTBEAT_INTERVAL_MS);
|
|
3659
|
+
unrefTimer(timer);
|
|
3660
|
+
return {
|
|
3661
|
+
stop: async () => {
|
|
3662
|
+
stopped = true;
|
|
3663
|
+
clearInterval(timer);
|
|
3664
|
+
await inFlight;
|
|
3665
|
+
}
|
|
3666
|
+
};
|
|
3667
|
+
}
|
|
3668
|
+
var REPLAY_HEARTBEAT_INTERVAL_MS;
|
|
3669
|
+
var init_replayHeartbeat = __esm({
|
|
3670
|
+
"src/replayHeartbeat.ts"() {
|
|
3671
|
+
"use strict";
|
|
3672
|
+
init_unrefTimer();
|
|
3673
|
+
REPLAY_HEARTBEAT_INTERVAL_MS = 6e4;
|
|
3674
|
+
}
|
|
3675
|
+
});
|
|
3676
|
+
|
|
3677
|
+
// src/replayInterrupt.ts
|
|
3678
|
+
function signalCapableProcess() {
|
|
3679
|
+
if (typeof process === "undefined" || typeof process.on !== "function") {
|
|
3680
|
+
return void 0;
|
|
3681
|
+
}
|
|
3682
|
+
return process;
|
|
3683
|
+
}
|
|
3684
|
+
function describeReplayInterrupt(experimentId, markedInterrupted) {
|
|
3685
|
+
if (markedInterrupted) {
|
|
3686
|
+
return `[replay] Experiment ${experimentId} interrupted.`;
|
|
3687
|
+
}
|
|
3688
|
+
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.`;
|
|
3689
|
+
}
|
|
3690
|
+
function reportInterrupted(experimentId, markedInterrupted) {
|
|
3691
|
+
try {
|
|
3692
|
+
console.error(describeReplayInterrupt(experimentId, markedInterrupted));
|
|
3693
|
+
} catch {
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
function installReplayInterruptHandlers(target) {
|
|
3697
|
+
const host = signalCapableProcess();
|
|
3698
|
+
if (host === void 0) {
|
|
3699
|
+
return { remove: () => {
|
|
3700
|
+
}, interrupted: () => false };
|
|
3701
|
+
}
|
|
3702
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
3703
|
+
const removeHandlers = () => {
|
|
3704
|
+
for (const [signal, handler] of handlers) {
|
|
3705
|
+
host.removeListener(signal, handler);
|
|
3706
|
+
}
|
|
3707
|
+
handlers.clear();
|
|
3708
|
+
};
|
|
3709
|
+
let interrupting = false;
|
|
3710
|
+
const handleSignal = async (signal) => {
|
|
3711
|
+
const hostHandlesSignal = host.listenerCount(signal) > 1;
|
|
3712
|
+
const markedInterrupted = await target.interrupt();
|
|
3713
|
+
removeHandlers();
|
|
3714
|
+
if (target.onInterrupt !== void 0) {
|
|
3715
|
+
try {
|
|
3716
|
+
target.onInterrupt({
|
|
3717
|
+
experimentId: target.experimentId,
|
|
3718
|
+
experimentUrl: target.experimentUrl,
|
|
3719
|
+
signal,
|
|
3720
|
+
markedInterrupted
|
|
3721
|
+
});
|
|
3722
|
+
} catch {
|
|
3723
|
+
}
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3726
|
+
reportInterrupted(target.experimentId, markedInterrupted);
|
|
3727
|
+
if (!hostHandlesSignal) {
|
|
3728
|
+
host.kill(host.pid, signal);
|
|
3729
|
+
}
|
|
3730
|
+
};
|
|
3731
|
+
for (const signal of REPLAY_INTERRUPT_SIGNALS) {
|
|
3732
|
+
const handler = () => {
|
|
3733
|
+
if (interrupting) {
|
|
3734
|
+
return;
|
|
3735
|
+
}
|
|
3736
|
+
interrupting = true;
|
|
3737
|
+
void handleSignal(signal);
|
|
3738
|
+
};
|
|
3739
|
+
handlers.set(signal, handler);
|
|
3740
|
+
host.on(signal, handler);
|
|
3741
|
+
}
|
|
3742
|
+
return { remove: removeHandlers, interrupted: () => interrupting };
|
|
3743
|
+
}
|
|
3744
|
+
var REPLAY_INTERRUPT_SIGNALS;
|
|
3745
|
+
var init_replayInterrupt = __esm({
|
|
3746
|
+
"src/replayInterrupt.ts"() {
|
|
3747
|
+
"use strict";
|
|
3748
|
+
REPLAY_INTERRUPT_SIGNALS = [
|
|
3749
|
+
"SIGINT",
|
|
3750
|
+
"SIGTERM"
|
|
3751
|
+
];
|
|
3752
|
+
}
|
|
3753
|
+
});
|
|
3754
|
+
|
|
3755
|
+
// src/selectiveReplay.ts
|
|
3756
|
+
function replayNodeKey(node) {
|
|
3757
|
+
return JSON.stringify([node.traceFunctionKey, node.spanName]);
|
|
3758
|
+
}
|
|
3759
|
+
function jsonFingerprint(value) {
|
|
3760
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3761
|
+
function encode(input) {
|
|
3762
|
+
if (input === null || typeof input === "boolean") {
|
|
3763
|
+
return JSON.stringify(input);
|
|
3764
|
+
}
|
|
3765
|
+
if (typeof input === "string" && !input.startsWith("<unserializable")) {
|
|
3766
|
+
return JSON.stringify(input);
|
|
3767
|
+
}
|
|
3768
|
+
if (typeof input === "number" && Number.isFinite(input) && !Object.is(input, -0)) {
|
|
3769
|
+
return JSON.stringify(input);
|
|
3770
|
+
}
|
|
3771
|
+
if (typeof input !== "object" || input === null || seen.has(input)) {
|
|
3772
|
+
throw new Error("Not a lossless JSON value");
|
|
3773
|
+
}
|
|
3774
|
+
seen.add(input);
|
|
3775
|
+
const prototype = Object.getPrototypeOf(input);
|
|
3776
|
+
if (prototype !== (Array.isArray(input) ? Array.prototype : Object.prototype)) {
|
|
3777
|
+
throw new Error("Not a plain JSON object");
|
|
3778
|
+
}
|
|
3779
|
+
if (Object.getOwnPropertySymbols(input).length > 0) {
|
|
3780
|
+
throw new Error("Symbol properties are not recorded");
|
|
3781
|
+
}
|
|
3782
|
+
const keys = Object.keys(input);
|
|
3783
|
+
if (Object.getOwnPropertyNames(input).length !== keys.length + (Array.isArray(input) ? 1 : 0)) {
|
|
3784
|
+
throw new Error("Non-enumerable properties are not recorded");
|
|
3785
|
+
}
|
|
3786
|
+
if (Array.isArray(input) && (keys.length !== input.length || keys.some((key, index) => key !== String(index)))) {
|
|
3787
|
+
throw new Error("Not a dense JSON array");
|
|
3788
|
+
}
|
|
3789
|
+
const properties = keys.map((key) => {
|
|
3790
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
3791
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
3792
|
+
throw new Error("Accessor properties are not replay inputs");
|
|
3793
|
+
}
|
|
3794
|
+
return `${Array.isArray(input) ? "" : `${JSON.stringify(key)}:`}${encode(descriptor.value)}`;
|
|
3795
|
+
});
|
|
3796
|
+
return Array.isArray(input) ? `[${properties.join(",")}]` : `{${properties.join(",")}}`;
|
|
3797
|
+
}
|
|
3798
|
+
try {
|
|
3799
|
+
return encode(value);
|
|
3800
|
+
} catch {
|
|
3801
|
+
return void 0;
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
var init_selectiveReplay = __esm({
|
|
3805
|
+
"src/selectiveReplay.ts"() {
|
|
3806
|
+
"use strict";
|
|
3807
|
+
}
|
|
3808
|
+
});
|
|
3809
|
+
|
|
3810
|
+
// src/selectiveReplayRuntime.ts
|
|
3811
|
+
function validateSelectiveReplayOptions(options) {
|
|
3812
|
+
const validNodes = (nodes) => Array.isArray(nodes) && nodes.length <= 100 && nodes.every(
|
|
3813
|
+
(node) => typeof node?.traceFunctionKey === "string" && node.traceFunctionKey.length > 0 && node.traceFunctionKey.length <= 500 && typeof node.spanName === "string" && node.spanName.length > 0 && node.spanName.length <= 500
|
|
3814
|
+
);
|
|
3815
|
+
if (!options || !validNodes(options.mustRun) || options.mustRun.length === 0 || Object.keys(options).some((key) => key !== "mustRun")) {
|
|
3816
|
+
throw new BitfabError(
|
|
3817
|
+
"experimentalSelectiveReplay accepts only 1\u2013100 mustRun with traceFunctionKey and spanName. Declare replayReusable on eligible spans, not reusableNodes in the replay request."
|
|
3818
|
+
);
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
function policyKey(options) {
|
|
3822
|
+
return JSON.stringify(options.mustRun.map(replayNodeKey).sort());
|
|
3823
|
+
}
|
|
3824
|
+
var SelectiveReplayRuntime;
|
|
3825
|
+
var init_selectiveReplayRuntime = __esm({
|
|
3826
|
+
"src/selectiveReplayRuntime.ts"() {
|
|
3827
|
+
"use strict";
|
|
3828
|
+
init_errors();
|
|
3829
|
+
init_selectiveReplay();
|
|
3830
|
+
SelectiveReplayRuntime = class {
|
|
3831
|
+
constructor(plan, options) {
|
|
3832
|
+
this.plan = plan;
|
|
3833
|
+
this.options = options;
|
|
3834
|
+
this.nodes = /* @__PURE__ */ new Map();
|
|
3835
|
+
this.children = /* @__PURE__ */ new Map();
|
|
3836
|
+
this.runtimeParents = /* @__PURE__ */ new Map();
|
|
3837
|
+
this.consumed = /* @__PURE__ */ new Map();
|
|
3838
|
+
this.requiredScopes = /* @__PURE__ */ new Set();
|
|
3839
|
+
this.recordings = /* @__PURE__ */ new Map();
|
|
3840
|
+
this.reached = /* @__PURE__ */ new Set();
|
|
3841
|
+
this.decisions = [];
|
|
3842
|
+
this.assertionNames = /* @__PURE__ */ new Set();
|
|
3843
|
+
this.assertionScopes = /* @__PURE__ */ new Set();
|
|
3844
|
+
this.observedNames = /* @__PURE__ */ new Map();
|
|
3845
|
+
this.unresolvedAssertions = [];
|
|
3846
|
+
validateSelectiveReplayOptions(options);
|
|
3847
|
+
if (plan?.version !== 4 || !Array.isArray(plan.nodes) || plan.nodes.length === 0 || plan.nodes.length > 500 || !Array.isArray(plan.unresolvedMustRun) || !Array.isArray(plan.assertions) || !plan.options) {
|
|
3848
|
+
throw new BitfabError(
|
|
3849
|
+
"Server did not return a compatible selective replay plan. Update the server or disable experimentalSelectiveReplay."
|
|
3850
|
+
);
|
|
3851
|
+
}
|
|
3852
|
+
validateSelectiveReplayOptions(plan.options);
|
|
3853
|
+
if (policyKey(plan.options) !== policyKey(options)) {
|
|
3854
|
+
throw new BitfabError(
|
|
3855
|
+
"Selective replay plan does not match the requested mustRun nodes."
|
|
3856
|
+
);
|
|
3857
|
+
}
|
|
3858
|
+
this.required = new Set(options.mustRun.map(replayNodeKey));
|
|
3859
|
+
for (const node of plan.nodes) {
|
|
3860
|
+
if (typeof node.id !== "string" || this.nodes.has(node.id) || typeof node.traceFunctionKey !== "string" || typeof node.spanName !== "string" || typeof node.mustRun !== "boolean" || typeof node.reusable !== "boolean") {
|
|
3861
|
+
throw new BitfabError("Invalid selective replay node.");
|
|
3862
|
+
}
|
|
3863
|
+
this.nodes.set(node.id, node);
|
|
3864
|
+
if (node.recording !== void 0) {
|
|
3865
|
+
this.recordings.set(node.id, {
|
|
3866
|
+
input: node.recording.kwargs === void 0 ? jsonFingerprint(node.recording.input) : void 0,
|
|
3867
|
+
output: jsonFingerprint(node.recording.output)
|
|
3868
|
+
});
|
|
3869
|
+
}
|
|
3870
|
+
if (node.parentId !== null) {
|
|
3871
|
+
const siblings = this.children.get(node.parentId) ?? [];
|
|
3872
|
+
siblings.push(node);
|
|
3873
|
+
this.children.set(node.parentId, siblings);
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
const root = this.nodes.get(plan.rootId);
|
|
3877
|
+
if (!root || root.parentId !== null || !root.mustRun) {
|
|
3878
|
+
throw new BitfabError("Selective replay requires a live recorded root.");
|
|
3879
|
+
}
|
|
3880
|
+
const assertionIds = /* @__PURE__ */ new Set();
|
|
3881
|
+
let wholeTrace = false;
|
|
3882
|
+
for (const assertion of plan.assertions) {
|
|
3883
|
+
const target = assertion?.target;
|
|
3884
|
+
if (typeof assertion?.id !== "string" || assertion.id.length === 0 || assertionIds.has(assertion.id) || target !== null && (typeof target !== "object" || target.kind !== "output" && target.kind !== "span")) {
|
|
3885
|
+
throw new BitfabError(
|
|
3886
|
+
"Invalid selective replay assertion requirements."
|
|
3887
|
+
);
|
|
3888
|
+
}
|
|
3889
|
+
assertionIds.add(assertion.id);
|
|
3890
|
+
if (target === null) {
|
|
3891
|
+
wholeTrace = true;
|
|
3892
|
+
} else if (target.kind === "span") {
|
|
3893
|
+
if (typeof target.name !== "string" || target.name.length === 0 || target.occurrence !== void 0 && target.occurrence !== "first" && target.occurrence !== "last" && !(Number.isInteger(target.occurrence) && typeof target.occurrence === "number" && target.occurrence >= 0)) {
|
|
3894
|
+
throw new BitfabError("Invalid selective replay assertion target.");
|
|
3895
|
+
}
|
|
3896
|
+
this.assertionNames.add(target.name);
|
|
3897
|
+
const count = plan.nodes.filter(
|
|
3898
|
+
(node) => node.spanName === target.name
|
|
3899
|
+
).length;
|
|
3900
|
+
if (count <= (typeof target.occurrence === "number" ? target.occurrence : 0)) {
|
|
3901
|
+
this.unresolvedAssertions.push(assertion.id);
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
this.fullTraceForAssertions = wholeTrace || this.unresolvedAssertions.length > 0;
|
|
3906
|
+
const recordedKeys = new Set(plan.nodes.map(replayNodeKey));
|
|
3907
|
+
const unresolved = options.mustRun.filter(
|
|
3908
|
+
(node) => !recordedKeys.has(replayNodeKey(node))
|
|
3909
|
+
);
|
|
3910
|
+
if (JSON.stringify(unresolved.map(replayNodeKey).sort()) !== JSON.stringify(plan.unresolvedMustRun.map(replayNodeKey).sort())) {
|
|
3911
|
+
throw new BitfabError(
|
|
3912
|
+
"Selective replay plan has incomplete mustRun coverage."
|
|
3913
|
+
);
|
|
3914
|
+
}
|
|
3915
|
+
for (const node of plan.nodes) {
|
|
3916
|
+
const ancestors = /* @__PURE__ */ new Set([node.id]);
|
|
3917
|
+
let requiredByManifest = this.required.has(replayNodeKey(node));
|
|
3918
|
+
let requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(node.spanName);
|
|
3919
|
+
let parentId = node.parentId;
|
|
3920
|
+
while (parentId !== null) {
|
|
3921
|
+
const parent = this.nodes.get(parentId);
|
|
3922
|
+
if (!parent || ancestors.has(parentId) || node.mustRun && !parent.mustRun) {
|
|
3923
|
+
throw new BitfabError("Invalid selective replay ancestry.");
|
|
3924
|
+
}
|
|
3925
|
+
ancestors.add(parentId);
|
|
3926
|
+
requiredByManifest || (requiredByManifest = this.required.has(replayNodeKey(parent)));
|
|
3927
|
+
requiredForAssertion || (requiredForAssertion = this.assertionNames.has(parent.spanName));
|
|
3928
|
+
parentId = parent.parentId;
|
|
3929
|
+
}
|
|
3930
|
+
if (!ancestors.has(plan.rootId) || (requiredByManifest || unresolved.length > 0) && !node.mustRun) {
|
|
3931
|
+
throw new BitfabError("Selective replay plan could hide required code.");
|
|
3932
|
+
}
|
|
3933
|
+
if (requiredForAssertion && !node.mustRun) {
|
|
3934
|
+
throw new BitfabError(
|
|
3935
|
+
"Selective replay plan could hide assertion evidence."
|
|
3936
|
+
);
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
enter(params) {
|
|
3941
|
+
const identity = {
|
|
3942
|
+
traceFunctionKey: params.traceFunctionKey,
|
|
3943
|
+
spanName: params.spanName
|
|
3944
|
+
};
|
|
3945
|
+
const key = replayNodeKey(identity);
|
|
3946
|
+
const fingerprint = jsonFingerprint(params.inputs);
|
|
3947
|
+
let node;
|
|
3948
|
+
let matchReason = "unmatched-call";
|
|
3949
|
+
if (params.parentSpanId === null) {
|
|
3950
|
+
node = this.nodes.get(this.plan.rootId);
|
|
3951
|
+
} else {
|
|
3952
|
+
const recordedParent = this.runtimeParents.get(params.parentSpanId);
|
|
3953
|
+
const consumed = this.consumed.get(params.parentSpanId) ?? /* @__PURE__ */ new Set();
|
|
3954
|
+
this.consumed.set(params.parentSpanId, consumed);
|
|
3955
|
+
const candidates = recordedParent ? (this.children.get(recordedParent) ?? []).filter(
|
|
3956
|
+
(candidate) => replayNodeKey(candidate) === key && !consumed.has(candidate.id)
|
|
3957
|
+
) : [];
|
|
3958
|
+
const exact = fingerprint === void 0 ? [] : candidates.filter(
|
|
3959
|
+
(candidate) => this.recordings.get(candidate.id)?.input === fingerprint
|
|
3960
|
+
);
|
|
3961
|
+
node = exact.length === 1 ? exact[0] : exact.length === 0 && candidates.length === 1 ? candidates[0] : void 0;
|
|
3962
|
+
if (node) {
|
|
3963
|
+
consumed.add(node.id);
|
|
3964
|
+
} else if (candidates.length > 0) {
|
|
3965
|
+
matchReason = "ambiguous-call";
|
|
3966
|
+
for (const candidate of candidates) {
|
|
3967
|
+
consumed.add(candidate.id);
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
this.runtimeParents.set(params.spanId, node?.id ?? null);
|
|
3972
|
+
const requiredByManifest = this.required.has(key) || params.parentSpanId !== null && this.requiredScopes.has(params.parentSpanId);
|
|
3973
|
+
if (requiredByManifest) {
|
|
3974
|
+
this.requiredScopes.add(params.spanId);
|
|
3975
|
+
}
|
|
3976
|
+
const requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(params.spanName) || params.parentSpanId !== null && this.assertionScopes.has(params.parentSpanId);
|
|
3977
|
+
if (requiredForAssertion) {
|
|
3978
|
+
this.assertionScopes.add(params.spanId);
|
|
3979
|
+
}
|
|
3980
|
+
const mustRun = params.parentSpanId === null || requiredByManifest || node?.mustRun === true || requiredForAssertion || this.plan.unresolvedMustRun.length > 0;
|
|
3981
|
+
const recording = node === void 0 ? void 0 : this.recordings.get(node.id);
|
|
3982
|
+
const equalInputs = fingerprint !== void 0 && recording?.input === fingerprint;
|
|
3983
|
+
const reusable = recording?.output !== void 0 && equalInputs && params.canReuseOutput;
|
|
3984
|
+
const reason = mustRun ? requiredByManifest ? "must-run" : requiredForAssertion ? "assertion" : node?.reason ?? "unresolved-must-run" : !node ? matchReason : !equalInputs ? "input-drift-or-missing-recording" : !params.canReuseOutput ? "unsupported-output-boundary" : !params.safetyMock && !params.reuseAllowed ? "reuse-not-declared" : node.reason;
|
|
3985
|
+
const decision = (action, why) => {
|
|
3986
|
+
this.decisions.push({
|
|
3987
|
+
...identity,
|
|
3988
|
+
originalSpanId: node?.id ?? null,
|
|
3989
|
+
action,
|
|
3990
|
+
reason: why
|
|
3991
|
+
});
|
|
3992
|
+
};
|
|
3993
|
+
if (params.safetyMock && (mustRun || !reusable)) {
|
|
3994
|
+
decision("blocked", `safety-conflict:${reason}`);
|
|
3995
|
+
this.failure = new BitfabError(
|
|
3996
|
+
`Selective replay cannot execute or safely reuse safety-mocked node ${params.spanName} (${reason}).`
|
|
3997
|
+
);
|
|
3998
|
+
throw this.failure;
|
|
3999
|
+
}
|
|
4000
|
+
if (!mustRun && reusable && (params.safetyMock || node?.reusable === true && params.reuseAllowed)) {
|
|
4001
|
+
decision("mock", params.safetyMock ? "safety-mock" : "unchanged-inputs");
|
|
4002
|
+
return {
|
|
4003
|
+
mock: true,
|
|
4004
|
+
output: JSON.parse(recording.output)
|
|
4005
|
+
};
|
|
4006
|
+
}
|
|
4007
|
+
decision("run", reason);
|
|
4008
|
+
this.observedNames.set(
|
|
4009
|
+
params.spanName,
|
|
4010
|
+
(this.observedNames.get(params.spanName) ?? 0) + 1
|
|
4011
|
+
);
|
|
4012
|
+
if (this.required.has(key)) {
|
|
4013
|
+
this.reached.add(key);
|
|
4014
|
+
}
|
|
4015
|
+
return { mock: false };
|
|
4016
|
+
}
|
|
4017
|
+
assertNoSafetyConflict() {
|
|
4018
|
+
if (this.failure) {
|
|
4019
|
+
throw this.failure;
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
4022
|
+
report() {
|
|
4023
|
+
return {
|
|
4024
|
+
decisions: this.decisions,
|
|
4025
|
+
mustRunNotReached: this.options.mustRun.filter(
|
|
4026
|
+
(node) => !this.reached.has(replayNodeKey(node))
|
|
4027
|
+
),
|
|
4028
|
+
unresolvedMustRun: this.plan.unresolvedMustRun,
|
|
4029
|
+
assertionIds: this.plan.assertions.map((assertion) => assertion.id),
|
|
4030
|
+
unresolvedAssertions: this.unresolvedAssertions,
|
|
4031
|
+
assertionTargetsNotReached: this.plan.assertions.flatMap(
|
|
4032
|
+
({ id, target }) => target?.kind === "span" && (this.observedNames.get(target.name) ?? 0) <= (typeof target.occurrence === "number" ? target.occurrence : 0) ? [id] : []
|
|
4033
|
+
)
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
4036
|
+
};
|
|
4037
|
+
}
|
|
4038
|
+
});
|
|
4039
|
+
|
|
3619
4040
|
// src/replay.ts
|
|
3620
4041
|
var replay_exports = {};
|
|
3621
4042
|
__export(replay_exports, {
|
|
@@ -3871,7 +4292,7 @@ function buildMockTree(rootNode) {
|
|
|
3871
4292
|
}
|
|
3872
4293
|
return { spans };
|
|
3873
4294
|
}
|
|
3874
|
-
async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
|
|
4295
|
+
async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun, selectiveOptions) {
|
|
3875
4296
|
let lease = includeDbBranchLease ? serverItem.dbBranchLease : void 0;
|
|
3876
4297
|
let leaseError = includeDbBranchLease ? serverItem.dbBranchLeaseError : void 0;
|
|
3877
4298
|
let dbSnapshotRef = serverItem.dbSnapshotRef;
|
|
@@ -3884,6 +4305,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3884
4305
|
let replayError = null;
|
|
3885
4306
|
let replayDurationMs = null;
|
|
3886
4307
|
let replayStarted = null;
|
|
4308
|
+
let selectiveReplay;
|
|
3887
4309
|
const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
|
|
3888
4310
|
const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
|
|
3889
4311
|
try {
|
|
@@ -3916,6 +4338,17 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3916
4338
|
originalTraceId
|
|
3917
4339
|
);
|
|
3918
4340
|
}
|
|
4341
|
+
if (selectiveOptions !== void 0) {
|
|
4342
|
+
if (serverItem.selectiveReplayPlan === void 0) {
|
|
4343
|
+
throw new BitfabError(
|
|
4344
|
+
"Server did not return a selective replay plan. Update the server or disable experimentalSelectiveReplay."
|
|
4345
|
+
);
|
|
4346
|
+
}
|
|
4347
|
+
selectiveReplay = new SelectiveReplayRuntime(
|
|
4348
|
+
serverItem.selectiveReplayPlan,
|
|
4349
|
+
selectiveOptions
|
|
4350
|
+
);
|
|
4351
|
+
}
|
|
3919
4352
|
const span = await httpClient.getExternalSpan(originalSpanId, {
|
|
3920
4353
|
view: "replay"
|
|
3921
4354
|
});
|
|
@@ -3935,7 +4368,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3935
4368
|
});
|
|
3936
4369
|
}
|
|
3937
4370
|
const hasOverrides = resolvedOverrides.length > 0;
|
|
3938
|
-
const needTree = mockStrategy === "all" || mockStrategy === "marked" || hasOverrides;
|
|
4371
|
+
const needTree = selectiveReplay === void 0 && (mockStrategy === "all" || mockStrategy === "marked" || hasOverrides);
|
|
3939
4372
|
const includeOutputs = mockStrategy === "all";
|
|
3940
4373
|
let mockTree;
|
|
3941
4374
|
if (needTree) {
|
|
@@ -4010,6 +4443,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
4010
4443
|
sourceBitfabTraceId: originalTraceId,
|
|
4011
4444
|
replayAttempt: attempt,
|
|
4012
4445
|
mockTree,
|
|
4446
|
+
selectiveReplay,
|
|
4013
4447
|
callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
|
|
4014
4448
|
mockStrategy,
|
|
4015
4449
|
mockOverrides: hasOverrides ? resolvedOverrides : void 0,
|
|
@@ -4021,6 +4455,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
4021
4455
|
() => fn(...inputs)
|
|
4022
4456
|
);
|
|
4023
4457
|
result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
|
|
4458
|
+
selectiveReplay?.assertNoSafetyConflict();
|
|
4024
4459
|
replayDurationMs = Math.round(performance.now() - replayStarted);
|
|
4025
4460
|
} catch (e) {
|
|
4026
4461
|
if (replayStarted !== null) {
|
|
@@ -4072,6 +4507,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
4072
4507
|
traceError,
|
|
4073
4508
|
replayError,
|
|
4074
4509
|
durationMs: replayDurationMs,
|
|
4510
|
+
...selectiveReplay === void 0 ? {} : { selectiveReplay: selectiveReplay.report() },
|
|
4075
4511
|
originalDurationMs,
|
|
4076
4512
|
originalTokens: serverItem.originalTokens ?? serverItem.tokens ?? null,
|
|
4077
4513
|
originalModel,
|
|
@@ -4176,6 +4612,14 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4176
4612
|
"force only applies when resuming an experiment. Pass it together with resume."
|
|
4177
4613
|
);
|
|
4178
4614
|
}
|
|
4615
|
+
if (options?.experimentalSelectiveReplay !== void 0) {
|
|
4616
|
+
validateSelectiveReplayOptions(options.experimentalSelectiveReplay);
|
|
4617
|
+
if ((options.mock ?? "marked") !== "marked" || normalizeMockOverrides(options.mockOverride).length > 0 || registeredOverrides.length > 0) {
|
|
4618
|
+
throw new BitfabError(
|
|
4619
|
+
"experimentalSelectiveReplay requires mock: 'marked' and cannot be combined with mock overrides."
|
|
4620
|
+
);
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4179
4623
|
if (options?.traceIds !== void 0) {
|
|
4180
4624
|
if (options.traceIds.length === 0) {
|
|
4181
4625
|
throw new BitfabError("traceIds must contain at least one trace ID.");
|
|
@@ -4217,7 +4661,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4217
4661
|
const includeDbBranchLease = dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true;
|
|
4218
4662
|
let session;
|
|
4219
4663
|
if (options?.resume === void 0) {
|
|
4220
|
-
const
|
|
4664
|
+
const started = await httpClient.startReplay(
|
|
4221
4665
|
traceFunctionKey,
|
|
4222
4666
|
// limit is meaningless with explicit traceIds (the ID list determines
|
|
4223
4667
|
// the count), so it's omitted from the request entirely.
|
|
@@ -4237,9 +4681,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4237
4681
|
requestedAttempts,
|
|
4238
4682
|
options?.adaptInputs !== void 0,
|
|
4239
4683
|
options?.onlyWithAssertions,
|
|
4240
|
-
options?.judgeAssertions
|
|
4684
|
+
options?.judgeAssertions,
|
|
4685
|
+
options?.experimentalSelectiveReplay
|
|
4241
4686
|
);
|
|
4242
|
-
if (
|
|
4687
|
+
if (started.items.length === 0) {
|
|
4243
4688
|
try {
|
|
4244
4689
|
console.warn(
|
|
4245
4690
|
`Bitfab: no traces matched "${traceFunctionKey}", so this replay ran nothing. Capture a trace, or seed one with seedTrace, before replaying.`
|
|
@@ -4248,10 +4693,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4248
4693
|
}
|
|
4249
4694
|
}
|
|
4250
4695
|
session = {
|
|
4251
|
-
experimentId:
|
|
4252
|
-
experimentUrl:
|
|
4696
|
+
experimentId: started.experimentId,
|
|
4697
|
+
experimentUrl: started.experimentUrl,
|
|
4253
4698
|
attempts: requestedAttempts,
|
|
4254
|
-
work: expandReplayWork(
|
|
4699
|
+
work: expandReplayWork(started.items, requestedAttempts),
|
|
4255
4700
|
finished: [],
|
|
4256
4701
|
includeDbBranchLease,
|
|
4257
4702
|
dbBranchSettings: resolveDbBranchSettings(options?.dbBranch)
|
|
@@ -4266,9 +4711,68 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4266
4711
|
options.dbBranch
|
|
4267
4712
|
);
|
|
4268
4713
|
}
|
|
4269
|
-
const { experimentId
|
|
4714
|
+
const { experimentId } = session;
|
|
4715
|
+
const fullExperimentUrl = `${serviceUrl}${session.experimentUrl}`;
|
|
4716
|
+
const heartbeat = startReplayHeartbeat(
|
|
4717
|
+
() => httpClient.heartbeatReplay(experimentId)
|
|
4718
|
+
);
|
|
4719
|
+
const interruptReplay = async () => {
|
|
4720
|
+
await heartbeat.stop();
|
|
4721
|
+
return httpClient.interruptReplay(experimentId);
|
|
4722
|
+
};
|
|
4723
|
+
const interruptHandlers = installReplayInterruptHandlers({
|
|
4724
|
+
experimentId,
|
|
4725
|
+
experimentUrl: fullExperimentUrl,
|
|
4726
|
+
interrupt: interruptReplay,
|
|
4727
|
+
onInterrupt: options?.onInterrupt
|
|
4728
|
+
});
|
|
4729
|
+
let completeReturned = false;
|
|
4730
|
+
const markCompleteReturned = () => {
|
|
4731
|
+
completeReturned = true;
|
|
4732
|
+
void heartbeat.stop();
|
|
4733
|
+
};
|
|
4734
|
+
try {
|
|
4735
|
+
return await runReplaySession(
|
|
4736
|
+
httpClient,
|
|
4737
|
+
fn,
|
|
4738
|
+
session,
|
|
4739
|
+
fullExperimentUrl,
|
|
4740
|
+
maxConcurrency,
|
|
4741
|
+
interruptHandlers.remove,
|
|
4742
|
+
markCompleteReturned,
|
|
4743
|
+
interruptHandlers.interrupted,
|
|
4744
|
+
options,
|
|
4745
|
+
registeredOverrides
|
|
4746
|
+
);
|
|
4747
|
+
} catch (error) {
|
|
4748
|
+
if (!completeReturned && !interruptHandlers.interrupted()) {
|
|
4749
|
+
await interruptReplay();
|
|
4750
|
+
}
|
|
4751
|
+
throw error;
|
|
4752
|
+
} finally {
|
|
4753
|
+
interruptHandlers.remove();
|
|
4754
|
+
await heartbeat.stop();
|
|
4755
|
+
}
|
|
4756
|
+
}
|
|
4757
|
+
function throwIfInterrupted(interrupted, items, experimentId, experimentUrl) {
|
|
4758
|
+
if (!interrupted()) {
|
|
4759
|
+
return;
|
|
4760
|
+
}
|
|
4761
|
+
const cause = new BitfabError(
|
|
4762
|
+
`Experiment ${experimentId} was interrupted, so it was not completed or graded. Resume it by passing { resume: "${experimentId}" } to replay, or with bitfab-replay --resume ${experimentId}.`,
|
|
4763
|
+
experimentUrl
|
|
4764
|
+
);
|
|
4765
|
+
throw new ReplayError(
|
|
4766
|
+
cause.message,
|
|
4767
|
+
items,
|
|
4768
|
+
experimentId,
|
|
4769
|
+
experimentUrl,
|
|
4770
|
+
cause
|
|
4771
|
+
);
|
|
4772
|
+
}
|
|
4773
|
+
async function runReplaySession(httpClient, fn, session, fullExperimentUrl, maxConcurrency, stopInterruptingOnSignal, markCompleteReturned, interrupted, options, registeredOverrides) {
|
|
4774
|
+
const { experimentId, attempts } = session;
|
|
4270
4775
|
const mockStrategy = options?.mock ?? "marked";
|
|
4271
|
-
const fullExperimentUrl = `${serviceUrl}${experimentUrl}`;
|
|
4272
4776
|
try {
|
|
4273
4777
|
options?.onExperimentStart?.({
|
|
4274
4778
|
experimentId,
|
|
@@ -4299,7 +4803,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4299
4803
|
session.includeDbBranchLease,
|
|
4300
4804
|
session.dbBranchSettings,
|
|
4301
4805
|
options?.adaptInputs,
|
|
4302
|
-
options?.dryRun === true
|
|
4806
|
+
options?.dryRun === true,
|
|
4807
|
+
options?.experimentalSelectiveReplay
|
|
4303
4808
|
)
|
|
4304
4809
|
);
|
|
4305
4810
|
const total = tasks.length + carriedOverItems.length;
|
|
@@ -4372,6 +4877,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4372
4877
|
traceError: item.traceError,
|
|
4373
4878
|
replayError: item.replayError,
|
|
4374
4879
|
durationMs: item.durationMs,
|
|
4880
|
+
...item.selectiveReplay === void 0 ? {} : { selectiveReplay: item.selectiveReplay },
|
|
4375
4881
|
originalDurationMs: item.originalDurationMs,
|
|
4376
4882
|
originalTokens: item.originalTokens,
|
|
4377
4883
|
originalModel: item.originalModel,
|
|
@@ -4416,7 +4922,15 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4416
4922
|
);
|
|
4417
4923
|
const resultItems = [...carriedOverItems, ...ranItems];
|
|
4418
4924
|
if (options?.dryRun === true) {
|
|
4925
|
+
throwIfInterrupted(
|
|
4926
|
+
interrupted,
|
|
4927
|
+
resultItems,
|
|
4928
|
+
experimentId,
|
|
4929
|
+
fullExperimentUrl
|
|
4930
|
+
);
|
|
4931
|
+
stopInterruptingOnSignal();
|
|
4419
4932
|
await httpClient.completeReplay(experimentId).catch(() => void 0);
|
|
4933
|
+
markCompleteReturned();
|
|
4420
4934
|
const dryResult = {
|
|
4421
4935
|
items: resultItems,
|
|
4422
4936
|
experimentId,
|
|
@@ -4441,12 +4955,15 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4441
4955
|
ranItems[index].traceId = readBack;
|
|
4442
4956
|
}
|
|
4443
4957
|
}
|
|
4958
|
+
throwIfInterrupted(interrupted, resultItems, experimentId, fullExperimentUrl);
|
|
4959
|
+
stopInterruptingOnSignal();
|
|
4444
4960
|
const completeResult = await preserveReplayFailure(
|
|
4445
4961
|
() => httpClient.completeReplay(experimentId),
|
|
4446
4962
|
resultItems,
|
|
4447
4963
|
experimentId,
|
|
4448
4964
|
fullExperimentUrl
|
|
4449
4965
|
);
|
|
4966
|
+
markCompleteReturned();
|
|
4450
4967
|
const serverTraceIds = completeResult.traceIds;
|
|
4451
4968
|
const replayTokens = completeResult.tokens;
|
|
4452
4969
|
if (serverTraceIds !== void 0) {
|
|
@@ -4550,6 +5067,9 @@ var init_replay = __esm({
|
|
|
4550
5067
|
init_mockOverride();
|
|
4551
5068
|
init_randomUuid();
|
|
4552
5069
|
init_replayContext();
|
|
5070
|
+
init_replayHeartbeat();
|
|
5071
|
+
init_replayInterrupt();
|
|
5072
|
+
init_selectiveReplayRuntime();
|
|
4553
5073
|
init_serialize();
|
|
4554
5074
|
REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
4555
5075
|
MAX_REPLAY_ATTEMPTS = 100;
|
|
@@ -4561,7 +5081,8 @@ var init_replay = __esm({
|
|
|
4561
5081
|
"attempts",
|
|
4562
5082
|
"graderIds",
|
|
4563
5083
|
"onlyWithAssertions",
|
|
4564
|
-
"dryRun"
|
|
5084
|
+
"dryRun",
|
|
5085
|
+
"experimentalSelectiveReplay"
|
|
4565
5086
|
];
|
|
4566
5087
|
CARRIED_OVER_ERROR = "This item errored in an earlier run of this experiment, before it was resumed.";
|
|
4567
5088
|
BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
|
|
@@ -9255,6 +9776,9 @@ var Bitfab = class {
|
|
|
9255
9776
|
...options.mockOnReplay !== void 0 && {
|
|
9256
9777
|
mockOnReplay: options.mockOnReplay
|
|
9257
9778
|
},
|
|
9779
|
+
...options.replayReusable !== void 0 && {
|
|
9780
|
+
replayReusable: options.replayReusable
|
|
9781
|
+
},
|
|
9258
9782
|
...options.finalize !== void 0 && { finalize: options.finalize }
|
|
9259
9783
|
};
|
|
9260
9784
|
}
|
|
@@ -9403,6 +9927,9 @@ var Bitfab = class {
|
|
|
9403
9927
|
...mockOnReplay !== void 0 && {
|
|
9404
9928
|
mockOnReplay
|
|
9405
9929
|
},
|
|
9930
|
+
...nodeConfiguration?.replayReusable !== void 0 && {
|
|
9931
|
+
replayReusable: nodeConfiguration.replayReusable
|
|
9932
|
+
},
|
|
9406
9933
|
...nodeConfiguration?.finalize !== void 0 && {
|
|
9407
9934
|
finalize: nodeConfiguration.finalize
|
|
9408
9935
|
}
|
|
@@ -10200,6 +10727,7 @@ var Bitfab = class {
|
|
|
10200
10727
|
instrumentation: options.instrumentation ?? "span",
|
|
10201
10728
|
functionId: options.functionId,
|
|
10202
10729
|
captureContent: options.captureContent ?? true,
|
|
10730
|
+
replayReusable: options.replayReusable,
|
|
10203
10731
|
autoTraceDefinition: options.autoTraceDefinition,
|
|
10204
10732
|
declaredNode: options.declaredNode === true,
|
|
10205
10733
|
nestedTrace: options.nestedTrace,
|
|
@@ -10319,6 +10847,27 @@ var Bitfab = class {
|
|
|
10319
10847
|
return result;
|
|
10320
10848
|
};
|
|
10321
10849
|
const replayCtxForMock = getReplayContext();
|
|
10850
|
+
if (replayCtxForMock?.selectiveReplay) {
|
|
10851
|
+
const decision = replayCtxForMock.selectiveReplay.enter({
|
|
10852
|
+
traceFunctionKey,
|
|
10853
|
+
spanName: baseSpanParams.spanName ?? traceFunctionKey,
|
|
10854
|
+
spanId,
|
|
10855
|
+
parentSpanId,
|
|
10856
|
+
inputs: args,
|
|
10857
|
+
safetyMock: !isRootSpan && options.mockOnReplay === true,
|
|
10858
|
+
reuseAllowed: options.replayReusable === true,
|
|
10859
|
+
canReuseOutput: options.finalize === void 0 && options.replayOutput === void 0 && !fn.constructor.name.includes("Generator")
|
|
10860
|
+
});
|
|
10861
|
+
if (decision.mock) {
|
|
10862
|
+
void sendSpan({
|
|
10863
|
+
result: decision.output,
|
|
10864
|
+
mocked: true,
|
|
10865
|
+
mockTarget: "output",
|
|
10866
|
+
mockSource: "recorded"
|
|
10867
|
+
});
|
|
10868
|
+
return fnReturnsPromise ? Promise.resolve(decision.output) : decision.output;
|
|
10869
|
+
}
|
|
10870
|
+
}
|
|
10322
10871
|
if (replayCtxForMock?.mockTree && !isRootSpan) {
|
|
10323
10872
|
const counters = replayCtxForMock.callCounters;
|
|
10324
10873
|
const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
|
|
@@ -10742,6 +11291,9 @@ var Bitfab = class {
|
|
|
10742
11291
|
span_origin: makeSpanOrigin(params.instrumentation),
|
|
10743
11292
|
span_data: {
|
|
10744
11293
|
name: params.spanName,
|
|
11294
|
+
...params.replayReusable !== void 0 && {
|
|
11295
|
+
replay_reusable: params.replayReusable
|
|
11296
|
+
},
|
|
10745
11297
|
type: params.spanType,
|
|
10746
11298
|
...params.functionId !== void 0 && {
|
|
10747
11299
|
function_id: params.functionId,
|
|
@@ -11293,6 +11845,7 @@ init_replay();
|
|
|
11293
11845
|
// src/replayRegistry.ts
|
|
11294
11846
|
init_errors();
|
|
11295
11847
|
init_replay();
|
|
11848
|
+
init_replayInterrupt();
|
|
11296
11849
|
function defineReplayRegistry(registry) {
|
|
11297
11850
|
return registry;
|
|
11298
11851
|
}
|