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