@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/node.cjs
CHANGED
|
@@ -88,7 +88,7 @@ var __version__, __packageName__;
|
|
|
88
88
|
var init_version_generated = __esm({
|
|
89
89
|
"src/version.generated.ts"() {
|
|
90
90
|
"use strict";
|
|
91
|
-
__version__ = "0.61.
|
|
91
|
+
__version__ = "0.61.3";
|
|
92
92
|
__packageName__ = "@bitfab/sdk";
|
|
93
93
|
}
|
|
94
94
|
});
|
|
@@ -1075,6 +1075,7 @@ var init_simulationPlan = __esm({
|
|
|
1075
1075
|
"input_meta",
|
|
1076
1076
|
"output",
|
|
1077
1077
|
"output_meta",
|
|
1078
|
+
"replay_recording",
|
|
1078
1079
|
"prompt"
|
|
1079
1080
|
];
|
|
1080
1081
|
SimulationPlan = class {
|
|
@@ -2889,8 +2890,11 @@ var init_http = __esm({
|
|
|
2889
2890
|
* Start a replay session by fetching historical traces.
|
|
2890
2891
|
* Blocking call - creates an experiment and returns lightweight item references.
|
|
2891
2892
|
*/
|
|
2892
|
-
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions) {
|
|
2893
|
+
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions, experimentalSelectiveReplay) {
|
|
2893
2894
|
const payload = { traceFunctionKey };
|
|
2895
|
+
if (experimentalSelectiveReplay !== void 0) {
|
|
2896
|
+
payload.experimentalSelectiveReplay = experimentalSelectiveReplay;
|
|
2897
|
+
}
|
|
2894
2898
|
if (limit !== void 0) {
|
|
2895
2899
|
payload.limit = limit;
|
|
2896
2900
|
}
|
|
@@ -2958,6 +2962,46 @@ var init_http = __esm({
|
|
|
2958
2962
|
}
|
|
2959
2963
|
return { ...rest, experimentId, experimentUrl };
|
|
2960
2964
|
}
|
|
2965
|
+
async resumeReplay(experimentId, traceFunctionKey, codeChangeFiles, force) {
|
|
2966
|
+
const payload = {
|
|
2967
|
+
experimentId,
|
|
2968
|
+
traceFunctionKey,
|
|
2969
|
+
lazyDbBranchLease: true
|
|
2970
|
+
};
|
|
2971
|
+
if (force === true) {
|
|
2972
|
+
payload.force = true;
|
|
2973
|
+
}
|
|
2974
|
+
if (codeChangeFiles !== void 0) {
|
|
2975
|
+
payload.codeChangeFiles = codeChangeFiles;
|
|
2976
|
+
}
|
|
2977
|
+
const git = await resolvedGitState();
|
|
2978
|
+
if (git && !isEmptyGitState(git)) {
|
|
2979
|
+
payload.git = git;
|
|
2980
|
+
}
|
|
2981
|
+
const response = await this.request(
|
|
2982
|
+
"/api/sdk/replay/resume",
|
|
2983
|
+
payload,
|
|
2984
|
+
{ timeout: REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS }
|
|
2985
|
+
);
|
|
2986
|
+
const { testRunId, testRunUrl, ...rest } = response;
|
|
2987
|
+
const resumedExperimentId = response.experimentId ?? testRunId;
|
|
2988
|
+
const experimentUrl = response.experimentUrl ?? testRunUrl;
|
|
2989
|
+
if (resumedExperimentId === void 0 || experimentUrl === void 0) {
|
|
2990
|
+
throw new BitfabError(
|
|
2991
|
+
"Replay resume response is missing the experiment ID or URL."
|
|
2992
|
+
);
|
|
2993
|
+
}
|
|
2994
|
+
return {
|
|
2995
|
+
...rest,
|
|
2996
|
+
experimentId: resumedExperimentId,
|
|
2997
|
+
experimentUrl,
|
|
2998
|
+
work: rest.work ?? [],
|
|
2999
|
+
finished: rest.finished ?? [],
|
|
3000
|
+
skippedOriginalTraceIds: rest.skippedOriginalTraceIds ?? [],
|
|
3001
|
+
includeDbBranchLease: rest.includeDbBranchLease ?? false,
|
|
3002
|
+
dbBranchSettings: rest.dbBranchSettings ?? null
|
|
3003
|
+
};
|
|
3004
|
+
}
|
|
2961
3005
|
/**
|
|
2962
3006
|
* Fetch an external span by ID.
|
|
2963
3007
|
* Blocking GET request.
|
|
@@ -3574,6 +3618,291 @@ var init_integrationFixture = __esm({
|
|
|
3574
3618
|
}
|
|
3575
3619
|
});
|
|
3576
3620
|
|
|
3621
|
+
// src/selectiveReplay.ts
|
|
3622
|
+
function replayNodeKey(node) {
|
|
3623
|
+
return JSON.stringify([node.traceFunctionKey, node.spanName]);
|
|
3624
|
+
}
|
|
3625
|
+
function jsonFingerprint(value) {
|
|
3626
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3627
|
+
function encode(input) {
|
|
3628
|
+
if (input === null || typeof input === "boolean") {
|
|
3629
|
+
return JSON.stringify(input);
|
|
3630
|
+
}
|
|
3631
|
+
if (typeof input === "string" && !input.startsWith("<unserializable")) {
|
|
3632
|
+
return JSON.stringify(input);
|
|
3633
|
+
}
|
|
3634
|
+
if (typeof input === "number" && Number.isFinite(input) && !Object.is(input, -0)) {
|
|
3635
|
+
return JSON.stringify(input);
|
|
3636
|
+
}
|
|
3637
|
+
if (typeof input !== "object" || input === null || seen.has(input)) {
|
|
3638
|
+
throw new Error("Not a lossless JSON value");
|
|
3639
|
+
}
|
|
3640
|
+
seen.add(input);
|
|
3641
|
+
const prototype = Object.getPrototypeOf(input);
|
|
3642
|
+
if (prototype !== (Array.isArray(input) ? Array.prototype : Object.prototype)) {
|
|
3643
|
+
throw new Error("Not a plain JSON object");
|
|
3644
|
+
}
|
|
3645
|
+
if (Object.getOwnPropertySymbols(input).length > 0) {
|
|
3646
|
+
throw new Error("Symbol properties are not recorded");
|
|
3647
|
+
}
|
|
3648
|
+
const keys = Object.keys(input);
|
|
3649
|
+
if (Object.getOwnPropertyNames(input).length !== keys.length + (Array.isArray(input) ? 1 : 0)) {
|
|
3650
|
+
throw new Error("Non-enumerable properties are not recorded");
|
|
3651
|
+
}
|
|
3652
|
+
if (Array.isArray(input) && (keys.length !== input.length || keys.some((key, index) => key !== String(index)))) {
|
|
3653
|
+
throw new Error("Not a dense JSON array");
|
|
3654
|
+
}
|
|
3655
|
+
const properties = keys.map((key) => {
|
|
3656
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
3657
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
3658
|
+
throw new Error("Accessor properties are not replay inputs");
|
|
3659
|
+
}
|
|
3660
|
+
return `${Array.isArray(input) ? "" : `${JSON.stringify(key)}:`}${encode(descriptor.value)}`;
|
|
3661
|
+
});
|
|
3662
|
+
return Array.isArray(input) ? `[${properties.join(",")}]` : `{${properties.join(",")}}`;
|
|
3663
|
+
}
|
|
3664
|
+
try {
|
|
3665
|
+
return encode(value);
|
|
3666
|
+
} catch {
|
|
3667
|
+
return void 0;
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
var init_selectiveReplay = __esm({
|
|
3671
|
+
"src/selectiveReplay.ts"() {
|
|
3672
|
+
"use strict";
|
|
3673
|
+
}
|
|
3674
|
+
});
|
|
3675
|
+
|
|
3676
|
+
// src/selectiveReplayRuntime.ts
|
|
3677
|
+
function validateSelectiveReplayOptions(options) {
|
|
3678
|
+
const validNodes = (nodes) => Array.isArray(nodes) && nodes.length <= 100 && nodes.every(
|
|
3679
|
+
(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
|
|
3680
|
+
);
|
|
3681
|
+
if (!options || !validNodes(options.mustRun) || options.mustRun.length === 0 || Object.keys(options).some((key) => key !== "mustRun")) {
|
|
3682
|
+
throw new BitfabError(
|
|
3683
|
+
"experimentalSelectiveReplay accepts only 1\u2013100 mustRun with traceFunctionKey and spanName. Declare replayReusable on eligible spans, not reusableNodes in the replay request."
|
|
3684
|
+
);
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
function policyKey(options) {
|
|
3688
|
+
return JSON.stringify(options.mustRun.map(replayNodeKey).sort());
|
|
3689
|
+
}
|
|
3690
|
+
var SelectiveReplayRuntime;
|
|
3691
|
+
var init_selectiveReplayRuntime = __esm({
|
|
3692
|
+
"src/selectiveReplayRuntime.ts"() {
|
|
3693
|
+
"use strict";
|
|
3694
|
+
init_errors();
|
|
3695
|
+
init_selectiveReplay();
|
|
3696
|
+
SelectiveReplayRuntime = class {
|
|
3697
|
+
constructor(plan, options) {
|
|
3698
|
+
this.plan = plan;
|
|
3699
|
+
this.options = options;
|
|
3700
|
+
this.nodes = /* @__PURE__ */ new Map();
|
|
3701
|
+
this.children = /* @__PURE__ */ new Map();
|
|
3702
|
+
this.runtimeParents = /* @__PURE__ */ new Map();
|
|
3703
|
+
this.consumed = /* @__PURE__ */ new Map();
|
|
3704
|
+
this.requiredScopes = /* @__PURE__ */ new Set();
|
|
3705
|
+
this.recordings = /* @__PURE__ */ new Map();
|
|
3706
|
+
this.reached = /* @__PURE__ */ new Set();
|
|
3707
|
+
this.decisions = [];
|
|
3708
|
+
this.assertionNames = /* @__PURE__ */ new Set();
|
|
3709
|
+
this.assertionScopes = /* @__PURE__ */ new Set();
|
|
3710
|
+
this.observedNames = /* @__PURE__ */ new Map();
|
|
3711
|
+
this.unresolvedAssertions = [];
|
|
3712
|
+
validateSelectiveReplayOptions(options);
|
|
3713
|
+
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) {
|
|
3714
|
+
throw new BitfabError(
|
|
3715
|
+
"Server did not return a compatible selective replay plan. Update the server or disable experimentalSelectiveReplay."
|
|
3716
|
+
);
|
|
3717
|
+
}
|
|
3718
|
+
validateSelectiveReplayOptions(plan.options);
|
|
3719
|
+
if (policyKey(plan.options) !== policyKey(options)) {
|
|
3720
|
+
throw new BitfabError(
|
|
3721
|
+
"Selective replay plan does not match the requested mustRun nodes."
|
|
3722
|
+
);
|
|
3723
|
+
}
|
|
3724
|
+
this.required = new Set(options.mustRun.map(replayNodeKey));
|
|
3725
|
+
for (const node of plan.nodes) {
|
|
3726
|
+
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") {
|
|
3727
|
+
throw new BitfabError("Invalid selective replay node.");
|
|
3728
|
+
}
|
|
3729
|
+
this.nodes.set(node.id, node);
|
|
3730
|
+
if (node.recording !== void 0) {
|
|
3731
|
+
this.recordings.set(node.id, {
|
|
3732
|
+
input: node.recording.kwargs === void 0 ? jsonFingerprint(node.recording.input) : void 0,
|
|
3733
|
+
output: jsonFingerprint(node.recording.output)
|
|
3734
|
+
});
|
|
3735
|
+
}
|
|
3736
|
+
if (node.parentId !== null) {
|
|
3737
|
+
const siblings = this.children.get(node.parentId) ?? [];
|
|
3738
|
+
siblings.push(node);
|
|
3739
|
+
this.children.set(node.parentId, siblings);
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
const root = this.nodes.get(plan.rootId);
|
|
3743
|
+
if (!root || root.parentId !== null || !root.mustRun) {
|
|
3744
|
+
throw new BitfabError("Selective replay requires a live recorded root.");
|
|
3745
|
+
}
|
|
3746
|
+
const assertionIds = /* @__PURE__ */ new Set();
|
|
3747
|
+
let wholeTrace = false;
|
|
3748
|
+
for (const assertion of plan.assertions) {
|
|
3749
|
+
const target = assertion?.target;
|
|
3750
|
+
if (typeof assertion?.id !== "string" || assertion.id.length === 0 || assertionIds.has(assertion.id) || target !== null && (typeof target !== "object" || target.kind !== "output" && target.kind !== "span")) {
|
|
3751
|
+
throw new BitfabError(
|
|
3752
|
+
"Invalid selective replay assertion requirements."
|
|
3753
|
+
);
|
|
3754
|
+
}
|
|
3755
|
+
assertionIds.add(assertion.id);
|
|
3756
|
+
if (target === null) {
|
|
3757
|
+
wholeTrace = true;
|
|
3758
|
+
} else if (target.kind === "span") {
|
|
3759
|
+
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)) {
|
|
3760
|
+
throw new BitfabError("Invalid selective replay assertion target.");
|
|
3761
|
+
}
|
|
3762
|
+
this.assertionNames.add(target.name);
|
|
3763
|
+
const count = plan.nodes.filter(
|
|
3764
|
+
(node) => node.spanName === target.name
|
|
3765
|
+
).length;
|
|
3766
|
+
if (count <= (typeof target.occurrence === "number" ? target.occurrence : 0)) {
|
|
3767
|
+
this.unresolvedAssertions.push(assertion.id);
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
this.fullTraceForAssertions = wholeTrace || this.unresolvedAssertions.length > 0;
|
|
3772
|
+
const recordedKeys = new Set(plan.nodes.map(replayNodeKey));
|
|
3773
|
+
const unresolved = options.mustRun.filter(
|
|
3774
|
+
(node) => !recordedKeys.has(replayNodeKey(node))
|
|
3775
|
+
);
|
|
3776
|
+
if (JSON.stringify(unresolved.map(replayNodeKey).sort()) !== JSON.stringify(plan.unresolvedMustRun.map(replayNodeKey).sort())) {
|
|
3777
|
+
throw new BitfabError(
|
|
3778
|
+
"Selective replay plan has incomplete mustRun coverage."
|
|
3779
|
+
);
|
|
3780
|
+
}
|
|
3781
|
+
for (const node of plan.nodes) {
|
|
3782
|
+
const ancestors = /* @__PURE__ */ new Set([node.id]);
|
|
3783
|
+
let requiredByManifest = this.required.has(replayNodeKey(node));
|
|
3784
|
+
let requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(node.spanName);
|
|
3785
|
+
let parentId = node.parentId;
|
|
3786
|
+
while (parentId !== null) {
|
|
3787
|
+
const parent = this.nodes.get(parentId);
|
|
3788
|
+
if (!parent || ancestors.has(parentId) || node.mustRun && !parent.mustRun) {
|
|
3789
|
+
throw new BitfabError("Invalid selective replay ancestry.");
|
|
3790
|
+
}
|
|
3791
|
+
ancestors.add(parentId);
|
|
3792
|
+
requiredByManifest || (requiredByManifest = this.required.has(replayNodeKey(parent)));
|
|
3793
|
+
requiredForAssertion || (requiredForAssertion = this.assertionNames.has(parent.spanName));
|
|
3794
|
+
parentId = parent.parentId;
|
|
3795
|
+
}
|
|
3796
|
+
if (!ancestors.has(plan.rootId) || (requiredByManifest || unresolved.length > 0) && !node.mustRun) {
|
|
3797
|
+
throw new BitfabError("Selective replay plan could hide required code.");
|
|
3798
|
+
}
|
|
3799
|
+
if (requiredForAssertion && !node.mustRun) {
|
|
3800
|
+
throw new BitfabError(
|
|
3801
|
+
"Selective replay plan could hide assertion evidence."
|
|
3802
|
+
);
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
enter(params) {
|
|
3807
|
+
const identity = {
|
|
3808
|
+
traceFunctionKey: params.traceFunctionKey,
|
|
3809
|
+
spanName: params.spanName
|
|
3810
|
+
};
|
|
3811
|
+
const key = replayNodeKey(identity);
|
|
3812
|
+
const fingerprint = jsonFingerprint(params.inputs);
|
|
3813
|
+
let node;
|
|
3814
|
+
let matchReason = "unmatched-call";
|
|
3815
|
+
if (params.parentSpanId === null) {
|
|
3816
|
+
node = this.nodes.get(this.plan.rootId);
|
|
3817
|
+
} else {
|
|
3818
|
+
const recordedParent = this.runtimeParents.get(params.parentSpanId);
|
|
3819
|
+
const consumed = this.consumed.get(params.parentSpanId) ?? /* @__PURE__ */ new Set();
|
|
3820
|
+
this.consumed.set(params.parentSpanId, consumed);
|
|
3821
|
+
const candidates = recordedParent ? (this.children.get(recordedParent) ?? []).filter(
|
|
3822
|
+
(candidate) => replayNodeKey(candidate) === key && !consumed.has(candidate.id)
|
|
3823
|
+
) : [];
|
|
3824
|
+
const exact = fingerprint === void 0 ? [] : candidates.filter(
|
|
3825
|
+
(candidate) => this.recordings.get(candidate.id)?.input === fingerprint
|
|
3826
|
+
);
|
|
3827
|
+
node = exact.length === 1 ? exact[0] : exact.length === 0 && candidates.length === 1 ? candidates[0] : void 0;
|
|
3828
|
+
if (node) {
|
|
3829
|
+
consumed.add(node.id);
|
|
3830
|
+
} else if (candidates.length > 0) {
|
|
3831
|
+
matchReason = "ambiguous-call";
|
|
3832
|
+
for (const candidate of candidates) {
|
|
3833
|
+
consumed.add(candidate.id);
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
this.runtimeParents.set(params.spanId, node?.id ?? null);
|
|
3838
|
+
const requiredByManifest = this.required.has(key) || params.parentSpanId !== null && this.requiredScopes.has(params.parentSpanId);
|
|
3839
|
+
if (requiredByManifest) {
|
|
3840
|
+
this.requiredScopes.add(params.spanId);
|
|
3841
|
+
}
|
|
3842
|
+
const requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(params.spanName) || params.parentSpanId !== null && this.assertionScopes.has(params.parentSpanId);
|
|
3843
|
+
if (requiredForAssertion) {
|
|
3844
|
+
this.assertionScopes.add(params.spanId);
|
|
3845
|
+
}
|
|
3846
|
+
const mustRun = params.parentSpanId === null || requiredByManifest || node?.mustRun === true || requiredForAssertion || this.plan.unresolvedMustRun.length > 0;
|
|
3847
|
+
const recording = node === void 0 ? void 0 : this.recordings.get(node.id);
|
|
3848
|
+
const equalInputs = fingerprint !== void 0 && recording?.input === fingerprint;
|
|
3849
|
+
const reusable = recording?.output !== void 0 && equalInputs && params.canReuseOutput;
|
|
3850
|
+
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;
|
|
3851
|
+
const decision = (action, why) => {
|
|
3852
|
+
this.decisions.push({
|
|
3853
|
+
...identity,
|
|
3854
|
+
originalSpanId: node?.id ?? null,
|
|
3855
|
+
action,
|
|
3856
|
+
reason: why
|
|
3857
|
+
});
|
|
3858
|
+
};
|
|
3859
|
+
if (params.safetyMock && (mustRun || !reusable)) {
|
|
3860
|
+
decision("blocked", `safety-conflict:${reason}`);
|
|
3861
|
+
this.failure = new BitfabError(
|
|
3862
|
+
`Selective replay cannot execute or safely reuse safety-mocked node ${params.spanName} (${reason}).`
|
|
3863
|
+
);
|
|
3864
|
+
throw this.failure;
|
|
3865
|
+
}
|
|
3866
|
+
if (!mustRun && reusable && (params.safetyMock || node?.reusable === true && params.reuseAllowed)) {
|
|
3867
|
+
decision("mock", params.safetyMock ? "safety-mock" : "unchanged-inputs");
|
|
3868
|
+
return {
|
|
3869
|
+
mock: true,
|
|
3870
|
+
output: JSON.parse(recording.output)
|
|
3871
|
+
};
|
|
3872
|
+
}
|
|
3873
|
+
decision("run", reason);
|
|
3874
|
+
this.observedNames.set(
|
|
3875
|
+
params.spanName,
|
|
3876
|
+
(this.observedNames.get(params.spanName) ?? 0) + 1
|
|
3877
|
+
);
|
|
3878
|
+
if (this.required.has(key)) {
|
|
3879
|
+
this.reached.add(key);
|
|
3880
|
+
}
|
|
3881
|
+
return { mock: false };
|
|
3882
|
+
}
|
|
3883
|
+
assertNoSafetyConflict() {
|
|
3884
|
+
if (this.failure) {
|
|
3885
|
+
throw this.failure;
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
report() {
|
|
3889
|
+
return {
|
|
3890
|
+
decisions: this.decisions,
|
|
3891
|
+
mustRunNotReached: this.options.mustRun.filter(
|
|
3892
|
+
(node) => !this.reached.has(replayNodeKey(node))
|
|
3893
|
+
),
|
|
3894
|
+
unresolvedMustRun: this.plan.unresolvedMustRun,
|
|
3895
|
+
assertionIds: this.plan.assertions.map((assertion) => assertion.id),
|
|
3896
|
+
unresolvedAssertions: this.unresolvedAssertions,
|
|
3897
|
+
assertionTargetsNotReached: this.plan.assertions.flatMap(
|
|
3898
|
+
({ id, target }) => target?.kind === "span" && (this.observedNames.get(target.name) ?? 0) <= (typeof target.occurrence === "number" ? target.occurrence : 0) ? [id] : []
|
|
3899
|
+
)
|
|
3900
|
+
};
|
|
3901
|
+
}
|
|
3902
|
+
};
|
|
3903
|
+
}
|
|
3904
|
+
});
|
|
3905
|
+
|
|
3577
3906
|
// src/replay.ts
|
|
3578
3907
|
var replay_exports = {};
|
|
3579
3908
|
__export(replay_exports, {
|
|
@@ -3593,6 +3922,88 @@ function expandReplayWork(serverItems, attempts) {
|
|
|
3593
3922
|
(_, attempt) => serverItems.map((serverItem) => ({ serverItem, attempt }))
|
|
3594
3923
|
).flat();
|
|
3595
3924
|
}
|
|
3925
|
+
function validateResume(resume, options) {
|
|
3926
|
+
if (resume.trim().length === 0) {
|
|
3927
|
+
throw new BitfabError("resume must be the ID of the experiment to resume.");
|
|
3928
|
+
}
|
|
3929
|
+
const conflicting = RESUME_CONFLICTING_OPTIONS.filter(
|
|
3930
|
+
(name) => options[name] !== void 0 && options[name] !== false
|
|
3931
|
+
);
|
|
3932
|
+
if (conflicting.length > 0) {
|
|
3933
|
+
throw new BitfabError(
|
|
3934
|
+
`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.`
|
|
3935
|
+
);
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
async function resumeReplaySession(httpClient, experimentId, traceFunctionKey, codeChangeFiles, force, requestedDbBranch) {
|
|
3939
|
+
const response = await httpClient.resumeReplay(
|
|
3940
|
+
experimentId,
|
|
3941
|
+
traceFunctionKey,
|
|
3942
|
+
codeChangeFiles,
|
|
3943
|
+
force
|
|
3944
|
+
);
|
|
3945
|
+
const work = response.work.map(({ itemIndex, attempt }) => {
|
|
3946
|
+
const serverItem = response.items[itemIndex];
|
|
3947
|
+
if (serverItem === void 0) {
|
|
3948
|
+
throw new BitfabError(
|
|
3949
|
+
`Replay resume for experiment ${response.experimentId} named item ${itemIndex}, but the server returned only ${response.items.length} item(s).`
|
|
3950
|
+
);
|
|
3951
|
+
}
|
|
3952
|
+
return { serverItem, attempt };
|
|
3953
|
+
});
|
|
3954
|
+
if (response.skippedOriginalTraceIds.length > 0) {
|
|
3955
|
+
try {
|
|
3956
|
+
console.warn(
|
|
3957
|
+
`Bitfab: ${response.skippedOriginalTraceIds.length} trace(s) selected when experiment ${response.experimentId} started no longer exist, so resume skips them: ${response.skippedOriginalTraceIds.join(", ")}`
|
|
3958
|
+
);
|
|
3959
|
+
} catch {
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
if (requestedDbBranch !== void 0 && dbBranchEnabled(requestedDbBranch) !== response.includeDbBranchLease) {
|
|
3963
|
+
try {
|
|
3964
|
+
console.warn(
|
|
3965
|
+
`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.`
|
|
3966
|
+
);
|
|
3967
|
+
} catch {
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
return {
|
|
3971
|
+
experimentId: response.experimentId,
|
|
3972
|
+
experimentUrl: response.experimentUrl,
|
|
3973
|
+
attempts: response.attempts,
|
|
3974
|
+
work,
|
|
3975
|
+
finished: response.finished,
|
|
3976
|
+
includeDbBranchLease: response.includeDbBranchLease,
|
|
3977
|
+
dbBranchSettings: response.dbBranchSettings ?? void 0
|
|
3978
|
+
};
|
|
3979
|
+
}
|
|
3980
|
+
function carriedOverReplayItem(finished) {
|
|
3981
|
+
return {
|
|
3982
|
+
traceId: finished.traceId,
|
|
3983
|
+
originalTraceId: finished.originalTraceId,
|
|
3984
|
+
originalSpanId: "",
|
|
3985
|
+
sourceTraceId: finished.originalTraceId,
|
|
3986
|
+
sourceSpanId: "",
|
|
3987
|
+
attempt: finished.attempt,
|
|
3988
|
+
input: [],
|
|
3989
|
+
result: void 0,
|
|
3990
|
+
originalOutput: void 0,
|
|
3991
|
+
error: finished.hasError ? CARRIED_OVER_ERROR : null,
|
|
3992
|
+
traceError: null,
|
|
3993
|
+
replayError: null,
|
|
3994
|
+
durationMs: null,
|
|
3995
|
+
originalDurationMs: null,
|
|
3996
|
+
originalTokens: null,
|
|
3997
|
+
originalModel: null,
|
|
3998
|
+
tokens: null,
|
|
3999
|
+
model: null,
|
|
4000
|
+
dbSnapshotRef: null,
|
|
4001
|
+
dbBranchTimings: null,
|
|
4002
|
+
traceOutline: null,
|
|
4003
|
+
originalTraceOutline: null,
|
|
4004
|
+
carriedOver: true
|
|
4005
|
+
};
|
|
4006
|
+
}
|
|
3596
4007
|
function resolveAttempts(attempts) {
|
|
3597
4008
|
if (attempts === void 0) {
|
|
3598
4009
|
return 1;
|
|
@@ -3747,7 +4158,7 @@ function buildMockTree(rootNode) {
|
|
|
3747
4158
|
}
|
|
3748
4159
|
return { spans };
|
|
3749
4160
|
}
|
|
3750
|
-
async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
|
|
4161
|
+
async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun, selectiveOptions) {
|
|
3751
4162
|
let lease = includeDbBranchLease ? serverItem.dbBranchLease : void 0;
|
|
3752
4163
|
let leaseError = includeDbBranchLease ? serverItem.dbBranchLeaseError : void 0;
|
|
3753
4164
|
let dbSnapshotRef = serverItem.dbSnapshotRef;
|
|
@@ -3760,6 +4171,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3760
4171
|
let replayError = null;
|
|
3761
4172
|
let replayDurationMs = null;
|
|
3762
4173
|
let replayStarted = null;
|
|
4174
|
+
let selectiveReplay;
|
|
3763
4175
|
const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
|
|
3764
4176
|
const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
|
|
3765
4177
|
try {
|
|
@@ -3792,6 +4204,17 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3792
4204
|
originalTraceId
|
|
3793
4205
|
);
|
|
3794
4206
|
}
|
|
4207
|
+
if (selectiveOptions !== void 0) {
|
|
4208
|
+
if (serverItem.selectiveReplayPlan === void 0) {
|
|
4209
|
+
throw new BitfabError(
|
|
4210
|
+
"Server did not return a selective replay plan. Update the server or disable experimentalSelectiveReplay."
|
|
4211
|
+
);
|
|
4212
|
+
}
|
|
4213
|
+
selectiveReplay = new SelectiveReplayRuntime(
|
|
4214
|
+
serverItem.selectiveReplayPlan,
|
|
4215
|
+
selectiveOptions
|
|
4216
|
+
);
|
|
4217
|
+
}
|
|
3795
4218
|
const span = await httpClient.getExternalSpan(originalSpanId, {
|
|
3796
4219
|
view: "replay"
|
|
3797
4220
|
});
|
|
@@ -3811,7 +4234,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3811
4234
|
});
|
|
3812
4235
|
}
|
|
3813
4236
|
const hasOverrides = resolvedOverrides.length > 0;
|
|
3814
|
-
const needTree = mockStrategy === "all" || mockStrategy === "marked" || hasOverrides;
|
|
4237
|
+
const needTree = selectiveReplay === void 0 && (mockStrategy === "all" || mockStrategy === "marked" || hasOverrides);
|
|
3815
4238
|
const includeOutputs = mockStrategy === "all";
|
|
3816
4239
|
let mockTree;
|
|
3817
4240
|
if (needTree) {
|
|
@@ -3886,6 +4309,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3886
4309
|
sourceBitfabTraceId: originalTraceId,
|
|
3887
4310
|
replayAttempt: attempt,
|
|
3888
4311
|
mockTree,
|
|
4312
|
+
selectiveReplay,
|
|
3889
4313
|
callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
|
|
3890
4314
|
mockStrategy,
|
|
3891
4315
|
mockOverrides: hasOverrides ? resolvedOverrides : void 0,
|
|
@@ -3897,6 +4321,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3897
4321
|
() => fn(...inputs)
|
|
3898
4322
|
);
|
|
3899
4323
|
result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
|
|
4324
|
+
selectiveReplay?.assertNoSafetyConflict();
|
|
3900
4325
|
replayDurationMs = Math.round(performance.now() - replayStarted);
|
|
3901
4326
|
} catch (e) {
|
|
3902
4327
|
if (replayStarted !== null) {
|
|
@@ -3948,6 +4373,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3948
4373
|
traceError,
|
|
3949
4374
|
replayError,
|
|
3950
4375
|
durationMs: replayDurationMs,
|
|
4376
|
+
...selectiveReplay === void 0 ? {} : { selectiveReplay: selectiveReplay.report() },
|
|
3951
4377
|
originalDurationMs,
|
|
3952
4378
|
originalTokens: serverItem.originalTokens ?? serverItem.tokens ?? null,
|
|
3953
4379
|
originalModel,
|
|
@@ -4045,6 +4471,21 @@ async function mapWithConcurrency2(tasks, maxConcurrency, onSettled, onStarted)
|
|
|
4045
4471
|
return results;
|
|
4046
4472
|
}
|
|
4047
4473
|
async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, registeredOverrides = []) {
|
|
4474
|
+
if (options?.resume !== void 0) {
|
|
4475
|
+
validateResume(options.resume, options);
|
|
4476
|
+
} else if (options?.force === true) {
|
|
4477
|
+
throw new BitfabError(
|
|
4478
|
+
"force only applies when resuming an experiment. Pass it together with resume."
|
|
4479
|
+
);
|
|
4480
|
+
}
|
|
4481
|
+
if (options?.experimentalSelectiveReplay !== void 0) {
|
|
4482
|
+
validateSelectiveReplayOptions(options.experimentalSelectiveReplay);
|
|
4483
|
+
if ((options.mock ?? "marked") !== "marked" || normalizeMockOverrides(options.mockOverride).length > 0 || registeredOverrides.length > 0) {
|
|
4484
|
+
throw new BitfabError(
|
|
4485
|
+
"experimentalSelectiveReplay requires mock: 'marked' and cannot be combined with mock overrides."
|
|
4486
|
+
);
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4048
4489
|
if (options?.traceIds !== void 0) {
|
|
4049
4490
|
if (options.traceIds.length === 0) {
|
|
4050
4491
|
throw new BitfabError("traceIds must contain at least one trace ID.");
|
|
@@ -4064,7 +4505,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4064
4505
|
} catch {
|
|
4065
4506
|
}
|
|
4066
4507
|
}
|
|
4067
|
-
const
|
|
4508
|
+
const requestedAttempts = resolveAttempts(options?.attempts);
|
|
4068
4509
|
const maxConcurrency = options?.maxConcurrency ?? 10;
|
|
4069
4510
|
if (!Number.isInteger(maxConcurrency) || maxConcurrency < 1) {
|
|
4070
4511
|
throw new BitfabError(
|
|
@@ -4083,40 +4524,60 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4083
4524
|
}
|
|
4084
4525
|
}
|
|
4085
4526
|
}
|
|
4086
|
-
const
|
|
4087
|
-
|
|
4088
|
-
|
|
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
|
-
|
|
4527
|
+
const includeDbBranchLease = dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true;
|
|
4528
|
+
let session;
|
|
4529
|
+
if (options?.resume === void 0) {
|
|
4530
|
+
const started2 = await httpClient.startReplay(
|
|
4531
|
+
traceFunctionKey,
|
|
4532
|
+
// limit is meaningless with explicit traceIds (the ID list determines
|
|
4533
|
+
// the count), so it's omitted from the request entirely.
|
|
4534
|
+
options?.traceIds ? void 0 : options?.limit ?? 5,
|
|
4535
|
+
options?.traceIds,
|
|
4536
|
+
options?.name,
|
|
4537
|
+
codeChangeDescription,
|
|
4538
|
+
codeChangeFiles,
|
|
4539
|
+
// A dry run executes nothing, so a branch would be provisioned (and billed)
|
|
4540
|
+
// for code that never runs, and a seeded source's refusal would fail the item
|
|
4541
|
+
// before it could report its resolved inputs.
|
|
4542
|
+
includeDbBranchLease,
|
|
4543
|
+
options?.experimentGroupId,
|
|
4544
|
+
datasetIds,
|
|
4545
|
+
options?.graderIds,
|
|
4546
|
+
resolveDbBranchSettings(options?.dbBranch),
|
|
4547
|
+
requestedAttempts,
|
|
4548
|
+
options?.adaptInputs !== void 0,
|
|
4549
|
+
options?.onlyWithAssertions,
|
|
4550
|
+
options?.judgeAssertions,
|
|
4551
|
+
options?.experimentalSelectiveReplay
|
|
4552
|
+
);
|
|
4553
|
+
if (started2.items.length === 0) {
|
|
4554
|
+
try {
|
|
4555
|
+
console.warn(
|
|
4556
|
+
`Bitfab: no traces matched "${traceFunctionKey}", so this replay ran nothing. Capture a trace, or seed one with seedTrace, before replaying.`
|
|
4557
|
+
);
|
|
4558
|
+
} catch {
|
|
4559
|
+
}
|
|
4118
4560
|
}
|
|
4561
|
+
session = {
|
|
4562
|
+
experimentId: started2.experimentId,
|
|
4563
|
+
experimentUrl: started2.experimentUrl,
|
|
4564
|
+
attempts: requestedAttempts,
|
|
4565
|
+
work: expandReplayWork(started2.items, requestedAttempts),
|
|
4566
|
+
finished: [],
|
|
4567
|
+
includeDbBranchLease,
|
|
4568
|
+
dbBranchSettings: resolveDbBranchSettings(options?.dbBranch)
|
|
4569
|
+
};
|
|
4570
|
+
} else {
|
|
4571
|
+
session = await resumeReplaySession(
|
|
4572
|
+
httpClient,
|
|
4573
|
+
options.resume,
|
|
4574
|
+
traceFunctionKey,
|
|
4575
|
+
codeChangeFiles,
|
|
4576
|
+
options.force,
|
|
4577
|
+
options.dbBranch
|
|
4578
|
+
);
|
|
4119
4579
|
}
|
|
4580
|
+
const { experimentId, experimentUrl, attempts } = session;
|
|
4120
4581
|
const mockStrategy = options?.mock ?? "marked";
|
|
4121
4582
|
const fullExperimentUrl = `${serviceUrl}${experimentUrl}`;
|
|
4122
4583
|
try {
|
|
@@ -4130,7 +4591,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4130
4591
|
...normalizeMockOverrides(options?.mockOverride),
|
|
4131
4592
|
...registeredOverrides
|
|
4132
4593
|
];
|
|
4133
|
-
const workItems =
|
|
4594
|
+
const workItems = session.work;
|
|
4595
|
+
const carriedOverItems = session.finished.map(
|
|
4596
|
+
(finished) => carriedOverReplayItem(finished)
|
|
4597
|
+
);
|
|
4134
4598
|
const replayedTraceIds = workItems.map(() => randomUuid());
|
|
4135
4599
|
httpClient.trackTraceDeliveries(replayedTraceIds);
|
|
4136
4600
|
const tasks = workItems.map(
|
|
@@ -4143,18 +4607,19 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4143
4607
|
resolvedOverrides,
|
|
4144
4608
|
replayedTraceIds[index],
|
|
4145
4609
|
attempt,
|
|
4146
|
-
|
|
4147
|
-
|
|
4610
|
+
session.includeDbBranchLease,
|
|
4611
|
+
session.dbBranchSettings,
|
|
4148
4612
|
options?.adaptInputs,
|
|
4149
|
-
options?.dryRun === true
|
|
4613
|
+
options?.dryRun === true,
|
|
4614
|
+
options?.experimentalSelectiveReplay
|
|
4150
4615
|
)
|
|
4151
4616
|
);
|
|
4152
|
-
const total = tasks.length;
|
|
4617
|
+
const total = tasks.length + carriedOverItems.length;
|
|
4153
4618
|
const onItemFinish = options?.onItemFinish ?? options?.onProgress;
|
|
4154
|
-
let completed =
|
|
4155
|
-
let started =
|
|
4156
|
-
let
|
|
4157
|
-
let
|
|
4619
|
+
let completed = carriedOverItems.length;
|
|
4620
|
+
let started = carriedOverItems.length;
|
|
4621
|
+
let errored = carriedOverItems.filter((item) => item.error !== null).length;
|
|
4622
|
+
let succeeded = completed - errored;
|
|
4158
4623
|
let flushInFlight = null;
|
|
4159
4624
|
let flushPending = false;
|
|
4160
4625
|
const flushFinishedItemTrace = () => {
|
|
@@ -4174,7 +4639,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4174
4639
|
}
|
|
4175
4640
|
return flushInFlight;
|
|
4176
4641
|
};
|
|
4177
|
-
const
|
|
4642
|
+
const ranItems = await mapWithConcurrency2(
|
|
4178
4643
|
tasks,
|
|
4179
4644
|
maxConcurrency,
|
|
4180
4645
|
async (item, index) => {
|
|
@@ -4219,6 +4684,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4219
4684
|
traceError: item.traceError,
|
|
4220
4685
|
replayError: item.replayError,
|
|
4221
4686
|
durationMs: item.durationMs,
|
|
4687
|
+
...item.selectiveReplay === void 0 ? {} : { selectiveReplay: item.selectiveReplay },
|
|
4222
4688
|
originalDurationMs: item.originalDurationMs,
|
|
4223
4689
|
originalTokens: item.originalTokens,
|
|
4224
4690
|
originalModel: item.originalModel,
|
|
@@ -4261,6 +4727,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4261
4727
|
}
|
|
4262
4728
|
} : void 0
|
|
4263
4729
|
);
|
|
4730
|
+
const resultItems = [...carriedOverItems, ...ranItems];
|
|
4264
4731
|
if (options?.dryRun === true) {
|
|
4265
4732
|
await httpClient.completeReplay(experimentId).catch(() => void 0);
|
|
4266
4733
|
const dryResult = {
|
|
@@ -4280,11 +4747,11 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4280
4747
|
experimentId,
|
|
4281
4748
|
fullExperimentUrl
|
|
4282
4749
|
);
|
|
4283
|
-
for (let index = 0; index <
|
|
4750
|
+
for (let index = 0; index < ranItems.length; index += 1) {
|
|
4284
4751
|
const localId = replayedTraceIds[index];
|
|
4285
4752
|
const readBack = localId ? deliveredTraceIds[localId] : void 0;
|
|
4286
4753
|
if (readBack !== void 0) {
|
|
4287
|
-
|
|
4754
|
+
ranItems[index].traceId = readBack;
|
|
4288
4755
|
}
|
|
4289
4756
|
}
|
|
4290
4757
|
const completeResult = await preserveReplayFailure(
|
|
@@ -4298,8 +4765,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4298
4765
|
if (serverTraceIds !== void 0) {
|
|
4299
4766
|
const missing = [];
|
|
4300
4767
|
let completedCount = 0;
|
|
4301
|
-
for (let index = 0; index <
|
|
4302
|
-
const item =
|
|
4768
|
+
for (let index = 0; index < ranItems.length; index += 1) {
|
|
4769
|
+
const item = ranItems[index];
|
|
4303
4770
|
const localId = replayedTraceIds[index];
|
|
4304
4771
|
const mapped = localId ? serverTraceIds[localId] : void 0;
|
|
4305
4772
|
item.traceId = item.traceId ?? mapped ?? null;
|
|
@@ -4385,7 +4852,7 @@ async function writeReplayResultFile(result) {
|
|
|
4385
4852
|
}
|
|
4386
4853
|
}
|
|
4387
4854
|
}
|
|
4388
|
-
var REPLAY_PERSISTENCE_TIMEOUT_MS, MAX_REPLAY_ATTEMPTS, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
|
|
4855
|
+
var REPLAY_PERSISTENCE_TIMEOUT_MS, MAX_REPLAY_ATTEMPTS, RESUME_CONFLICTING_OPTIONS, CARRIED_OVER_ERROR, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
|
|
4389
4856
|
var init_replay = __esm({
|
|
4390
4857
|
"src/replay.ts"() {
|
|
4391
4858
|
"use strict";
|
|
@@ -4396,9 +4863,22 @@ var init_replay = __esm({
|
|
|
4396
4863
|
init_mockOverride();
|
|
4397
4864
|
init_randomUuid();
|
|
4398
4865
|
init_replayContext();
|
|
4866
|
+
init_selectiveReplayRuntime();
|
|
4399
4867
|
init_serialize();
|
|
4400
4868
|
REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
4401
4869
|
MAX_REPLAY_ATTEMPTS = 100;
|
|
4870
|
+
RESUME_CONFLICTING_OPTIONS = [
|
|
4871
|
+
"traceIds",
|
|
4872
|
+
"limit",
|
|
4873
|
+
"datasetId",
|
|
4874
|
+
"datasetIds",
|
|
4875
|
+
"attempts",
|
|
4876
|
+
"graderIds",
|
|
4877
|
+
"onlyWithAssertions",
|
|
4878
|
+
"dryRun",
|
|
4879
|
+
"experimentalSelectiveReplay"
|
|
4880
|
+
];
|
|
4881
|
+
CARRIED_OVER_ERROR = "This item errored in an earlier run of this experiment, before it was resumed.";
|
|
4402
4882
|
BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
|
|
4403
4883
|
ReplayError = class extends BitfabError {
|
|
4404
4884
|
constructor(message, items, experimentId, experimentUrl, cause) {
|
|
@@ -9097,6 +9577,9 @@ var Bitfab = class {
|
|
|
9097
9577
|
...options.mockOnReplay !== void 0 && {
|
|
9098
9578
|
mockOnReplay: options.mockOnReplay
|
|
9099
9579
|
},
|
|
9580
|
+
...options.replayReusable !== void 0 && {
|
|
9581
|
+
replayReusable: options.replayReusable
|
|
9582
|
+
},
|
|
9100
9583
|
...options.finalize !== void 0 && { finalize: options.finalize }
|
|
9101
9584
|
};
|
|
9102
9585
|
}
|
|
@@ -9245,6 +9728,9 @@ var Bitfab = class {
|
|
|
9245
9728
|
...mockOnReplay !== void 0 && {
|
|
9246
9729
|
mockOnReplay
|
|
9247
9730
|
},
|
|
9731
|
+
...nodeConfiguration?.replayReusable !== void 0 && {
|
|
9732
|
+
replayReusable: nodeConfiguration.replayReusable
|
|
9733
|
+
},
|
|
9248
9734
|
...nodeConfiguration?.finalize !== void 0 && {
|
|
9249
9735
|
finalize: nodeConfiguration.finalize
|
|
9250
9736
|
}
|
|
@@ -10042,6 +10528,7 @@ var Bitfab = class {
|
|
|
10042
10528
|
instrumentation: options.instrumentation ?? "span",
|
|
10043
10529
|
functionId: options.functionId,
|
|
10044
10530
|
captureContent: options.captureContent ?? true,
|
|
10531
|
+
replayReusable: options.replayReusable,
|
|
10045
10532
|
autoTraceDefinition: options.autoTraceDefinition,
|
|
10046
10533
|
declaredNode: options.declaredNode === true,
|
|
10047
10534
|
nestedTrace: options.nestedTrace,
|
|
@@ -10161,6 +10648,27 @@ var Bitfab = class {
|
|
|
10161
10648
|
return result;
|
|
10162
10649
|
};
|
|
10163
10650
|
const replayCtxForMock = getReplayContext();
|
|
10651
|
+
if (replayCtxForMock?.selectiveReplay) {
|
|
10652
|
+
const decision = replayCtxForMock.selectiveReplay.enter({
|
|
10653
|
+
traceFunctionKey,
|
|
10654
|
+
spanName: baseSpanParams.spanName ?? traceFunctionKey,
|
|
10655
|
+
spanId,
|
|
10656
|
+
parentSpanId,
|
|
10657
|
+
inputs: args,
|
|
10658
|
+
safetyMock: !isRootSpan && options.mockOnReplay === true,
|
|
10659
|
+
reuseAllowed: options.replayReusable === true,
|
|
10660
|
+
canReuseOutput: options.finalize === void 0 && options.replayOutput === void 0 && !fn.constructor.name.includes("Generator")
|
|
10661
|
+
});
|
|
10662
|
+
if (decision.mock) {
|
|
10663
|
+
void sendSpan({
|
|
10664
|
+
result: decision.output,
|
|
10665
|
+
mocked: true,
|
|
10666
|
+
mockTarget: "output",
|
|
10667
|
+
mockSource: "recorded"
|
|
10668
|
+
});
|
|
10669
|
+
return fnReturnsPromise ? Promise.resolve(decision.output) : decision.output;
|
|
10670
|
+
}
|
|
10671
|
+
}
|
|
10164
10672
|
if (replayCtxForMock?.mockTree && !isRootSpan) {
|
|
10165
10673
|
const counters = replayCtxForMock.callCounters;
|
|
10166
10674
|
const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
|
|
@@ -10584,6 +11092,9 @@ var Bitfab = class {
|
|
|
10584
11092
|
span_origin: makeSpanOrigin(params.instrumentation),
|
|
10585
11093
|
span_data: {
|
|
10586
11094
|
name: params.spanName,
|
|
11095
|
+
...params.replayReusable !== void 0 && {
|
|
11096
|
+
replay_reusable: params.replayReusable
|
|
11097
|
+
},
|
|
10587
11098
|
type: params.spanType,
|
|
10588
11099
|
...params.functionId !== void 0 && {
|
|
10589
11100
|
function_id: params.functionId,
|