bitfab 0.61.1 → 0.61.3
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-HKUHPLAM.js → chunk-6IMUONDN.js} +47 -3
- package/dist/chunk-6IMUONDN.js.map +1 -0
- package/dist/{chunk-I5J2FDKH.js → chunk-HVEZHLVQ.js} +46 -3
- package/dist/chunk-HVEZHLVQ.js.map +1 -0
- package/dist/{chunk-VKGLQL7V.js → chunk-IZUZ5NBB.js} +473 -51
- package/dist/chunk-IZUZ5NBB.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-ENHRFXCR.js → chunk-PAUXHTNF.js} +38 -7
- package/dist/chunk-PAUXHTNF.js.map +1 -0
- package/dist/{chunk-S4WE6U2Y.js → chunk-WOHIAKZF.js} +122 -52
- package/dist/chunk-WOHIAKZF.js.map +1 -0
- package/dist/{http-5DA23DKC.js → http-3IYU5Z54.js} +2 -2
- package/dist/{http-NY6M2AK3.js → http-BYICBOWP.js} +2 -2
- package/dist/index.cjs +563 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +146 -3
- package/dist/index.d.ts +146 -3
- package/dist/index.js +4 -4
- package/dist/node.cjs +563 -52
- 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-KVENJRCX.js → replay-SXHX5BBU.js} +3 -3
- package/dist/replayCli.js +4 -3
- package/dist/replayCli.js.map +1 -1
- package/dist/seedCli.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-BMJNDIVA.js.map +0 -1
- package/dist/chunk-ENHRFXCR.js.map +0 -1
- package/dist/chunk-HKUHPLAM.js.map +0 -1
- package/dist/chunk-I5J2FDKH.js.map +0 -1
- package/dist/chunk-S4WE6U2Y.js.map +0 -1
- package/dist/chunk-VKGLQL7V.js.map +0 -1
- /package/dist/{http-5DA23DKC.js.map → http-3IYU5Z54.js.map} +0 -0
- /package/dist/{http-NY6M2AK3.js.map → http-BYICBOWP.js.map} +0 -0
- /package/dist/{replay-KVENJRCX.js.map → replay-SXHX5BBU.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.3";
|
|
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 {
|
|
@@ -2891,8 +2892,11 @@ var init_http = __esm({
|
|
|
2891
2892
|
* Start a replay session by fetching historical traces.
|
|
2892
2893
|
* Blocking call - creates an experiment and returns lightweight item references.
|
|
2893
2894
|
*/
|
|
2894
|
-
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) {
|
|
2895
2896
|
const payload = { traceFunctionKey };
|
|
2897
|
+
if (experimentalSelectiveReplay !== void 0) {
|
|
2898
|
+
payload.experimentalSelectiveReplay = experimentalSelectiveReplay;
|
|
2899
|
+
}
|
|
2896
2900
|
if (limit !== void 0) {
|
|
2897
2901
|
payload.limit = limit;
|
|
2898
2902
|
}
|
|
@@ -2960,6 +2964,46 @@ var init_http = __esm({
|
|
|
2960
2964
|
}
|
|
2961
2965
|
return { ...rest, experimentId, experimentUrl };
|
|
2962
2966
|
}
|
|
2967
|
+
async resumeReplay(experimentId, traceFunctionKey, codeChangeFiles, force) {
|
|
2968
|
+
const payload = {
|
|
2969
|
+
experimentId,
|
|
2970
|
+
traceFunctionKey,
|
|
2971
|
+
lazyDbBranchLease: true
|
|
2972
|
+
};
|
|
2973
|
+
if (force === true) {
|
|
2974
|
+
payload.force = true;
|
|
2975
|
+
}
|
|
2976
|
+
if (codeChangeFiles !== void 0) {
|
|
2977
|
+
payload.codeChangeFiles = codeChangeFiles;
|
|
2978
|
+
}
|
|
2979
|
+
const git = await resolvedGitState();
|
|
2980
|
+
if (git && !isEmptyGitState(git)) {
|
|
2981
|
+
payload.git = git;
|
|
2982
|
+
}
|
|
2983
|
+
const response = await this.request(
|
|
2984
|
+
"/api/sdk/replay/resume",
|
|
2985
|
+
payload,
|
|
2986
|
+
{ timeout: REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS }
|
|
2987
|
+
);
|
|
2988
|
+
const { testRunId, testRunUrl, ...rest } = response;
|
|
2989
|
+
const resumedExperimentId = response.experimentId ?? testRunId;
|
|
2990
|
+
const experimentUrl = response.experimentUrl ?? testRunUrl;
|
|
2991
|
+
if (resumedExperimentId === void 0 || experimentUrl === void 0) {
|
|
2992
|
+
throw new BitfabError(
|
|
2993
|
+
"Replay resume response is missing the experiment ID or URL."
|
|
2994
|
+
);
|
|
2995
|
+
}
|
|
2996
|
+
return {
|
|
2997
|
+
...rest,
|
|
2998
|
+
experimentId: resumedExperimentId,
|
|
2999
|
+
experimentUrl,
|
|
3000
|
+
work: rest.work ?? [],
|
|
3001
|
+
finished: rest.finished ?? [],
|
|
3002
|
+
skippedOriginalTraceIds: rest.skippedOriginalTraceIds ?? [],
|
|
3003
|
+
includeDbBranchLease: rest.includeDbBranchLease ?? false,
|
|
3004
|
+
dbBranchSettings: rest.dbBranchSettings ?? null
|
|
3005
|
+
};
|
|
3006
|
+
}
|
|
2963
3007
|
/**
|
|
2964
3008
|
* Fetch an external span by ID.
|
|
2965
3009
|
* Blocking GET request.
|
|
@@ -3576,6 +3620,291 @@ var init_integrationFixture = __esm({
|
|
|
3576
3620
|
}
|
|
3577
3621
|
});
|
|
3578
3622
|
|
|
3623
|
+
// src/selectiveReplay.ts
|
|
3624
|
+
function replayNodeKey(node) {
|
|
3625
|
+
return JSON.stringify([node.traceFunctionKey, node.spanName]);
|
|
3626
|
+
}
|
|
3627
|
+
function jsonFingerprint(value) {
|
|
3628
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3629
|
+
function encode(input) {
|
|
3630
|
+
if (input === null || typeof input === "boolean") {
|
|
3631
|
+
return JSON.stringify(input);
|
|
3632
|
+
}
|
|
3633
|
+
if (typeof input === "string" && !input.startsWith("<unserializable")) {
|
|
3634
|
+
return JSON.stringify(input);
|
|
3635
|
+
}
|
|
3636
|
+
if (typeof input === "number" && Number.isFinite(input) && !Object.is(input, -0)) {
|
|
3637
|
+
return JSON.stringify(input);
|
|
3638
|
+
}
|
|
3639
|
+
if (typeof input !== "object" || input === null || seen.has(input)) {
|
|
3640
|
+
throw new Error("Not a lossless JSON value");
|
|
3641
|
+
}
|
|
3642
|
+
seen.add(input);
|
|
3643
|
+
const prototype = Object.getPrototypeOf(input);
|
|
3644
|
+
if (prototype !== (Array.isArray(input) ? Array.prototype : Object.prototype)) {
|
|
3645
|
+
throw new Error("Not a plain JSON object");
|
|
3646
|
+
}
|
|
3647
|
+
if (Object.getOwnPropertySymbols(input).length > 0) {
|
|
3648
|
+
throw new Error("Symbol properties are not recorded");
|
|
3649
|
+
}
|
|
3650
|
+
const keys = Object.keys(input);
|
|
3651
|
+
if (Object.getOwnPropertyNames(input).length !== keys.length + (Array.isArray(input) ? 1 : 0)) {
|
|
3652
|
+
throw new Error("Non-enumerable properties are not recorded");
|
|
3653
|
+
}
|
|
3654
|
+
if (Array.isArray(input) && (keys.length !== input.length || keys.some((key, index) => key !== String(index)))) {
|
|
3655
|
+
throw new Error("Not a dense JSON array");
|
|
3656
|
+
}
|
|
3657
|
+
const properties = keys.map((key) => {
|
|
3658
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
3659
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
3660
|
+
throw new Error("Accessor properties are not replay inputs");
|
|
3661
|
+
}
|
|
3662
|
+
return `${Array.isArray(input) ? "" : `${JSON.stringify(key)}:`}${encode(descriptor.value)}`;
|
|
3663
|
+
});
|
|
3664
|
+
return Array.isArray(input) ? `[${properties.join(",")}]` : `{${properties.join(",")}}`;
|
|
3665
|
+
}
|
|
3666
|
+
try {
|
|
3667
|
+
return encode(value);
|
|
3668
|
+
} catch {
|
|
3669
|
+
return void 0;
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
var init_selectiveReplay = __esm({
|
|
3673
|
+
"src/selectiveReplay.ts"() {
|
|
3674
|
+
"use strict";
|
|
3675
|
+
}
|
|
3676
|
+
});
|
|
3677
|
+
|
|
3678
|
+
// src/selectiveReplayRuntime.ts
|
|
3679
|
+
function validateSelectiveReplayOptions(options) {
|
|
3680
|
+
const validNodes = (nodes) => Array.isArray(nodes) && nodes.length <= 100 && nodes.every(
|
|
3681
|
+
(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
|
|
3682
|
+
);
|
|
3683
|
+
if (!options || !validNodes(options.mustRun) || options.mustRun.length === 0 || Object.keys(options).some((key) => key !== "mustRun")) {
|
|
3684
|
+
throw new BitfabError(
|
|
3685
|
+
"experimentalSelectiveReplay accepts only 1\u2013100 mustRun with traceFunctionKey and spanName. Declare replayReusable on eligible spans, not reusableNodes in the replay request."
|
|
3686
|
+
);
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
function policyKey(options) {
|
|
3690
|
+
return JSON.stringify(options.mustRun.map(replayNodeKey).sort());
|
|
3691
|
+
}
|
|
3692
|
+
var SelectiveReplayRuntime;
|
|
3693
|
+
var init_selectiveReplayRuntime = __esm({
|
|
3694
|
+
"src/selectiveReplayRuntime.ts"() {
|
|
3695
|
+
"use strict";
|
|
3696
|
+
init_errors();
|
|
3697
|
+
init_selectiveReplay();
|
|
3698
|
+
SelectiveReplayRuntime = class {
|
|
3699
|
+
constructor(plan, options) {
|
|
3700
|
+
this.plan = plan;
|
|
3701
|
+
this.options = options;
|
|
3702
|
+
this.nodes = /* @__PURE__ */ new Map();
|
|
3703
|
+
this.children = /* @__PURE__ */ new Map();
|
|
3704
|
+
this.runtimeParents = /* @__PURE__ */ new Map();
|
|
3705
|
+
this.consumed = /* @__PURE__ */ new Map();
|
|
3706
|
+
this.requiredScopes = /* @__PURE__ */ new Set();
|
|
3707
|
+
this.recordings = /* @__PURE__ */ new Map();
|
|
3708
|
+
this.reached = /* @__PURE__ */ new Set();
|
|
3709
|
+
this.decisions = [];
|
|
3710
|
+
this.assertionNames = /* @__PURE__ */ new Set();
|
|
3711
|
+
this.assertionScopes = /* @__PURE__ */ new Set();
|
|
3712
|
+
this.observedNames = /* @__PURE__ */ new Map();
|
|
3713
|
+
this.unresolvedAssertions = [];
|
|
3714
|
+
validateSelectiveReplayOptions(options);
|
|
3715
|
+
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) {
|
|
3716
|
+
throw new BitfabError(
|
|
3717
|
+
"Server did not return a compatible selective replay plan. Update the server or disable experimentalSelectiveReplay."
|
|
3718
|
+
);
|
|
3719
|
+
}
|
|
3720
|
+
validateSelectiveReplayOptions(plan.options);
|
|
3721
|
+
if (policyKey(plan.options) !== policyKey(options)) {
|
|
3722
|
+
throw new BitfabError(
|
|
3723
|
+
"Selective replay plan does not match the requested mustRun nodes."
|
|
3724
|
+
);
|
|
3725
|
+
}
|
|
3726
|
+
this.required = new Set(options.mustRun.map(replayNodeKey));
|
|
3727
|
+
for (const node of plan.nodes) {
|
|
3728
|
+
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") {
|
|
3729
|
+
throw new BitfabError("Invalid selective replay node.");
|
|
3730
|
+
}
|
|
3731
|
+
this.nodes.set(node.id, node);
|
|
3732
|
+
if (node.recording !== void 0) {
|
|
3733
|
+
this.recordings.set(node.id, {
|
|
3734
|
+
input: node.recording.kwargs === void 0 ? jsonFingerprint(node.recording.input) : void 0,
|
|
3735
|
+
output: jsonFingerprint(node.recording.output)
|
|
3736
|
+
});
|
|
3737
|
+
}
|
|
3738
|
+
if (node.parentId !== null) {
|
|
3739
|
+
const siblings = this.children.get(node.parentId) ?? [];
|
|
3740
|
+
siblings.push(node);
|
|
3741
|
+
this.children.set(node.parentId, siblings);
|
|
3742
|
+
}
|
|
3743
|
+
}
|
|
3744
|
+
const root = this.nodes.get(plan.rootId);
|
|
3745
|
+
if (!root || root.parentId !== null || !root.mustRun) {
|
|
3746
|
+
throw new BitfabError("Selective replay requires a live recorded root.");
|
|
3747
|
+
}
|
|
3748
|
+
const assertionIds = /* @__PURE__ */ new Set();
|
|
3749
|
+
let wholeTrace = false;
|
|
3750
|
+
for (const assertion of plan.assertions) {
|
|
3751
|
+
const target = assertion?.target;
|
|
3752
|
+
if (typeof assertion?.id !== "string" || assertion.id.length === 0 || assertionIds.has(assertion.id) || target !== null && (typeof target !== "object" || target.kind !== "output" && target.kind !== "span")) {
|
|
3753
|
+
throw new BitfabError(
|
|
3754
|
+
"Invalid selective replay assertion requirements."
|
|
3755
|
+
);
|
|
3756
|
+
}
|
|
3757
|
+
assertionIds.add(assertion.id);
|
|
3758
|
+
if (target === null) {
|
|
3759
|
+
wholeTrace = true;
|
|
3760
|
+
} else if (target.kind === "span") {
|
|
3761
|
+
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)) {
|
|
3762
|
+
throw new BitfabError("Invalid selective replay assertion target.");
|
|
3763
|
+
}
|
|
3764
|
+
this.assertionNames.add(target.name);
|
|
3765
|
+
const count = plan.nodes.filter(
|
|
3766
|
+
(node) => node.spanName === target.name
|
|
3767
|
+
).length;
|
|
3768
|
+
if (count <= (typeof target.occurrence === "number" ? target.occurrence : 0)) {
|
|
3769
|
+
this.unresolvedAssertions.push(assertion.id);
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
this.fullTraceForAssertions = wholeTrace || this.unresolvedAssertions.length > 0;
|
|
3774
|
+
const recordedKeys = new Set(plan.nodes.map(replayNodeKey));
|
|
3775
|
+
const unresolved = options.mustRun.filter(
|
|
3776
|
+
(node) => !recordedKeys.has(replayNodeKey(node))
|
|
3777
|
+
);
|
|
3778
|
+
if (JSON.stringify(unresolved.map(replayNodeKey).sort()) !== JSON.stringify(plan.unresolvedMustRun.map(replayNodeKey).sort())) {
|
|
3779
|
+
throw new BitfabError(
|
|
3780
|
+
"Selective replay plan has incomplete mustRun coverage."
|
|
3781
|
+
);
|
|
3782
|
+
}
|
|
3783
|
+
for (const node of plan.nodes) {
|
|
3784
|
+
const ancestors = /* @__PURE__ */ new Set([node.id]);
|
|
3785
|
+
let requiredByManifest = this.required.has(replayNodeKey(node));
|
|
3786
|
+
let requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(node.spanName);
|
|
3787
|
+
let parentId = node.parentId;
|
|
3788
|
+
while (parentId !== null) {
|
|
3789
|
+
const parent = this.nodes.get(parentId);
|
|
3790
|
+
if (!parent || ancestors.has(parentId) || node.mustRun && !parent.mustRun) {
|
|
3791
|
+
throw new BitfabError("Invalid selective replay ancestry.");
|
|
3792
|
+
}
|
|
3793
|
+
ancestors.add(parentId);
|
|
3794
|
+
requiredByManifest || (requiredByManifest = this.required.has(replayNodeKey(parent)));
|
|
3795
|
+
requiredForAssertion || (requiredForAssertion = this.assertionNames.has(parent.spanName));
|
|
3796
|
+
parentId = parent.parentId;
|
|
3797
|
+
}
|
|
3798
|
+
if (!ancestors.has(plan.rootId) || (requiredByManifest || unresolved.length > 0) && !node.mustRun) {
|
|
3799
|
+
throw new BitfabError("Selective replay plan could hide required code.");
|
|
3800
|
+
}
|
|
3801
|
+
if (requiredForAssertion && !node.mustRun) {
|
|
3802
|
+
throw new BitfabError(
|
|
3803
|
+
"Selective replay plan could hide assertion evidence."
|
|
3804
|
+
);
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
enter(params) {
|
|
3809
|
+
const identity = {
|
|
3810
|
+
traceFunctionKey: params.traceFunctionKey,
|
|
3811
|
+
spanName: params.spanName
|
|
3812
|
+
};
|
|
3813
|
+
const key = replayNodeKey(identity);
|
|
3814
|
+
const fingerprint = jsonFingerprint(params.inputs);
|
|
3815
|
+
let node;
|
|
3816
|
+
let matchReason = "unmatched-call";
|
|
3817
|
+
if (params.parentSpanId === null) {
|
|
3818
|
+
node = this.nodes.get(this.plan.rootId);
|
|
3819
|
+
} else {
|
|
3820
|
+
const recordedParent = this.runtimeParents.get(params.parentSpanId);
|
|
3821
|
+
const consumed = this.consumed.get(params.parentSpanId) ?? /* @__PURE__ */ new Set();
|
|
3822
|
+
this.consumed.set(params.parentSpanId, consumed);
|
|
3823
|
+
const candidates = recordedParent ? (this.children.get(recordedParent) ?? []).filter(
|
|
3824
|
+
(candidate) => replayNodeKey(candidate) === key && !consumed.has(candidate.id)
|
|
3825
|
+
) : [];
|
|
3826
|
+
const exact = fingerprint === void 0 ? [] : candidates.filter(
|
|
3827
|
+
(candidate) => this.recordings.get(candidate.id)?.input === fingerprint
|
|
3828
|
+
);
|
|
3829
|
+
node = exact.length === 1 ? exact[0] : exact.length === 0 && candidates.length === 1 ? candidates[0] : void 0;
|
|
3830
|
+
if (node) {
|
|
3831
|
+
consumed.add(node.id);
|
|
3832
|
+
} else if (candidates.length > 0) {
|
|
3833
|
+
matchReason = "ambiguous-call";
|
|
3834
|
+
for (const candidate of candidates) {
|
|
3835
|
+
consumed.add(candidate.id);
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
this.runtimeParents.set(params.spanId, node?.id ?? null);
|
|
3840
|
+
const requiredByManifest = this.required.has(key) || params.parentSpanId !== null && this.requiredScopes.has(params.parentSpanId);
|
|
3841
|
+
if (requiredByManifest) {
|
|
3842
|
+
this.requiredScopes.add(params.spanId);
|
|
3843
|
+
}
|
|
3844
|
+
const requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(params.spanName) || params.parentSpanId !== null && this.assertionScopes.has(params.parentSpanId);
|
|
3845
|
+
if (requiredForAssertion) {
|
|
3846
|
+
this.assertionScopes.add(params.spanId);
|
|
3847
|
+
}
|
|
3848
|
+
const mustRun = params.parentSpanId === null || requiredByManifest || node?.mustRun === true || requiredForAssertion || this.plan.unresolvedMustRun.length > 0;
|
|
3849
|
+
const recording = node === void 0 ? void 0 : this.recordings.get(node.id);
|
|
3850
|
+
const equalInputs = fingerprint !== void 0 && recording?.input === fingerprint;
|
|
3851
|
+
const reusable = recording?.output !== void 0 && equalInputs && params.canReuseOutput;
|
|
3852
|
+
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;
|
|
3853
|
+
const decision = (action, why) => {
|
|
3854
|
+
this.decisions.push({
|
|
3855
|
+
...identity,
|
|
3856
|
+
originalSpanId: node?.id ?? null,
|
|
3857
|
+
action,
|
|
3858
|
+
reason: why
|
|
3859
|
+
});
|
|
3860
|
+
};
|
|
3861
|
+
if (params.safetyMock && (mustRun || !reusable)) {
|
|
3862
|
+
decision("blocked", `safety-conflict:${reason}`);
|
|
3863
|
+
this.failure = new BitfabError(
|
|
3864
|
+
`Selective replay cannot execute or safely reuse safety-mocked node ${params.spanName} (${reason}).`
|
|
3865
|
+
);
|
|
3866
|
+
throw this.failure;
|
|
3867
|
+
}
|
|
3868
|
+
if (!mustRun && reusable && (params.safetyMock || node?.reusable === true && params.reuseAllowed)) {
|
|
3869
|
+
decision("mock", params.safetyMock ? "safety-mock" : "unchanged-inputs");
|
|
3870
|
+
return {
|
|
3871
|
+
mock: true,
|
|
3872
|
+
output: JSON.parse(recording.output)
|
|
3873
|
+
};
|
|
3874
|
+
}
|
|
3875
|
+
decision("run", reason);
|
|
3876
|
+
this.observedNames.set(
|
|
3877
|
+
params.spanName,
|
|
3878
|
+
(this.observedNames.get(params.spanName) ?? 0) + 1
|
|
3879
|
+
);
|
|
3880
|
+
if (this.required.has(key)) {
|
|
3881
|
+
this.reached.add(key);
|
|
3882
|
+
}
|
|
3883
|
+
return { mock: false };
|
|
3884
|
+
}
|
|
3885
|
+
assertNoSafetyConflict() {
|
|
3886
|
+
if (this.failure) {
|
|
3887
|
+
throw this.failure;
|
|
3888
|
+
}
|
|
3889
|
+
}
|
|
3890
|
+
report() {
|
|
3891
|
+
return {
|
|
3892
|
+
decisions: this.decisions,
|
|
3893
|
+
mustRunNotReached: this.options.mustRun.filter(
|
|
3894
|
+
(node) => !this.reached.has(replayNodeKey(node))
|
|
3895
|
+
),
|
|
3896
|
+
unresolvedMustRun: this.plan.unresolvedMustRun,
|
|
3897
|
+
assertionIds: this.plan.assertions.map((assertion) => assertion.id),
|
|
3898
|
+
unresolvedAssertions: this.unresolvedAssertions,
|
|
3899
|
+
assertionTargetsNotReached: this.plan.assertions.flatMap(
|
|
3900
|
+
({ id, target }) => target?.kind === "span" && (this.observedNames.get(target.name) ?? 0) <= (typeof target.occurrence === "number" ? target.occurrence : 0) ? [id] : []
|
|
3901
|
+
)
|
|
3902
|
+
};
|
|
3903
|
+
}
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3906
|
+
});
|
|
3907
|
+
|
|
3579
3908
|
// src/replay.ts
|
|
3580
3909
|
var replay_exports = {};
|
|
3581
3910
|
__export(replay_exports, {
|
|
@@ -3595,6 +3924,88 @@ function expandReplayWork(serverItems, attempts) {
|
|
|
3595
3924
|
(_, attempt) => serverItems.map((serverItem) => ({ serverItem, attempt }))
|
|
3596
3925
|
).flat();
|
|
3597
3926
|
}
|
|
3927
|
+
function validateResume(resume, options) {
|
|
3928
|
+
if (resume.trim().length === 0) {
|
|
3929
|
+
throw new BitfabError("resume must be the ID of the experiment to resume.");
|
|
3930
|
+
}
|
|
3931
|
+
const conflicting = RESUME_CONFLICTING_OPTIONS.filter(
|
|
3932
|
+
(name) => options[name] !== void 0 && options[name] !== false
|
|
3933
|
+
);
|
|
3934
|
+
if (conflicting.length > 0) {
|
|
3935
|
+
throw new BitfabError(
|
|
3936
|
+
`resume continues experiment ${resume} with the traces, attempts, and graders it started with, so it cannot be combined with ${conflicting.join(", ")}. Drop those options, or start a new replay without resume.`
|
|
3937
|
+
);
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
async function resumeReplaySession(httpClient, experimentId, traceFunctionKey, codeChangeFiles, force, requestedDbBranch) {
|
|
3941
|
+
const response = await httpClient.resumeReplay(
|
|
3942
|
+
experimentId,
|
|
3943
|
+
traceFunctionKey,
|
|
3944
|
+
codeChangeFiles,
|
|
3945
|
+
force
|
|
3946
|
+
);
|
|
3947
|
+
const work = response.work.map(({ itemIndex, attempt }) => {
|
|
3948
|
+
const serverItem = response.items[itemIndex];
|
|
3949
|
+
if (serverItem === void 0) {
|
|
3950
|
+
throw new BitfabError(
|
|
3951
|
+
`Replay resume for experiment ${response.experimentId} named item ${itemIndex}, but the server returned only ${response.items.length} item(s).`
|
|
3952
|
+
);
|
|
3953
|
+
}
|
|
3954
|
+
return { serverItem, attempt };
|
|
3955
|
+
});
|
|
3956
|
+
if (response.skippedOriginalTraceIds.length > 0) {
|
|
3957
|
+
try {
|
|
3958
|
+
console.warn(
|
|
3959
|
+
`Bitfab: ${response.skippedOriginalTraceIds.length} trace(s) selected when experiment ${response.experimentId} started no longer exist, so resume skips them: ${response.skippedOriginalTraceIds.join(", ")}`
|
|
3960
|
+
);
|
|
3961
|
+
} catch {
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3964
|
+
if (requestedDbBranch !== void 0 && dbBranchEnabled(requestedDbBranch) !== response.includeDbBranchLease) {
|
|
3965
|
+
try {
|
|
3966
|
+
console.warn(
|
|
3967
|
+
`Bitfab: experiment ${response.experimentId} started with database branching ${response.includeDbBranchLease ? "on" : "off"}, so resume keeps it ${response.includeDbBranchLease ? "on" : "off"} and ignores the dbBranch option passed here.`
|
|
3968
|
+
);
|
|
3969
|
+
} catch {
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
return {
|
|
3973
|
+
experimentId: response.experimentId,
|
|
3974
|
+
experimentUrl: response.experimentUrl,
|
|
3975
|
+
attempts: response.attempts,
|
|
3976
|
+
work,
|
|
3977
|
+
finished: response.finished,
|
|
3978
|
+
includeDbBranchLease: response.includeDbBranchLease,
|
|
3979
|
+
dbBranchSettings: response.dbBranchSettings ?? void 0
|
|
3980
|
+
};
|
|
3981
|
+
}
|
|
3982
|
+
function carriedOverReplayItem(finished) {
|
|
3983
|
+
return {
|
|
3984
|
+
traceId: finished.traceId,
|
|
3985
|
+
originalTraceId: finished.originalTraceId,
|
|
3986
|
+
originalSpanId: "",
|
|
3987
|
+
sourceTraceId: finished.originalTraceId,
|
|
3988
|
+
sourceSpanId: "",
|
|
3989
|
+
attempt: finished.attempt,
|
|
3990
|
+
input: [],
|
|
3991
|
+
result: void 0,
|
|
3992
|
+
originalOutput: void 0,
|
|
3993
|
+
error: finished.hasError ? CARRIED_OVER_ERROR : null,
|
|
3994
|
+
traceError: null,
|
|
3995
|
+
replayError: null,
|
|
3996
|
+
durationMs: null,
|
|
3997
|
+
originalDurationMs: null,
|
|
3998
|
+
originalTokens: null,
|
|
3999
|
+
originalModel: null,
|
|
4000
|
+
tokens: null,
|
|
4001
|
+
model: null,
|
|
4002
|
+
dbSnapshotRef: null,
|
|
4003
|
+
dbBranchTimings: null,
|
|
4004
|
+
traceOutline: null,
|
|
4005
|
+
originalTraceOutline: null,
|
|
4006
|
+
carriedOver: true
|
|
4007
|
+
};
|
|
4008
|
+
}
|
|
3598
4009
|
function resolveAttempts(attempts) {
|
|
3599
4010
|
if (attempts === void 0) {
|
|
3600
4011
|
return 1;
|
|
@@ -3749,7 +4160,7 @@ function buildMockTree(rootNode) {
|
|
|
3749
4160
|
}
|
|
3750
4161
|
return { spans };
|
|
3751
4162
|
}
|
|
3752
|
-
async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
|
|
4163
|
+
async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun, selectiveOptions) {
|
|
3753
4164
|
let lease = includeDbBranchLease ? serverItem.dbBranchLease : void 0;
|
|
3754
4165
|
let leaseError = includeDbBranchLease ? serverItem.dbBranchLeaseError : void 0;
|
|
3755
4166
|
let dbSnapshotRef = serverItem.dbSnapshotRef;
|
|
@@ -3762,6 +4173,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3762
4173
|
let replayError = null;
|
|
3763
4174
|
let replayDurationMs = null;
|
|
3764
4175
|
let replayStarted = null;
|
|
4176
|
+
let selectiveReplay;
|
|
3765
4177
|
const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
|
|
3766
4178
|
const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
|
|
3767
4179
|
try {
|
|
@@ -3794,6 +4206,17 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3794
4206
|
originalTraceId
|
|
3795
4207
|
);
|
|
3796
4208
|
}
|
|
4209
|
+
if (selectiveOptions !== void 0) {
|
|
4210
|
+
if (serverItem.selectiveReplayPlan === void 0) {
|
|
4211
|
+
throw new BitfabError(
|
|
4212
|
+
"Server did not return a selective replay plan. Update the server or disable experimentalSelectiveReplay."
|
|
4213
|
+
);
|
|
4214
|
+
}
|
|
4215
|
+
selectiveReplay = new SelectiveReplayRuntime(
|
|
4216
|
+
serverItem.selectiveReplayPlan,
|
|
4217
|
+
selectiveOptions
|
|
4218
|
+
);
|
|
4219
|
+
}
|
|
3797
4220
|
const span = await httpClient.getExternalSpan(originalSpanId, {
|
|
3798
4221
|
view: "replay"
|
|
3799
4222
|
});
|
|
@@ -3813,7 +4236,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3813
4236
|
});
|
|
3814
4237
|
}
|
|
3815
4238
|
const hasOverrides = resolvedOverrides.length > 0;
|
|
3816
|
-
const needTree = mockStrategy === "all" || mockStrategy === "marked" || hasOverrides;
|
|
4239
|
+
const needTree = selectiveReplay === void 0 && (mockStrategy === "all" || mockStrategy === "marked" || hasOverrides);
|
|
3817
4240
|
const includeOutputs = mockStrategy === "all";
|
|
3818
4241
|
let mockTree;
|
|
3819
4242
|
if (needTree) {
|
|
@@ -3888,6 +4311,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3888
4311
|
sourceBitfabTraceId: originalTraceId,
|
|
3889
4312
|
replayAttempt: attempt,
|
|
3890
4313
|
mockTree,
|
|
4314
|
+
selectiveReplay,
|
|
3891
4315
|
callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
|
|
3892
4316
|
mockStrategy,
|
|
3893
4317
|
mockOverrides: hasOverrides ? resolvedOverrides : void 0,
|
|
@@ -3899,6 +4323,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3899
4323
|
() => fn(...inputs)
|
|
3900
4324
|
);
|
|
3901
4325
|
result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
|
|
4326
|
+
selectiveReplay?.assertNoSafetyConflict();
|
|
3902
4327
|
replayDurationMs = Math.round(performance.now() - replayStarted);
|
|
3903
4328
|
} catch (e) {
|
|
3904
4329
|
if (replayStarted !== null) {
|
|
@@ -3950,6 +4375,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3950
4375
|
traceError,
|
|
3951
4376
|
replayError,
|
|
3952
4377
|
durationMs: replayDurationMs,
|
|
4378
|
+
...selectiveReplay === void 0 ? {} : { selectiveReplay: selectiveReplay.report() },
|
|
3953
4379
|
originalDurationMs,
|
|
3954
4380
|
originalTokens: serverItem.originalTokens ?? serverItem.tokens ?? null,
|
|
3955
4381
|
originalModel,
|
|
@@ -4047,6 +4473,21 @@ async function mapWithConcurrency2(tasks, maxConcurrency, onSettled, onStarted)
|
|
|
4047
4473
|
return results;
|
|
4048
4474
|
}
|
|
4049
4475
|
async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, registeredOverrides = []) {
|
|
4476
|
+
if (options?.resume !== void 0) {
|
|
4477
|
+
validateResume(options.resume, options);
|
|
4478
|
+
} else if (options?.force === true) {
|
|
4479
|
+
throw new BitfabError(
|
|
4480
|
+
"force only applies when resuming an experiment. Pass it together with resume."
|
|
4481
|
+
);
|
|
4482
|
+
}
|
|
4483
|
+
if (options?.experimentalSelectiveReplay !== void 0) {
|
|
4484
|
+
validateSelectiveReplayOptions(options.experimentalSelectiveReplay);
|
|
4485
|
+
if ((options.mock ?? "marked") !== "marked" || normalizeMockOverrides(options.mockOverride).length > 0 || registeredOverrides.length > 0) {
|
|
4486
|
+
throw new BitfabError(
|
|
4487
|
+
"experimentalSelectiveReplay requires mock: 'marked' and cannot be combined with mock overrides."
|
|
4488
|
+
);
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4050
4491
|
if (options?.traceIds !== void 0) {
|
|
4051
4492
|
if (options.traceIds.length === 0) {
|
|
4052
4493
|
throw new BitfabError("traceIds must contain at least one trace ID.");
|
|
@@ -4066,7 +4507,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4066
4507
|
} catch {
|
|
4067
4508
|
}
|
|
4068
4509
|
}
|
|
4069
|
-
const
|
|
4510
|
+
const requestedAttempts = resolveAttempts(options?.attempts);
|
|
4070
4511
|
const maxConcurrency = options?.maxConcurrency ?? 10;
|
|
4071
4512
|
if (!Number.isInteger(maxConcurrency) || maxConcurrency < 1) {
|
|
4072
4513
|
throw new BitfabError(
|
|
@@ -4085,40 +4526,60 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4085
4526
|
}
|
|
4086
4527
|
}
|
|
4087
4528
|
}
|
|
4088
|
-
const
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4529
|
+
const includeDbBranchLease = dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true;
|
|
4530
|
+
let session;
|
|
4531
|
+
if (options?.resume === void 0) {
|
|
4532
|
+
const started2 = await httpClient.startReplay(
|
|
4533
|
+
traceFunctionKey,
|
|
4534
|
+
// limit is meaningless with explicit traceIds (the ID list determines
|
|
4535
|
+
// the count), so it's omitted from the request entirely.
|
|
4536
|
+
options?.traceIds ? void 0 : options?.limit ?? 5,
|
|
4537
|
+
options?.traceIds,
|
|
4538
|
+
options?.name,
|
|
4539
|
+
codeChangeDescription,
|
|
4540
|
+
codeChangeFiles,
|
|
4541
|
+
// A dry run executes nothing, so a branch would be provisioned (and billed)
|
|
4542
|
+
// for code that never runs, and a seeded source's refusal would fail the item
|
|
4543
|
+
// before it could report its resolved inputs.
|
|
4544
|
+
includeDbBranchLease,
|
|
4545
|
+
options?.experimentGroupId,
|
|
4546
|
+
datasetIds,
|
|
4547
|
+
options?.graderIds,
|
|
4548
|
+
resolveDbBranchSettings(options?.dbBranch),
|
|
4549
|
+
requestedAttempts,
|
|
4550
|
+
options?.adaptInputs !== void 0,
|
|
4551
|
+
options?.onlyWithAssertions,
|
|
4552
|
+
options?.judgeAssertions,
|
|
4553
|
+
options?.experimentalSelectiveReplay
|
|
4554
|
+
);
|
|
4555
|
+
if (started2.items.length === 0) {
|
|
4556
|
+
try {
|
|
4557
|
+
console.warn(
|
|
4558
|
+
`Bitfab: no traces matched "${traceFunctionKey}", so this replay ran nothing. Capture a trace, or seed one with seedTrace, before replaying.`
|
|
4559
|
+
);
|
|
4560
|
+
} catch {
|
|
4561
|
+
}
|
|
4120
4562
|
}
|
|
4563
|
+
session = {
|
|
4564
|
+
experimentId: started2.experimentId,
|
|
4565
|
+
experimentUrl: started2.experimentUrl,
|
|
4566
|
+
attempts: requestedAttempts,
|
|
4567
|
+
work: expandReplayWork(started2.items, requestedAttempts),
|
|
4568
|
+
finished: [],
|
|
4569
|
+
includeDbBranchLease,
|
|
4570
|
+
dbBranchSettings: resolveDbBranchSettings(options?.dbBranch)
|
|
4571
|
+
};
|
|
4572
|
+
} else {
|
|
4573
|
+
session = await resumeReplaySession(
|
|
4574
|
+
httpClient,
|
|
4575
|
+
options.resume,
|
|
4576
|
+
traceFunctionKey,
|
|
4577
|
+
codeChangeFiles,
|
|
4578
|
+
options.force,
|
|
4579
|
+
options.dbBranch
|
|
4580
|
+
);
|
|
4121
4581
|
}
|
|
4582
|
+
const { experimentId, experimentUrl, attempts } = session;
|
|
4122
4583
|
const mockStrategy = options?.mock ?? "marked";
|
|
4123
4584
|
const fullExperimentUrl = `${serviceUrl}${experimentUrl}`;
|
|
4124
4585
|
try {
|
|
@@ -4132,7 +4593,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4132
4593
|
...normalizeMockOverrides(options?.mockOverride),
|
|
4133
4594
|
...registeredOverrides
|
|
4134
4595
|
];
|
|
4135
|
-
const workItems =
|
|
4596
|
+
const workItems = session.work;
|
|
4597
|
+
const carriedOverItems = session.finished.map(
|
|
4598
|
+
(finished) => carriedOverReplayItem(finished)
|
|
4599
|
+
);
|
|
4136
4600
|
const replayedTraceIds = workItems.map(() => randomUuid());
|
|
4137
4601
|
httpClient.trackTraceDeliveries(replayedTraceIds);
|
|
4138
4602
|
const tasks = workItems.map(
|
|
@@ -4145,18 +4609,19 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4145
4609
|
resolvedOverrides,
|
|
4146
4610
|
replayedTraceIds[index],
|
|
4147
4611
|
attempt,
|
|
4148
|
-
|
|
4149
|
-
|
|
4612
|
+
session.includeDbBranchLease,
|
|
4613
|
+
session.dbBranchSettings,
|
|
4150
4614
|
options?.adaptInputs,
|
|
4151
|
-
options?.dryRun === true
|
|
4615
|
+
options?.dryRun === true,
|
|
4616
|
+
options?.experimentalSelectiveReplay
|
|
4152
4617
|
)
|
|
4153
4618
|
);
|
|
4154
|
-
const total = tasks.length;
|
|
4619
|
+
const total = tasks.length + carriedOverItems.length;
|
|
4155
4620
|
const onItemFinish = options?.onItemFinish ?? options?.onProgress;
|
|
4156
|
-
let completed =
|
|
4157
|
-
let started =
|
|
4158
|
-
let
|
|
4159
|
-
let
|
|
4621
|
+
let completed = carriedOverItems.length;
|
|
4622
|
+
let started = carriedOverItems.length;
|
|
4623
|
+
let errored = carriedOverItems.filter((item) => item.error !== null).length;
|
|
4624
|
+
let succeeded = completed - errored;
|
|
4160
4625
|
let flushInFlight = null;
|
|
4161
4626
|
let flushPending = false;
|
|
4162
4627
|
const flushFinishedItemTrace = () => {
|
|
@@ -4176,7 +4641,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4176
4641
|
}
|
|
4177
4642
|
return flushInFlight;
|
|
4178
4643
|
};
|
|
4179
|
-
const
|
|
4644
|
+
const ranItems = await mapWithConcurrency2(
|
|
4180
4645
|
tasks,
|
|
4181
4646
|
maxConcurrency,
|
|
4182
4647
|
async (item, index) => {
|
|
@@ -4221,6 +4686,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4221
4686
|
traceError: item.traceError,
|
|
4222
4687
|
replayError: item.replayError,
|
|
4223
4688
|
durationMs: item.durationMs,
|
|
4689
|
+
...item.selectiveReplay === void 0 ? {} : { selectiveReplay: item.selectiveReplay },
|
|
4224
4690
|
originalDurationMs: item.originalDurationMs,
|
|
4225
4691
|
originalTokens: item.originalTokens,
|
|
4226
4692
|
originalModel: item.originalModel,
|
|
@@ -4263,6 +4729,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4263
4729
|
}
|
|
4264
4730
|
} : void 0
|
|
4265
4731
|
);
|
|
4732
|
+
const resultItems = [...carriedOverItems, ...ranItems];
|
|
4266
4733
|
if (options?.dryRun === true) {
|
|
4267
4734
|
await httpClient.completeReplay(experimentId).catch(() => void 0);
|
|
4268
4735
|
const dryResult = {
|
|
@@ -4282,11 +4749,11 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4282
4749
|
experimentId,
|
|
4283
4750
|
fullExperimentUrl
|
|
4284
4751
|
);
|
|
4285
|
-
for (let index = 0; index <
|
|
4752
|
+
for (let index = 0; index < ranItems.length; index += 1) {
|
|
4286
4753
|
const localId = replayedTraceIds[index];
|
|
4287
4754
|
const readBack = localId ? deliveredTraceIds[localId] : void 0;
|
|
4288
4755
|
if (readBack !== void 0) {
|
|
4289
|
-
|
|
4756
|
+
ranItems[index].traceId = readBack;
|
|
4290
4757
|
}
|
|
4291
4758
|
}
|
|
4292
4759
|
const completeResult = await preserveReplayFailure(
|
|
@@ -4300,8 +4767,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4300
4767
|
if (serverTraceIds !== void 0) {
|
|
4301
4768
|
const missing = [];
|
|
4302
4769
|
let completedCount = 0;
|
|
4303
|
-
for (let index = 0; index <
|
|
4304
|
-
const item =
|
|
4770
|
+
for (let index = 0; index < ranItems.length; index += 1) {
|
|
4771
|
+
const item = ranItems[index];
|
|
4305
4772
|
const localId = replayedTraceIds[index];
|
|
4306
4773
|
const mapped = localId ? serverTraceIds[localId] : void 0;
|
|
4307
4774
|
item.traceId = item.traceId ?? mapped ?? null;
|
|
@@ -4387,7 +4854,7 @@ async function writeReplayResultFile(result) {
|
|
|
4387
4854
|
}
|
|
4388
4855
|
}
|
|
4389
4856
|
}
|
|
4390
|
-
var REPLAY_PERSISTENCE_TIMEOUT_MS, MAX_REPLAY_ATTEMPTS, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
|
|
4857
|
+
var REPLAY_PERSISTENCE_TIMEOUT_MS, MAX_REPLAY_ATTEMPTS, RESUME_CONFLICTING_OPTIONS, CARRIED_OVER_ERROR, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
|
|
4391
4858
|
var init_replay = __esm({
|
|
4392
4859
|
"src/replay.ts"() {
|
|
4393
4860
|
"use strict";
|
|
@@ -4398,9 +4865,22 @@ var init_replay = __esm({
|
|
|
4398
4865
|
init_mockOverride();
|
|
4399
4866
|
init_randomUuid();
|
|
4400
4867
|
init_replayContext();
|
|
4868
|
+
init_selectiveReplayRuntime();
|
|
4401
4869
|
init_serialize();
|
|
4402
4870
|
REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
4403
4871
|
MAX_REPLAY_ATTEMPTS = 100;
|
|
4872
|
+
RESUME_CONFLICTING_OPTIONS = [
|
|
4873
|
+
"traceIds",
|
|
4874
|
+
"limit",
|
|
4875
|
+
"datasetId",
|
|
4876
|
+
"datasetIds",
|
|
4877
|
+
"attempts",
|
|
4878
|
+
"graderIds",
|
|
4879
|
+
"onlyWithAssertions",
|
|
4880
|
+
"dryRun",
|
|
4881
|
+
"experimentalSelectiveReplay"
|
|
4882
|
+
];
|
|
4883
|
+
CARRIED_OVER_ERROR = "This item errored in an earlier run of this experiment, before it was resumed.";
|
|
4404
4884
|
BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
|
|
4405
4885
|
ReplayError = class extends BitfabError {
|
|
4406
4886
|
constructor(message, items, experimentId, experimentUrl, cause) {
|
|
@@ -9092,6 +9572,9 @@ var Bitfab = class {
|
|
|
9092
9572
|
...options.mockOnReplay !== void 0 && {
|
|
9093
9573
|
mockOnReplay: options.mockOnReplay
|
|
9094
9574
|
},
|
|
9575
|
+
...options.replayReusable !== void 0 && {
|
|
9576
|
+
replayReusable: options.replayReusable
|
|
9577
|
+
},
|
|
9095
9578
|
...options.finalize !== void 0 && { finalize: options.finalize }
|
|
9096
9579
|
};
|
|
9097
9580
|
}
|
|
@@ -9240,6 +9723,9 @@ var Bitfab = class {
|
|
|
9240
9723
|
...mockOnReplay !== void 0 && {
|
|
9241
9724
|
mockOnReplay
|
|
9242
9725
|
},
|
|
9726
|
+
...nodeConfiguration?.replayReusable !== void 0 && {
|
|
9727
|
+
replayReusable: nodeConfiguration.replayReusable
|
|
9728
|
+
},
|
|
9243
9729
|
...nodeConfiguration?.finalize !== void 0 && {
|
|
9244
9730
|
finalize: nodeConfiguration.finalize
|
|
9245
9731
|
}
|
|
@@ -10037,6 +10523,7 @@ var Bitfab = class {
|
|
|
10037
10523
|
instrumentation: options.instrumentation ?? "span",
|
|
10038
10524
|
functionId: options.functionId,
|
|
10039
10525
|
captureContent: options.captureContent ?? true,
|
|
10526
|
+
replayReusable: options.replayReusable,
|
|
10040
10527
|
autoTraceDefinition: options.autoTraceDefinition,
|
|
10041
10528
|
declaredNode: options.declaredNode === true,
|
|
10042
10529
|
nestedTrace: options.nestedTrace,
|
|
@@ -10156,6 +10643,27 @@ var Bitfab = class {
|
|
|
10156
10643
|
return result;
|
|
10157
10644
|
};
|
|
10158
10645
|
const replayCtxForMock = getReplayContext();
|
|
10646
|
+
if (replayCtxForMock?.selectiveReplay) {
|
|
10647
|
+
const decision = replayCtxForMock.selectiveReplay.enter({
|
|
10648
|
+
traceFunctionKey,
|
|
10649
|
+
spanName: baseSpanParams.spanName ?? traceFunctionKey,
|
|
10650
|
+
spanId,
|
|
10651
|
+
parentSpanId,
|
|
10652
|
+
inputs: args,
|
|
10653
|
+
safetyMock: !isRootSpan && options.mockOnReplay === true,
|
|
10654
|
+
reuseAllowed: options.replayReusable === true,
|
|
10655
|
+
canReuseOutput: options.finalize === void 0 && options.replayOutput === void 0 && !fn.constructor.name.includes("Generator")
|
|
10656
|
+
});
|
|
10657
|
+
if (decision.mock) {
|
|
10658
|
+
void sendSpan({
|
|
10659
|
+
result: decision.output,
|
|
10660
|
+
mocked: true,
|
|
10661
|
+
mockTarget: "output",
|
|
10662
|
+
mockSource: "recorded"
|
|
10663
|
+
});
|
|
10664
|
+
return fnReturnsPromise ? Promise.resolve(decision.output) : decision.output;
|
|
10665
|
+
}
|
|
10666
|
+
}
|
|
10159
10667
|
if (replayCtxForMock?.mockTree && !isRootSpan) {
|
|
10160
10668
|
const counters = replayCtxForMock.callCounters;
|
|
10161
10669
|
const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
|
|
@@ -10579,6 +11087,9 @@ var Bitfab = class {
|
|
|
10579
11087
|
span_origin: makeSpanOrigin(params.instrumentation),
|
|
10580
11088
|
span_data: {
|
|
10581
11089
|
name: params.spanName,
|
|
11090
|
+
...params.replayReusable !== void 0 && {
|
|
11091
|
+
replay_reusable: params.replayReusable
|
|
11092
|
+
},
|
|
10582
11093
|
type: params.spanType,
|
|
10583
11094
|
...params.functionId !== void 0 && {
|
|
10584
11095
|
function_id: params.functionId,
|