@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.
Files changed (41) hide show
  1. package/dist/autoTrace.cjs.map +1 -1
  2. package/dist/autoTrace.d.cts +1 -0
  3. package/dist/autoTrace.d.ts +1 -0
  4. package/dist/autoTrace.js +1 -1
  5. package/dist/{chunk-X5DRPZJJ.js → chunk-34UFYAE6.js} +306 -7
  6. package/dist/chunk-34UFYAE6.js.map +1 -0
  7. package/dist/{chunk-SJPMWVJ3.js → chunk-65QKCHI2.js} +7 -3
  8. package/dist/chunk-65QKCHI2.js.map +1 -0
  9. package/dist/{chunk-IR5KCKHF.js → chunk-7BU5LUMR.js} +6 -3
  10. package/dist/chunk-7BU5LUMR.js.map +1 -0
  11. package/dist/{chunk-BMJNDIVA.js → chunk-K4XCWQBC.js} +1 -1
  12. package/dist/chunk-K4XCWQBC.js.map +1 -0
  13. package/dist/{chunk-DSVQPSAL.js → chunk-OWXP3YXA.js} +3 -3
  14. package/dist/chunk-OWXP3YXA.js.map +1 -0
  15. package/dist/{chunk-HNEKBDI7.js → chunk-TZSTKVZF.js} +38 -7
  16. package/dist/chunk-TZSTKVZF.js.map +1 -0
  17. package/dist/{http-OEABHRHY.js → http-VTIALK4P.js} +2 -2
  18. package/dist/{http-RNI6PKQ3.js → http-ZWENQXQA.js} +2 -2
  19. package/dist/index.cjs +355 -7
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +120 -3
  22. package/dist/index.d.ts +120 -3
  23. package/dist/index.js +4 -4
  24. package/dist/node.cjs +355 -7
  25. package/dist/node.cjs.map +1 -1
  26. package/dist/node.d.cts +1 -1
  27. package/dist/node.d.ts +1 -1
  28. package/dist/node.js +4 -4
  29. package/dist/{replay-3ERB6W6N.js → replay-YQG3OGDN.js} +3 -3
  30. package/dist/replayCli.js +2 -2
  31. package/dist/seedCli.js +2 -2
  32. package/package.json +1 -1
  33. package/dist/chunk-BMJNDIVA.js.map +0 -1
  34. package/dist/chunk-DSVQPSAL.js.map +0 -1
  35. package/dist/chunk-HNEKBDI7.js.map +0 -1
  36. package/dist/chunk-IR5KCKHF.js.map +0 -1
  37. package/dist/chunk-SJPMWVJ3.js.map +0 -1
  38. package/dist/chunk-X5DRPZJJ.js.map +0 -1
  39. /package/dist/{http-OEABHRHY.js.map → http-VTIALK4P.js.map} +0 -0
  40. /package/dist/{http-RNI6PKQ3.js.map → http-ZWENQXQA.js.map} +0 -0
  41. /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-IR5KCKHF.js";
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-OEABHRHY.js.map
21
+ //# sourceMappingURL=http-VTIALK4P.js.map
@@ -7,7 +7,7 @@ import {
7
7
  flushTraces,
8
8
  parseRetryAfterMs,
9
9
  serializePayloadBody
10
- } from "./chunk-SJPMWVJ3.js";
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-RNI6PKQ3.js.map
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.2";
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,