@bitfab/sdk 0.61.2 → 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-X5DRPZJJ.js → chunk-34UFYAE6.js} +306 -7
- package/dist/chunk-34UFYAE6.js.map +1 -0
- package/dist/{chunk-SJPMWVJ3.js → chunk-65QKCHI2.js} +7 -3
- package/dist/chunk-65QKCHI2.js.map +1 -0
- package/dist/{chunk-IR5KCKHF.js → chunk-7BU5LUMR.js} +6 -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-DSVQPSAL.js → chunk-OWXP3YXA.js} +3 -3
- package/dist/chunk-OWXP3YXA.js.map +1 -0
- package/dist/{chunk-HNEKBDI7.js → chunk-TZSTKVZF.js} +38 -7
- package/dist/chunk-TZSTKVZF.js.map +1 -0
- package/dist/{http-OEABHRHY.js → http-VTIALK4P.js} +2 -2
- package/dist/{http-RNI6PKQ3.js → http-ZWENQXQA.js} +2 -2
- package/dist/index.cjs +355 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -3
- package/dist/index.d.ts +120 -3
- package/dist/index.js +4 -4
- package/dist/node.cjs +355 -7
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +4 -4
- package/dist/{replay-3ERB6W6N.js → replay-YQG3OGDN.js} +3 -3
- package/dist/replayCli.js +2 -2
- package/dist/seedCli.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-BMJNDIVA.js.map +0 -1
- package/dist/chunk-DSVQPSAL.js.map +0 -1
- package/dist/chunk-HNEKBDI7.js.map +0 -1
- package/dist/chunk-IR5KCKHF.js.map +0 -1
- package/dist/chunk-SJPMWVJ3.js.map +0 -1
- package/dist/chunk-X5DRPZJJ.js.map +0 -1
- /package/dist/{http-OEABHRHY.js.map → http-VTIALK4P.js.map} +0 -0
- /package/dist/{http-RNI6PKQ3.js.map → http-ZWENQXQA.js.map} +0 -0
- /package/dist/{replay-3ERB6W6N.js.map → replay-YQG3OGDN.js.map} +0 -0
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
flushTraces,
|
|
8
8
|
parseRetryAfterMs,
|
|
9
9
|
serializePayloadBody
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-7BU5LUMR.js";
|
|
11
11
|
export {
|
|
12
12
|
BitfabError,
|
|
13
13
|
HttpClient,
|
|
@@ -18,4 +18,4 @@ export {
|
|
|
18
18
|
parseRetryAfterMs,
|
|
19
19
|
serializePayloadBody
|
|
20
20
|
};
|
|
21
|
-
//# sourceMappingURL=http-
|
|
21
|
+
//# sourceMappingURL=http-VTIALK4P.js.map
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
flushTraces,
|
|
8
8
|
parseRetryAfterMs,
|
|
9
9
|
serializePayloadBody
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-65QKCHI2.js";
|
|
11
11
|
import "./chunk-BAOIRE5T.js";
|
|
12
12
|
export {
|
|
13
13
|
BitfabError,
|
|
@@ -19,4 +19,4 @@ export {
|
|
|
19
19
|
parseRetryAfterMs,
|
|
20
20
|
serializePayloadBody
|
|
21
21
|
};
|
|
22
|
-
//# sourceMappingURL=http-
|
|
22
|
+
//# sourceMappingURL=http-ZWENQXQA.js.map
|
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
|
}
|
|
@@ -3607,6 +3611,291 @@ var init_integrationFixture = __esm({
|
|
|
3607
3611
|
}
|
|
3608
3612
|
});
|
|
3609
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
|
+
|
|
3610
3899
|
// src/replay.ts
|
|
3611
3900
|
var replay_exports = {};
|
|
3612
3901
|
__export(replay_exports, {
|
|
@@ -3862,7 +4151,7 @@ function buildMockTree(rootNode) {
|
|
|
3862
4151
|
}
|
|
3863
4152
|
return { spans };
|
|
3864
4153
|
}
|
|
3865
|
-
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) {
|
|
3866
4155
|
let lease = includeDbBranchLease ? serverItem.dbBranchLease : void 0;
|
|
3867
4156
|
let leaseError = includeDbBranchLease ? serverItem.dbBranchLeaseError : void 0;
|
|
3868
4157
|
let dbSnapshotRef = serverItem.dbSnapshotRef;
|
|
@@ -3875,6 +4164,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3875
4164
|
let replayError = null;
|
|
3876
4165
|
let replayDurationMs = null;
|
|
3877
4166
|
let replayStarted = null;
|
|
4167
|
+
let selectiveReplay;
|
|
3878
4168
|
const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
|
|
3879
4169
|
const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
|
|
3880
4170
|
try {
|
|
@@ -3907,6 +4197,17 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3907
4197
|
originalTraceId
|
|
3908
4198
|
);
|
|
3909
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
|
+
}
|
|
3910
4211
|
const span = await httpClient.getExternalSpan(originalSpanId, {
|
|
3911
4212
|
view: "replay"
|
|
3912
4213
|
});
|
|
@@ -3926,7 +4227,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3926
4227
|
});
|
|
3927
4228
|
}
|
|
3928
4229
|
const hasOverrides = resolvedOverrides.length > 0;
|
|
3929
|
-
const needTree = mockStrategy === "all" || mockStrategy === "marked" || hasOverrides;
|
|
4230
|
+
const needTree = selectiveReplay === void 0 && (mockStrategy === "all" || mockStrategy === "marked" || hasOverrides);
|
|
3930
4231
|
const includeOutputs = mockStrategy === "all";
|
|
3931
4232
|
let mockTree;
|
|
3932
4233
|
if (needTree) {
|
|
@@ -4001,6 +4302,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
4001
4302
|
sourceBitfabTraceId: originalTraceId,
|
|
4002
4303
|
replayAttempt: attempt,
|
|
4003
4304
|
mockTree,
|
|
4305
|
+
selectiveReplay,
|
|
4004
4306
|
callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
|
|
4005
4307
|
mockStrategy,
|
|
4006
4308
|
mockOverrides: hasOverrides ? resolvedOverrides : void 0,
|
|
@@ -4012,6 +4314,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
4012
4314
|
() => fn(...inputs)
|
|
4013
4315
|
);
|
|
4014
4316
|
result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
|
|
4317
|
+
selectiveReplay?.assertNoSafetyConflict();
|
|
4015
4318
|
replayDurationMs = Math.round(performance.now() - replayStarted);
|
|
4016
4319
|
} catch (e) {
|
|
4017
4320
|
if (replayStarted !== null) {
|
|
@@ -4063,6 +4366,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
4063
4366
|
traceError,
|
|
4064
4367
|
replayError,
|
|
4065
4368
|
durationMs: replayDurationMs,
|
|
4369
|
+
...selectiveReplay === void 0 ? {} : { selectiveReplay: selectiveReplay.report() },
|
|
4066
4370
|
originalDurationMs,
|
|
4067
4371
|
originalTokens: serverItem.originalTokens ?? serverItem.tokens ?? null,
|
|
4068
4372
|
originalModel,
|
|
@@ -4167,6 +4471,14 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4167
4471
|
"force only applies when resuming an experiment. Pass it together with resume."
|
|
4168
4472
|
);
|
|
4169
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
|
+
}
|
|
4170
4482
|
if (options?.traceIds !== void 0) {
|
|
4171
4483
|
if (options.traceIds.length === 0) {
|
|
4172
4484
|
throw new BitfabError("traceIds must contain at least one trace ID.");
|
|
@@ -4228,7 +4540,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4228
4540
|
requestedAttempts,
|
|
4229
4541
|
options?.adaptInputs !== void 0,
|
|
4230
4542
|
options?.onlyWithAssertions,
|
|
4231
|
-
options?.judgeAssertions
|
|
4543
|
+
options?.judgeAssertions,
|
|
4544
|
+
options?.experimentalSelectiveReplay
|
|
4232
4545
|
);
|
|
4233
4546
|
if (started2.items.length === 0) {
|
|
4234
4547
|
try {
|
|
@@ -4290,7 +4603,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4290
4603
|
session.includeDbBranchLease,
|
|
4291
4604
|
session.dbBranchSettings,
|
|
4292
4605
|
options?.adaptInputs,
|
|
4293
|
-
options?.dryRun === true
|
|
4606
|
+
options?.dryRun === true,
|
|
4607
|
+
options?.experimentalSelectiveReplay
|
|
4294
4608
|
)
|
|
4295
4609
|
);
|
|
4296
4610
|
const total = tasks.length + carriedOverItems.length;
|
|
@@ -4363,6 +4677,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4363
4677
|
traceError: item.traceError,
|
|
4364
4678
|
replayError: item.replayError,
|
|
4365
4679
|
durationMs: item.durationMs,
|
|
4680
|
+
...item.selectiveReplay === void 0 ? {} : { selectiveReplay: item.selectiveReplay },
|
|
4366
4681
|
originalDurationMs: item.originalDurationMs,
|
|
4367
4682
|
originalTokens: item.originalTokens,
|
|
4368
4683
|
originalModel: item.originalModel,
|
|
@@ -4541,6 +4856,7 @@ var init_replay = __esm({
|
|
|
4541
4856
|
init_mockOverride();
|
|
4542
4857
|
init_randomUuid();
|
|
4543
4858
|
init_replayContext();
|
|
4859
|
+
init_selectiveReplayRuntime();
|
|
4544
4860
|
init_serialize();
|
|
4545
4861
|
REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
4546
4862
|
MAX_REPLAY_ATTEMPTS = 100;
|
|
@@ -4552,7 +4868,8 @@ var init_replay = __esm({
|
|
|
4552
4868
|
"attempts",
|
|
4553
4869
|
"graderIds",
|
|
4554
4870
|
"onlyWithAssertions",
|
|
4555
|
-
"dryRun"
|
|
4871
|
+
"dryRun",
|
|
4872
|
+
"experimentalSelectiveReplay"
|
|
4556
4873
|
];
|
|
4557
4874
|
CARRIED_OVER_ERROR = "This item errored in an earlier run of this experiment, before it was resumed.";
|
|
4558
4875
|
BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
|
|
@@ -9246,6 +9563,9 @@ var Bitfab = class {
|
|
|
9246
9563
|
...options.mockOnReplay !== void 0 && {
|
|
9247
9564
|
mockOnReplay: options.mockOnReplay
|
|
9248
9565
|
},
|
|
9566
|
+
...options.replayReusable !== void 0 && {
|
|
9567
|
+
replayReusable: options.replayReusable
|
|
9568
|
+
},
|
|
9249
9569
|
...options.finalize !== void 0 && { finalize: options.finalize }
|
|
9250
9570
|
};
|
|
9251
9571
|
}
|
|
@@ -9394,6 +9714,9 @@ var Bitfab = class {
|
|
|
9394
9714
|
...mockOnReplay !== void 0 && {
|
|
9395
9715
|
mockOnReplay
|
|
9396
9716
|
},
|
|
9717
|
+
...nodeConfiguration?.replayReusable !== void 0 && {
|
|
9718
|
+
replayReusable: nodeConfiguration.replayReusable
|
|
9719
|
+
},
|
|
9397
9720
|
...nodeConfiguration?.finalize !== void 0 && {
|
|
9398
9721
|
finalize: nodeConfiguration.finalize
|
|
9399
9722
|
}
|
|
@@ -10191,6 +10514,7 @@ var Bitfab = class {
|
|
|
10191
10514
|
instrumentation: options.instrumentation ?? "span",
|
|
10192
10515
|
functionId: options.functionId,
|
|
10193
10516
|
captureContent: options.captureContent ?? true,
|
|
10517
|
+
replayReusable: options.replayReusable,
|
|
10194
10518
|
autoTraceDefinition: options.autoTraceDefinition,
|
|
10195
10519
|
declaredNode: options.declaredNode === true,
|
|
10196
10520
|
nestedTrace: options.nestedTrace,
|
|
@@ -10310,6 +10634,27 @@ var Bitfab = class {
|
|
|
10310
10634
|
return result;
|
|
10311
10635
|
};
|
|
10312
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
|
+
}
|
|
10313
10658
|
if (replayCtxForMock?.mockTree && !isRootSpan) {
|
|
10314
10659
|
const counters = replayCtxForMock.callCounters;
|
|
10315
10660
|
const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
|
|
@@ -10733,6 +11078,9 @@ var Bitfab = class {
|
|
|
10733
11078
|
span_origin: makeSpanOrigin(params.instrumentation),
|
|
10734
11079
|
span_data: {
|
|
10735
11080
|
name: params.spanName,
|
|
11081
|
+
...params.replayReusable !== void 0 && {
|
|
11082
|
+
replay_reusable: params.replayReusable
|
|
11083
|
+
},
|
|
10736
11084
|
type: params.spanType,
|
|
10737
11085
|
...params.functionId !== void 0 && {
|
|
10738
11086
|
function_id: params.functionId,
|