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/index.cjs CHANGED
@@ -51,7 +51,7 @@ var __version__, __packageName__;
51
51
  var init_version_generated = __esm({
52
52
  "src/version.generated.ts"() {
53
53
  "use strict";
54
- __version__ = "0.61.2";
54
+ __version__ = "0.61.3";
55
55
  __packageName__ = "bitfab";
56
56
  }
57
57
  });
@@ -1077,6 +1077,7 @@ var init_simulationPlan = __esm({
1077
1077
  "input_meta",
1078
1078
  "output",
1079
1079
  "output_meta",
1080
+ "replay_recording",
1080
1081
  "prompt"
1081
1082
  ];
1082
1083
  SimulationPlan = class {
@@ -2891,8 +2892,11 @@ var init_http = __esm({
2891
2892
  * Start a replay session by fetching historical traces.
2892
2893
  * Blocking call - creates an experiment and returns lightweight item references.
2893
2894
  */
2894
- async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions) {
2895
+ async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions, experimentalSelectiveReplay) {
2895
2896
  const payload = { traceFunctionKey };
2897
+ if (experimentalSelectiveReplay !== void 0) {
2898
+ payload.experimentalSelectiveReplay = experimentalSelectiveReplay;
2899
+ }
2896
2900
  if (limit !== void 0) {
2897
2901
  payload.limit = limit;
2898
2902
  }
@@ -3616,6 +3620,291 @@ var init_integrationFixture = __esm({
3616
3620
  }
3617
3621
  });
3618
3622
 
3623
+ // src/selectiveReplay.ts
3624
+ function replayNodeKey(node) {
3625
+ return JSON.stringify([node.traceFunctionKey, node.spanName]);
3626
+ }
3627
+ function jsonFingerprint(value) {
3628
+ const seen = /* @__PURE__ */ new Set();
3629
+ function encode(input) {
3630
+ if (input === null || typeof input === "boolean") {
3631
+ return JSON.stringify(input);
3632
+ }
3633
+ if (typeof input === "string" && !input.startsWith("<unserializable")) {
3634
+ return JSON.stringify(input);
3635
+ }
3636
+ if (typeof input === "number" && Number.isFinite(input) && !Object.is(input, -0)) {
3637
+ return JSON.stringify(input);
3638
+ }
3639
+ if (typeof input !== "object" || input === null || seen.has(input)) {
3640
+ throw new Error("Not a lossless JSON value");
3641
+ }
3642
+ seen.add(input);
3643
+ const prototype = Object.getPrototypeOf(input);
3644
+ if (prototype !== (Array.isArray(input) ? Array.prototype : Object.prototype)) {
3645
+ throw new Error("Not a plain JSON object");
3646
+ }
3647
+ if (Object.getOwnPropertySymbols(input).length > 0) {
3648
+ throw new Error("Symbol properties are not recorded");
3649
+ }
3650
+ const keys = Object.keys(input);
3651
+ if (Object.getOwnPropertyNames(input).length !== keys.length + (Array.isArray(input) ? 1 : 0)) {
3652
+ throw new Error("Non-enumerable properties are not recorded");
3653
+ }
3654
+ if (Array.isArray(input) && (keys.length !== input.length || keys.some((key, index) => key !== String(index)))) {
3655
+ throw new Error("Not a dense JSON array");
3656
+ }
3657
+ const properties = keys.map((key) => {
3658
+ const descriptor = Object.getOwnPropertyDescriptor(input, key);
3659
+ if (!descriptor || !("value" in descriptor)) {
3660
+ throw new Error("Accessor properties are not replay inputs");
3661
+ }
3662
+ return `${Array.isArray(input) ? "" : `${JSON.stringify(key)}:`}${encode(descriptor.value)}`;
3663
+ });
3664
+ return Array.isArray(input) ? `[${properties.join(",")}]` : `{${properties.join(",")}}`;
3665
+ }
3666
+ try {
3667
+ return encode(value);
3668
+ } catch {
3669
+ return void 0;
3670
+ }
3671
+ }
3672
+ var init_selectiveReplay = __esm({
3673
+ "src/selectiveReplay.ts"() {
3674
+ "use strict";
3675
+ }
3676
+ });
3677
+
3678
+ // src/selectiveReplayRuntime.ts
3679
+ function validateSelectiveReplayOptions(options) {
3680
+ const validNodes = (nodes) => Array.isArray(nodes) && nodes.length <= 100 && nodes.every(
3681
+ (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
3682
+ );
3683
+ if (!options || !validNodes(options.mustRun) || options.mustRun.length === 0 || Object.keys(options).some((key) => key !== "mustRun")) {
3684
+ throw new BitfabError(
3685
+ "experimentalSelectiveReplay accepts only 1\u2013100 mustRun with traceFunctionKey and spanName. Declare replayReusable on eligible spans, not reusableNodes in the replay request."
3686
+ );
3687
+ }
3688
+ }
3689
+ function policyKey(options) {
3690
+ return JSON.stringify(options.mustRun.map(replayNodeKey).sort());
3691
+ }
3692
+ var SelectiveReplayRuntime;
3693
+ var init_selectiveReplayRuntime = __esm({
3694
+ "src/selectiveReplayRuntime.ts"() {
3695
+ "use strict";
3696
+ init_errors();
3697
+ init_selectiveReplay();
3698
+ SelectiveReplayRuntime = class {
3699
+ constructor(plan, options) {
3700
+ this.plan = plan;
3701
+ this.options = options;
3702
+ this.nodes = /* @__PURE__ */ new Map();
3703
+ this.children = /* @__PURE__ */ new Map();
3704
+ this.runtimeParents = /* @__PURE__ */ new Map();
3705
+ this.consumed = /* @__PURE__ */ new Map();
3706
+ this.requiredScopes = /* @__PURE__ */ new Set();
3707
+ this.recordings = /* @__PURE__ */ new Map();
3708
+ this.reached = /* @__PURE__ */ new Set();
3709
+ this.decisions = [];
3710
+ this.assertionNames = /* @__PURE__ */ new Set();
3711
+ this.assertionScopes = /* @__PURE__ */ new Set();
3712
+ this.observedNames = /* @__PURE__ */ new Map();
3713
+ this.unresolvedAssertions = [];
3714
+ validateSelectiveReplayOptions(options);
3715
+ 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) {
3716
+ throw new BitfabError(
3717
+ "Server did not return a compatible selective replay plan. Update the server or disable experimentalSelectiveReplay."
3718
+ );
3719
+ }
3720
+ validateSelectiveReplayOptions(plan.options);
3721
+ if (policyKey(plan.options) !== policyKey(options)) {
3722
+ throw new BitfabError(
3723
+ "Selective replay plan does not match the requested mustRun nodes."
3724
+ );
3725
+ }
3726
+ this.required = new Set(options.mustRun.map(replayNodeKey));
3727
+ for (const node of plan.nodes) {
3728
+ 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") {
3729
+ throw new BitfabError("Invalid selective replay node.");
3730
+ }
3731
+ this.nodes.set(node.id, node);
3732
+ if (node.recording !== void 0) {
3733
+ this.recordings.set(node.id, {
3734
+ input: node.recording.kwargs === void 0 ? jsonFingerprint(node.recording.input) : void 0,
3735
+ output: jsonFingerprint(node.recording.output)
3736
+ });
3737
+ }
3738
+ if (node.parentId !== null) {
3739
+ const siblings = this.children.get(node.parentId) ?? [];
3740
+ siblings.push(node);
3741
+ this.children.set(node.parentId, siblings);
3742
+ }
3743
+ }
3744
+ const root = this.nodes.get(plan.rootId);
3745
+ if (!root || root.parentId !== null || !root.mustRun) {
3746
+ throw new BitfabError("Selective replay requires a live recorded root.");
3747
+ }
3748
+ const assertionIds = /* @__PURE__ */ new Set();
3749
+ let wholeTrace = false;
3750
+ for (const assertion of plan.assertions) {
3751
+ const target = assertion?.target;
3752
+ if (typeof assertion?.id !== "string" || assertion.id.length === 0 || assertionIds.has(assertion.id) || target !== null && (typeof target !== "object" || target.kind !== "output" && target.kind !== "span")) {
3753
+ throw new BitfabError(
3754
+ "Invalid selective replay assertion requirements."
3755
+ );
3756
+ }
3757
+ assertionIds.add(assertion.id);
3758
+ if (target === null) {
3759
+ wholeTrace = true;
3760
+ } else if (target.kind === "span") {
3761
+ 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)) {
3762
+ throw new BitfabError("Invalid selective replay assertion target.");
3763
+ }
3764
+ this.assertionNames.add(target.name);
3765
+ const count = plan.nodes.filter(
3766
+ (node) => node.spanName === target.name
3767
+ ).length;
3768
+ if (count <= (typeof target.occurrence === "number" ? target.occurrence : 0)) {
3769
+ this.unresolvedAssertions.push(assertion.id);
3770
+ }
3771
+ }
3772
+ }
3773
+ this.fullTraceForAssertions = wholeTrace || this.unresolvedAssertions.length > 0;
3774
+ const recordedKeys = new Set(plan.nodes.map(replayNodeKey));
3775
+ const unresolved = options.mustRun.filter(
3776
+ (node) => !recordedKeys.has(replayNodeKey(node))
3777
+ );
3778
+ if (JSON.stringify(unresolved.map(replayNodeKey).sort()) !== JSON.stringify(plan.unresolvedMustRun.map(replayNodeKey).sort())) {
3779
+ throw new BitfabError(
3780
+ "Selective replay plan has incomplete mustRun coverage."
3781
+ );
3782
+ }
3783
+ for (const node of plan.nodes) {
3784
+ const ancestors = /* @__PURE__ */ new Set([node.id]);
3785
+ let requiredByManifest = this.required.has(replayNodeKey(node));
3786
+ let requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(node.spanName);
3787
+ let parentId = node.parentId;
3788
+ while (parentId !== null) {
3789
+ const parent = this.nodes.get(parentId);
3790
+ if (!parent || ancestors.has(parentId) || node.mustRun && !parent.mustRun) {
3791
+ throw new BitfabError("Invalid selective replay ancestry.");
3792
+ }
3793
+ ancestors.add(parentId);
3794
+ requiredByManifest || (requiredByManifest = this.required.has(replayNodeKey(parent)));
3795
+ requiredForAssertion || (requiredForAssertion = this.assertionNames.has(parent.spanName));
3796
+ parentId = parent.parentId;
3797
+ }
3798
+ if (!ancestors.has(plan.rootId) || (requiredByManifest || unresolved.length > 0) && !node.mustRun) {
3799
+ throw new BitfabError("Selective replay plan could hide required code.");
3800
+ }
3801
+ if (requiredForAssertion && !node.mustRun) {
3802
+ throw new BitfabError(
3803
+ "Selective replay plan could hide assertion evidence."
3804
+ );
3805
+ }
3806
+ }
3807
+ }
3808
+ enter(params) {
3809
+ const identity = {
3810
+ traceFunctionKey: params.traceFunctionKey,
3811
+ spanName: params.spanName
3812
+ };
3813
+ const key = replayNodeKey(identity);
3814
+ const fingerprint = jsonFingerprint(params.inputs);
3815
+ let node;
3816
+ let matchReason = "unmatched-call";
3817
+ if (params.parentSpanId === null) {
3818
+ node = this.nodes.get(this.plan.rootId);
3819
+ } else {
3820
+ const recordedParent = this.runtimeParents.get(params.parentSpanId);
3821
+ const consumed = this.consumed.get(params.parentSpanId) ?? /* @__PURE__ */ new Set();
3822
+ this.consumed.set(params.parentSpanId, consumed);
3823
+ const candidates = recordedParent ? (this.children.get(recordedParent) ?? []).filter(
3824
+ (candidate) => replayNodeKey(candidate) === key && !consumed.has(candidate.id)
3825
+ ) : [];
3826
+ const exact = fingerprint === void 0 ? [] : candidates.filter(
3827
+ (candidate) => this.recordings.get(candidate.id)?.input === fingerprint
3828
+ );
3829
+ node = exact.length === 1 ? exact[0] : exact.length === 0 && candidates.length === 1 ? candidates[0] : void 0;
3830
+ if (node) {
3831
+ consumed.add(node.id);
3832
+ } else if (candidates.length > 0) {
3833
+ matchReason = "ambiguous-call";
3834
+ for (const candidate of candidates) {
3835
+ consumed.add(candidate.id);
3836
+ }
3837
+ }
3838
+ }
3839
+ this.runtimeParents.set(params.spanId, node?.id ?? null);
3840
+ const requiredByManifest = this.required.has(key) || params.parentSpanId !== null && this.requiredScopes.has(params.parentSpanId);
3841
+ if (requiredByManifest) {
3842
+ this.requiredScopes.add(params.spanId);
3843
+ }
3844
+ const requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(params.spanName) || params.parentSpanId !== null && this.assertionScopes.has(params.parentSpanId);
3845
+ if (requiredForAssertion) {
3846
+ this.assertionScopes.add(params.spanId);
3847
+ }
3848
+ const mustRun = params.parentSpanId === null || requiredByManifest || node?.mustRun === true || requiredForAssertion || this.plan.unresolvedMustRun.length > 0;
3849
+ const recording = node === void 0 ? void 0 : this.recordings.get(node.id);
3850
+ const equalInputs = fingerprint !== void 0 && recording?.input === fingerprint;
3851
+ const reusable = recording?.output !== void 0 && equalInputs && params.canReuseOutput;
3852
+ 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;
3853
+ const decision = (action, why) => {
3854
+ this.decisions.push({
3855
+ ...identity,
3856
+ originalSpanId: node?.id ?? null,
3857
+ action,
3858
+ reason: why
3859
+ });
3860
+ };
3861
+ if (params.safetyMock && (mustRun || !reusable)) {
3862
+ decision("blocked", `safety-conflict:${reason}`);
3863
+ this.failure = new BitfabError(
3864
+ `Selective replay cannot execute or safely reuse safety-mocked node ${params.spanName} (${reason}).`
3865
+ );
3866
+ throw this.failure;
3867
+ }
3868
+ if (!mustRun && reusable && (params.safetyMock || node?.reusable === true && params.reuseAllowed)) {
3869
+ decision("mock", params.safetyMock ? "safety-mock" : "unchanged-inputs");
3870
+ return {
3871
+ mock: true,
3872
+ output: JSON.parse(recording.output)
3873
+ };
3874
+ }
3875
+ decision("run", reason);
3876
+ this.observedNames.set(
3877
+ params.spanName,
3878
+ (this.observedNames.get(params.spanName) ?? 0) + 1
3879
+ );
3880
+ if (this.required.has(key)) {
3881
+ this.reached.add(key);
3882
+ }
3883
+ return { mock: false };
3884
+ }
3885
+ assertNoSafetyConflict() {
3886
+ if (this.failure) {
3887
+ throw this.failure;
3888
+ }
3889
+ }
3890
+ report() {
3891
+ return {
3892
+ decisions: this.decisions,
3893
+ mustRunNotReached: this.options.mustRun.filter(
3894
+ (node) => !this.reached.has(replayNodeKey(node))
3895
+ ),
3896
+ unresolvedMustRun: this.plan.unresolvedMustRun,
3897
+ assertionIds: this.plan.assertions.map((assertion) => assertion.id),
3898
+ unresolvedAssertions: this.unresolvedAssertions,
3899
+ assertionTargetsNotReached: this.plan.assertions.flatMap(
3900
+ ({ id, target }) => target?.kind === "span" && (this.observedNames.get(target.name) ?? 0) <= (typeof target.occurrence === "number" ? target.occurrence : 0) ? [id] : []
3901
+ )
3902
+ };
3903
+ }
3904
+ };
3905
+ }
3906
+ });
3907
+
3619
3908
  // src/replay.ts
3620
3909
  var replay_exports = {};
3621
3910
  __export(replay_exports, {
@@ -3871,7 +4160,7 @@ function buildMockTree(rootNode) {
3871
4160
  }
3872
4161
  return { spans };
3873
4162
  }
3874
- async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
4163
+ async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun, selectiveOptions) {
3875
4164
  let lease = includeDbBranchLease ? serverItem.dbBranchLease : void 0;
3876
4165
  let leaseError = includeDbBranchLease ? serverItem.dbBranchLeaseError : void 0;
3877
4166
  let dbSnapshotRef = serverItem.dbSnapshotRef;
@@ -3884,6 +4173,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
3884
4173
  let replayError = null;
3885
4174
  let replayDurationMs = null;
3886
4175
  let replayStarted = null;
4176
+ let selectiveReplay;
3887
4177
  const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
3888
4178
  const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
3889
4179
  try {
@@ -3916,6 +4206,17 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
3916
4206
  originalTraceId
3917
4207
  );
3918
4208
  }
4209
+ if (selectiveOptions !== void 0) {
4210
+ if (serverItem.selectiveReplayPlan === void 0) {
4211
+ throw new BitfabError(
4212
+ "Server did not return a selective replay plan. Update the server or disable experimentalSelectiveReplay."
4213
+ );
4214
+ }
4215
+ selectiveReplay = new SelectiveReplayRuntime(
4216
+ serverItem.selectiveReplayPlan,
4217
+ selectiveOptions
4218
+ );
4219
+ }
3919
4220
  const span = await httpClient.getExternalSpan(originalSpanId, {
3920
4221
  view: "replay"
3921
4222
  });
@@ -3935,7 +4236,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
3935
4236
  });
3936
4237
  }
3937
4238
  const hasOverrides = resolvedOverrides.length > 0;
3938
- const needTree = mockStrategy === "all" || mockStrategy === "marked" || hasOverrides;
4239
+ const needTree = selectiveReplay === void 0 && (mockStrategy === "all" || mockStrategy === "marked" || hasOverrides);
3939
4240
  const includeOutputs = mockStrategy === "all";
3940
4241
  let mockTree;
3941
4242
  if (needTree) {
@@ -4010,6 +4311,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
4010
4311
  sourceBitfabTraceId: originalTraceId,
4011
4312
  replayAttempt: attempt,
4012
4313
  mockTree,
4314
+ selectiveReplay,
4013
4315
  callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
4014
4316
  mockStrategy,
4015
4317
  mockOverrides: hasOverrides ? resolvedOverrides : void 0,
@@ -4021,6 +4323,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
4021
4323
  () => fn(...inputs)
4022
4324
  );
4023
4325
  result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
4326
+ selectiveReplay?.assertNoSafetyConflict();
4024
4327
  replayDurationMs = Math.round(performance.now() - replayStarted);
4025
4328
  } catch (e) {
4026
4329
  if (replayStarted !== null) {
@@ -4072,6 +4375,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
4072
4375
  traceError,
4073
4376
  replayError,
4074
4377
  durationMs: replayDurationMs,
4378
+ ...selectiveReplay === void 0 ? {} : { selectiveReplay: selectiveReplay.report() },
4075
4379
  originalDurationMs,
4076
4380
  originalTokens: serverItem.originalTokens ?? serverItem.tokens ?? null,
4077
4381
  originalModel,
@@ -4176,6 +4480,14 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4176
4480
  "force only applies when resuming an experiment. Pass it together with resume."
4177
4481
  );
4178
4482
  }
4483
+ if (options?.experimentalSelectiveReplay !== void 0) {
4484
+ validateSelectiveReplayOptions(options.experimentalSelectiveReplay);
4485
+ if ((options.mock ?? "marked") !== "marked" || normalizeMockOverrides(options.mockOverride).length > 0 || registeredOverrides.length > 0) {
4486
+ throw new BitfabError(
4487
+ "experimentalSelectiveReplay requires mock: 'marked' and cannot be combined with mock overrides."
4488
+ );
4489
+ }
4490
+ }
4179
4491
  if (options?.traceIds !== void 0) {
4180
4492
  if (options.traceIds.length === 0) {
4181
4493
  throw new BitfabError("traceIds must contain at least one trace ID.");
@@ -4237,7 +4549,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4237
4549
  requestedAttempts,
4238
4550
  options?.adaptInputs !== void 0,
4239
4551
  options?.onlyWithAssertions,
4240
- options?.judgeAssertions
4552
+ options?.judgeAssertions,
4553
+ options?.experimentalSelectiveReplay
4241
4554
  );
4242
4555
  if (started2.items.length === 0) {
4243
4556
  try {
@@ -4299,7 +4612,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4299
4612
  session.includeDbBranchLease,
4300
4613
  session.dbBranchSettings,
4301
4614
  options?.adaptInputs,
4302
- options?.dryRun === true
4615
+ options?.dryRun === true,
4616
+ options?.experimentalSelectiveReplay
4303
4617
  )
4304
4618
  );
4305
4619
  const total = tasks.length + carriedOverItems.length;
@@ -4372,6 +4686,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4372
4686
  traceError: item.traceError,
4373
4687
  replayError: item.replayError,
4374
4688
  durationMs: item.durationMs,
4689
+ ...item.selectiveReplay === void 0 ? {} : { selectiveReplay: item.selectiveReplay },
4375
4690
  originalDurationMs: item.originalDurationMs,
4376
4691
  originalTokens: item.originalTokens,
4377
4692
  originalModel: item.originalModel,
@@ -4550,6 +4865,7 @@ var init_replay = __esm({
4550
4865
  init_mockOverride();
4551
4866
  init_randomUuid();
4552
4867
  init_replayContext();
4868
+ init_selectiveReplayRuntime();
4553
4869
  init_serialize();
4554
4870
  REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
4555
4871
  MAX_REPLAY_ATTEMPTS = 100;
@@ -4561,7 +4877,8 @@ var init_replay = __esm({
4561
4877
  "attempts",
4562
4878
  "graderIds",
4563
4879
  "onlyWithAssertions",
4564
- "dryRun"
4880
+ "dryRun",
4881
+ "experimentalSelectiveReplay"
4565
4882
  ];
4566
4883
  CARRIED_OVER_ERROR = "This item errored in an earlier run of this experiment, before it was resumed.";
4567
4884
  BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
@@ -9255,6 +9572,9 @@ var Bitfab = class {
9255
9572
  ...options.mockOnReplay !== void 0 && {
9256
9573
  mockOnReplay: options.mockOnReplay
9257
9574
  },
9575
+ ...options.replayReusable !== void 0 && {
9576
+ replayReusable: options.replayReusable
9577
+ },
9258
9578
  ...options.finalize !== void 0 && { finalize: options.finalize }
9259
9579
  };
9260
9580
  }
@@ -9403,6 +9723,9 @@ var Bitfab = class {
9403
9723
  ...mockOnReplay !== void 0 && {
9404
9724
  mockOnReplay
9405
9725
  },
9726
+ ...nodeConfiguration?.replayReusable !== void 0 && {
9727
+ replayReusable: nodeConfiguration.replayReusable
9728
+ },
9406
9729
  ...nodeConfiguration?.finalize !== void 0 && {
9407
9730
  finalize: nodeConfiguration.finalize
9408
9731
  }
@@ -10200,6 +10523,7 @@ var Bitfab = class {
10200
10523
  instrumentation: options.instrumentation ?? "span",
10201
10524
  functionId: options.functionId,
10202
10525
  captureContent: options.captureContent ?? true,
10526
+ replayReusable: options.replayReusable,
10203
10527
  autoTraceDefinition: options.autoTraceDefinition,
10204
10528
  declaredNode: options.declaredNode === true,
10205
10529
  nestedTrace: options.nestedTrace,
@@ -10319,6 +10643,27 @@ var Bitfab = class {
10319
10643
  return result;
10320
10644
  };
10321
10645
  const replayCtxForMock = getReplayContext();
10646
+ if (replayCtxForMock?.selectiveReplay) {
10647
+ const decision = replayCtxForMock.selectiveReplay.enter({
10648
+ traceFunctionKey,
10649
+ spanName: baseSpanParams.spanName ?? traceFunctionKey,
10650
+ spanId,
10651
+ parentSpanId,
10652
+ inputs: args,
10653
+ safetyMock: !isRootSpan && options.mockOnReplay === true,
10654
+ reuseAllowed: options.replayReusable === true,
10655
+ canReuseOutput: options.finalize === void 0 && options.replayOutput === void 0 && !fn.constructor.name.includes("Generator")
10656
+ });
10657
+ if (decision.mock) {
10658
+ void sendSpan({
10659
+ result: decision.output,
10660
+ mocked: true,
10661
+ mockTarget: "output",
10662
+ mockSource: "recorded"
10663
+ });
10664
+ return fnReturnsPromise ? Promise.resolve(decision.output) : decision.output;
10665
+ }
10666
+ }
10322
10667
  if (replayCtxForMock?.mockTree && !isRootSpan) {
10323
10668
  const counters = replayCtxForMock.callCounters;
10324
10669
  const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
@@ -10742,6 +11087,9 @@ var Bitfab = class {
10742
11087
  span_origin: makeSpanOrigin(params.instrumentation),
10743
11088
  span_data: {
10744
11089
  name: params.spanName,
11090
+ ...params.replayReusable !== void 0 && {
11091
+ replay_reusable: params.replayReusable
11092
+ },
10745
11093
  type: params.spanType,
10746
11094
  ...params.functionId !== void 0 && {
10747
11095
  function_id: params.functionId,