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