@bitfab/sdk 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-HNEKBDI7.js → chunk-2ZZBNQVA.js} +38 -7
- package/dist/chunk-2ZZBNQVA.js.map +1 -0
- package/dist/{chunk-IR5KCKHF.js → chunk-3GSEECNP.js} +30 -3
- package/dist/chunk-3GSEECNP.js.map +1 -0
- package/dist/{chunk-DSVQPSAL.js → chunk-BRSQCK3F.js} +21 -3
- package/dist/chunk-BRSQCK3F.js.map +1 -0
- package/dist/{chunk-X5DRPZJJ.js → chunk-FL6BNPSO.js} +480 -15
- package/dist/chunk-FL6BNPSO.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-SJPMWVJ3.js → chunk-THGHTVB6.js} +32 -3
- package/dist/chunk-THGHTVB6.js.map +1 -0
- package/dist/{http-OEABHRHY.js → http-J2DYDKWJ.js} +2 -2
- package/dist/{http-RNI6PKQ3.js → http-RH3SW32W.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-3ERB6W6N.js → replay-CZWFAAIY.js} +3 -3
- package/dist/replayCli.js +2 -2
- package/dist/seedCli.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-BMJNDIVA.js.map +0 -1
- package/dist/chunk-DSVQPSAL.js.map +0 -1
- package/dist/chunk-HNEKBDI7.js.map +0 -1
- package/dist/chunk-IR5KCKHF.js.map +0 -1
- package/dist/chunk-SJPMWVJ3.js.map +0 -1
- package/dist/chunk-X5DRPZJJ.js.map +0 -1
- /package/dist/{http-OEABHRHY.js.map → http-J2DYDKWJ.js.map} +0 -0
- /package/dist/{http-RNI6PKQ3.js.map → http-RH3SW32W.js.map} +0 -0
- /package/dist/{replay-3ERB6W6N.js.map → replay-CZWFAAIY.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -42,7 +42,7 @@ var __version__, __packageName__;
|
|
|
42
42
|
var init_version_generated = __esm({
|
|
43
43
|
"src/version.generated.ts"() {
|
|
44
44
|
"use strict";
|
|
45
|
-
__version__ = "0.61.
|
|
45
|
+
__version__ = "0.61.4";
|
|
46
46
|
__packageName__ = "@bitfab/sdk";
|
|
47
47
|
}
|
|
48
48
|
});
|
|
@@ -1068,6 +1068,7 @@ var init_simulationPlan = __esm({
|
|
|
1068
1068
|
"input_meta",
|
|
1069
1069
|
"output",
|
|
1070
1070
|
"output_meta",
|
|
1071
|
+
"replay_recording",
|
|
1071
1072
|
"prompt"
|
|
1072
1073
|
];
|
|
1073
1074
|
SimulationPlan = class {
|
|
@@ -2276,7 +2277,7 @@ function warnDroppedExternalTrace(error) {
|
|
|
2276
2277
|
function experimentIdWireFields(experimentId) {
|
|
2277
2278
|
return { experimentId, testRunId: experimentId };
|
|
2278
2279
|
}
|
|
2279
|
-
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;
|
|
2280
|
+
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;
|
|
2280
2281
|
var init_http = __esm({
|
|
2281
2282
|
"src/http.ts"() {
|
|
2282
2283
|
"use strict";
|
|
@@ -2295,6 +2296,8 @@ var init_http = __esm({
|
|
|
2295
2296
|
init_warnOnce();
|
|
2296
2297
|
REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS = 3e5;
|
|
2297
2298
|
REPLAY_COMPLETE_REQUEST_TIMEOUT_MS = 12e4;
|
|
2299
|
+
REPLAY_INTERRUPT_REQUEST_TIMEOUT_MS = 5e3;
|
|
2300
|
+
REPLAY_HEARTBEAT_REQUEST_TIMEOUT_MS = 5e3;
|
|
2298
2301
|
OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
|
|
2299
2302
|
RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
2300
2303
|
EXIT_FLUSH_TIMEOUT_MS = 5e3;
|
|
@@ -2882,8 +2885,11 @@ var init_http = __esm({
|
|
|
2882
2885
|
* Start a replay session by fetching historical traces.
|
|
2883
2886
|
* Blocking call - creates an experiment and returns lightweight item references.
|
|
2884
2887
|
*/
|
|
2885
|
-
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions) {
|
|
2888
|
+
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions, experimentalSelectiveReplay) {
|
|
2886
2889
|
const payload = { traceFunctionKey };
|
|
2890
|
+
if (experimentalSelectiveReplay !== void 0) {
|
|
2891
|
+
payload.experimentalSelectiveReplay = experimentalSelectiveReplay;
|
|
2892
|
+
}
|
|
2887
2893
|
if (limit !== void 0) {
|
|
2888
2894
|
payload.limit = limit;
|
|
2889
2895
|
}
|
|
@@ -3104,6 +3110,28 @@ var init_http = __esm({
|
|
|
3104
3110
|
{ timeout: REPLAY_COMPLETE_REQUEST_TIMEOUT_MS }
|
|
3105
3111
|
);
|
|
3106
3112
|
}
|
|
3113
|
+
async interruptReplay(experimentId) {
|
|
3114
|
+
try {
|
|
3115
|
+
await this.request(
|
|
3116
|
+
"/api/sdk/replay/interrupt",
|
|
3117
|
+
experimentIdWireFields(experimentId),
|
|
3118
|
+
{ timeout: REPLAY_INTERRUPT_REQUEST_TIMEOUT_MS }
|
|
3119
|
+
);
|
|
3120
|
+
return true;
|
|
3121
|
+
} catch {
|
|
3122
|
+
return false;
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
async heartbeatReplay(experimentId) {
|
|
3126
|
+
try {
|
|
3127
|
+
await this.request(
|
|
3128
|
+
"/api/sdk/replay/heartbeat",
|
|
3129
|
+
experimentIdWireFields(experimentId),
|
|
3130
|
+
{ timeout: REPLAY_HEARTBEAT_REQUEST_TIMEOUT_MS }
|
|
3131
|
+
);
|
|
3132
|
+
} catch {
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3107
3135
|
async getReseedSource(traceId) {
|
|
3108
3136
|
return this.get(
|
|
3109
3137
|
`/api/sdk/traces/${encodeURIComponent(traceId)}/reseedSource`
|
|
@@ -3607,6 +3635,399 @@ var init_integrationFixture = __esm({
|
|
|
3607
3635
|
}
|
|
3608
3636
|
});
|
|
3609
3637
|
|
|
3638
|
+
// src/replayHeartbeat.ts
|
|
3639
|
+
function startReplayHeartbeat(send) {
|
|
3640
|
+
let stopped = false;
|
|
3641
|
+
let inFlight;
|
|
3642
|
+
const timer = setInterval(() => {
|
|
3643
|
+
if (stopped || inFlight !== void 0) {
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3646
|
+
inFlight = send().catch(() => void 0).finally(() => {
|
|
3647
|
+
inFlight = void 0;
|
|
3648
|
+
});
|
|
3649
|
+
}, REPLAY_HEARTBEAT_INTERVAL_MS);
|
|
3650
|
+
unrefTimer(timer);
|
|
3651
|
+
return {
|
|
3652
|
+
stop: async () => {
|
|
3653
|
+
stopped = true;
|
|
3654
|
+
clearInterval(timer);
|
|
3655
|
+
await inFlight;
|
|
3656
|
+
}
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3659
|
+
var REPLAY_HEARTBEAT_INTERVAL_MS;
|
|
3660
|
+
var init_replayHeartbeat = __esm({
|
|
3661
|
+
"src/replayHeartbeat.ts"() {
|
|
3662
|
+
"use strict";
|
|
3663
|
+
init_unrefTimer();
|
|
3664
|
+
REPLAY_HEARTBEAT_INTERVAL_MS = 6e4;
|
|
3665
|
+
}
|
|
3666
|
+
});
|
|
3667
|
+
|
|
3668
|
+
// src/replayInterrupt.ts
|
|
3669
|
+
function signalCapableProcess() {
|
|
3670
|
+
if (typeof process === "undefined" || typeof process.on !== "function") {
|
|
3671
|
+
return void 0;
|
|
3672
|
+
}
|
|
3673
|
+
return process;
|
|
3674
|
+
}
|
|
3675
|
+
function describeReplayInterrupt(experimentId, markedInterrupted) {
|
|
3676
|
+
if (markedInterrupted) {
|
|
3677
|
+
return `[replay] Experiment ${experimentId} interrupted.`;
|
|
3678
|
+
}
|
|
3679
|
+
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.`;
|
|
3680
|
+
}
|
|
3681
|
+
function reportInterrupted(experimentId, markedInterrupted) {
|
|
3682
|
+
try {
|
|
3683
|
+
console.error(describeReplayInterrupt(experimentId, markedInterrupted));
|
|
3684
|
+
} catch {
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
function installReplayInterruptHandlers(target) {
|
|
3688
|
+
const host = signalCapableProcess();
|
|
3689
|
+
if (host === void 0) {
|
|
3690
|
+
return { remove: () => {
|
|
3691
|
+
}, interrupted: () => false };
|
|
3692
|
+
}
|
|
3693
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
3694
|
+
const removeHandlers = () => {
|
|
3695
|
+
for (const [signal, handler] of handlers) {
|
|
3696
|
+
host.removeListener(signal, handler);
|
|
3697
|
+
}
|
|
3698
|
+
handlers.clear();
|
|
3699
|
+
};
|
|
3700
|
+
let interrupting = false;
|
|
3701
|
+
const handleSignal = async (signal) => {
|
|
3702
|
+
const hostHandlesSignal = host.listenerCount(signal) > 1;
|
|
3703
|
+
const markedInterrupted = await target.interrupt();
|
|
3704
|
+
removeHandlers();
|
|
3705
|
+
if (target.onInterrupt !== void 0) {
|
|
3706
|
+
try {
|
|
3707
|
+
target.onInterrupt({
|
|
3708
|
+
experimentId: target.experimentId,
|
|
3709
|
+
experimentUrl: target.experimentUrl,
|
|
3710
|
+
signal,
|
|
3711
|
+
markedInterrupted
|
|
3712
|
+
});
|
|
3713
|
+
} catch {
|
|
3714
|
+
}
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
reportInterrupted(target.experimentId, markedInterrupted);
|
|
3718
|
+
if (!hostHandlesSignal) {
|
|
3719
|
+
host.kill(host.pid, signal);
|
|
3720
|
+
}
|
|
3721
|
+
};
|
|
3722
|
+
for (const signal of REPLAY_INTERRUPT_SIGNALS) {
|
|
3723
|
+
const handler = () => {
|
|
3724
|
+
if (interrupting) {
|
|
3725
|
+
return;
|
|
3726
|
+
}
|
|
3727
|
+
interrupting = true;
|
|
3728
|
+
void handleSignal(signal);
|
|
3729
|
+
};
|
|
3730
|
+
handlers.set(signal, handler);
|
|
3731
|
+
host.on(signal, handler);
|
|
3732
|
+
}
|
|
3733
|
+
return { remove: removeHandlers, interrupted: () => interrupting };
|
|
3734
|
+
}
|
|
3735
|
+
var REPLAY_INTERRUPT_SIGNALS;
|
|
3736
|
+
var init_replayInterrupt = __esm({
|
|
3737
|
+
"src/replayInterrupt.ts"() {
|
|
3738
|
+
"use strict";
|
|
3739
|
+
REPLAY_INTERRUPT_SIGNALS = [
|
|
3740
|
+
"SIGINT",
|
|
3741
|
+
"SIGTERM"
|
|
3742
|
+
];
|
|
3743
|
+
}
|
|
3744
|
+
});
|
|
3745
|
+
|
|
3746
|
+
// src/selectiveReplay.ts
|
|
3747
|
+
function replayNodeKey(node) {
|
|
3748
|
+
return JSON.stringify([node.traceFunctionKey, node.spanName]);
|
|
3749
|
+
}
|
|
3750
|
+
function jsonFingerprint(value) {
|
|
3751
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3752
|
+
function encode(input) {
|
|
3753
|
+
if (input === null || typeof input === "boolean") {
|
|
3754
|
+
return JSON.stringify(input);
|
|
3755
|
+
}
|
|
3756
|
+
if (typeof input === "string" && !input.startsWith("<unserializable")) {
|
|
3757
|
+
return JSON.stringify(input);
|
|
3758
|
+
}
|
|
3759
|
+
if (typeof input === "number" && Number.isFinite(input) && !Object.is(input, -0)) {
|
|
3760
|
+
return JSON.stringify(input);
|
|
3761
|
+
}
|
|
3762
|
+
if (typeof input !== "object" || input === null || seen.has(input)) {
|
|
3763
|
+
throw new Error("Not a lossless JSON value");
|
|
3764
|
+
}
|
|
3765
|
+
seen.add(input);
|
|
3766
|
+
const prototype = Object.getPrototypeOf(input);
|
|
3767
|
+
if (prototype !== (Array.isArray(input) ? Array.prototype : Object.prototype)) {
|
|
3768
|
+
throw new Error("Not a plain JSON object");
|
|
3769
|
+
}
|
|
3770
|
+
if (Object.getOwnPropertySymbols(input).length > 0) {
|
|
3771
|
+
throw new Error("Symbol properties are not recorded");
|
|
3772
|
+
}
|
|
3773
|
+
const keys = Object.keys(input);
|
|
3774
|
+
if (Object.getOwnPropertyNames(input).length !== keys.length + (Array.isArray(input) ? 1 : 0)) {
|
|
3775
|
+
throw new Error("Non-enumerable properties are not recorded");
|
|
3776
|
+
}
|
|
3777
|
+
if (Array.isArray(input) && (keys.length !== input.length || keys.some((key, index) => key !== String(index)))) {
|
|
3778
|
+
throw new Error("Not a dense JSON array");
|
|
3779
|
+
}
|
|
3780
|
+
const properties = keys.map((key) => {
|
|
3781
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
3782
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
3783
|
+
throw new Error("Accessor properties are not replay inputs");
|
|
3784
|
+
}
|
|
3785
|
+
return `${Array.isArray(input) ? "" : `${JSON.stringify(key)}:`}${encode(descriptor.value)}`;
|
|
3786
|
+
});
|
|
3787
|
+
return Array.isArray(input) ? `[${properties.join(",")}]` : `{${properties.join(",")}}`;
|
|
3788
|
+
}
|
|
3789
|
+
try {
|
|
3790
|
+
return encode(value);
|
|
3791
|
+
} catch {
|
|
3792
|
+
return void 0;
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
var init_selectiveReplay = __esm({
|
|
3796
|
+
"src/selectiveReplay.ts"() {
|
|
3797
|
+
"use strict";
|
|
3798
|
+
}
|
|
3799
|
+
});
|
|
3800
|
+
|
|
3801
|
+
// src/selectiveReplayRuntime.ts
|
|
3802
|
+
function validateSelectiveReplayOptions(options) {
|
|
3803
|
+
const validNodes = (nodes) => Array.isArray(nodes) && nodes.length <= 100 && nodes.every(
|
|
3804
|
+
(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
|
|
3805
|
+
);
|
|
3806
|
+
if (!options || !validNodes(options.mustRun) || options.mustRun.length === 0 || Object.keys(options).some((key) => key !== "mustRun")) {
|
|
3807
|
+
throw new BitfabError(
|
|
3808
|
+
"experimentalSelectiveReplay accepts only 1\u2013100 mustRun with traceFunctionKey and spanName. Declare replayReusable on eligible spans, not reusableNodes in the replay request."
|
|
3809
|
+
);
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
function policyKey(options) {
|
|
3813
|
+
return JSON.stringify(options.mustRun.map(replayNodeKey).sort());
|
|
3814
|
+
}
|
|
3815
|
+
var SelectiveReplayRuntime;
|
|
3816
|
+
var init_selectiveReplayRuntime = __esm({
|
|
3817
|
+
"src/selectiveReplayRuntime.ts"() {
|
|
3818
|
+
"use strict";
|
|
3819
|
+
init_errors();
|
|
3820
|
+
init_selectiveReplay();
|
|
3821
|
+
SelectiveReplayRuntime = class {
|
|
3822
|
+
constructor(plan, options) {
|
|
3823
|
+
this.plan = plan;
|
|
3824
|
+
this.options = options;
|
|
3825
|
+
this.nodes = /* @__PURE__ */ new Map();
|
|
3826
|
+
this.children = /* @__PURE__ */ new Map();
|
|
3827
|
+
this.runtimeParents = /* @__PURE__ */ new Map();
|
|
3828
|
+
this.consumed = /* @__PURE__ */ new Map();
|
|
3829
|
+
this.requiredScopes = /* @__PURE__ */ new Set();
|
|
3830
|
+
this.recordings = /* @__PURE__ */ new Map();
|
|
3831
|
+
this.reached = /* @__PURE__ */ new Set();
|
|
3832
|
+
this.decisions = [];
|
|
3833
|
+
this.assertionNames = /* @__PURE__ */ new Set();
|
|
3834
|
+
this.assertionScopes = /* @__PURE__ */ new Set();
|
|
3835
|
+
this.observedNames = /* @__PURE__ */ new Map();
|
|
3836
|
+
this.unresolvedAssertions = [];
|
|
3837
|
+
validateSelectiveReplayOptions(options);
|
|
3838
|
+
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) {
|
|
3839
|
+
throw new BitfabError(
|
|
3840
|
+
"Server did not return a compatible selective replay plan. Update the server or disable experimentalSelectiveReplay."
|
|
3841
|
+
);
|
|
3842
|
+
}
|
|
3843
|
+
validateSelectiveReplayOptions(plan.options);
|
|
3844
|
+
if (policyKey(plan.options) !== policyKey(options)) {
|
|
3845
|
+
throw new BitfabError(
|
|
3846
|
+
"Selective replay plan does not match the requested mustRun nodes."
|
|
3847
|
+
);
|
|
3848
|
+
}
|
|
3849
|
+
this.required = new Set(options.mustRun.map(replayNodeKey));
|
|
3850
|
+
for (const node of plan.nodes) {
|
|
3851
|
+
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") {
|
|
3852
|
+
throw new BitfabError("Invalid selective replay node.");
|
|
3853
|
+
}
|
|
3854
|
+
this.nodes.set(node.id, node);
|
|
3855
|
+
if (node.recording !== void 0) {
|
|
3856
|
+
this.recordings.set(node.id, {
|
|
3857
|
+
input: node.recording.kwargs === void 0 ? jsonFingerprint(node.recording.input) : void 0,
|
|
3858
|
+
output: jsonFingerprint(node.recording.output)
|
|
3859
|
+
});
|
|
3860
|
+
}
|
|
3861
|
+
if (node.parentId !== null) {
|
|
3862
|
+
const siblings = this.children.get(node.parentId) ?? [];
|
|
3863
|
+
siblings.push(node);
|
|
3864
|
+
this.children.set(node.parentId, siblings);
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
const root = this.nodes.get(plan.rootId);
|
|
3868
|
+
if (!root || root.parentId !== null || !root.mustRun) {
|
|
3869
|
+
throw new BitfabError("Selective replay requires a live recorded root.");
|
|
3870
|
+
}
|
|
3871
|
+
const assertionIds = /* @__PURE__ */ new Set();
|
|
3872
|
+
let wholeTrace = false;
|
|
3873
|
+
for (const assertion of plan.assertions) {
|
|
3874
|
+
const target = assertion?.target;
|
|
3875
|
+
if (typeof assertion?.id !== "string" || assertion.id.length === 0 || assertionIds.has(assertion.id) || target !== null && (typeof target !== "object" || target.kind !== "output" && target.kind !== "span")) {
|
|
3876
|
+
throw new BitfabError(
|
|
3877
|
+
"Invalid selective replay assertion requirements."
|
|
3878
|
+
);
|
|
3879
|
+
}
|
|
3880
|
+
assertionIds.add(assertion.id);
|
|
3881
|
+
if (target === null) {
|
|
3882
|
+
wholeTrace = true;
|
|
3883
|
+
} else if (target.kind === "span") {
|
|
3884
|
+
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)) {
|
|
3885
|
+
throw new BitfabError("Invalid selective replay assertion target.");
|
|
3886
|
+
}
|
|
3887
|
+
this.assertionNames.add(target.name);
|
|
3888
|
+
const count = plan.nodes.filter(
|
|
3889
|
+
(node) => node.spanName === target.name
|
|
3890
|
+
).length;
|
|
3891
|
+
if (count <= (typeof target.occurrence === "number" ? target.occurrence : 0)) {
|
|
3892
|
+
this.unresolvedAssertions.push(assertion.id);
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
this.fullTraceForAssertions = wholeTrace || this.unresolvedAssertions.length > 0;
|
|
3897
|
+
const recordedKeys = new Set(plan.nodes.map(replayNodeKey));
|
|
3898
|
+
const unresolved = options.mustRun.filter(
|
|
3899
|
+
(node) => !recordedKeys.has(replayNodeKey(node))
|
|
3900
|
+
);
|
|
3901
|
+
if (JSON.stringify(unresolved.map(replayNodeKey).sort()) !== JSON.stringify(plan.unresolvedMustRun.map(replayNodeKey).sort())) {
|
|
3902
|
+
throw new BitfabError(
|
|
3903
|
+
"Selective replay plan has incomplete mustRun coverage."
|
|
3904
|
+
);
|
|
3905
|
+
}
|
|
3906
|
+
for (const node of plan.nodes) {
|
|
3907
|
+
const ancestors = /* @__PURE__ */ new Set([node.id]);
|
|
3908
|
+
let requiredByManifest = this.required.has(replayNodeKey(node));
|
|
3909
|
+
let requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(node.spanName);
|
|
3910
|
+
let parentId = node.parentId;
|
|
3911
|
+
while (parentId !== null) {
|
|
3912
|
+
const parent = this.nodes.get(parentId);
|
|
3913
|
+
if (!parent || ancestors.has(parentId) || node.mustRun && !parent.mustRun) {
|
|
3914
|
+
throw new BitfabError("Invalid selective replay ancestry.");
|
|
3915
|
+
}
|
|
3916
|
+
ancestors.add(parentId);
|
|
3917
|
+
requiredByManifest || (requiredByManifest = this.required.has(replayNodeKey(parent)));
|
|
3918
|
+
requiredForAssertion || (requiredForAssertion = this.assertionNames.has(parent.spanName));
|
|
3919
|
+
parentId = parent.parentId;
|
|
3920
|
+
}
|
|
3921
|
+
if (!ancestors.has(plan.rootId) || (requiredByManifest || unresolved.length > 0) && !node.mustRun) {
|
|
3922
|
+
throw new BitfabError("Selective replay plan could hide required code.");
|
|
3923
|
+
}
|
|
3924
|
+
if (requiredForAssertion && !node.mustRun) {
|
|
3925
|
+
throw new BitfabError(
|
|
3926
|
+
"Selective replay plan could hide assertion evidence."
|
|
3927
|
+
);
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
enter(params) {
|
|
3932
|
+
const identity = {
|
|
3933
|
+
traceFunctionKey: params.traceFunctionKey,
|
|
3934
|
+
spanName: params.spanName
|
|
3935
|
+
};
|
|
3936
|
+
const key = replayNodeKey(identity);
|
|
3937
|
+
const fingerprint = jsonFingerprint(params.inputs);
|
|
3938
|
+
let node;
|
|
3939
|
+
let matchReason = "unmatched-call";
|
|
3940
|
+
if (params.parentSpanId === null) {
|
|
3941
|
+
node = this.nodes.get(this.plan.rootId);
|
|
3942
|
+
} else {
|
|
3943
|
+
const recordedParent = this.runtimeParents.get(params.parentSpanId);
|
|
3944
|
+
const consumed = this.consumed.get(params.parentSpanId) ?? /* @__PURE__ */ new Set();
|
|
3945
|
+
this.consumed.set(params.parentSpanId, consumed);
|
|
3946
|
+
const candidates = recordedParent ? (this.children.get(recordedParent) ?? []).filter(
|
|
3947
|
+
(candidate) => replayNodeKey(candidate) === key && !consumed.has(candidate.id)
|
|
3948
|
+
) : [];
|
|
3949
|
+
const exact = fingerprint === void 0 ? [] : candidates.filter(
|
|
3950
|
+
(candidate) => this.recordings.get(candidate.id)?.input === fingerprint
|
|
3951
|
+
);
|
|
3952
|
+
node = exact.length === 1 ? exact[0] : exact.length === 0 && candidates.length === 1 ? candidates[0] : void 0;
|
|
3953
|
+
if (node) {
|
|
3954
|
+
consumed.add(node.id);
|
|
3955
|
+
} else if (candidates.length > 0) {
|
|
3956
|
+
matchReason = "ambiguous-call";
|
|
3957
|
+
for (const candidate of candidates) {
|
|
3958
|
+
consumed.add(candidate.id);
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
this.runtimeParents.set(params.spanId, node?.id ?? null);
|
|
3963
|
+
const requiredByManifest = this.required.has(key) || params.parentSpanId !== null && this.requiredScopes.has(params.parentSpanId);
|
|
3964
|
+
if (requiredByManifest) {
|
|
3965
|
+
this.requiredScopes.add(params.spanId);
|
|
3966
|
+
}
|
|
3967
|
+
const requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(params.spanName) || params.parentSpanId !== null && this.assertionScopes.has(params.parentSpanId);
|
|
3968
|
+
if (requiredForAssertion) {
|
|
3969
|
+
this.assertionScopes.add(params.spanId);
|
|
3970
|
+
}
|
|
3971
|
+
const mustRun = params.parentSpanId === null || requiredByManifest || node?.mustRun === true || requiredForAssertion || this.plan.unresolvedMustRun.length > 0;
|
|
3972
|
+
const recording = node === void 0 ? void 0 : this.recordings.get(node.id);
|
|
3973
|
+
const equalInputs = fingerprint !== void 0 && recording?.input === fingerprint;
|
|
3974
|
+
const reusable = recording?.output !== void 0 && equalInputs && params.canReuseOutput;
|
|
3975
|
+
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;
|
|
3976
|
+
const decision = (action, why) => {
|
|
3977
|
+
this.decisions.push({
|
|
3978
|
+
...identity,
|
|
3979
|
+
originalSpanId: node?.id ?? null,
|
|
3980
|
+
action,
|
|
3981
|
+
reason: why
|
|
3982
|
+
});
|
|
3983
|
+
};
|
|
3984
|
+
if (params.safetyMock && (mustRun || !reusable)) {
|
|
3985
|
+
decision("blocked", `safety-conflict:${reason}`);
|
|
3986
|
+
this.failure = new BitfabError(
|
|
3987
|
+
`Selective replay cannot execute or safely reuse safety-mocked node ${params.spanName} (${reason}).`
|
|
3988
|
+
);
|
|
3989
|
+
throw this.failure;
|
|
3990
|
+
}
|
|
3991
|
+
if (!mustRun && reusable && (params.safetyMock || node?.reusable === true && params.reuseAllowed)) {
|
|
3992
|
+
decision("mock", params.safetyMock ? "safety-mock" : "unchanged-inputs");
|
|
3993
|
+
return {
|
|
3994
|
+
mock: true,
|
|
3995
|
+
output: JSON.parse(recording.output)
|
|
3996
|
+
};
|
|
3997
|
+
}
|
|
3998
|
+
decision("run", reason);
|
|
3999
|
+
this.observedNames.set(
|
|
4000
|
+
params.spanName,
|
|
4001
|
+
(this.observedNames.get(params.spanName) ?? 0) + 1
|
|
4002
|
+
);
|
|
4003
|
+
if (this.required.has(key)) {
|
|
4004
|
+
this.reached.add(key);
|
|
4005
|
+
}
|
|
4006
|
+
return { mock: false };
|
|
4007
|
+
}
|
|
4008
|
+
assertNoSafetyConflict() {
|
|
4009
|
+
if (this.failure) {
|
|
4010
|
+
throw this.failure;
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
report() {
|
|
4014
|
+
return {
|
|
4015
|
+
decisions: this.decisions,
|
|
4016
|
+
mustRunNotReached: this.options.mustRun.filter(
|
|
4017
|
+
(node) => !this.reached.has(replayNodeKey(node))
|
|
4018
|
+
),
|
|
4019
|
+
unresolvedMustRun: this.plan.unresolvedMustRun,
|
|
4020
|
+
assertionIds: this.plan.assertions.map((assertion) => assertion.id),
|
|
4021
|
+
unresolvedAssertions: this.unresolvedAssertions,
|
|
4022
|
+
assertionTargetsNotReached: this.plan.assertions.flatMap(
|
|
4023
|
+
({ id, target }) => target?.kind === "span" && (this.observedNames.get(target.name) ?? 0) <= (typeof target.occurrence === "number" ? target.occurrence : 0) ? [id] : []
|
|
4024
|
+
)
|
|
4025
|
+
};
|
|
4026
|
+
}
|
|
4027
|
+
};
|
|
4028
|
+
}
|
|
4029
|
+
});
|
|
4030
|
+
|
|
3610
4031
|
// src/replay.ts
|
|
3611
4032
|
var replay_exports = {};
|
|
3612
4033
|
__export(replay_exports, {
|
|
@@ -3862,7 +4283,7 @@ function buildMockTree(rootNode) {
|
|
|
3862
4283
|
}
|
|
3863
4284
|
return { spans };
|
|
3864
4285
|
}
|
|
3865
|
-
async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
|
|
4286
|
+
async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun, selectiveOptions) {
|
|
3866
4287
|
let lease = includeDbBranchLease ? serverItem.dbBranchLease : void 0;
|
|
3867
4288
|
let leaseError = includeDbBranchLease ? serverItem.dbBranchLeaseError : void 0;
|
|
3868
4289
|
let dbSnapshotRef = serverItem.dbSnapshotRef;
|
|
@@ -3875,6 +4296,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3875
4296
|
let replayError = null;
|
|
3876
4297
|
let replayDurationMs = null;
|
|
3877
4298
|
let replayStarted = null;
|
|
4299
|
+
let selectiveReplay;
|
|
3878
4300
|
const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
|
|
3879
4301
|
const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
|
|
3880
4302
|
try {
|
|
@@ -3907,6 +4329,17 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3907
4329
|
originalTraceId
|
|
3908
4330
|
);
|
|
3909
4331
|
}
|
|
4332
|
+
if (selectiveOptions !== void 0) {
|
|
4333
|
+
if (serverItem.selectiveReplayPlan === void 0) {
|
|
4334
|
+
throw new BitfabError(
|
|
4335
|
+
"Server did not return a selective replay plan. Update the server or disable experimentalSelectiveReplay."
|
|
4336
|
+
);
|
|
4337
|
+
}
|
|
4338
|
+
selectiveReplay = new SelectiveReplayRuntime(
|
|
4339
|
+
serverItem.selectiveReplayPlan,
|
|
4340
|
+
selectiveOptions
|
|
4341
|
+
);
|
|
4342
|
+
}
|
|
3910
4343
|
const span = await httpClient.getExternalSpan(originalSpanId, {
|
|
3911
4344
|
view: "replay"
|
|
3912
4345
|
});
|
|
@@ -3926,7 +4359,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3926
4359
|
});
|
|
3927
4360
|
}
|
|
3928
4361
|
const hasOverrides = resolvedOverrides.length > 0;
|
|
3929
|
-
const needTree = mockStrategy === "all" || mockStrategy === "marked" || hasOverrides;
|
|
4362
|
+
const needTree = selectiveReplay === void 0 && (mockStrategy === "all" || mockStrategy === "marked" || hasOverrides);
|
|
3930
4363
|
const includeOutputs = mockStrategy === "all";
|
|
3931
4364
|
let mockTree;
|
|
3932
4365
|
if (needTree) {
|
|
@@ -4001,6 +4434,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
4001
4434
|
sourceBitfabTraceId: originalTraceId,
|
|
4002
4435
|
replayAttempt: attempt,
|
|
4003
4436
|
mockTree,
|
|
4437
|
+
selectiveReplay,
|
|
4004
4438
|
callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
|
|
4005
4439
|
mockStrategy,
|
|
4006
4440
|
mockOverrides: hasOverrides ? resolvedOverrides : void 0,
|
|
@@ -4012,6 +4446,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
4012
4446
|
() => fn(...inputs)
|
|
4013
4447
|
);
|
|
4014
4448
|
result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
|
|
4449
|
+
selectiveReplay?.assertNoSafetyConflict();
|
|
4015
4450
|
replayDurationMs = Math.round(performance.now() - replayStarted);
|
|
4016
4451
|
} catch (e) {
|
|
4017
4452
|
if (replayStarted !== null) {
|
|
@@ -4063,6 +4498,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
4063
4498
|
traceError,
|
|
4064
4499
|
replayError,
|
|
4065
4500
|
durationMs: replayDurationMs,
|
|
4501
|
+
...selectiveReplay === void 0 ? {} : { selectiveReplay: selectiveReplay.report() },
|
|
4066
4502
|
originalDurationMs,
|
|
4067
4503
|
originalTokens: serverItem.originalTokens ?? serverItem.tokens ?? null,
|
|
4068
4504
|
originalModel,
|
|
@@ -4167,6 +4603,14 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4167
4603
|
"force only applies when resuming an experiment. Pass it together with resume."
|
|
4168
4604
|
);
|
|
4169
4605
|
}
|
|
4606
|
+
if (options?.experimentalSelectiveReplay !== void 0) {
|
|
4607
|
+
validateSelectiveReplayOptions(options.experimentalSelectiveReplay);
|
|
4608
|
+
if ((options.mock ?? "marked") !== "marked" || normalizeMockOverrides(options.mockOverride).length > 0 || registeredOverrides.length > 0) {
|
|
4609
|
+
throw new BitfabError(
|
|
4610
|
+
"experimentalSelectiveReplay requires mock: 'marked' and cannot be combined with mock overrides."
|
|
4611
|
+
);
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4170
4614
|
if (options?.traceIds !== void 0) {
|
|
4171
4615
|
if (options.traceIds.length === 0) {
|
|
4172
4616
|
throw new BitfabError("traceIds must contain at least one trace ID.");
|
|
@@ -4208,7 +4652,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4208
4652
|
const includeDbBranchLease = dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true;
|
|
4209
4653
|
let session;
|
|
4210
4654
|
if (options?.resume === void 0) {
|
|
4211
|
-
const
|
|
4655
|
+
const started = await httpClient.startReplay(
|
|
4212
4656
|
traceFunctionKey,
|
|
4213
4657
|
// limit is meaningless with explicit traceIds (the ID list determines
|
|
4214
4658
|
// the count), so it's omitted from the request entirely.
|
|
@@ -4228,9 +4672,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4228
4672
|
requestedAttempts,
|
|
4229
4673
|
options?.adaptInputs !== void 0,
|
|
4230
4674
|
options?.onlyWithAssertions,
|
|
4231
|
-
options?.judgeAssertions
|
|
4675
|
+
options?.judgeAssertions,
|
|
4676
|
+
options?.experimentalSelectiveReplay
|
|
4232
4677
|
);
|
|
4233
|
-
if (
|
|
4678
|
+
if (started.items.length === 0) {
|
|
4234
4679
|
try {
|
|
4235
4680
|
console.warn(
|
|
4236
4681
|
`Bitfab: no traces matched "${traceFunctionKey}", so this replay ran nothing. Capture a trace, or seed one with seedTrace, before replaying.`
|
|
@@ -4239,10 +4684,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4239
4684
|
}
|
|
4240
4685
|
}
|
|
4241
4686
|
session = {
|
|
4242
|
-
experimentId:
|
|
4243
|
-
experimentUrl:
|
|
4687
|
+
experimentId: started.experimentId,
|
|
4688
|
+
experimentUrl: started.experimentUrl,
|
|
4244
4689
|
attempts: requestedAttempts,
|
|
4245
|
-
work: expandReplayWork(
|
|
4690
|
+
work: expandReplayWork(started.items, requestedAttempts),
|
|
4246
4691
|
finished: [],
|
|
4247
4692
|
includeDbBranchLease,
|
|
4248
4693
|
dbBranchSettings: resolveDbBranchSettings(options?.dbBranch)
|
|
@@ -4257,9 +4702,68 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4257
4702
|
options.dbBranch
|
|
4258
4703
|
);
|
|
4259
4704
|
}
|
|
4260
|
-
const { experimentId
|
|
4705
|
+
const { experimentId } = session;
|
|
4706
|
+
const fullExperimentUrl = `${serviceUrl}${session.experimentUrl}`;
|
|
4707
|
+
const heartbeat = startReplayHeartbeat(
|
|
4708
|
+
() => httpClient.heartbeatReplay(experimentId)
|
|
4709
|
+
);
|
|
4710
|
+
const interruptReplay = async () => {
|
|
4711
|
+
await heartbeat.stop();
|
|
4712
|
+
return httpClient.interruptReplay(experimentId);
|
|
4713
|
+
};
|
|
4714
|
+
const interruptHandlers = installReplayInterruptHandlers({
|
|
4715
|
+
experimentId,
|
|
4716
|
+
experimentUrl: fullExperimentUrl,
|
|
4717
|
+
interrupt: interruptReplay,
|
|
4718
|
+
onInterrupt: options?.onInterrupt
|
|
4719
|
+
});
|
|
4720
|
+
let completeReturned = false;
|
|
4721
|
+
const markCompleteReturned = () => {
|
|
4722
|
+
completeReturned = true;
|
|
4723
|
+
void heartbeat.stop();
|
|
4724
|
+
};
|
|
4725
|
+
try {
|
|
4726
|
+
return await runReplaySession(
|
|
4727
|
+
httpClient,
|
|
4728
|
+
fn,
|
|
4729
|
+
session,
|
|
4730
|
+
fullExperimentUrl,
|
|
4731
|
+
maxConcurrency,
|
|
4732
|
+
interruptHandlers.remove,
|
|
4733
|
+
markCompleteReturned,
|
|
4734
|
+
interruptHandlers.interrupted,
|
|
4735
|
+
options,
|
|
4736
|
+
registeredOverrides
|
|
4737
|
+
);
|
|
4738
|
+
} catch (error) {
|
|
4739
|
+
if (!completeReturned && !interruptHandlers.interrupted()) {
|
|
4740
|
+
await interruptReplay();
|
|
4741
|
+
}
|
|
4742
|
+
throw error;
|
|
4743
|
+
} finally {
|
|
4744
|
+
interruptHandlers.remove();
|
|
4745
|
+
await heartbeat.stop();
|
|
4746
|
+
}
|
|
4747
|
+
}
|
|
4748
|
+
function throwIfInterrupted(interrupted, items, experimentId, experimentUrl) {
|
|
4749
|
+
if (!interrupted()) {
|
|
4750
|
+
return;
|
|
4751
|
+
}
|
|
4752
|
+
const cause = new BitfabError(
|
|
4753
|
+
`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}.`,
|
|
4754
|
+
experimentUrl
|
|
4755
|
+
);
|
|
4756
|
+
throw new ReplayError(
|
|
4757
|
+
cause.message,
|
|
4758
|
+
items,
|
|
4759
|
+
experimentId,
|
|
4760
|
+
experimentUrl,
|
|
4761
|
+
cause
|
|
4762
|
+
);
|
|
4763
|
+
}
|
|
4764
|
+
async function runReplaySession(httpClient, fn, session, fullExperimentUrl, maxConcurrency, stopInterruptingOnSignal, markCompleteReturned, interrupted, options, registeredOverrides) {
|
|
4765
|
+
const { experimentId, attempts } = session;
|
|
4261
4766
|
const mockStrategy = options?.mock ?? "marked";
|
|
4262
|
-
const fullExperimentUrl = `${serviceUrl}${experimentUrl}`;
|
|
4263
4767
|
try {
|
|
4264
4768
|
options?.onExperimentStart?.({
|
|
4265
4769
|
experimentId,
|
|
@@ -4290,7 +4794,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4290
4794
|
session.includeDbBranchLease,
|
|
4291
4795
|
session.dbBranchSettings,
|
|
4292
4796
|
options?.adaptInputs,
|
|
4293
|
-
options?.dryRun === true
|
|
4797
|
+
options?.dryRun === true,
|
|
4798
|
+
options?.experimentalSelectiveReplay
|
|
4294
4799
|
)
|
|
4295
4800
|
);
|
|
4296
4801
|
const total = tasks.length + carriedOverItems.length;
|
|
@@ -4363,6 +4868,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4363
4868
|
traceError: item.traceError,
|
|
4364
4869
|
replayError: item.replayError,
|
|
4365
4870
|
durationMs: item.durationMs,
|
|
4871
|
+
...item.selectiveReplay === void 0 ? {} : { selectiveReplay: item.selectiveReplay },
|
|
4366
4872
|
originalDurationMs: item.originalDurationMs,
|
|
4367
4873
|
originalTokens: item.originalTokens,
|
|
4368
4874
|
originalModel: item.originalModel,
|
|
@@ -4407,7 +4913,15 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4407
4913
|
);
|
|
4408
4914
|
const resultItems = [...carriedOverItems, ...ranItems];
|
|
4409
4915
|
if (options?.dryRun === true) {
|
|
4916
|
+
throwIfInterrupted(
|
|
4917
|
+
interrupted,
|
|
4918
|
+
resultItems,
|
|
4919
|
+
experimentId,
|
|
4920
|
+
fullExperimentUrl
|
|
4921
|
+
);
|
|
4922
|
+
stopInterruptingOnSignal();
|
|
4410
4923
|
await httpClient.completeReplay(experimentId).catch(() => void 0);
|
|
4924
|
+
markCompleteReturned();
|
|
4411
4925
|
const dryResult = {
|
|
4412
4926
|
items: resultItems,
|
|
4413
4927
|
experimentId,
|
|
@@ -4432,12 +4946,15 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4432
4946
|
ranItems[index].traceId = readBack;
|
|
4433
4947
|
}
|
|
4434
4948
|
}
|
|
4949
|
+
throwIfInterrupted(interrupted, resultItems, experimentId, fullExperimentUrl);
|
|
4950
|
+
stopInterruptingOnSignal();
|
|
4435
4951
|
const completeResult = await preserveReplayFailure(
|
|
4436
4952
|
() => httpClient.completeReplay(experimentId),
|
|
4437
4953
|
resultItems,
|
|
4438
4954
|
experimentId,
|
|
4439
4955
|
fullExperimentUrl
|
|
4440
4956
|
);
|
|
4957
|
+
markCompleteReturned();
|
|
4441
4958
|
const serverTraceIds = completeResult.traceIds;
|
|
4442
4959
|
const replayTokens = completeResult.tokens;
|
|
4443
4960
|
if (serverTraceIds !== void 0) {
|
|
@@ -4541,6 +5058,9 @@ var init_replay = __esm({
|
|
|
4541
5058
|
init_mockOverride();
|
|
4542
5059
|
init_randomUuid();
|
|
4543
5060
|
init_replayContext();
|
|
5061
|
+
init_replayHeartbeat();
|
|
5062
|
+
init_replayInterrupt();
|
|
5063
|
+
init_selectiveReplayRuntime();
|
|
4544
5064
|
init_serialize();
|
|
4545
5065
|
REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
4546
5066
|
MAX_REPLAY_ATTEMPTS = 100;
|
|
@@ -4552,7 +5072,8 @@ var init_replay = __esm({
|
|
|
4552
5072
|
"attempts",
|
|
4553
5073
|
"graderIds",
|
|
4554
5074
|
"onlyWithAssertions",
|
|
4555
|
-
"dryRun"
|
|
5075
|
+
"dryRun",
|
|
5076
|
+
"experimentalSelectiveReplay"
|
|
4556
5077
|
];
|
|
4557
5078
|
CARRIED_OVER_ERROR = "This item errored in an earlier run of this experiment, before it was resumed.";
|
|
4558
5079
|
BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
|
|
@@ -9246,6 +9767,9 @@ var Bitfab = class {
|
|
|
9246
9767
|
...options.mockOnReplay !== void 0 && {
|
|
9247
9768
|
mockOnReplay: options.mockOnReplay
|
|
9248
9769
|
},
|
|
9770
|
+
...options.replayReusable !== void 0 && {
|
|
9771
|
+
replayReusable: options.replayReusable
|
|
9772
|
+
},
|
|
9249
9773
|
...options.finalize !== void 0 && { finalize: options.finalize }
|
|
9250
9774
|
};
|
|
9251
9775
|
}
|
|
@@ -9394,6 +9918,9 @@ var Bitfab = class {
|
|
|
9394
9918
|
...mockOnReplay !== void 0 && {
|
|
9395
9919
|
mockOnReplay
|
|
9396
9920
|
},
|
|
9921
|
+
...nodeConfiguration?.replayReusable !== void 0 && {
|
|
9922
|
+
replayReusable: nodeConfiguration.replayReusable
|
|
9923
|
+
},
|
|
9397
9924
|
...nodeConfiguration?.finalize !== void 0 && {
|
|
9398
9925
|
finalize: nodeConfiguration.finalize
|
|
9399
9926
|
}
|
|
@@ -10191,6 +10718,7 @@ var Bitfab = class {
|
|
|
10191
10718
|
instrumentation: options.instrumentation ?? "span",
|
|
10192
10719
|
functionId: options.functionId,
|
|
10193
10720
|
captureContent: options.captureContent ?? true,
|
|
10721
|
+
replayReusable: options.replayReusable,
|
|
10194
10722
|
autoTraceDefinition: options.autoTraceDefinition,
|
|
10195
10723
|
declaredNode: options.declaredNode === true,
|
|
10196
10724
|
nestedTrace: options.nestedTrace,
|
|
@@ -10310,6 +10838,27 @@ var Bitfab = class {
|
|
|
10310
10838
|
return result;
|
|
10311
10839
|
};
|
|
10312
10840
|
const replayCtxForMock = getReplayContext();
|
|
10841
|
+
if (replayCtxForMock?.selectiveReplay) {
|
|
10842
|
+
const decision = replayCtxForMock.selectiveReplay.enter({
|
|
10843
|
+
traceFunctionKey,
|
|
10844
|
+
spanName: baseSpanParams.spanName ?? traceFunctionKey,
|
|
10845
|
+
spanId,
|
|
10846
|
+
parentSpanId,
|
|
10847
|
+
inputs: args,
|
|
10848
|
+
safetyMock: !isRootSpan && options.mockOnReplay === true,
|
|
10849
|
+
reuseAllowed: options.replayReusable === true,
|
|
10850
|
+
canReuseOutput: options.finalize === void 0 && options.replayOutput === void 0 && !fn.constructor.name.includes("Generator")
|
|
10851
|
+
});
|
|
10852
|
+
if (decision.mock) {
|
|
10853
|
+
void sendSpan({
|
|
10854
|
+
result: decision.output,
|
|
10855
|
+
mocked: true,
|
|
10856
|
+
mockTarget: "output",
|
|
10857
|
+
mockSource: "recorded"
|
|
10858
|
+
});
|
|
10859
|
+
return fnReturnsPromise ? Promise.resolve(decision.output) : decision.output;
|
|
10860
|
+
}
|
|
10861
|
+
}
|
|
10313
10862
|
if (replayCtxForMock?.mockTree && !isRootSpan) {
|
|
10314
10863
|
const counters = replayCtxForMock.callCounters;
|
|
10315
10864
|
const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
|
|
@@ -10733,6 +11282,9 @@ var Bitfab = class {
|
|
|
10733
11282
|
span_origin: makeSpanOrigin(params.instrumentation),
|
|
10734
11283
|
span_data: {
|
|
10735
11284
|
name: params.spanName,
|
|
11285
|
+
...params.replayReusable !== void 0 && {
|
|
11286
|
+
replay_reusable: params.replayReusable
|
|
11287
|
+
},
|
|
10736
11288
|
type: params.spanType,
|
|
10737
11289
|
...params.functionId !== void 0 && {
|
|
10738
11290
|
function_id: params.functionId,
|
|
@@ -11284,6 +11836,7 @@ init_replay();
|
|
|
11284
11836
|
// src/replayRegistry.ts
|
|
11285
11837
|
init_errors();
|
|
11286
11838
|
init_replay();
|
|
11839
|
+
init_replayInterrupt();
|
|
11287
11840
|
function defineReplayRegistry(registry) {
|
|
11288
11841
|
return registry;
|
|
11289
11842
|
}
|