@bitfab/sdk 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-RC7KHIHD.js → chunk-34UFYAE6.js} +473 -51
- package/dist/chunk-34UFYAE6.js.map +1 -0
- package/dist/{chunk-MODZ4MFI.js → chunk-65QKCHI2.js} +47 -3
- package/dist/chunk-65QKCHI2.js.map +1 -0
- package/dist/{chunk-SVTA5IYD.js → chunk-7BU5LUMR.js} +46 -3
- package/dist/chunk-7BU5LUMR.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-G6MTQ6EP.js → chunk-OWXP3YXA.js} +122 -52
- package/dist/chunk-OWXP3YXA.js.map +1 -0
- package/dist/{chunk-KVKPBKUD.js → chunk-TZSTKVZF.js} +38 -7
- package/dist/chunk-TZSTKVZF.js.map +1 -0
- package/dist/{http-VORTCQPP.js → http-VTIALK4P.js} +2 -2
- package/dist/{http-363LXXP7.js → http-ZWENQXQA.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-IM2AEZRZ.js → replay-YQG3OGDN.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-G6MTQ6EP.js.map +0 -1
- package/dist/chunk-KVKPBKUD.js.map +0 -1
- package/dist/chunk-MODZ4MFI.js.map +0 -1
- package/dist/chunk-RC7KHIHD.js.map +0 -1
- package/dist/chunk-SVTA5IYD.js.map +0 -1
- /package/dist/{http-363LXXP7.js.map → http-VTIALK4P.js.map} +0 -0
- /package/dist/{http-VORTCQPP.js.map → http-ZWENQXQA.js.map} +0 -0
- /package/dist/{replay-IM2AEZRZ.js.map → replay-YQG3OGDN.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -42,7 +42,7 @@ var __version__, __packageName__;
|
|
|
42
42
|
var init_version_generated = __esm({
|
|
43
43
|
"src/version.generated.ts"() {
|
|
44
44
|
"use strict";
|
|
45
|
-
__version__ = "0.61.
|
|
45
|
+
__version__ = "0.61.3";
|
|
46
46
|
__packageName__ = "@bitfab/sdk";
|
|
47
47
|
}
|
|
48
48
|
});
|
|
@@ -1068,6 +1068,7 @@ var init_simulationPlan = __esm({
|
|
|
1068
1068
|
"input_meta",
|
|
1069
1069
|
"output",
|
|
1070
1070
|
"output_meta",
|
|
1071
|
+
"replay_recording",
|
|
1071
1072
|
"prompt"
|
|
1072
1073
|
];
|
|
1073
1074
|
SimulationPlan = class {
|
|
@@ -2882,8 +2883,11 @@ var init_http = __esm({
|
|
|
2882
2883
|
* Start a replay session by fetching historical traces.
|
|
2883
2884
|
* Blocking call - creates an experiment and returns lightweight item references.
|
|
2884
2885
|
*/
|
|
2885
|
-
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions) {
|
|
2886
|
+
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions, experimentalSelectiveReplay) {
|
|
2886
2887
|
const payload = { traceFunctionKey };
|
|
2888
|
+
if (experimentalSelectiveReplay !== void 0) {
|
|
2889
|
+
payload.experimentalSelectiveReplay = experimentalSelectiveReplay;
|
|
2890
|
+
}
|
|
2887
2891
|
if (limit !== void 0) {
|
|
2888
2892
|
payload.limit = limit;
|
|
2889
2893
|
}
|
|
@@ -2951,6 +2955,46 @@ var init_http = __esm({
|
|
|
2951
2955
|
}
|
|
2952
2956
|
return { ...rest, experimentId, experimentUrl };
|
|
2953
2957
|
}
|
|
2958
|
+
async resumeReplay(experimentId, traceFunctionKey, codeChangeFiles, force) {
|
|
2959
|
+
const payload = {
|
|
2960
|
+
experimentId,
|
|
2961
|
+
traceFunctionKey,
|
|
2962
|
+
lazyDbBranchLease: true
|
|
2963
|
+
};
|
|
2964
|
+
if (force === true) {
|
|
2965
|
+
payload.force = true;
|
|
2966
|
+
}
|
|
2967
|
+
if (codeChangeFiles !== void 0) {
|
|
2968
|
+
payload.codeChangeFiles = codeChangeFiles;
|
|
2969
|
+
}
|
|
2970
|
+
const git = await resolvedGitState();
|
|
2971
|
+
if (git && !isEmptyGitState(git)) {
|
|
2972
|
+
payload.git = git;
|
|
2973
|
+
}
|
|
2974
|
+
const response = await this.request(
|
|
2975
|
+
"/api/sdk/replay/resume",
|
|
2976
|
+
payload,
|
|
2977
|
+
{ timeout: REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS }
|
|
2978
|
+
);
|
|
2979
|
+
const { testRunId, testRunUrl, ...rest } = response;
|
|
2980
|
+
const resumedExperimentId = response.experimentId ?? testRunId;
|
|
2981
|
+
const experimentUrl = response.experimentUrl ?? testRunUrl;
|
|
2982
|
+
if (resumedExperimentId === void 0 || experimentUrl === void 0) {
|
|
2983
|
+
throw new BitfabError(
|
|
2984
|
+
"Replay resume response is missing the experiment ID or URL."
|
|
2985
|
+
);
|
|
2986
|
+
}
|
|
2987
|
+
return {
|
|
2988
|
+
...rest,
|
|
2989
|
+
experimentId: resumedExperimentId,
|
|
2990
|
+
experimentUrl,
|
|
2991
|
+
work: rest.work ?? [],
|
|
2992
|
+
finished: rest.finished ?? [],
|
|
2993
|
+
skippedOriginalTraceIds: rest.skippedOriginalTraceIds ?? [],
|
|
2994
|
+
includeDbBranchLease: rest.includeDbBranchLease ?? false,
|
|
2995
|
+
dbBranchSettings: rest.dbBranchSettings ?? null
|
|
2996
|
+
};
|
|
2997
|
+
}
|
|
2954
2998
|
/**
|
|
2955
2999
|
* Fetch an external span by ID.
|
|
2956
3000
|
* Blocking GET request.
|
|
@@ -3567,6 +3611,291 @@ var init_integrationFixture = __esm({
|
|
|
3567
3611
|
}
|
|
3568
3612
|
});
|
|
3569
3613
|
|
|
3614
|
+
// src/selectiveReplay.ts
|
|
3615
|
+
function replayNodeKey(node) {
|
|
3616
|
+
return JSON.stringify([node.traceFunctionKey, node.spanName]);
|
|
3617
|
+
}
|
|
3618
|
+
function jsonFingerprint(value) {
|
|
3619
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3620
|
+
function encode(input) {
|
|
3621
|
+
if (input === null || typeof input === "boolean") {
|
|
3622
|
+
return JSON.stringify(input);
|
|
3623
|
+
}
|
|
3624
|
+
if (typeof input === "string" && !input.startsWith("<unserializable")) {
|
|
3625
|
+
return JSON.stringify(input);
|
|
3626
|
+
}
|
|
3627
|
+
if (typeof input === "number" && Number.isFinite(input) && !Object.is(input, -0)) {
|
|
3628
|
+
return JSON.stringify(input);
|
|
3629
|
+
}
|
|
3630
|
+
if (typeof input !== "object" || input === null || seen.has(input)) {
|
|
3631
|
+
throw new Error("Not a lossless JSON value");
|
|
3632
|
+
}
|
|
3633
|
+
seen.add(input);
|
|
3634
|
+
const prototype = Object.getPrototypeOf(input);
|
|
3635
|
+
if (prototype !== (Array.isArray(input) ? Array.prototype : Object.prototype)) {
|
|
3636
|
+
throw new Error("Not a plain JSON object");
|
|
3637
|
+
}
|
|
3638
|
+
if (Object.getOwnPropertySymbols(input).length > 0) {
|
|
3639
|
+
throw new Error("Symbol properties are not recorded");
|
|
3640
|
+
}
|
|
3641
|
+
const keys = Object.keys(input);
|
|
3642
|
+
if (Object.getOwnPropertyNames(input).length !== keys.length + (Array.isArray(input) ? 1 : 0)) {
|
|
3643
|
+
throw new Error("Non-enumerable properties are not recorded");
|
|
3644
|
+
}
|
|
3645
|
+
if (Array.isArray(input) && (keys.length !== input.length || keys.some((key, index) => key !== String(index)))) {
|
|
3646
|
+
throw new Error("Not a dense JSON array");
|
|
3647
|
+
}
|
|
3648
|
+
const properties = keys.map((key) => {
|
|
3649
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
3650
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
3651
|
+
throw new Error("Accessor properties are not replay inputs");
|
|
3652
|
+
}
|
|
3653
|
+
return `${Array.isArray(input) ? "" : `${JSON.stringify(key)}:`}${encode(descriptor.value)}`;
|
|
3654
|
+
});
|
|
3655
|
+
return Array.isArray(input) ? `[${properties.join(",")}]` : `{${properties.join(",")}}`;
|
|
3656
|
+
}
|
|
3657
|
+
try {
|
|
3658
|
+
return encode(value);
|
|
3659
|
+
} catch {
|
|
3660
|
+
return void 0;
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
var init_selectiveReplay = __esm({
|
|
3664
|
+
"src/selectiveReplay.ts"() {
|
|
3665
|
+
"use strict";
|
|
3666
|
+
}
|
|
3667
|
+
});
|
|
3668
|
+
|
|
3669
|
+
// src/selectiveReplayRuntime.ts
|
|
3670
|
+
function validateSelectiveReplayOptions(options) {
|
|
3671
|
+
const validNodes = (nodes) => Array.isArray(nodes) && nodes.length <= 100 && nodes.every(
|
|
3672
|
+
(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
|
|
3673
|
+
);
|
|
3674
|
+
if (!options || !validNodes(options.mustRun) || options.mustRun.length === 0 || Object.keys(options).some((key) => key !== "mustRun")) {
|
|
3675
|
+
throw new BitfabError(
|
|
3676
|
+
"experimentalSelectiveReplay accepts only 1\u2013100 mustRun with traceFunctionKey and spanName. Declare replayReusable on eligible spans, not reusableNodes in the replay request."
|
|
3677
|
+
);
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
function policyKey(options) {
|
|
3681
|
+
return JSON.stringify(options.mustRun.map(replayNodeKey).sort());
|
|
3682
|
+
}
|
|
3683
|
+
var SelectiveReplayRuntime;
|
|
3684
|
+
var init_selectiveReplayRuntime = __esm({
|
|
3685
|
+
"src/selectiveReplayRuntime.ts"() {
|
|
3686
|
+
"use strict";
|
|
3687
|
+
init_errors();
|
|
3688
|
+
init_selectiveReplay();
|
|
3689
|
+
SelectiveReplayRuntime = class {
|
|
3690
|
+
constructor(plan, options) {
|
|
3691
|
+
this.plan = plan;
|
|
3692
|
+
this.options = options;
|
|
3693
|
+
this.nodes = /* @__PURE__ */ new Map();
|
|
3694
|
+
this.children = /* @__PURE__ */ new Map();
|
|
3695
|
+
this.runtimeParents = /* @__PURE__ */ new Map();
|
|
3696
|
+
this.consumed = /* @__PURE__ */ new Map();
|
|
3697
|
+
this.requiredScopes = /* @__PURE__ */ new Set();
|
|
3698
|
+
this.recordings = /* @__PURE__ */ new Map();
|
|
3699
|
+
this.reached = /* @__PURE__ */ new Set();
|
|
3700
|
+
this.decisions = [];
|
|
3701
|
+
this.assertionNames = /* @__PURE__ */ new Set();
|
|
3702
|
+
this.assertionScopes = /* @__PURE__ */ new Set();
|
|
3703
|
+
this.observedNames = /* @__PURE__ */ new Map();
|
|
3704
|
+
this.unresolvedAssertions = [];
|
|
3705
|
+
validateSelectiveReplayOptions(options);
|
|
3706
|
+
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) {
|
|
3707
|
+
throw new BitfabError(
|
|
3708
|
+
"Server did not return a compatible selective replay plan. Update the server or disable experimentalSelectiveReplay."
|
|
3709
|
+
);
|
|
3710
|
+
}
|
|
3711
|
+
validateSelectiveReplayOptions(plan.options);
|
|
3712
|
+
if (policyKey(plan.options) !== policyKey(options)) {
|
|
3713
|
+
throw new BitfabError(
|
|
3714
|
+
"Selective replay plan does not match the requested mustRun nodes."
|
|
3715
|
+
);
|
|
3716
|
+
}
|
|
3717
|
+
this.required = new Set(options.mustRun.map(replayNodeKey));
|
|
3718
|
+
for (const node of plan.nodes) {
|
|
3719
|
+
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") {
|
|
3720
|
+
throw new BitfabError("Invalid selective replay node.");
|
|
3721
|
+
}
|
|
3722
|
+
this.nodes.set(node.id, node);
|
|
3723
|
+
if (node.recording !== void 0) {
|
|
3724
|
+
this.recordings.set(node.id, {
|
|
3725
|
+
input: node.recording.kwargs === void 0 ? jsonFingerprint(node.recording.input) : void 0,
|
|
3726
|
+
output: jsonFingerprint(node.recording.output)
|
|
3727
|
+
});
|
|
3728
|
+
}
|
|
3729
|
+
if (node.parentId !== null) {
|
|
3730
|
+
const siblings = this.children.get(node.parentId) ?? [];
|
|
3731
|
+
siblings.push(node);
|
|
3732
|
+
this.children.set(node.parentId, siblings);
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
const root = this.nodes.get(plan.rootId);
|
|
3736
|
+
if (!root || root.parentId !== null || !root.mustRun) {
|
|
3737
|
+
throw new BitfabError("Selective replay requires a live recorded root.");
|
|
3738
|
+
}
|
|
3739
|
+
const assertionIds = /* @__PURE__ */ new Set();
|
|
3740
|
+
let wholeTrace = false;
|
|
3741
|
+
for (const assertion of plan.assertions) {
|
|
3742
|
+
const target = assertion?.target;
|
|
3743
|
+
if (typeof assertion?.id !== "string" || assertion.id.length === 0 || assertionIds.has(assertion.id) || target !== null && (typeof target !== "object" || target.kind !== "output" && target.kind !== "span")) {
|
|
3744
|
+
throw new BitfabError(
|
|
3745
|
+
"Invalid selective replay assertion requirements."
|
|
3746
|
+
);
|
|
3747
|
+
}
|
|
3748
|
+
assertionIds.add(assertion.id);
|
|
3749
|
+
if (target === null) {
|
|
3750
|
+
wholeTrace = true;
|
|
3751
|
+
} else if (target.kind === "span") {
|
|
3752
|
+
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)) {
|
|
3753
|
+
throw new BitfabError("Invalid selective replay assertion target.");
|
|
3754
|
+
}
|
|
3755
|
+
this.assertionNames.add(target.name);
|
|
3756
|
+
const count = plan.nodes.filter(
|
|
3757
|
+
(node) => node.spanName === target.name
|
|
3758
|
+
).length;
|
|
3759
|
+
if (count <= (typeof target.occurrence === "number" ? target.occurrence : 0)) {
|
|
3760
|
+
this.unresolvedAssertions.push(assertion.id);
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
this.fullTraceForAssertions = wholeTrace || this.unresolvedAssertions.length > 0;
|
|
3765
|
+
const recordedKeys = new Set(plan.nodes.map(replayNodeKey));
|
|
3766
|
+
const unresolved = options.mustRun.filter(
|
|
3767
|
+
(node) => !recordedKeys.has(replayNodeKey(node))
|
|
3768
|
+
);
|
|
3769
|
+
if (JSON.stringify(unresolved.map(replayNodeKey).sort()) !== JSON.stringify(plan.unresolvedMustRun.map(replayNodeKey).sort())) {
|
|
3770
|
+
throw new BitfabError(
|
|
3771
|
+
"Selective replay plan has incomplete mustRun coverage."
|
|
3772
|
+
);
|
|
3773
|
+
}
|
|
3774
|
+
for (const node of plan.nodes) {
|
|
3775
|
+
const ancestors = /* @__PURE__ */ new Set([node.id]);
|
|
3776
|
+
let requiredByManifest = this.required.has(replayNodeKey(node));
|
|
3777
|
+
let requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(node.spanName);
|
|
3778
|
+
let parentId = node.parentId;
|
|
3779
|
+
while (parentId !== null) {
|
|
3780
|
+
const parent = this.nodes.get(parentId);
|
|
3781
|
+
if (!parent || ancestors.has(parentId) || node.mustRun && !parent.mustRun) {
|
|
3782
|
+
throw new BitfabError("Invalid selective replay ancestry.");
|
|
3783
|
+
}
|
|
3784
|
+
ancestors.add(parentId);
|
|
3785
|
+
requiredByManifest || (requiredByManifest = this.required.has(replayNodeKey(parent)));
|
|
3786
|
+
requiredForAssertion || (requiredForAssertion = this.assertionNames.has(parent.spanName));
|
|
3787
|
+
parentId = parent.parentId;
|
|
3788
|
+
}
|
|
3789
|
+
if (!ancestors.has(plan.rootId) || (requiredByManifest || unresolved.length > 0) && !node.mustRun) {
|
|
3790
|
+
throw new BitfabError("Selective replay plan could hide required code.");
|
|
3791
|
+
}
|
|
3792
|
+
if (requiredForAssertion && !node.mustRun) {
|
|
3793
|
+
throw new BitfabError(
|
|
3794
|
+
"Selective replay plan could hide assertion evidence."
|
|
3795
|
+
);
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
enter(params) {
|
|
3800
|
+
const identity = {
|
|
3801
|
+
traceFunctionKey: params.traceFunctionKey,
|
|
3802
|
+
spanName: params.spanName
|
|
3803
|
+
};
|
|
3804
|
+
const key = replayNodeKey(identity);
|
|
3805
|
+
const fingerprint = jsonFingerprint(params.inputs);
|
|
3806
|
+
let node;
|
|
3807
|
+
let matchReason = "unmatched-call";
|
|
3808
|
+
if (params.parentSpanId === null) {
|
|
3809
|
+
node = this.nodes.get(this.plan.rootId);
|
|
3810
|
+
} else {
|
|
3811
|
+
const recordedParent = this.runtimeParents.get(params.parentSpanId);
|
|
3812
|
+
const consumed = this.consumed.get(params.parentSpanId) ?? /* @__PURE__ */ new Set();
|
|
3813
|
+
this.consumed.set(params.parentSpanId, consumed);
|
|
3814
|
+
const candidates = recordedParent ? (this.children.get(recordedParent) ?? []).filter(
|
|
3815
|
+
(candidate) => replayNodeKey(candidate) === key && !consumed.has(candidate.id)
|
|
3816
|
+
) : [];
|
|
3817
|
+
const exact = fingerprint === void 0 ? [] : candidates.filter(
|
|
3818
|
+
(candidate) => this.recordings.get(candidate.id)?.input === fingerprint
|
|
3819
|
+
);
|
|
3820
|
+
node = exact.length === 1 ? exact[0] : exact.length === 0 && candidates.length === 1 ? candidates[0] : void 0;
|
|
3821
|
+
if (node) {
|
|
3822
|
+
consumed.add(node.id);
|
|
3823
|
+
} else if (candidates.length > 0) {
|
|
3824
|
+
matchReason = "ambiguous-call";
|
|
3825
|
+
for (const candidate of candidates) {
|
|
3826
|
+
consumed.add(candidate.id);
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
this.runtimeParents.set(params.spanId, node?.id ?? null);
|
|
3831
|
+
const requiredByManifest = this.required.has(key) || params.parentSpanId !== null && this.requiredScopes.has(params.parentSpanId);
|
|
3832
|
+
if (requiredByManifest) {
|
|
3833
|
+
this.requiredScopes.add(params.spanId);
|
|
3834
|
+
}
|
|
3835
|
+
const requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(params.spanName) || params.parentSpanId !== null && this.assertionScopes.has(params.parentSpanId);
|
|
3836
|
+
if (requiredForAssertion) {
|
|
3837
|
+
this.assertionScopes.add(params.spanId);
|
|
3838
|
+
}
|
|
3839
|
+
const mustRun = params.parentSpanId === null || requiredByManifest || node?.mustRun === true || requiredForAssertion || this.plan.unresolvedMustRun.length > 0;
|
|
3840
|
+
const recording = node === void 0 ? void 0 : this.recordings.get(node.id);
|
|
3841
|
+
const equalInputs = fingerprint !== void 0 && recording?.input === fingerprint;
|
|
3842
|
+
const reusable = recording?.output !== void 0 && equalInputs && params.canReuseOutput;
|
|
3843
|
+
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;
|
|
3844
|
+
const decision = (action, why) => {
|
|
3845
|
+
this.decisions.push({
|
|
3846
|
+
...identity,
|
|
3847
|
+
originalSpanId: node?.id ?? null,
|
|
3848
|
+
action,
|
|
3849
|
+
reason: why
|
|
3850
|
+
});
|
|
3851
|
+
};
|
|
3852
|
+
if (params.safetyMock && (mustRun || !reusable)) {
|
|
3853
|
+
decision("blocked", `safety-conflict:${reason}`);
|
|
3854
|
+
this.failure = new BitfabError(
|
|
3855
|
+
`Selective replay cannot execute or safely reuse safety-mocked node ${params.spanName} (${reason}).`
|
|
3856
|
+
);
|
|
3857
|
+
throw this.failure;
|
|
3858
|
+
}
|
|
3859
|
+
if (!mustRun && reusable && (params.safetyMock || node?.reusable === true && params.reuseAllowed)) {
|
|
3860
|
+
decision("mock", params.safetyMock ? "safety-mock" : "unchanged-inputs");
|
|
3861
|
+
return {
|
|
3862
|
+
mock: true,
|
|
3863
|
+
output: JSON.parse(recording.output)
|
|
3864
|
+
};
|
|
3865
|
+
}
|
|
3866
|
+
decision("run", reason);
|
|
3867
|
+
this.observedNames.set(
|
|
3868
|
+
params.spanName,
|
|
3869
|
+
(this.observedNames.get(params.spanName) ?? 0) + 1
|
|
3870
|
+
);
|
|
3871
|
+
if (this.required.has(key)) {
|
|
3872
|
+
this.reached.add(key);
|
|
3873
|
+
}
|
|
3874
|
+
return { mock: false };
|
|
3875
|
+
}
|
|
3876
|
+
assertNoSafetyConflict() {
|
|
3877
|
+
if (this.failure) {
|
|
3878
|
+
throw this.failure;
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
report() {
|
|
3882
|
+
return {
|
|
3883
|
+
decisions: this.decisions,
|
|
3884
|
+
mustRunNotReached: this.options.mustRun.filter(
|
|
3885
|
+
(node) => !this.reached.has(replayNodeKey(node))
|
|
3886
|
+
),
|
|
3887
|
+
unresolvedMustRun: this.plan.unresolvedMustRun,
|
|
3888
|
+
assertionIds: this.plan.assertions.map((assertion) => assertion.id),
|
|
3889
|
+
unresolvedAssertions: this.unresolvedAssertions,
|
|
3890
|
+
assertionTargetsNotReached: this.plan.assertions.flatMap(
|
|
3891
|
+
({ id, target }) => target?.kind === "span" && (this.observedNames.get(target.name) ?? 0) <= (typeof target.occurrence === "number" ? target.occurrence : 0) ? [id] : []
|
|
3892
|
+
)
|
|
3893
|
+
};
|
|
3894
|
+
}
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
});
|
|
3898
|
+
|
|
3570
3899
|
// src/replay.ts
|
|
3571
3900
|
var replay_exports = {};
|
|
3572
3901
|
__export(replay_exports, {
|
|
@@ -3586,6 +3915,88 @@ function expandReplayWork(serverItems, attempts) {
|
|
|
3586
3915
|
(_, attempt) => serverItems.map((serverItem) => ({ serverItem, attempt }))
|
|
3587
3916
|
).flat();
|
|
3588
3917
|
}
|
|
3918
|
+
function validateResume(resume, options) {
|
|
3919
|
+
if (resume.trim().length === 0) {
|
|
3920
|
+
throw new BitfabError("resume must be the ID of the experiment to resume.");
|
|
3921
|
+
}
|
|
3922
|
+
const conflicting = RESUME_CONFLICTING_OPTIONS.filter(
|
|
3923
|
+
(name) => options[name] !== void 0 && options[name] !== false
|
|
3924
|
+
);
|
|
3925
|
+
if (conflicting.length > 0) {
|
|
3926
|
+
throw new BitfabError(
|
|
3927
|
+
`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.`
|
|
3928
|
+
);
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
async function resumeReplaySession(httpClient, experimentId, traceFunctionKey, codeChangeFiles, force, requestedDbBranch) {
|
|
3932
|
+
const response = await httpClient.resumeReplay(
|
|
3933
|
+
experimentId,
|
|
3934
|
+
traceFunctionKey,
|
|
3935
|
+
codeChangeFiles,
|
|
3936
|
+
force
|
|
3937
|
+
);
|
|
3938
|
+
const work = response.work.map(({ itemIndex, attempt }) => {
|
|
3939
|
+
const serverItem = response.items[itemIndex];
|
|
3940
|
+
if (serverItem === void 0) {
|
|
3941
|
+
throw new BitfabError(
|
|
3942
|
+
`Replay resume for experiment ${response.experimentId} named item ${itemIndex}, but the server returned only ${response.items.length} item(s).`
|
|
3943
|
+
);
|
|
3944
|
+
}
|
|
3945
|
+
return { serverItem, attempt };
|
|
3946
|
+
});
|
|
3947
|
+
if (response.skippedOriginalTraceIds.length > 0) {
|
|
3948
|
+
try {
|
|
3949
|
+
console.warn(
|
|
3950
|
+
`Bitfab: ${response.skippedOriginalTraceIds.length} trace(s) selected when experiment ${response.experimentId} started no longer exist, so resume skips them: ${response.skippedOriginalTraceIds.join(", ")}`
|
|
3951
|
+
);
|
|
3952
|
+
} catch {
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
if (requestedDbBranch !== void 0 && dbBranchEnabled(requestedDbBranch) !== response.includeDbBranchLease) {
|
|
3956
|
+
try {
|
|
3957
|
+
console.warn(
|
|
3958
|
+
`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.`
|
|
3959
|
+
);
|
|
3960
|
+
} catch {
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
return {
|
|
3964
|
+
experimentId: response.experimentId,
|
|
3965
|
+
experimentUrl: response.experimentUrl,
|
|
3966
|
+
attempts: response.attempts,
|
|
3967
|
+
work,
|
|
3968
|
+
finished: response.finished,
|
|
3969
|
+
includeDbBranchLease: response.includeDbBranchLease,
|
|
3970
|
+
dbBranchSettings: response.dbBranchSettings ?? void 0
|
|
3971
|
+
};
|
|
3972
|
+
}
|
|
3973
|
+
function carriedOverReplayItem(finished) {
|
|
3974
|
+
return {
|
|
3975
|
+
traceId: finished.traceId,
|
|
3976
|
+
originalTraceId: finished.originalTraceId,
|
|
3977
|
+
originalSpanId: "",
|
|
3978
|
+
sourceTraceId: finished.originalTraceId,
|
|
3979
|
+
sourceSpanId: "",
|
|
3980
|
+
attempt: finished.attempt,
|
|
3981
|
+
input: [],
|
|
3982
|
+
result: void 0,
|
|
3983
|
+
originalOutput: void 0,
|
|
3984
|
+
error: finished.hasError ? CARRIED_OVER_ERROR : null,
|
|
3985
|
+
traceError: null,
|
|
3986
|
+
replayError: null,
|
|
3987
|
+
durationMs: null,
|
|
3988
|
+
originalDurationMs: null,
|
|
3989
|
+
originalTokens: null,
|
|
3990
|
+
originalModel: null,
|
|
3991
|
+
tokens: null,
|
|
3992
|
+
model: null,
|
|
3993
|
+
dbSnapshotRef: null,
|
|
3994
|
+
dbBranchTimings: null,
|
|
3995
|
+
traceOutline: null,
|
|
3996
|
+
originalTraceOutline: null,
|
|
3997
|
+
carriedOver: true
|
|
3998
|
+
};
|
|
3999
|
+
}
|
|
3589
4000
|
function resolveAttempts(attempts) {
|
|
3590
4001
|
if (attempts === void 0) {
|
|
3591
4002
|
return 1;
|
|
@@ -3740,7 +4151,7 @@ function buildMockTree(rootNode) {
|
|
|
3740
4151
|
}
|
|
3741
4152
|
return { spans };
|
|
3742
4153
|
}
|
|
3743
|
-
async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
|
|
4154
|
+
async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun, selectiveOptions) {
|
|
3744
4155
|
let lease = includeDbBranchLease ? serverItem.dbBranchLease : void 0;
|
|
3745
4156
|
let leaseError = includeDbBranchLease ? serverItem.dbBranchLeaseError : void 0;
|
|
3746
4157
|
let dbSnapshotRef = serverItem.dbSnapshotRef;
|
|
@@ -3753,6 +4164,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3753
4164
|
let replayError = null;
|
|
3754
4165
|
let replayDurationMs = null;
|
|
3755
4166
|
let replayStarted = null;
|
|
4167
|
+
let selectiveReplay;
|
|
3756
4168
|
const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
|
|
3757
4169
|
const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
|
|
3758
4170
|
try {
|
|
@@ -3785,6 +4197,17 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3785
4197
|
originalTraceId
|
|
3786
4198
|
);
|
|
3787
4199
|
}
|
|
4200
|
+
if (selectiveOptions !== void 0) {
|
|
4201
|
+
if (serverItem.selectiveReplayPlan === void 0) {
|
|
4202
|
+
throw new BitfabError(
|
|
4203
|
+
"Server did not return a selective replay plan. Update the server or disable experimentalSelectiveReplay."
|
|
4204
|
+
);
|
|
4205
|
+
}
|
|
4206
|
+
selectiveReplay = new SelectiveReplayRuntime(
|
|
4207
|
+
serverItem.selectiveReplayPlan,
|
|
4208
|
+
selectiveOptions
|
|
4209
|
+
);
|
|
4210
|
+
}
|
|
3788
4211
|
const span = await httpClient.getExternalSpan(originalSpanId, {
|
|
3789
4212
|
view: "replay"
|
|
3790
4213
|
});
|
|
@@ -3804,7 +4227,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3804
4227
|
});
|
|
3805
4228
|
}
|
|
3806
4229
|
const hasOverrides = resolvedOverrides.length > 0;
|
|
3807
|
-
const needTree = mockStrategy === "all" || mockStrategy === "marked" || hasOverrides;
|
|
4230
|
+
const needTree = selectiveReplay === void 0 && (mockStrategy === "all" || mockStrategy === "marked" || hasOverrides);
|
|
3808
4231
|
const includeOutputs = mockStrategy === "all";
|
|
3809
4232
|
let mockTree;
|
|
3810
4233
|
if (needTree) {
|
|
@@ -3879,6 +4302,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3879
4302
|
sourceBitfabTraceId: originalTraceId,
|
|
3880
4303
|
replayAttempt: attempt,
|
|
3881
4304
|
mockTree,
|
|
4305
|
+
selectiveReplay,
|
|
3882
4306
|
callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
|
|
3883
4307
|
mockStrategy,
|
|
3884
4308
|
mockOverrides: hasOverrides ? resolvedOverrides : void 0,
|
|
@@ -3890,6 +4314,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3890
4314
|
() => fn(...inputs)
|
|
3891
4315
|
);
|
|
3892
4316
|
result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
|
|
4317
|
+
selectiveReplay?.assertNoSafetyConflict();
|
|
3893
4318
|
replayDurationMs = Math.round(performance.now() - replayStarted);
|
|
3894
4319
|
} catch (e) {
|
|
3895
4320
|
if (replayStarted !== null) {
|
|
@@ -3941,6 +4366,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3941
4366
|
traceError,
|
|
3942
4367
|
replayError,
|
|
3943
4368
|
durationMs: replayDurationMs,
|
|
4369
|
+
...selectiveReplay === void 0 ? {} : { selectiveReplay: selectiveReplay.report() },
|
|
3944
4370
|
originalDurationMs,
|
|
3945
4371
|
originalTokens: serverItem.originalTokens ?? serverItem.tokens ?? null,
|
|
3946
4372
|
originalModel,
|
|
@@ -4038,6 +4464,21 @@ async function mapWithConcurrency2(tasks, maxConcurrency, onSettled, onStarted)
|
|
|
4038
4464
|
return results;
|
|
4039
4465
|
}
|
|
4040
4466
|
async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, registeredOverrides = []) {
|
|
4467
|
+
if (options?.resume !== void 0) {
|
|
4468
|
+
validateResume(options.resume, options);
|
|
4469
|
+
} else if (options?.force === true) {
|
|
4470
|
+
throw new BitfabError(
|
|
4471
|
+
"force only applies when resuming an experiment. Pass it together with resume."
|
|
4472
|
+
);
|
|
4473
|
+
}
|
|
4474
|
+
if (options?.experimentalSelectiveReplay !== void 0) {
|
|
4475
|
+
validateSelectiveReplayOptions(options.experimentalSelectiveReplay);
|
|
4476
|
+
if ((options.mock ?? "marked") !== "marked" || normalizeMockOverrides(options.mockOverride).length > 0 || registeredOverrides.length > 0) {
|
|
4477
|
+
throw new BitfabError(
|
|
4478
|
+
"experimentalSelectiveReplay requires mock: 'marked' and cannot be combined with mock overrides."
|
|
4479
|
+
);
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4041
4482
|
if (options?.traceIds !== void 0) {
|
|
4042
4483
|
if (options.traceIds.length === 0) {
|
|
4043
4484
|
throw new BitfabError("traceIds must contain at least one trace ID.");
|
|
@@ -4057,7 +4498,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4057
4498
|
} catch {
|
|
4058
4499
|
}
|
|
4059
4500
|
}
|
|
4060
|
-
const
|
|
4501
|
+
const requestedAttempts = resolveAttempts(options?.attempts);
|
|
4061
4502
|
const maxConcurrency = options?.maxConcurrency ?? 10;
|
|
4062
4503
|
if (!Number.isInteger(maxConcurrency) || maxConcurrency < 1) {
|
|
4063
4504
|
throw new BitfabError(
|
|
@@ -4076,40 +4517,60 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4076
4517
|
}
|
|
4077
4518
|
}
|
|
4078
4519
|
}
|
|
4079
|
-
const
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4520
|
+
const includeDbBranchLease = dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true;
|
|
4521
|
+
let session;
|
|
4522
|
+
if (options?.resume === void 0) {
|
|
4523
|
+
const started2 = await httpClient.startReplay(
|
|
4524
|
+
traceFunctionKey,
|
|
4525
|
+
// limit is meaningless with explicit traceIds (the ID list determines
|
|
4526
|
+
// the count), so it's omitted from the request entirely.
|
|
4527
|
+
options?.traceIds ? void 0 : options?.limit ?? 5,
|
|
4528
|
+
options?.traceIds,
|
|
4529
|
+
options?.name,
|
|
4530
|
+
codeChangeDescription,
|
|
4531
|
+
codeChangeFiles,
|
|
4532
|
+
// A dry run executes nothing, so a branch would be provisioned (and billed)
|
|
4533
|
+
// for code that never runs, and a seeded source's refusal would fail the item
|
|
4534
|
+
// before it could report its resolved inputs.
|
|
4535
|
+
includeDbBranchLease,
|
|
4536
|
+
options?.experimentGroupId,
|
|
4537
|
+
datasetIds,
|
|
4538
|
+
options?.graderIds,
|
|
4539
|
+
resolveDbBranchSettings(options?.dbBranch),
|
|
4540
|
+
requestedAttempts,
|
|
4541
|
+
options?.adaptInputs !== void 0,
|
|
4542
|
+
options?.onlyWithAssertions,
|
|
4543
|
+
options?.judgeAssertions,
|
|
4544
|
+
options?.experimentalSelectiveReplay
|
|
4545
|
+
);
|
|
4546
|
+
if (started2.items.length === 0) {
|
|
4547
|
+
try {
|
|
4548
|
+
console.warn(
|
|
4549
|
+
`Bitfab: no traces matched "${traceFunctionKey}", so this replay ran nothing. Capture a trace, or seed one with seedTrace, before replaying.`
|
|
4550
|
+
);
|
|
4551
|
+
} catch {
|
|
4552
|
+
}
|
|
4111
4553
|
}
|
|
4554
|
+
session = {
|
|
4555
|
+
experimentId: started2.experimentId,
|
|
4556
|
+
experimentUrl: started2.experimentUrl,
|
|
4557
|
+
attempts: requestedAttempts,
|
|
4558
|
+
work: expandReplayWork(started2.items, requestedAttempts),
|
|
4559
|
+
finished: [],
|
|
4560
|
+
includeDbBranchLease,
|
|
4561
|
+
dbBranchSettings: resolveDbBranchSettings(options?.dbBranch)
|
|
4562
|
+
};
|
|
4563
|
+
} else {
|
|
4564
|
+
session = await resumeReplaySession(
|
|
4565
|
+
httpClient,
|
|
4566
|
+
options.resume,
|
|
4567
|
+
traceFunctionKey,
|
|
4568
|
+
codeChangeFiles,
|
|
4569
|
+
options.force,
|
|
4570
|
+
options.dbBranch
|
|
4571
|
+
);
|
|
4112
4572
|
}
|
|
4573
|
+
const { experimentId, experimentUrl, attempts } = session;
|
|
4113
4574
|
const mockStrategy = options?.mock ?? "marked";
|
|
4114
4575
|
const fullExperimentUrl = `${serviceUrl}${experimentUrl}`;
|
|
4115
4576
|
try {
|
|
@@ -4123,7 +4584,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4123
4584
|
...normalizeMockOverrides(options?.mockOverride),
|
|
4124
4585
|
...registeredOverrides
|
|
4125
4586
|
];
|
|
4126
|
-
const workItems =
|
|
4587
|
+
const workItems = session.work;
|
|
4588
|
+
const carriedOverItems = session.finished.map(
|
|
4589
|
+
(finished) => carriedOverReplayItem(finished)
|
|
4590
|
+
);
|
|
4127
4591
|
const replayedTraceIds = workItems.map(() => randomUuid());
|
|
4128
4592
|
httpClient.trackTraceDeliveries(replayedTraceIds);
|
|
4129
4593
|
const tasks = workItems.map(
|
|
@@ -4136,18 +4600,19 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4136
4600
|
resolvedOverrides,
|
|
4137
4601
|
replayedTraceIds[index],
|
|
4138
4602
|
attempt,
|
|
4139
|
-
|
|
4140
|
-
|
|
4603
|
+
session.includeDbBranchLease,
|
|
4604
|
+
session.dbBranchSettings,
|
|
4141
4605
|
options?.adaptInputs,
|
|
4142
|
-
options?.dryRun === true
|
|
4606
|
+
options?.dryRun === true,
|
|
4607
|
+
options?.experimentalSelectiveReplay
|
|
4143
4608
|
)
|
|
4144
4609
|
);
|
|
4145
|
-
const total = tasks.length;
|
|
4610
|
+
const total = tasks.length + carriedOverItems.length;
|
|
4146
4611
|
const onItemFinish = options?.onItemFinish ?? options?.onProgress;
|
|
4147
|
-
let completed =
|
|
4148
|
-
let started =
|
|
4149
|
-
let
|
|
4150
|
-
let
|
|
4612
|
+
let completed = carriedOverItems.length;
|
|
4613
|
+
let started = carriedOverItems.length;
|
|
4614
|
+
let errored = carriedOverItems.filter((item) => item.error !== null).length;
|
|
4615
|
+
let succeeded = completed - errored;
|
|
4151
4616
|
let flushInFlight = null;
|
|
4152
4617
|
let flushPending = false;
|
|
4153
4618
|
const flushFinishedItemTrace = () => {
|
|
@@ -4167,7 +4632,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4167
4632
|
}
|
|
4168
4633
|
return flushInFlight;
|
|
4169
4634
|
};
|
|
4170
|
-
const
|
|
4635
|
+
const ranItems = await mapWithConcurrency2(
|
|
4171
4636
|
tasks,
|
|
4172
4637
|
maxConcurrency,
|
|
4173
4638
|
async (item, index) => {
|
|
@@ -4212,6 +4677,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4212
4677
|
traceError: item.traceError,
|
|
4213
4678
|
replayError: item.replayError,
|
|
4214
4679
|
durationMs: item.durationMs,
|
|
4680
|
+
...item.selectiveReplay === void 0 ? {} : { selectiveReplay: item.selectiveReplay },
|
|
4215
4681
|
originalDurationMs: item.originalDurationMs,
|
|
4216
4682
|
originalTokens: item.originalTokens,
|
|
4217
4683
|
originalModel: item.originalModel,
|
|
@@ -4254,6 +4720,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4254
4720
|
}
|
|
4255
4721
|
} : void 0
|
|
4256
4722
|
);
|
|
4723
|
+
const resultItems = [...carriedOverItems, ...ranItems];
|
|
4257
4724
|
if (options?.dryRun === true) {
|
|
4258
4725
|
await httpClient.completeReplay(experimentId).catch(() => void 0);
|
|
4259
4726
|
const dryResult = {
|
|
@@ -4273,11 +4740,11 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4273
4740
|
experimentId,
|
|
4274
4741
|
fullExperimentUrl
|
|
4275
4742
|
);
|
|
4276
|
-
for (let index = 0; index <
|
|
4743
|
+
for (let index = 0; index < ranItems.length; index += 1) {
|
|
4277
4744
|
const localId = replayedTraceIds[index];
|
|
4278
4745
|
const readBack = localId ? deliveredTraceIds[localId] : void 0;
|
|
4279
4746
|
if (readBack !== void 0) {
|
|
4280
|
-
|
|
4747
|
+
ranItems[index].traceId = readBack;
|
|
4281
4748
|
}
|
|
4282
4749
|
}
|
|
4283
4750
|
const completeResult = await preserveReplayFailure(
|
|
@@ -4291,8 +4758,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4291
4758
|
if (serverTraceIds !== void 0) {
|
|
4292
4759
|
const missing = [];
|
|
4293
4760
|
let completedCount = 0;
|
|
4294
|
-
for (let index = 0; index <
|
|
4295
|
-
const item =
|
|
4761
|
+
for (let index = 0; index < ranItems.length; index += 1) {
|
|
4762
|
+
const item = ranItems[index];
|
|
4296
4763
|
const localId = replayedTraceIds[index];
|
|
4297
4764
|
const mapped = localId ? serverTraceIds[localId] : void 0;
|
|
4298
4765
|
item.traceId = item.traceId ?? mapped ?? null;
|
|
@@ -4378,7 +4845,7 @@ async function writeReplayResultFile(result) {
|
|
|
4378
4845
|
}
|
|
4379
4846
|
}
|
|
4380
4847
|
}
|
|
4381
|
-
var REPLAY_PERSISTENCE_TIMEOUT_MS, MAX_REPLAY_ATTEMPTS, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
|
|
4848
|
+
var REPLAY_PERSISTENCE_TIMEOUT_MS, MAX_REPLAY_ATTEMPTS, RESUME_CONFLICTING_OPTIONS, CARRIED_OVER_ERROR, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
|
|
4382
4849
|
var init_replay = __esm({
|
|
4383
4850
|
"src/replay.ts"() {
|
|
4384
4851
|
"use strict";
|
|
@@ -4389,9 +4856,22 @@ var init_replay = __esm({
|
|
|
4389
4856
|
init_mockOverride();
|
|
4390
4857
|
init_randomUuid();
|
|
4391
4858
|
init_replayContext();
|
|
4859
|
+
init_selectiveReplayRuntime();
|
|
4392
4860
|
init_serialize();
|
|
4393
4861
|
REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
4394
4862
|
MAX_REPLAY_ATTEMPTS = 100;
|
|
4863
|
+
RESUME_CONFLICTING_OPTIONS = [
|
|
4864
|
+
"traceIds",
|
|
4865
|
+
"limit",
|
|
4866
|
+
"datasetId",
|
|
4867
|
+
"datasetIds",
|
|
4868
|
+
"attempts",
|
|
4869
|
+
"graderIds",
|
|
4870
|
+
"onlyWithAssertions",
|
|
4871
|
+
"dryRun",
|
|
4872
|
+
"experimentalSelectiveReplay"
|
|
4873
|
+
];
|
|
4874
|
+
CARRIED_OVER_ERROR = "This item errored in an earlier run of this experiment, before it was resumed.";
|
|
4395
4875
|
BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
|
|
4396
4876
|
ReplayError = class extends BitfabError {
|
|
4397
4877
|
constructor(message, items, experimentId, experimentUrl, cause) {
|
|
@@ -9083,6 +9563,9 @@ var Bitfab = class {
|
|
|
9083
9563
|
...options.mockOnReplay !== void 0 && {
|
|
9084
9564
|
mockOnReplay: options.mockOnReplay
|
|
9085
9565
|
},
|
|
9566
|
+
...options.replayReusable !== void 0 && {
|
|
9567
|
+
replayReusable: options.replayReusable
|
|
9568
|
+
},
|
|
9086
9569
|
...options.finalize !== void 0 && { finalize: options.finalize }
|
|
9087
9570
|
};
|
|
9088
9571
|
}
|
|
@@ -9231,6 +9714,9 @@ var Bitfab = class {
|
|
|
9231
9714
|
...mockOnReplay !== void 0 && {
|
|
9232
9715
|
mockOnReplay
|
|
9233
9716
|
},
|
|
9717
|
+
...nodeConfiguration?.replayReusable !== void 0 && {
|
|
9718
|
+
replayReusable: nodeConfiguration.replayReusable
|
|
9719
|
+
},
|
|
9234
9720
|
...nodeConfiguration?.finalize !== void 0 && {
|
|
9235
9721
|
finalize: nodeConfiguration.finalize
|
|
9236
9722
|
}
|
|
@@ -10028,6 +10514,7 @@ var Bitfab = class {
|
|
|
10028
10514
|
instrumentation: options.instrumentation ?? "span",
|
|
10029
10515
|
functionId: options.functionId,
|
|
10030
10516
|
captureContent: options.captureContent ?? true,
|
|
10517
|
+
replayReusable: options.replayReusable,
|
|
10031
10518
|
autoTraceDefinition: options.autoTraceDefinition,
|
|
10032
10519
|
declaredNode: options.declaredNode === true,
|
|
10033
10520
|
nestedTrace: options.nestedTrace,
|
|
@@ -10147,6 +10634,27 @@ var Bitfab = class {
|
|
|
10147
10634
|
return result;
|
|
10148
10635
|
};
|
|
10149
10636
|
const replayCtxForMock = getReplayContext();
|
|
10637
|
+
if (replayCtxForMock?.selectiveReplay) {
|
|
10638
|
+
const decision = replayCtxForMock.selectiveReplay.enter({
|
|
10639
|
+
traceFunctionKey,
|
|
10640
|
+
spanName: baseSpanParams.spanName ?? traceFunctionKey,
|
|
10641
|
+
spanId,
|
|
10642
|
+
parentSpanId,
|
|
10643
|
+
inputs: args,
|
|
10644
|
+
safetyMock: !isRootSpan && options.mockOnReplay === true,
|
|
10645
|
+
reuseAllowed: options.replayReusable === true,
|
|
10646
|
+
canReuseOutput: options.finalize === void 0 && options.replayOutput === void 0 && !fn.constructor.name.includes("Generator")
|
|
10647
|
+
});
|
|
10648
|
+
if (decision.mock) {
|
|
10649
|
+
void sendSpan({
|
|
10650
|
+
result: decision.output,
|
|
10651
|
+
mocked: true,
|
|
10652
|
+
mockTarget: "output",
|
|
10653
|
+
mockSource: "recorded"
|
|
10654
|
+
});
|
|
10655
|
+
return fnReturnsPromise ? Promise.resolve(decision.output) : decision.output;
|
|
10656
|
+
}
|
|
10657
|
+
}
|
|
10150
10658
|
if (replayCtxForMock?.mockTree && !isRootSpan) {
|
|
10151
10659
|
const counters = replayCtxForMock.callCounters;
|
|
10152
10660
|
const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
|
|
@@ -10570,6 +11078,9 @@ var Bitfab = class {
|
|
|
10570
11078
|
span_origin: makeSpanOrigin(params.instrumentation),
|
|
10571
11079
|
span_data: {
|
|
10572
11080
|
name: params.spanName,
|
|
11081
|
+
...params.replayReusable !== void 0 && {
|
|
11082
|
+
replay_reusable: params.replayReusable
|
|
11083
|
+
},
|
|
10573
11084
|
type: params.spanType,
|
|
10574
11085
|
...params.functionId !== void 0 && {
|
|
10575
11086
|
function_id: params.functionId,
|