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/node.cjs
CHANGED
|
@@ -97,7 +97,7 @@ var __version__, __packageName__;
|
|
|
97
97
|
var init_version_generated = __esm({
|
|
98
98
|
"src/version.generated.ts"() {
|
|
99
99
|
"use strict";
|
|
100
|
-
__version__ = "0.61.
|
|
100
|
+
__version__ = "0.61.3";
|
|
101
101
|
__packageName__ = "bitfab";
|
|
102
102
|
}
|
|
103
103
|
});
|
|
@@ -1084,6 +1084,7 @@ var init_simulationPlan = __esm({
|
|
|
1084
1084
|
"input_meta",
|
|
1085
1085
|
"output",
|
|
1086
1086
|
"output_meta",
|
|
1087
|
+
"replay_recording",
|
|
1087
1088
|
"prompt"
|
|
1088
1089
|
];
|
|
1089
1090
|
SimulationPlan = class {
|
|
@@ -2898,8 +2899,11 @@ var init_http = __esm({
|
|
|
2898
2899
|
* Start a replay session by fetching historical traces.
|
|
2899
2900
|
* Blocking call - creates an experiment and returns lightweight item references.
|
|
2900
2901
|
*/
|
|
2901
|
-
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions) {
|
|
2902
|
+
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions, experimentalSelectiveReplay) {
|
|
2902
2903
|
const payload = { traceFunctionKey };
|
|
2904
|
+
if (experimentalSelectiveReplay !== void 0) {
|
|
2905
|
+
payload.experimentalSelectiveReplay = experimentalSelectiveReplay;
|
|
2906
|
+
}
|
|
2903
2907
|
if (limit !== void 0) {
|
|
2904
2908
|
payload.limit = limit;
|
|
2905
2909
|
}
|
|
@@ -2967,6 +2971,46 @@ var init_http = __esm({
|
|
|
2967
2971
|
}
|
|
2968
2972
|
return { ...rest, experimentId, experimentUrl };
|
|
2969
2973
|
}
|
|
2974
|
+
async resumeReplay(experimentId, traceFunctionKey, codeChangeFiles, force) {
|
|
2975
|
+
const payload = {
|
|
2976
|
+
experimentId,
|
|
2977
|
+
traceFunctionKey,
|
|
2978
|
+
lazyDbBranchLease: true
|
|
2979
|
+
};
|
|
2980
|
+
if (force === true) {
|
|
2981
|
+
payload.force = true;
|
|
2982
|
+
}
|
|
2983
|
+
if (codeChangeFiles !== void 0) {
|
|
2984
|
+
payload.codeChangeFiles = codeChangeFiles;
|
|
2985
|
+
}
|
|
2986
|
+
const git = await resolvedGitState();
|
|
2987
|
+
if (git && !isEmptyGitState(git)) {
|
|
2988
|
+
payload.git = git;
|
|
2989
|
+
}
|
|
2990
|
+
const response = await this.request(
|
|
2991
|
+
"/api/sdk/replay/resume",
|
|
2992
|
+
payload,
|
|
2993
|
+
{ timeout: REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS }
|
|
2994
|
+
);
|
|
2995
|
+
const { testRunId, testRunUrl, ...rest } = response;
|
|
2996
|
+
const resumedExperimentId = response.experimentId ?? testRunId;
|
|
2997
|
+
const experimentUrl = response.experimentUrl ?? testRunUrl;
|
|
2998
|
+
if (resumedExperimentId === void 0 || experimentUrl === void 0) {
|
|
2999
|
+
throw new BitfabError(
|
|
3000
|
+
"Replay resume response is missing the experiment ID or URL."
|
|
3001
|
+
);
|
|
3002
|
+
}
|
|
3003
|
+
return {
|
|
3004
|
+
...rest,
|
|
3005
|
+
experimentId: resumedExperimentId,
|
|
3006
|
+
experimentUrl,
|
|
3007
|
+
work: rest.work ?? [],
|
|
3008
|
+
finished: rest.finished ?? [],
|
|
3009
|
+
skippedOriginalTraceIds: rest.skippedOriginalTraceIds ?? [],
|
|
3010
|
+
includeDbBranchLease: rest.includeDbBranchLease ?? false,
|
|
3011
|
+
dbBranchSettings: rest.dbBranchSettings ?? null
|
|
3012
|
+
};
|
|
3013
|
+
}
|
|
2970
3014
|
/**
|
|
2971
3015
|
* Fetch an external span by ID.
|
|
2972
3016
|
* Blocking GET request.
|
|
@@ -3583,6 +3627,291 @@ var init_integrationFixture = __esm({
|
|
|
3583
3627
|
}
|
|
3584
3628
|
});
|
|
3585
3629
|
|
|
3630
|
+
// src/selectiveReplay.ts
|
|
3631
|
+
function replayNodeKey(node) {
|
|
3632
|
+
return JSON.stringify([node.traceFunctionKey, node.spanName]);
|
|
3633
|
+
}
|
|
3634
|
+
function jsonFingerprint(value) {
|
|
3635
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3636
|
+
function encode(input) {
|
|
3637
|
+
if (input === null || typeof input === "boolean") {
|
|
3638
|
+
return JSON.stringify(input);
|
|
3639
|
+
}
|
|
3640
|
+
if (typeof input === "string" && !input.startsWith("<unserializable")) {
|
|
3641
|
+
return JSON.stringify(input);
|
|
3642
|
+
}
|
|
3643
|
+
if (typeof input === "number" && Number.isFinite(input) && !Object.is(input, -0)) {
|
|
3644
|
+
return JSON.stringify(input);
|
|
3645
|
+
}
|
|
3646
|
+
if (typeof input !== "object" || input === null || seen.has(input)) {
|
|
3647
|
+
throw new Error("Not a lossless JSON value");
|
|
3648
|
+
}
|
|
3649
|
+
seen.add(input);
|
|
3650
|
+
const prototype = Object.getPrototypeOf(input);
|
|
3651
|
+
if (prototype !== (Array.isArray(input) ? Array.prototype : Object.prototype)) {
|
|
3652
|
+
throw new Error("Not a plain JSON object");
|
|
3653
|
+
}
|
|
3654
|
+
if (Object.getOwnPropertySymbols(input).length > 0) {
|
|
3655
|
+
throw new Error("Symbol properties are not recorded");
|
|
3656
|
+
}
|
|
3657
|
+
const keys = Object.keys(input);
|
|
3658
|
+
if (Object.getOwnPropertyNames(input).length !== keys.length + (Array.isArray(input) ? 1 : 0)) {
|
|
3659
|
+
throw new Error("Non-enumerable properties are not recorded");
|
|
3660
|
+
}
|
|
3661
|
+
if (Array.isArray(input) && (keys.length !== input.length || keys.some((key, index) => key !== String(index)))) {
|
|
3662
|
+
throw new Error("Not a dense JSON array");
|
|
3663
|
+
}
|
|
3664
|
+
const properties = keys.map((key) => {
|
|
3665
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
3666
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
3667
|
+
throw new Error("Accessor properties are not replay inputs");
|
|
3668
|
+
}
|
|
3669
|
+
return `${Array.isArray(input) ? "" : `${JSON.stringify(key)}:`}${encode(descriptor.value)}`;
|
|
3670
|
+
});
|
|
3671
|
+
return Array.isArray(input) ? `[${properties.join(",")}]` : `{${properties.join(",")}}`;
|
|
3672
|
+
}
|
|
3673
|
+
try {
|
|
3674
|
+
return encode(value);
|
|
3675
|
+
} catch {
|
|
3676
|
+
return void 0;
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
var init_selectiveReplay = __esm({
|
|
3680
|
+
"src/selectiveReplay.ts"() {
|
|
3681
|
+
"use strict";
|
|
3682
|
+
}
|
|
3683
|
+
});
|
|
3684
|
+
|
|
3685
|
+
// src/selectiveReplayRuntime.ts
|
|
3686
|
+
function validateSelectiveReplayOptions(options) {
|
|
3687
|
+
const validNodes = (nodes) => Array.isArray(nodes) && nodes.length <= 100 && nodes.every(
|
|
3688
|
+
(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
|
|
3689
|
+
);
|
|
3690
|
+
if (!options || !validNodes(options.mustRun) || options.mustRun.length === 0 || Object.keys(options).some((key) => key !== "mustRun")) {
|
|
3691
|
+
throw new BitfabError(
|
|
3692
|
+
"experimentalSelectiveReplay accepts only 1\u2013100 mustRun with traceFunctionKey and spanName. Declare replayReusable on eligible spans, not reusableNodes in the replay request."
|
|
3693
|
+
);
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
function policyKey(options) {
|
|
3697
|
+
return JSON.stringify(options.mustRun.map(replayNodeKey).sort());
|
|
3698
|
+
}
|
|
3699
|
+
var SelectiveReplayRuntime;
|
|
3700
|
+
var init_selectiveReplayRuntime = __esm({
|
|
3701
|
+
"src/selectiveReplayRuntime.ts"() {
|
|
3702
|
+
"use strict";
|
|
3703
|
+
init_errors();
|
|
3704
|
+
init_selectiveReplay();
|
|
3705
|
+
SelectiveReplayRuntime = class {
|
|
3706
|
+
constructor(plan, options) {
|
|
3707
|
+
this.plan = plan;
|
|
3708
|
+
this.options = options;
|
|
3709
|
+
this.nodes = /* @__PURE__ */ new Map();
|
|
3710
|
+
this.children = /* @__PURE__ */ new Map();
|
|
3711
|
+
this.runtimeParents = /* @__PURE__ */ new Map();
|
|
3712
|
+
this.consumed = /* @__PURE__ */ new Map();
|
|
3713
|
+
this.requiredScopes = /* @__PURE__ */ new Set();
|
|
3714
|
+
this.recordings = /* @__PURE__ */ new Map();
|
|
3715
|
+
this.reached = /* @__PURE__ */ new Set();
|
|
3716
|
+
this.decisions = [];
|
|
3717
|
+
this.assertionNames = /* @__PURE__ */ new Set();
|
|
3718
|
+
this.assertionScopes = /* @__PURE__ */ new Set();
|
|
3719
|
+
this.observedNames = /* @__PURE__ */ new Map();
|
|
3720
|
+
this.unresolvedAssertions = [];
|
|
3721
|
+
validateSelectiveReplayOptions(options);
|
|
3722
|
+
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) {
|
|
3723
|
+
throw new BitfabError(
|
|
3724
|
+
"Server did not return a compatible selective replay plan. Update the server or disable experimentalSelectiveReplay."
|
|
3725
|
+
);
|
|
3726
|
+
}
|
|
3727
|
+
validateSelectiveReplayOptions(plan.options);
|
|
3728
|
+
if (policyKey(plan.options) !== policyKey(options)) {
|
|
3729
|
+
throw new BitfabError(
|
|
3730
|
+
"Selective replay plan does not match the requested mustRun nodes."
|
|
3731
|
+
);
|
|
3732
|
+
}
|
|
3733
|
+
this.required = new Set(options.mustRun.map(replayNodeKey));
|
|
3734
|
+
for (const node of plan.nodes) {
|
|
3735
|
+
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") {
|
|
3736
|
+
throw new BitfabError("Invalid selective replay node.");
|
|
3737
|
+
}
|
|
3738
|
+
this.nodes.set(node.id, node);
|
|
3739
|
+
if (node.recording !== void 0) {
|
|
3740
|
+
this.recordings.set(node.id, {
|
|
3741
|
+
input: node.recording.kwargs === void 0 ? jsonFingerprint(node.recording.input) : void 0,
|
|
3742
|
+
output: jsonFingerprint(node.recording.output)
|
|
3743
|
+
});
|
|
3744
|
+
}
|
|
3745
|
+
if (node.parentId !== null) {
|
|
3746
|
+
const siblings = this.children.get(node.parentId) ?? [];
|
|
3747
|
+
siblings.push(node);
|
|
3748
|
+
this.children.set(node.parentId, siblings);
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
const root = this.nodes.get(plan.rootId);
|
|
3752
|
+
if (!root || root.parentId !== null || !root.mustRun) {
|
|
3753
|
+
throw new BitfabError("Selective replay requires a live recorded root.");
|
|
3754
|
+
}
|
|
3755
|
+
const assertionIds = /* @__PURE__ */ new Set();
|
|
3756
|
+
let wholeTrace = false;
|
|
3757
|
+
for (const assertion of plan.assertions) {
|
|
3758
|
+
const target = assertion?.target;
|
|
3759
|
+
if (typeof assertion?.id !== "string" || assertion.id.length === 0 || assertionIds.has(assertion.id) || target !== null && (typeof target !== "object" || target.kind !== "output" && target.kind !== "span")) {
|
|
3760
|
+
throw new BitfabError(
|
|
3761
|
+
"Invalid selective replay assertion requirements."
|
|
3762
|
+
);
|
|
3763
|
+
}
|
|
3764
|
+
assertionIds.add(assertion.id);
|
|
3765
|
+
if (target === null) {
|
|
3766
|
+
wholeTrace = true;
|
|
3767
|
+
} else if (target.kind === "span") {
|
|
3768
|
+
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)) {
|
|
3769
|
+
throw new BitfabError("Invalid selective replay assertion target.");
|
|
3770
|
+
}
|
|
3771
|
+
this.assertionNames.add(target.name);
|
|
3772
|
+
const count = plan.nodes.filter(
|
|
3773
|
+
(node) => node.spanName === target.name
|
|
3774
|
+
).length;
|
|
3775
|
+
if (count <= (typeof target.occurrence === "number" ? target.occurrence : 0)) {
|
|
3776
|
+
this.unresolvedAssertions.push(assertion.id);
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
this.fullTraceForAssertions = wholeTrace || this.unresolvedAssertions.length > 0;
|
|
3781
|
+
const recordedKeys = new Set(plan.nodes.map(replayNodeKey));
|
|
3782
|
+
const unresolved = options.mustRun.filter(
|
|
3783
|
+
(node) => !recordedKeys.has(replayNodeKey(node))
|
|
3784
|
+
);
|
|
3785
|
+
if (JSON.stringify(unresolved.map(replayNodeKey).sort()) !== JSON.stringify(plan.unresolvedMustRun.map(replayNodeKey).sort())) {
|
|
3786
|
+
throw new BitfabError(
|
|
3787
|
+
"Selective replay plan has incomplete mustRun coverage."
|
|
3788
|
+
);
|
|
3789
|
+
}
|
|
3790
|
+
for (const node of plan.nodes) {
|
|
3791
|
+
const ancestors = /* @__PURE__ */ new Set([node.id]);
|
|
3792
|
+
let requiredByManifest = this.required.has(replayNodeKey(node));
|
|
3793
|
+
let requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(node.spanName);
|
|
3794
|
+
let parentId = node.parentId;
|
|
3795
|
+
while (parentId !== null) {
|
|
3796
|
+
const parent = this.nodes.get(parentId);
|
|
3797
|
+
if (!parent || ancestors.has(parentId) || node.mustRun && !parent.mustRun) {
|
|
3798
|
+
throw new BitfabError("Invalid selective replay ancestry.");
|
|
3799
|
+
}
|
|
3800
|
+
ancestors.add(parentId);
|
|
3801
|
+
requiredByManifest || (requiredByManifest = this.required.has(replayNodeKey(parent)));
|
|
3802
|
+
requiredForAssertion || (requiredForAssertion = this.assertionNames.has(parent.spanName));
|
|
3803
|
+
parentId = parent.parentId;
|
|
3804
|
+
}
|
|
3805
|
+
if (!ancestors.has(plan.rootId) || (requiredByManifest || unresolved.length > 0) && !node.mustRun) {
|
|
3806
|
+
throw new BitfabError("Selective replay plan could hide required code.");
|
|
3807
|
+
}
|
|
3808
|
+
if (requiredForAssertion && !node.mustRun) {
|
|
3809
|
+
throw new BitfabError(
|
|
3810
|
+
"Selective replay plan could hide assertion evidence."
|
|
3811
|
+
);
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
enter(params) {
|
|
3816
|
+
const identity = {
|
|
3817
|
+
traceFunctionKey: params.traceFunctionKey,
|
|
3818
|
+
spanName: params.spanName
|
|
3819
|
+
};
|
|
3820
|
+
const key = replayNodeKey(identity);
|
|
3821
|
+
const fingerprint = jsonFingerprint(params.inputs);
|
|
3822
|
+
let node;
|
|
3823
|
+
let matchReason = "unmatched-call";
|
|
3824
|
+
if (params.parentSpanId === null) {
|
|
3825
|
+
node = this.nodes.get(this.plan.rootId);
|
|
3826
|
+
} else {
|
|
3827
|
+
const recordedParent = this.runtimeParents.get(params.parentSpanId);
|
|
3828
|
+
const consumed = this.consumed.get(params.parentSpanId) ?? /* @__PURE__ */ new Set();
|
|
3829
|
+
this.consumed.set(params.parentSpanId, consumed);
|
|
3830
|
+
const candidates = recordedParent ? (this.children.get(recordedParent) ?? []).filter(
|
|
3831
|
+
(candidate) => replayNodeKey(candidate) === key && !consumed.has(candidate.id)
|
|
3832
|
+
) : [];
|
|
3833
|
+
const exact = fingerprint === void 0 ? [] : candidates.filter(
|
|
3834
|
+
(candidate) => this.recordings.get(candidate.id)?.input === fingerprint
|
|
3835
|
+
);
|
|
3836
|
+
node = exact.length === 1 ? exact[0] : exact.length === 0 && candidates.length === 1 ? candidates[0] : void 0;
|
|
3837
|
+
if (node) {
|
|
3838
|
+
consumed.add(node.id);
|
|
3839
|
+
} else if (candidates.length > 0) {
|
|
3840
|
+
matchReason = "ambiguous-call";
|
|
3841
|
+
for (const candidate of candidates) {
|
|
3842
|
+
consumed.add(candidate.id);
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
this.runtimeParents.set(params.spanId, node?.id ?? null);
|
|
3847
|
+
const requiredByManifest = this.required.has(key) || params.parentSpanId !== null && this.requiredScopes.has(params.parentSpanId);
|
|
3848
|
+
if (requiredByManifest) {
|
|
3849
|
+
this.requiredScopes.add(params.spanId);
|
|
3850
|
+
}
|
|
3851
|
+
const requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(params.spanName) || params.parentSpanId !== null && this.assertionScopes.has(params.parentSpanId);
|
|
3852
|
+
if (requiredForAssertion) {
|
|
3853
|
+
this.assertionScopes.add(params.spanId);
|
|
3854
|
+
}
|
|
3855
|
+
const mustRun = params.parentSpanId === null || requiredByManifest || node?.mustRun === true || requiredForAssertion || this.plan.unresolvedMustRun.length > 0;
|
|
3856
|
+
const recording = node === void 0 ? void 0 : this.recordings.get(node.id);
|
|
3857
|
+
const equalInputs = fingerprint !== void 0 && recording?.input === fingerprint;
|
|
3858
|
+
const reusable = recording?.output !== void 0 && equalInputs && params.canReuseOutput;
|
|
3859
|
+
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;
|
|
3860
|
+
const decision = (action, why) => {
|
|
3861
|
+
this.decisions.push({
|
|
3862
|
+
...identity,
|
|
3863
|
+
originalSpanId: node?.id ?? null,
|
|
3864
|
+
action,
|
|
3865
|
+
reason: why
|
|
3866
|
+
});
|
|
3867
|
+
};
|
|
3868
|
+
if (params.safetyMock && (mustRun || !reusable)) {
|
|
3869
|
+
decision("blocked", `safety-conflict:${reason}`);
|
|
3870
|
+
this.failure = new BitfabError(
|
|
3871
|
+
`Selective replay cannot execute or safely reuse safety-mocked node ${params.spanName} (${reason}).`
|
|
3872
|
+
);
|
|
3873
|
+
throw this.failure;
|
|
3874
|
+
}
|
|
3875
|
+
if (!mustRun && reusable && (params.safetyMock || node?.reusable === true && params.reuseAllowed)) {
|
|
3876
|
+
decision("mock", params.safetyMock ? "safety-mock" : "unchanged-inputs");
|
|
3877
|
+
return {
|
|
3878
|
+
mock: true,
|
|
3879
|
+
output: JSON.parse(recording.output)
|
|
3880
|
+
};
|
|
3881
|
+
}
|
|
3882
|
+
decision("run", reason);
|
|
3883
|
+
this.observedNames.set(
|
|
3884
|
+
params.spanName,
|
|
3885
|
+
(this.observedNames.get(params.spanName) ?? 0) + 1
|
|
3886
|
+
);
|
|
3887
|
+
if (this.required.has(key)) {
|
|
3888
|
+
this.reached.add(key);
|
|
3889
|
+
}
|
|
3890
|
+
return { mock: false };
|
|
3891
|
+
}
|
|
3892
|
+
assertNoSafetyConflict() {
|
|
3893
|
+
if (this.failure) {
|
|
3894
|
+
throw this.failure;
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
report() {
|
|
3898
|
+
return {
|
|
3899
|
+
decisions: this.decisions,
|
|
3900
|
+
mustRunNotReached: this.options.mustRun.filter(
|
|
3901
|
+
(node) => !this.reached.has(replayNodeKey(node))
|
|
3902
|
+
),
|
|
3903
|
+
unresolvedMustRun: this.plan.unresolvedMustRun,
|
|
3904
|
+
assertionIds: this.plan.assertions.map((assertion) => assertion.id),
|
|
3905
|
+
unresolvedAssertions: this.unresolvedAssertions,
|
|
3906
|
+
assertionTargetsNotReached: this.plan.assertions.flatMap(
|
|
3907
|
+
({ id, target }) => target?.kind === "span" && (this.observedNames.get(target.name) ?? 0) <= (typeof target.occurrence === "number" ? target.occurrence : 0) ? [id] : []
|
|
3908
|
+
)
|
|
3909
|
+
};
|
|
3910
|
+
}
|
|
3911
|
+
};
|
|
3912
|
+
}
|
|
3913
|
+
});
|
|
3914
|
+
|
|
3586
3915
|
// src/replay.ts
|
|
3587
3916
|
var replay_exports = {};
|
|
3588
3917
|
__export(replay_exports, {
|
|
@@ -3602,6 +3931,88 @@ function expandReplayWork(serverItems, attempts) {
|
|
|
3602
3931
|
(_, attempt) => serverItems.map((serverItem) => ({ serverItem, attempt }))
|
|
3603
3932
|
).flat();
|
|
3604
3933
|
}
|
|
3934
|
+
function validateResume(resume, options) {
|
|
3935
|
+
if (resume.trim().length === 0) {
|
|
3936
|
+
throw new BitfabError("resume must be the ID of the experiment to resume.");
|
|
3937
|
+
}
|
|
3938
|
+
const conflicting = RESUME_CONFLICTING_OPTIONS.filter(
|
|
3939
|
+
(name) => options[name] !== void 0 && options[name] !== false
|
|
3940
|
+
);
|
|
3941
|
+
if (conflicting.length > 0) {
|
|
3942
|
+
throw new BitfabError(
|
|
3943
|
+
`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.`
|
|
3944
|
+
);
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
async function resumeReplaySession(httpClient, experimentId, traceFunctionKey, codeChangeFiles, force, requestedDbBranch) {
|
|
3948
|
+
const response = await httpClient.resumeReplay(
|
|
3949
|
+
experimentId,
|
|
3950
|
+
traceFunctionKey,
|
|
3951
|
+
codeChangeFiles,
|
|
3952
|
+
force
|
|
3953
|
+
);
|
|
3954
|
+
const work = response.work.map(({ itemIndex, attempt }) => {
|
|
3955
|
+
const serverItem = response.items[itemIndex];
|
|
3956
|
+
if (serverItem === void 0) {
|
|
3957
|
+
throw new BitfabError(
|
|
3958
|
+
`Replay resume for experiment ${response.experimentId} named item ${itemIndex}, but the server returned only ${response.items.length} item(s).`
|
|
3959
|
+
);
|
|
3960
|
+
}
|
|
3961
|
+
return { serverItem, attempt };
|
|
3962
|
+
});
|
|
3963
|
+
if (response.skippedOriginalTraceIds.length > 0) {
|
|
3964
|
+
try {
|
|
3965
|
+
console.warn(
|
|
3966
|
+
`Bitfab: ${response.skippedOriginalTraceIds.length} trace(s) selected when experiment ${response.experimentId} started no longer exist, so resume skips them: ${response.skippedOriginalTraceIds.join(", ")}`
|
|
3967
|
+
);
|
|
3968
|
+
} catch {
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
if (requestedDbBranch !== void 0 && dbBranchEnabled(requestedDbBranch) !== response.includeDbBranchLease) {
|
|
3972
|
+
try {
|
|
3973
|
+
console.warn(
|
|
3974
|
+
`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.`
|
|
3975
|
+
);
|
|
3976
|
+
} catch {
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
return {
|
|
3980
|
+
experimentId: response.experimentId,
|
|
3981
|
+
experimentUrl: response.experimentUrl,
|
|
3982
|
+
attempts: response.attempts,
|
|
3983
|
+
work,
|
|
3984
|
+
finished: response.finished,
|
|
3985
|
+
includeDbBranchLease: response.includeDbBranchLease,
|
|
3986
|
+
dbBranchSettings: response.dbBranchSettings ?? void 0
|
|
3987
|
+
};
|
|
3988
|
+
}
|
|
3989
|
+
function carriedOverReplayItem(finished) {
|
|
3990
|
+
return {
|
|
3991
|
+
traceId: finished.traceId,
|
|
3992
|
+
originalTraceId: finished.originalTraceId,
|
|
3993
|
+
originalSpanId: "",
|
|
3994
|
+
sourceTraceId: finished.originalTraceId,
|
|
3995
|
+
sourceSpanId: "",
|
|
3996
|
+
attempt: finished.attempt,
|
|
3997
|
+
input: [],
|
|
3998
|
+
result: void 0,
|
|
3999
|
+
originalOutput: void 0,
|
|
4000
|
+
error: finished.hasError ? CARRIED_OVER_ERROR : null,
|
|
4001
|
+
traceError: null,
|
|
4002
|
+
replayError: null,
|
|
4003
|
+
durationMs: null,
|
|
4004
|
+
originalDurationMs: null,
|
|
4005
|
+
originalTokens: null,
|
|
4006
|
+
originalModel: null,
|
|
4007
|
+
tokens: null,
|
|
4008
|
+
model: null,
|
|
4009
|
+
dbSnapshotRef: null,
|
|
4010
|
+
dbBranchTimings: null,
|
|
4011
|
+
traceOutline: null,
|
|
4012
|
+
originalTraceOutline: null,
|
|
4013
|
+
carriedOver: true
|
|
4014
|
+
};
|
|
4015
|
+
}
|
|
3605
4016
|
function resolveAttempts(attempts) {
|
|
3606
4017
|
if (attempts === void 0) {
|
|
3607
4018
|
return 1;
|
|
@@ -3756,7 +4167,7 @@ function buildMockTree(rootNode) {
|
|
|
3756
4167
|
}
|
|
3757
4168
|
return { spans };
|
|
3758
4169
|
}
|
|
3759
|
-
async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
|
|
4170
|
+
async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun, selectiveOptions) {
|
|
3760
4171
|
let lease = includeDbBranchLease ? serverItem.dbBranchLease : void 0;
|
|
3761
4172
|
let leaseError = includeDbBranchLease ? serverItem.dbBranchLeaseError : void 0;
|
|
3762
4173
|
let dbSnapshotRef = serverItem.dbSnapshotRef;
|
|
@@ -3769,6 +4180,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3769
4180
|
let replayError = null;
|
|
3770
4181
|
let replayDurationMs = null;
|
|
3771
4182
|
let replayStarted = null;
|
|
4183
|
+
let selectiveReplay;
|
|
3772
4184
|
const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
|
|
3773
4185
|
const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
|
|
3774
4186
|
try {
|
|
@@ -3801,6 +4213,17 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3801
4213
|
originalTraceId
|
|
3802
4214
|
);
|
|
3803
4215
|
}
|
|
4216
|
+
if (selectiveOptions !== void 0) {
|
|
4217
|
+
if (serverItem.selectiveReplayPlan === void 0) {
|
|
4218
|
+
throw new BitfabError(
|
|
4219
|
+
"Server did not return a selective replay plan. Update the server or disable experimentalSelectiveReplay."
|
|
4220
|
+
);
|
|
4221
|
+
}
|
|
4222
|
+
selectiveReplay = new SelectiveReplayRuntime(
|
|
4223
|
+
serverItem.selectiveReplayPlan,
|
|
4224
|
+
selectiveOptions
|
|
4225
|
+
);
|
|
4226
|
+
}
|
|
3804
4227
|
const span = await httpClient.getExternalSpan(originalSpanId, {
|
|
3805
4228
|
view: "replay"
|
|
3806
4229
|
});
|
|
@@ -3820,7 +4243,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3820
4243
|
});
|
|
3821
4244
|
}
|
|
3822
4245
|
const hasOverrides = resolvedOverrides.length > 0;
|
|
3823
|
-
const needTree = mockStrategy === "all" || mockStrategy === "marked" || hasOverrides;
|
|
4246
|
+
const needTree = selectiveReplay === void 0 && (mockStrategy === "all" || mockStrategy === "marked" || hasOverrides);
|
|
3824
4247
|
const includeOutputs = mockStrategy === "all";
|
|
3825
4248
|
let mockTree;
|
|
3826
4249
|
if (needTree) {
|
|
@@ -3895,6 +4318,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3895
4318
|
sourceBitfabTraceId: originalTraceId,
|
|
3896
4319
|
replayAttempt: attempt,
|
|
3897
4320
|
mockTree,
|
|
4321
|
+
selectiveReplay,
|
|
3898
4322
|
callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
|
|
3899
4323
|
mockStrategy,
|
|
3900
4324
|
mockOverrides: hasOverrides ? resolvedOverrides : void 0,
|
|
@@ -3906,6 +4330,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3906
4330
|
() => fn(...inputs)
|
|
3907
4331
|
);
|
|
3908
4332
|
result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
|
|
4333
|
+
selectiveReplay?.assertNoSafetyConflict();
|
|
3909
4334
|
replayDurationMs = Math.round(performance.now() - replayStarted);
|
|
3910
4335
|
} catch (e) {
|
|
3911
4336
|
if (replayStarted !== null) {
|
|
@@ -3957,6 +4382,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3957
4382
|
traceError,
|
|
3958
4383
|
replayError,
|
|
3959
4384
|
durationMs: replayDurationMs,
|
|
4385
|
+
...selectiveReplay === void 0 ? {} : { selectiveReplay: selectiveReplay.report() },
|
|
3960
4386
|
originalDurationMs,
|
|
3961
4387
|
originalTokens: serverItem.originalTokens ?? serverItem.tokens ?? null,
|
|
3962
4388
|
originalModel,
|
|
@@ -4054,6 +4480,21 @@ async function mapWithConcurrency2(tasks, maxConcurrency, onSettled, onStarted)
|
|
|
4054
4480
|
return results;
|
|
4055
4481
|
}
|
|
4056
4482
|
async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, registeredOverrides = []) {
|
|
4483
|
+
if (options?.resume !== void 0) {
|
|
4484
|
+
validateResume(options.resume, options);
|
|
4485
|
+
} else if (options?.force === true) {
|
|
4486
|
+
throw new BitfabError(
|
|
4487
|
+
"force only applies when resuming an experiment. Pass it together with resume."
|
|
4488
|
+
);
|
|
4489
|
+
}
|
|
4490
|
+
if (options?.experimentalSelectiveReplay !== void 0) {
|
|
4491
|
+
validateSelectiveReplayOptions(options.experimentalSelectiveReplay);
|
|
4492
|
+
if ((options.mock ?? "marked") !== "marked" || normalizeMockOverrides(options.mockOverride).length > 0 || registeredOverrides.length > 0) {
|
|
4493
|
+
throw new BitfabError(
|
|
4494
|
+
"experimentalSelectiveReplay requires mock: 'marked' and cannot be combined with mock overrides."
|
|
4495
|
+
);
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4057
4498
|
if (options?.traceIds !== void 0) {
|
|
4058
4499
|
if (options.traceIds.length === 0) {
|
|
4059
4500
|
throw new BitfabError("traceIds must contain at least one trace ID.");
|
|
@@ -4073,7 +4514,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4073
4514
|
} catch {
|
|
4074
4515
|
}
|
|
4075
4516
|
}
|
|
4076
|
-
const
|
|
4517
|
+
const requestedAttempts = resolveAttempts(options?.attempts);
|
|
4077
4518
|
const maxConcurrency = options?.maxConcurrency ?? 10;
|
|
4078
4519
|
if (!Number.isInteger(maxConcurrency) || maxConcurrency < 1) {
|
|
4079
4520
|
throw new BitfabError(
|
|
@@ -4092,40 +4533,60 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4092
4533
|
}
|
|
4093
4534
|
}
|
|
4094
4535
|
}
|
|
4095
|
-
const
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4536
|
+
const includeDbBranchLease = dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true;
|
|
4537
|
+
let session;
|
|
4538
|
+
if (options?.resume === void 0) {
|
|
4539
|
+
const started2 = await httpClient.startReplay(
|
|
4540
|
+
traceFunctionKey,
|
|
4541
|
+
// limit is meaningless with explicit traceIds (the ID list determines
|
|
4542
|
+
// the count), so it's omitted from the request entirely.
|
|
4543
|
+
options?.traceIds ? void 0 : options?.limit ?? 5,
|
|
4544
|
+
options?.traceIds,
|
|
4545
|
+
options?.name,
|
|
4546
|
+
codeChangeDescription,
|
|
4547
|
+
codeChangeFiles,
|
|
4548
|
+
// A dry run executes nothing, so a branch would be provisioned (and billed)
|
|
4549
|
+
// for code that never runs, and a seeded source's refusal would fail the item
|
|
4550
|
+
// before it could report its resolved inputs.
|
|
4551
|
+
includeDbBranchLease,
|
|
4552
|
+
options?.experimentGroupId,
|
|
4553
|
+
datasetIds,
|
|
4554
|
+
options?.graderIds,
|
|
4555
|
+
resolveDbBranchSettings(options?.dbBranch),
|
|
4556
|
+
requestedAttempts,
|
|
4557
|
+
options?.adaptInputs !== void 0,
|
|
4558
|
+
options?.onlyWithAssertions,
|
|
4559
|
+
options?.judgeAssertions,
|
|
4560
|
+
options?.experimentalSelectiveReplay
|
|
4561
|
+
);
|
|
4562
|
+
if (started2.items.length === 0) {
|
|
4563
|
+
try {
|
|
4564
|
+
console.warn(
|
|
4565
|
+
`Bitfab: no traces matched "${traceFunctionKey}", so this replay ran nothing. Capture a trace, or seed one with seedTrace, before replaying.`
|
|
4566
|
+
);
|
|
4567
|
+
} catch {
|
|
4568
|
+
}
|
|
4127
4569
|
}
|
|
4570
|
+
session = {
|
|
4571
|
+
experimentId: started2.experimentId,
|
|
4572
|
+
experimentUrl: started2.experimentUrl,
|
|
4573
|
+
attempts: requestedAttempts,
|
|
4574
|
+
work: expandReplayWork(started2.items, requestedAttempts),
|
|
4575
|
+
finished: [],
|
|
4576
|
+
includeDbBranchLease,
|
|
4577
|
+
dbBranchSettings: resolveDbBranchSettings(options?.dbBranch)
|
|
4578
|
+
};
|
|
4579
|
+
} else {
|
|
4580
|
+
session = await resumeReplaySession(
|
|
4581
|
+
httpClient,
|
|
4582
|
+
options.resume,
|
|
4583
|
+
traceFunctionKey,
|
|
4584
|
+
codeChangeFiles,
|
|
4585
|
+
options.force,
|
|
4586
|
+
options.dbBranch
|
|
4587
|
+
);
|
|
4128
4588
|
}
|
|
4589
|
+
const { experimentId, experimentUrl, attempts } = session;
|
|
4129
4590
|
const mockStrategy = options?.mock ?? "marked";
|
|
4130
4591
|
const fullExperimentUrl = `${serviceUrl}${experimentUrl}`;
|
|
4131
4592
|
try {
|
|
@@ -4139,7 +4600,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4139
4600
|
...normalizeMockOverrides(options?.mockOverride),
|
|
4140
4601
|
...registeredOverrides
|
|
4141
4602
|
];
|
|
4142
|
-
const workItems =
|
|
4603
|
+
const workItems = session.work;
|
|
4604
|
+
const carriedOverItems = session.finished.map(
|
|
4605
|
+
(finished) => carriedOverReplayItem(finished)
|
|
4606
|
+
);
|
|
4143
4607
|
const replayedTraceIds = workItems.map(() => randomUuid());
|
|
4144
4608
|
httpClient.trackTraceDeliveries(replayedTraceIds);
|
|
4145
4609
|
const tasks = workItems.map(
|
|
@@ -4152,18 +4616,19 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4152
4616
|
resolvedOverrides,
|
|
4153
4617
|
replayedTraceIds[index],
|
|
4154
4618
|
attempt,
|
|
4155
|
-
|
|
4156
|
-
|
|
4619
|
+
session.includeDbBranchLease,
|
|
4620
|
+
session.dbBranchSettings,
|
|
4157
4621
|
options?.adaptInputs,
|
|
4158
|
-
options?.dryRun === true
|
|
4622
|
+
options?.dryRun === true,
|
|
4623
|
+
options?.experimentalSelectiveReplay
|
|
4159
4624
|
)
|
|
4160
4625
|
);
|
|
4161
|
-
const total = tasks.length;
|
|
4626
|
+
const total = tasks.length + carriedOverItems.length;
|
|
4162
4627
|
const onItemFinish = options?.onItemFinish ?? options?.onProgress;
|
|
4163
|
-
let completed =
|
|
4164
|
-
let started =
|
|
4165
|
-
let
|
|
4166
|
-
let
|
|
4628
|
+
let completed = carriedOverItems.length;
|
|
4629
|
+
let started = carriedOverItems.length;
|
|
4630
|
+
let errored = carriedOverItems.filter((item) => item.error !== null).length;
|
|
4631
|
+
let succeeded = completed - errored;
|
|
4167
4632
|
let flushInFlight = null;
|
|
4168
4633
|
let flushPending = false;
|
|
4169
4634
|
const flushFinishedItemTrace = () => {
|
|
@@ -4183,7 +4648,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4183
4648
|
}
|
|
4184
4649
|
return flushInFlight;
|
|
4185
4650
|
};
|
|
4186
|
-
const
|
|
4651
|
+
const ranItems = await mapWithConcurrency2(
|
|
4187
4652
|
tasks,
|
|
4188
4653
|
maxConcurrency,
|
|
4189
4654
|
async (item, index) => {
|
|
@@ -4228,6 +4693,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4228
4693
|
traceError: item.traceError,
|
|
4229
4694
|
replayError: item.replayError,
|
|
4230
4695
|
durationMs: item.durationMs,
|
|
4696
|
+
...item.selectiveReplay === void 0 ? {} : { selectiveReplay: item.selectiveReplay },
|
|
4231
4697
|
originalDurationMs: item.originalDurationMs,
|
|
4232
4698
|
originalTokens: item.originalTokens,
|
|
4233
4699
|
originalModel: item.originalModel,
|
|
@@ -4270,6 +4736,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4270
4736
|
}
|
|
4271
4737
|
} : void 0
|
|
4272
4738
|
);
|
|
4739
|
+
const resultItems = [...carriedOverItems, ...ranItems];
|
|
4273
4740
|
if (options?.dryRun === true) {
|
|
4274
4741
|
await httpClient.completeReplay(experimentId).catch(() => void 0);
|
|
4275
4742
|
const dryResult = {
|
|
@@ -4289,11 +4756,11 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4289
4756
|
experimentId,
|
|
4290
4757
|
fullExperimentUrl
|
|
4291
4758
|
);
|
|
4292
|
-
for (let index = 0; index <
|
|
4759
|
+
for (let index = 0; index < ranItems.length; index += 1) {
|
|
4293
4760
|
const localId = replayedTraceIds[index];
|
|
4294
4761
|
const readBack = localId ? deliveredTraceIds[localId] : void 0;
|
|
4295
4762
|
if (readBack !== void 0) {
|
|
4296
|
-
|
|
4763
|
+
ranItems[index].traceId = readBack;
|
|
4297
4764
|
}
|
|
4298
4765
|
}
|
|
4299
4766
|
const completeResult = await preserveReplayFailure(
|
|
@@ -4307,8 +4774,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4307
4774
|
if (serverTraceIds !== void 0) {
|
|
4308
4775
|
const missing = [];
|
|
4309
4776
|
let completedCount = 0;
|
|
4310
|
-
for (let index = 0; index <
|
|
4311
|
-
const item =
|
|
4777
|
+
for (let index = 0; index < ranItems.length; index += 1) {
|
|
4778
|
+
const item = ranItems[index];
|
|
4312
4779
|
const localId = replayedTraceIds[index];
|
|
4313
4780
|
const mapped = localId ? serverTraceIds[localId] : void 0;
|
|
4314
4781
|
item.traceId = item.traceId ?? mapped ?? null;
|
|
@@ -4394,7 +4861,7 @@ async function writeReplayResultFile(result) {
|
|
|
4394
4861
|
}
|
|
4395
4862
|
}
|
|
4396
4863
|
}
|
|
4397
|
-
var REPLAY_PERSISTENCE_TIMEOUT_MS, MAX_REPLAY_ATTEMPTS, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
|
|
4864
|
+
var REPLAY_PERSISTENCE_TIMEOUT_MS, MAX_REPLAY_ATTEMPTS, RESUME_CONFLICTING_OPTIONS, CARRIED_OVER_ERROR, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
|
|
4398
4865
|
var init_replay = __esm({
|
|
4399
4866
|
"src/replay.ts"() {
|
|
4400
4867
|
"use strict";
|
|
@@ -4405,9 +4872,22 @@ var init_replay = __esm({
|
|
|
4405
4872
|
init_mockOverride();
|
|
4406
4873
|
init_randomUuid();
|
|
4407
4874
|
init_replayContext();
|
|
4875
|
+
init_selectiveReplayRuntime();
|
|
4408
4876
|
init_serialize();
|
|
4409
4877
|
REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
4410
4878
|
MAX_REPLAY_ATTEMPTS = 100;
|
|
4879
|
+
RESUME_CONFLICTING_OPTIONS = [
|
|
4880
|
+
"traceIds",
|
|
4881
|
+
"limit",
|
|
4882
|
+
"datasetId",
|
|
4883
|
+
"datasetIds",
|
|
4884
|
+
"attempts",
|
|
4885
|
+
"graderIds",
|
|
4886
|
+
"onlyWithAssertions",
|
|
4887
|
+
"dryRun",
|
|
4888
|
+
"experimentalSelectiveReplay"
|
|
4889
|
+
];
|
|
4890
|
+
CARRIED_OVER_ERROR = "This item errored in an earlier run of this experiment, before it was resumed.";
|
|
4411
4891
|
BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
|
|
4412
4892
|
ReplayError = class extends BitfabError {
|
|
4413
4893
|
constructor(message, items, experimentId, experimentUrl, cause) {
|
|
@@ -9106,6 +9586,9 @@ var Bitfab = class {
|
|
|
9106
9586
|
...options.mockOnReplay !== void 0 && {
|
|
9107
9587
|
mockOnReplay: options.mockOnReplay
|
|
9108
9588
|
},
|
|
9589
|
+
...options.replayReusable !== void 0 && {
|
|
9590
|
+
replayReusable: options.replayReusable
|
|
9591
|
+
},
|
|
9109
9592
|
...options.finalize !== void 0 && { finalize: options.finalize }
|
|
9110
9593
|
};
|
|
9111
9594
|
}
|
|
@@ -9254,6 +9737,9 @@ var Bitfab = class {
|
|
|
9254
9737
|
...mockOnReplay !== void 0 && {
|
|
9255
9738
|
mockOnReplay
|
|
9256
9739
|
},
|
|
9740
|
+
...nodeConfiguration?.replayReusable !== void 0 && {
|
|
9741
|
+
replayReusable: nodeConfiguration.replayReusable
|
|
9742
|
+
},
|
|
9257
9743
|
...nodeConfiguration?.finalize !== void 0 && {
|
|
9258
9744
|
finalize: nodeConfiguration.finalize
|
|
9259
9745
|
}
|
|
@@ -10051,6 +10537,7 @@ var Bitfab = class {
|
|
|
10051
10537
|
instrumentation: options.instrumentation ?? "span",
|
|
10052
10538
|
functionId: options.functionId,
|
|
10053
10539
|
captureContent: options.captureContent ?? true,
|
|
10540
|
+
replayReusable: options.replayReusable,
|
|
10054
10541
|
autoTraceDefinition: options.autoTraceDefinition,
|
|
10055
10542
|
declaredNode: options.declaredNode === true,
|
|
10056
10543
|
nestedTrace: options.nestedTrace,
|
|
@@ -10170,6 +10657,27 @@ var Bitfab = class {
|
|
|
10170
10657
|
return result;
|
|
10171
10658
|
};
|
|
10172
10659
|
const replayCtxForMock = getReplayContext();
|
|
10660
|
+
if (replayCtxForMock?.selectiveReplay) {
|
|
10661
|
+
const decision = replayCtxForMock.selectiveReplay.enter({
|
|
10662
|
+
traceFunctionKey,
|
|
10663
|
+
spanName: baseSpanParams.spanName ?? traceFunctionKey,
|
|
10664
|
+
spanId,
|
|
10665
|
+
parentSpanId,
|
|
10666
|
+
inputs: args,
|
|
10667
|
+
safetyMock: !isRootSpan && options.mockOnReplay === true,
|
|
10668
|
+
reuseAllowed: options.replayReusable === true,
|
|
10669
|
+
canReuseOutput: options.finalize === void 0 && options.replayOutput === void 0 && !fn.constructor.name.includes("Generator")
|
|
10670
|
+
});
|
|
10671
|
+
if (decision.mock) {
|
|
10672
|
+
void sendSpan({
|
|
10673
|
+
result: decision.output,
|
|
10674
|
+
mocked: true,
|
|
10675
|
+
mockTarget: "output",
|
|
10676
|
+
mockSource: "recorded"
|
|
10677
|
+
});
|
|
10678
|
+
return fnReturnsPromise ? Promise.resolve(decision.output) : decision.output;
|
|
10679
|
+
}
|
|
10680
|
+
}
|
|
10173
10681
|
if (replayCtxForMock?.mockTree && !isRootSpan) {
|
|
10174
10682
|
const counters = replayCtxForMock.callCounters;
|
|
10175
10683
|
const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
|
|
@@ -10593,6 +11101,9 @@ var Bitfab = class {
|
|
|
10593
11101
|
span_origin: makeSpanOrigin(params.instrumentation),
|
|
10594
11102
|
span_data: {
|
|
10595
11103
|
name: params.spanName,
|
|
11104
|
+
...params.replayReusable !== void 0 && {
|
|
11105
|
+
replay_reusable: params.replayReusable
|
|
11106
|
+
},
|
|
10596
11107
|
type: params.spanType,
|
|
10597
11108
|
...params.functionId !== void 0 && {
|
|
10598
11109
|
function_id: params.functionId,
|