bitfab 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-INA6W7OB.js → chunk-6IMUONDN.js} +7 -3
  6. package/dist/chunk-6IMUONDN.js.map +1 -0
  7. package/dist/{chunk-ONZFMRRF.js → chunk-HVEZHLVQ.js} +6 -3
  8. package/dist/chunk-HVEZHLVQ.js.map +1 -0
  9. package/dist/{chunk-A4UVO6BQ.js → chunk-IZUZ5NBB.js} +306 -7
  10. package/dist/chunk-IZUZ5NBB.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-LUOCT4HG.js → chunk-PAUXHTNF.js} +38 -7
  14. package/dist/chunk-PAUXHTNF.js.map +1 -0
  15. package/dist/{chunk-MTK6PZHA.js → chunk-WOHIAKZF.js} +3 -3
  16. package/dist/chunk-WOHIAKZF.js.map +1 -0
  17. package/dist/{http-KM6QWKAP.js → http-3IYU5Z54.js} +2 -2
  18. package/dist/{http-2R6MSRVA.js → http-BYICBOWP.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-6UUBJBLK.js → replay-SXHX5BBU.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-A4UVO6BQ.js.map +0 -1
  34. package/dist/chunk-BMJNDIVA.js.map +0 -1
  35. package/dist/chunk-INA6W7OB.js.map +0 -1
  36. package/dist/chunk-LUOCT4HG.js.map +0 -1
  37. package/dist/chunk-MTK6PZHA.js.map +0 -1
  38. package/dist/chunk-ONZFMRRF.js.map +0 -1
  39. /package/dist/{http-2R6MSRVA.js.map → http-3IYU5Z54.js.map} +0 -0
  40. /package/dist/{http-KM6QWKAP.js.map → http-BYICBOWP.js.map} +0 -0
  41. /package/dist/{replay-6UUBJBLK.js.map → replay-SXHX5BBU.js.map} +0 -0
package/dist/node.cjs CHANGED
@@ -97,7 +97,7 @@ var __version__, __packageName__;
97
97
  var init_version_generated = __esm({
98
98
  "src/version.generated.ts"() {
99
99
  "use strict";
100
- __version__ = "0.61.2";
100
+ __version__ = "0.61.3";
101
101
  __packageName__ = "bitfab";
102
102
  }
103
103
  });
@@ -1084,6 +1084,7 @@ var init_simulationPlan = __esm({
1084
1084
  "input_meta",
1085
1085
  "output",
1086
1086
  "output_meta",
1087
+ "replay_recording",
1087
1088
  "prompt"
1088
1089
  ];
1089
1090
  SimulationPlan = class {
@@ -2898,8 +2899,11 @@ var init_http = __esm({
2898
2899
  * Start a replay session by fetching historical traces.
2899
2900
  * Blocking call - creates an experiment and returns lightweight item references.
2900
2901
  */
2901
- async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions) {
2902
+ async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions, experimentalSelectiveReplay) {
2902
2903
  const payload = { traceFunctionKey };
2904
+ if (experimentalSelectiveReplay !== void 0) {
2905
+ payload.experimentalSelectiveReplay = experimentalSelectiveReplay;
2906
+ }
2903
2907
  if (limit !== void 0) {
2904
2908
  payload.limit = limit;
2905
2909
  }
@@ -3623,6 +3627,291 @@ var init_integrationFixture = __esm({
3623
3627
  }
3624
3628
  });
3625
3629
 
3630
+ // src/selectiveReplay.ts
3631
+ function replayNodeKey(node) {
3632
+ return JSON.stringify([node.traceFunctionKey, node.spanName]);
3633
+ }
3634
+ function jsonFingerprint(value) {
3635
+ const seen = /* @__PURE__ */ new Set();
3636
+ function encode(input) {
3637
+ if (input === null || typeof input === "boolean") {
3638
+ return JSON.stringify(input);
3639
+ }
3640
+ if (typeof input === "string" && !input.startsWith("<unserializable")) {
3641
+ return JSON.stringify(input);
3642
+ }
3643
+ if (typeof input === "number" && Number.isFinite(input) && !Object.is(input, -0)) {
3644
+ return JSON.stringify(input);
3645
+ }
3646
+ if (typeof input !== "object" || input === null || seen.has(input)) {
3647
+ throw new Error("Not a lossless JSON value");
3648
+ }
3649
+ seen.add(input);
3650
+ const prototype = Object.getPrototypeOf(input);
3651
+ if (prototype !== (Array.isArray(input) ? Array.prototype : Object.prototype)) {
3652
+ throw new Error("Not a plain JSON object");
3653
+ }
3654
+ if (Object.getOwnPropertySymbols(input).length > 0) {
3655
+ throw new Error("Symbol properties are not recorded");
3656
+ }
3657
+ const keys = Object.keys(input);
3658
+ if (Object.getOwnPropertyNames(input).length !== keys.length + (Array.isArray(input) ? 1 : 0)) {
3659
+ throw new Error("Non-enumerable properties are not recorded");
3660
+ }
3661
+ if (Array.isArray(input) && (keys.length !== input.length || keys.some((key, index) => key !== String(index)))) {
3662
+ throw new Error("Not a dense JSON array");
3663
+ }
3664
+ const properties = keys.map((key) => {
3665
+ const descriptor = Object.getOwnPropertyDescriptor(input, key);
3666
+ if (!descriptor || !("value" in descriptor)) {
3667
+ throw new Error("Accessor properties are not replay inputs");
3668
+ }
3669
+ return `${Array.isArray(input) ? "" : `${JSON.stringify(key)}:`}${encode(descriptor.value)}`;
3670
+ });
3671
+ return Array.isArray(input) ? `[${properties.join(",")}]` : `{${properties.join(",")}}`;
3672
+ }
3673
+ try {
3674
+ return encode(value);
3675
+ } catch {
3676
+ return void 0;
3677
+ }
3678
+ }
3679
+ var init_selectiveReplay = __esm({
3680
+ "src/selectiveReplay.ts"() {
3681
+ "use strict";
3682
+ }
3683
+ });
3684
+
3685
+ // src/selectiveReplayRuntime.ts
3686
+ function validateSelectiveReplayOptions(options) {
3687
+ const validNodes = (nodes) => Array.isArray(nodes) && nodes.length <= 100 && nodes.every(
3688
+ (node) => typeof node?.traceFunctionKey === "string" && node.traceFunctionKey.length > 0 && node.traceFunctionKey.length <= 500 && typeof node.spanName === "string" && node.spanName.length > 0 && node.spanName.length <= 500
3689
+ );
3690
+ if (!options || !validNodes(options.mustRun) || options.mustRun.length === 0 || Object.keys(options).some((key) => key !== "mustRun")) {
3691
+ throw new BitfabError(
3692
+ "experimentalSelectiveReplay accepts only 1\u2013100 mustRun with traceFunctionKey and spanName. Declare replayReusable on eligible spans, not reusableNodes in the replay request."
3693
+ );
3694
+ }
3695
+ }
3696
+ function policyKey(options) {
3697
+ return JSON.stringify(options.mustRun.map(replayNodeKey).sort());
3698
+ }
3699
+ var SelectiveReplayRuntime;
3700
+ var init_selectiveReplayRuntime = __esm({
3701
+ "src/selectiveReplayRuntime.ts"() {
3702
+ "use strict";
3703
+ init_errors();
3704
+ init_selectiveReplay();
3705
+ SelectiveReplayRuntime = class {
3706
+ constructor(plan, options) {
3707
+ this.plan = plan;
3708
+ this.options = options;
3709
+ this.nodes = /* @__PURE__ */ new Map();
3710
+ this.children = /* @__PURE__ */ new Map();
3711
+ this.runtimeParents = /* @__PURE__ */ new Map();
3712
+ this.consumed = /* @__PURE__ */ new Map();
3713
+ this.requiredScopes = /* @__PURE__ */ new Set();
3714
+ this.recordings = /* @__PURE__ */ new Map();
3715
+ this.reached = /* @__PURE__ */ new Set();
3716
+ this.decisions = [];
3717
+ this.assertionNames = /* @__PURE__ */ new Set();
3718
+ this.assertionScopes = /* @__PURE__ */ new Set();
3719
+ this.observedNames = /* @__PURE__ */ new Map();
3720
+ this.unresolvedAssertions = [];
3721
+ validateSelectiveReplayOptions(options);
3722
+ if (plan?.version !== 4 || !Array.isArray(plan.nodes) || plan.nodes.length === 0 || plan.nodes.length > 500 || !Array.isArray(plan.unresolvedMustRun) || !Array.isArray(plan.assertions) || !plan.options) {
3723
+ throw new BitfabError(
3724
+ "Server did not return a compatible selective replay plan. Update the server or disable experimentalSelectiveReplay."
3725
+ );
3726
+ }
3727
+ validateSelectiveReplayOptions(plan.options);
3728
+ if (policyKey(plan.options) !== policyKey(options)) {
3729
+ throw new BitfabError(
3730
+ "Selective replay plan does not match the requested mustRun nodes."
3731
+ );
3732
+ }
3733
+ this.required = new Set(options.mustRun.map(replayNodeKey));
3734
+ for (const node of plan.nodes) {
3735
+ if (typeof node.id !== "string" || this.nodes.has(node.id) || typeof node.traceFunctionKey !== "string" || typeof node.spanName !== "string" || typeof node.mustRun !== "boolean" || typeof node.reusable !== "boolean") {
3736
+ throw new BitfabError("Invalid selective replay node.");
3737
+ }
3738
+ this.nodes.set(node.id, node);
3739
+ if (node.recording !== void 0) {
3740
+ this.recordings.set(node.id, {
3741
+ input: node.recording.kwargs === void 0 ? jsonFingerprint(node.recording.input) : void 0,
3742
+ output: jsonFingerprint(node.recording.output)
3743
+ });
3744
+ }
3745
+ if (node.parentId !== null) {
3746
+ const siblings = this.children.get(node.parentId) ?? [];
3747
+ siblings.push(node);
3748
+ this.children.set(node.parentId, siblings);
3749
+ }
3750
+ }
3751
+ const root = this.nodes.get(plan.rootId);
3752
+ if (!root || root.parentId !== null || !root.mustRun) {
3753
+ throw new BitfabError("Selective replay requires a live recorded root.");
3754
+ }
3755
+ const assertionIds = /* @__PURE__ */ new Set();
3756
+ let wholeTrace = false;
3757
+ for (const assertion of plan.assertions) {
3758
+ const target = assertion?.target;
3759
+ if (typeof assertion?.id !== "string" || assertion.id.length === 0 || assertionIds.has(assertion.id) || target !== null && (typeof target !== "object" || target.kind !== "output" && target.kind !== "span")) {
3760
+ throw new BitfabError(
3761
+ "Invalid selective replay assertion requirements."
3762
+ );
3763
+ }
3764
+ assertionIds.add(assertion.id);
3765
+ if (target === null) {
3766
+ wholeTrace = true;
3767
+ } else if (target.kind === "span") {
3768
+ if (typeof target.name !== "string" || target.name.length === 0 || target.occurrence !== void 0 && target.occurrence !== "first" && target.occurrence !== "last" && !(Number.isInteger(target.occurrence) && typeof target.occurrence === "number" && target.occurrence >= 0)) {
3769
+ throw new BitfabError("Invalid selective replay assertion target.");
3770
+ }
3771
+ this.assertionNames.add(target.name);
3772
+ const count = plan.nodes.filter(
3773
+ (node) => node.spanName === target.name
3774
+ ).length;
3775
+ if (count <= (typeof target.occurrence === "number" ? target.occurrence : 0)) {
3776
+ this.unresolvedAssertions.push(assertion.id);
3777
+ }
3778
+ }
3779
+ }
3780
+ this.fullTraceForAssertions = wholeTrace || this.unresolvedAssertions.length > 0;
3781
+ const recordedKeys = new Set(plan.nodes.map(replayNodeKey));
3782
+ const unresolved = options.mustRun.filter(
3783
+ (node) => !recordedKeys.has(replayNodeKey(node))
3784
+ );
3785
+ if (JSON.stringify(unresolved.map(replayNodeKey).sort()) !== JSON.stringify(plan.unresolvedMustRun.map(replayNodeKey).sort())) {
3786
+ throw new BitfabError(
3787
+ "Selective replay plan has incomplete mustRun coverage."
3788
+ );
3789
+ }
3790
+ for (const node of plan.nodes) {
3791
+ const ancestors = /* @__PURE__ */ new Set([node.id]);
3792
+ let requiredByManifest = this.required.has(replayNodeKey(node));
3793
+ let requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(node.spanName);
3794
+ let parentId = node.parentId;
3795
+ while (parentId !== null) {
3796
+ const parent = this.nodes.get(parentId);
3797
+ if (!parent || ancestors.has(parentId) || node.mustRun && !parent.mustRun) {
3798
+ throw new BitfabError("Invalid selective replay ancestry.");
3799
+ }
3800
+ ancestors.add(parentId);
3801
+ requiredByManifest || (requiredByManifest = this.required.has(replayNodeKey(parent)));
3802
+ requiredForAssertion || (requiredForAssertion = this.assertionNames.has(parent.spanName));
3803
+ parentId = parent.parentId;
3804
+ }
3805
+ if (!ancestors.has(plan.rootId) || (requiredByManifest || unresolved.length > 0) && !node.mustRun) {
3806
+ throw new BitfabError("Selective replay plan could hide required code.");
3807
+ }
3808
+ if (requiredForAssertion && !node.mustRun) {
3809
+ throw new BitfabError(
3810
+ "Selective replay plan could hide assertion evidence."
3811
+ );
3812
+ }
3813
+ }
3814
+ }
3815
+ enter(params) {
3816
+ const identity = {
3817
+ traceFunctionKey: params.traceFunctionKey,
3818
+ spanName: params.spanName
3819
+ };
3820
+ const key = replayNodeKey(identity);
3821
+ const fingerprint = jsonFingerprint(params.inputs);
3822
+ let node;
3823
+ let matchReason = "unmatched-call";
3824
+ if (params.parentSpanId === null) {
3825
+ node = this.nodes.get(this.plan.rootId);
3826
+ } else {
3827
+ const recordedParent = this.runtimeParents.get(params.parentSpanId);
3828
+ const consumed = this.consumed.get(params.parentSpanId) ?? /* @__PURE__ */ new Set();
3829
+ this.consumed.set(params.parentSpanId, consumed);
3830
+ const candidates = recordedParent ? (this.children.get(recordedParent) ?? []).filter(
3831
+ (candidate) => replayNodeKey(candidate) === key && !consumed.has(candidate.id)
3832
+ ) : [];
3833
+ const exact = fingerprint === void 0 ? [] : candidates.filter(
3834
+ (candidate) => this.recordings.get(candidate.id)?.input === fingerprint
3835
+ );
3836
+ node = exact.length === 1 ? exact[0] : exact.length === 0 && candidates.length === 1 ? candidates[0] : void 0;
3837
+ if (node) {
3838
+ consumed.add(node.id);
3839
+ } else if (candidates.length > 0) {
3840
+ matchReason = "ambiguous-call";
3841
+ for (const candidate of candidates) {
3842
+ consumed.add(candidate.id);
3843
+ }
3844
+ }
3845
+ }
3846
+ this.runtimeParents.set(params.spanId, node?.id ?? null);
3847
+ const requiredByManifest = this.required.has(key) || params.parentSpanId !== null && this.requiredScopes.has(params.parentSpanId);
3848
+ if (requiredByManifest) {
3849
+ this.requiredScopes.add(params.spanId);
3850
+ }
3851
+ const requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(params.spanName) || params.parentSpanId !== null && this.assertionScopes.has(params.parentSpanId);
3852
+ if (requiredForAssertion) {
3853
+ this.assertionScopes.add(params.spanId);
3854
+ }
3855
+ const mustRun = params.parentSpanId === null || requiredByManifest || node?.mustRun === true || requiredForAssertion || this.plan.unresolvedMustRun.length > 0;
3856
+ const recording = node === void 0 ? void 0 : this.recordings.get(node.id);
3857
+ const equalInputs = fingerprint !== void 0 && recording?.input === fingerprint;
3858
+ const reusable = recording?.output !== void 0 && equalInputs && params.canReuseOutput;
3859
+ const reason = mustRun ? requiredByManifest ? "must-run" : requiredForAssertion ? "assertion" : node?.reason ?? "unresolved-must-run" : !node ? matchReason : !equalInputs ? "input-drift-or-missing-recording" : !params.canReuseOutput ? "unsupported-output-boundary" : !params.safetyMock && !params.reuseAllowed ? "reuse-not-declared" : node.reason;
3860
+ const decision = (action, why) => {
3861
+ this.decisions.push({
3862
+ ...identity,
3863
+ originalSpanId: node?.id ?? null,
3864
+ action,
3865
+ reason: why
3866
+ });
3867
+ };
3868
+ if (params.safetyMock && (mustRun || !reusable)) {
3869
+ decision("blocked", `safety-conflict:${reason}`);
3870
+ this.failure = new BitfabError(
3871
+ `Selective replay cannot execute or safely reuse safety-mocked node ${params.spanName} (${reason}).`
3872
+ );
3873
+ throw this.failure;
3874
+ }
3875
+ if (!mustRun && reusable && (params.safetyMock || node?.reusable === true && params.reuseAllowed)) {
3876
+ decision("mock", params.safetyMock ? "safety-mock" : "unchanged-inputs");
3877
+ return {
3878
+ mock: true,
3879
+ output: JSON.parse(recording.output)
3880
+ };
3881
+ }
3882
+ decision("run", reason);
3883
+ this.observedNames.set(
3884
+ params.spanName,
3885
+ (this.observedNames.get(params.spanName) ?? 0) + 1
3886
+ );
3887
+ if (this.required.has(key)) {
3888
+ this.reached.add(key);
3889
+ }
3890
+ return { mock: false };
3891
+ }
3892
+ assertNoSafetyConflict() {
3893
+ if (this.failure) {
3894
+ throw this.failure;
3895
+ }
3896
+ }
3897
+ report() {
3898
+ return {
3899
+ decisions: this.decisions,
3900
+ mustRunNotReached: this.options.mustRun.filter(
3901
+ (node) => !this.reached.has(replayNodeKey(node))
3902
+ ),
3903
+ unresolvedMustRun: this.plan.unresolvedMustRun,
3904
+ assertionIds: this.plan.assertions.map((assertion) => assertion.id),
3905
+ unresolvedAssertions: this.unresolvedAssertions,
3906
+ assertionTargetsNotReached: this.plan.assertions.flatMap(
3907
+ ({ id, target }) => target?.kind === "span" && (this.observedNames.get(target.name) ?? 0) <= (typeof target.occurrence === "number" ? target.occurrence : 0) ? [id] : []
3908
+ )
3909
+ };
3910
+ }
3911
+ };
3912
+ }
3913
+ });
3914
+
3626
3915
  // src/replay.ts
3627
3916
  var replay_exports = {};
3628
3917
  __export(replay_exports, {
@@ -3878,7 +4167,7 @@ function buildMockTree(rootNode) {
3878
4167
  }
3879
4168
  return { spans };
3880
4169
  }
3881
- async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
4170
+ async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun, selectiveOptions) {
3882
4171
  let lease = includeDbBranchLease ? serverItem.dbBranchLease : void 0;
3883
4172
  let leaseError = includeDbBranchLease ? serverItem.dbBranchLeaseError : void 0;
3884
4173
  let dbSnapshotRef = serverItem.dbSnapshotRef;
@@ -3891,6 +4180,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
3891
4180
  let replayError = null;
3892
4181
  let replayDurationMs = null;
3893
4182
  let replayStarted = null;
4183
+ let selectiveReplay;
3894
4184
  const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
3895
4185
  const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
3896
4186
  try {
@@ -3923,6 +4213,17 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
3923
4213
  originalTraceId
3924
4214
  );
3925
4215
  }
4216
+ if (selectiveOptions !== void 0) {
4217
+ if (serverItem.selectiveReplayPlan === void 0) {
4218
+ throw new BitfabError(
4219
+ "Server did not return a selective replay plan. Update the server or disable experimentalSelectiveReplay."
4220
+ );
4221
+ }
4222
+ selectiveReplay = new SelectiveReplayRuntime(
4223
+ serverItem.selectiveReplayPlan,
4224
+ selectiveOptions
4225
+ );
4226
+ }
3926
4227
  const span = await httpClient.getExternalSpan(originalSpanId, {
3927
4228
  view: "replay"
3928
4229
  });
@@ -3942,7 +4243,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
3942
4243
  });
3943
4244
  }
3944
4245
  const hasOverrides = resolvedOverrides.length > 0;
3945
- const needTree = mockStrategy === "all" || mockStrategy === "marked" || hasOverrides;
4246
+ const needTree = selectiveReplay === void 0 && (mockStrategy === "all" || mockStrategy === "marked" || hasOverrides);
3946
4247
  const includeOutputs = mockStrategy === "all";
3947
4248
  let mockTree;
3948
4249
  if (needTree) {
@@ -4017,6 +4318,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
4017
4318
  sourceBitfabTraceId: originalTraceId,
4018
4319
  replayAttempt: attempt,
4019
4320
  mockTree,
4321
+ selectiveReplay,
4020
4322
  callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
4021
4323
  mockStrategy,
4022
4324
  mockOverrides: hasOverrides ? resolvedOverrides : void 0,
@@ -4028,6 +4330,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
4028
4330
  () => fn(...inputs)
4029
4331
  );
4030
4332
  result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
4333
+ selectiveReplay?.assertNoSafetyConflict();
4031
4334
  replayDurationMs = Math.round(performance.now() - replayStarted);
4032
4335
  } catch (e) {
4033
4336
  if (replayStarted !== null) {
@@ -4079,6 +4382,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
4079
4382
  traceError,
4080
4383
  replayError,
4081
4384
  durationMs: replayDurationMs,
4385
+ ...selectiveReplay === void 0 ? {} : { selectiveReplay: selectiveReplay.report() },
4082
4386
  originalDurationMs,
4083
4387
  originalTokens: serverItem.originalTokens ?? serverItem.tokens ?? null,
4084
4388
  originalModel,
@@ -4183,6 +4487,14 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4183
4487
  "force only applies when resuming an experiment. Pass it together with resume."
4184
4488
  );
4185
4489
  }
4490
+ if (options?.experimentalSelectiveReplay !== void 0) {
4491
+ validateSelectiveReplayOptions(options.experimentalSelectiveReplay);
4492
+ if ((options.mock ?? "marked") !== "marked" || normalizeMockOverrides(options.mockOverride).length > 0 || registeredOverrides.length > 0) {
4493
+ throw new BitfabError(
4494
+ "experimentalSelectiveReplay requires mock: 'marked' and cannot be combined with mock overrides."
4495
+ );
4496
+ }
4497
+ }
4186
4498
  if (options?.traceIds !== void 0) {
4187
4499
  if (options.traceIds.length === 0) {
4188
4500
  throw new BitfabError("traceIds must contain at least one trace ID.");
@@ -4244,7 +4556,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4244
4556
  requestedAttempts,
4245
4557
  options?.adaptInputs !== void 0,
4246
4558
  options?.onlyWithAssertions,
4247
- options?.judgeAssertions
4559
+ options?.judgeAssertions,
4560
+ options?.experimentalSelectiveReplay
4248
4561
  );
4249
4562
  if (started2.items.length === 0) {
4250
4563
  try {
@@ -4306,7 +4619,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4306
4619
  session.includeDbBranchLease,
4307
4620
  session.dbBranchSettings,
4308
4621
  options?.adaptInputs,
4309
- options?.dryRun === true
4622
+ options?.dryRun === true,
4623
+ options?.experimentalSelectiveReplay
4310
4624
  )
4311
4625
  );
4312
4626
  const total = tasks.length + carriedOverItems.length;
@@ -4379,6 +4693,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4379
4693
  traceError: item.traceError,
4380
4694
  replayError: item.replayError,
4381
4695
  durationMs: item.durationMs,
4696
+ ...item.selectiveReplay === void 0 ? {} : { selectiveReplay: item.selectiveReplay },
4382
4697
  originalDurationMs: item.originalDurationMs,
4383
4698
  originalTokens: item.originalTokens,
4384
4699
  originalModel: item.originalModel,
@@ -4557,6 +4872,7 @@ var init_replay = __esm({
4557
4872
  init_mockOverride();
4558
4873
  init_randomUuid();
4559
4874
  init_replayContext();
4875
+ init_selectiveReplayRuntime();
4560
4876
  init_serialize();
4561
4877
  REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
4562
4878
  MAX_REPLAY_ATTEMPTS = 100;
@@ -4568,7 +4884,8 @@ var init_replay = __esm({
4568
4884
  "attempts",
4569
4885
  "graderIds",
4570
4886
  "onlyWithAssertions",
4571
- "dryRun"
4887
+ "dryRun",
4888
+ "experimentalSelectiveReplay"
4572
4889
  ];
4573
4890
  CARRIED_OVER_ERROR = "This item errored in an earlier run of this experiment, before it was resumed.";
4574
4891
  BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
@@ -9269,6 +9586,9 @@ var Bitfab = class {
9269
9586
  ...options.mockOnReplay !== void 0 && {
9270
9587
  mockOnReplay: options.mockOnReplay
9271
9588
  },
9589
+ ...options.replayReusable !== void 0 && {
9590
+ replayReusable: options.replayReusable
9591
+ },
9272
9592
  ...options.finalize !== void 0 && { finalize: options.finalize }
9273
9593
  };
9274
9594
  }
@@ -9417,6 +9737,9 @@ var Bitfab = class {
9417
9737
  ...mockOnReplay !== void 0 && {
9418
9738
  mockOnReplay
9419
9739
  },
9740
+ ...nodeConfiguration?.replayReusable !== void 0 && {
9741
+ replayReusable: nodeConfiguration.replayReusable
9742
+ },
9420
9743
  ...nodeConfiguration?.finalize !== void 0 && {
9421
9744
  finalize: nodeConfiguration.finalize
9422
9745
  }
@@ -10214,6 +10537,7 @@ var Bitfab = class {
10214
10537
  instrumentation: options.instrumentation ?? "span",
10215
10538
  functionId: options.functionId,
10216
10539
  captureContent: options.captureContent ?? true,
10540
+ replayReusable: options.replayReusable,
10217
10541
  autoTraceDefinition: options.autoTraceDefinition,
10218
10542
  declaredNode: options.declaredNode === true,
10219
10543
  nestedTrace: options.nestedTrace,
@@ -10333,6 +10657,27 @@ var Bitfab = class {
10333
10657
  return result;
10334
10658
  };
10335
10659
  const replayCtxForMock = getReplayContext();
10660
+ if (replayCtxForMock?.selectiveReplay) {
10661
+ const decision = replayCtxForMock.selectiveReplay.enter({
10662
+ traceFunctionKey,
10663
+ spanName: baseSpanParams.spanName ?? traceFunctionKey,
10664
+ spanId,
10665
+ parentSpanId,
10666
+ inputs: args,
10667
+ safetyMock: !isRootSpan && options.mockOnReplay === true,
10668
+ reuseAllowed: options.replayReusable === true,
10669
+ canReuseOutput: options.finalize === void 0 && options.replayOutput === void 0 && !fn.constructor.name.includes("Generator")
10670
+ });
10671
+ if (decision.mock) {
10672
+ void sendSpan({
10673
+ result: decision.output,
10674
+ mocked: true,
10675
+ mockTarget: "output",
10676
+ mockSource: "recorded"
10677
+ });
10678
+ return fnReturnsPromise ? Promise.resolve(decision.output) : decision.output;
10679
+ }
10680
+ }
10336
10681
  if (replayCtxForMock?.mockTree && !isRootSpan) {
10337
10682
  const counters = replayCtxForMock.callCounters;
10338
10683
  const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
@@ -10756,6 +11101,9 @@ var Bitfab = class {
10756
11101
  span_origin: makeSpanOrigin(params.instrumentation),
10757
11102
  span_data: {
10758
11103
  name: params.spanName,
11104
+ ...params.replayReusable !== void 0 && {
11105
+ replay_reusable: params.replayReusable
11106
+ },
10759
11107
  type: params.spanType,
10760
11108
  ...params.functionId !== void 0 && {
10761
11109
  function_id: params.functionId,