bitfab 0.61.1 → 0.61.2

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.
@@ -7,7 +7,7 @@ import {
7
7
  flushTraces,
8
8
  parseRetryAfterMs,
9
9
  serializePayloadBody
10
- } from "./chunk-I5J2FDKH.js";
10
+ } from "./chunk-ONZFMRRF.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-NY6M2AK3.js.map
21
+ //# sourceMappingURL=http-2R6MSRVA.js.map
@@ -7,7 +7,7 @@ import {
7
7
  flushTraces,
8
8
  parseRetryAfterMs,
9
9
  serializePayloadBody
10
- } from "./chunk-HKUHPLAM.js";
10
+ } from "./chunk-INA6W7OB.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-5DA23DKC.js.map
22
+ //# sourceMappingURL=http-KM6QWKAP.js.map
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.1";
54
+ __version__ = "0.61.2";
55
55
  __packageName__ = "bitfab";
56
56
  }
57
57
  });
@@ -2960,6 +2960,46 @@ var init_http = __esm({
2960
2960
  }
2961
2961
  return { ...rest, experimentId, experimentUrl };
2962
2962
  }
2963
+ async resumeReplay(experimentId, traceFunctionKey, codeChangeFiles, force) {
2964
+ const payload = {
2965
+ experimentId,
2966
+ traceFunctionKey,
2967
+ lazyDbBranchLease: true
2968
+ };
2969
+ if (force === true) {
2970
+ payload.force = true;
2971
+ }
2972
+ if (codeChangeFiles !== void 0) {
2973
+ payload.codeChangeFiles = codeChangeFiles;
2974
+ }
2975
+ const git = await resolvedGitState();
2976
+ if (git && !isEmptyGitState(git)) {
2977
+ payload.git = git;
2978
+ }
2979
+ const response = await this.request(
2980
+ "/api/sdk/replay/resume",
2981
+ payload,
2982
+ { timeout: REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS }
2983
+ );
2984
+ const { testRunId, testRunUrl, ...rest } = response;
2985
+ const resumedExperimentId = response.experimentId ?? testRunId;
2986
+ const experimentUrl = response.experimentUrl ?? testRunUrl;
2987
+ if (resumedExperimentId === void 0 || experimentUrl === void 0) {
2988
+ throw new BitfabError(
2989
+ "Replay resume response is missing the experiment ID or URL."
2990
+ );
2991
+ }
2992
+ return {
2993
+ ...rest,
2994
+ experimentId: resumedExperimentId,
2995
+ experimentUrl,
2996
+ work: rest.work ?? [],
2997
+ finished: rest.finished ?? [],
2998
+ skippedOriginalTraceIds: rest.skippedOriginalTraceIds ?? [],
2999
+ includeDbBranchLease: rest.includeDbBranchLease ?? false,
3000
+ dbBranchSettings: rest.dbBranchSettings ?? null
3001
+ };
3002
+ }
2963
3003
  /**
2964
3004
  * Fetch an external span by ID.
2965
3005
  * Blocking GET request.
@@ -3595,6 +3635,88 @@ function expandReplayWork(serverItems, attempts) {
3595
3635
  (_, attempt) => serverItems.map((serverItem) => ({ serverItem, attempt }))
3596
3636
  ).flat();
3597
3637
  }
3638
+ function validateResume(resume, options) {
3639
+ if (resume.trim().length === 0) {
3640
+ throw new BitfabError("resume must be the ID of the experiment to resume.");
3641
+ }
3642
+ const conflicting = RESUME_CONFLICTING_OPTIONS.filter(
3643
+ (name) => options[name] !== void 0 && options[name] !== false
3644
+ );
3645
+ if (conflicting.length > 0) {
3646
+ throw new BitfabError(
3647
+ `resume continues experiment ${resume} with the traces, attempts, and graders it started with, so it cannot be combined with ${conflicting.join(", ")}. Drop those options, or start a new replay without resume.`
3648
+ );
3649
+ }
3650
+ }
3651
+ async function resumeReplaySession(httpClient, experimentId, traceFunctionKey, codeChangeFiles, force, requestedDbBranch) {
3652
+ const response = await httpClient.resumeReplay(
3653
+ experimentId,
3654
+ traceFunctionKey,
3655
+ codeChangeFiles,
3656
+ force
3657
+ );
3658
+ const work = response.work.map(({ itemIndex, attempt }) => {
3659
+ const serverItem = response.items[itemIndex];
3660
+ if (serverItem === void 0) {
3661
+ throw new BitfabError(
3662
+ `Replay resume for experiment ${response.experimentId} named item ${itemIndex}, but the server returned only ${response.items.length} item(s).`
3663
+ );
3664
+ }
3665
+ return { serverItem, attempt };
3666
+ });
3667
+ if (response.skippedOriginalTraceIds.length > 0) {
3668
+ try {
3669
+ console.warn(
3670
+ `Bitfab: ${response.skippedOriginalTraceIds.length} trace(s) selected when experiment ${response.experimentId} started no longer exist, so resume skips them: ${response.skippedOriginalTraceIds.join(", ")}`
3671
+ );
3672
+ } catch {
3673
+ }
3674
+ }
3675
+ if (requestedDbBranch !== void 0 && dbBranchEnabled(requestedDbBranch) !== response.includeDbBranchLease) {
3676
+ try {
3677
+ console.warn(
3678
+ `Bitfab: experiment ${response.experimentId} started with database branching ${response.includeDbBranchLease ? "on" : "off"}, so resume keeps it ${response.includeDbBranchLease ? "on" : "off"} and ignores the dbBranch option passed here.`
3679
+ );
3680
+ } catch {
3681
+ }
3682
+ }
3683
+ return {
3684
+ experimentId: response.experimentId,
3685
+ experimentUrl: response.experimentUrl,
3686
+ attempts: response.attempts,
3687
+ work,
3688
+ finished: response.finished,
3689
+ includeDbBranchLease: response.includeDbBranchLease,
3690
+ dbBranchSettings: response.dbBranchSettings ?? void 0
3691
+ };
3692
+ }
3693
+ function carriedOverReplayItem(finished) {
3694
+ return {
3695
+ traceId: finished.traceId,
3696
+ originalTraceId: finished.originalTraceId,
3697
+ originalSpanId: "",
3698
+ sourceTraceId: finished.originalTraceId,
3699
+ sourceSpanId: "",
3700
+ attempt: finished.attempt,
3701
+ input: [],
3702
+ result: void 0,
3703
+ originalOutput: void 0,
3704
+ error: finished.hasError ? CARRIED_OVER_ERROR : null,
3705
+ traceError: null,
3706
+ replayError: null,
3707
+ durationMs: null,
3708
+ originalDurationMs: null,
3709
+ originalTokens: null,
3710
+ originalModel: null,
3711
+ tokens: null,
3712
+ model: null,
3713
+ dbSnapshotRef: null,
3714
+ dbBranchTimings: null,
3715
+ traceOutline: null,
3716
+ originalTraceOutline: null,
3717
+ carriedOver: true
3718
+ };
3719
+ }
3598
3720
  function resolveAttempts(attempts) {
3599
3721
  if (attempts === void 0) {
3600
3722
  return 1;
@@ -4047,6 +4169,13 @@ async function mapWithConcurrency2(tasks, maxConcurrency, onSettled, onStarted)
4047
4169
  return results;
4048
4170
  }
4049
4171
  async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, registeredOverrides = []) {
4172
+ if (options?.resume !== void 0) {
4173
+ validateResume(options.resume, options);
4174
+ } else if (options?.force === true) {
4175
+ throw new BitfabError(
4176
+ "force only applies when resuming an experiment. Pass it together with resume."
4177
+ );
4178
+ }
4050
4179
  if (options?.traceIds !== void 0) {
4051
4180
  if (options.traceIds.length === 0) {
4052
4181
  throw new BitfabError("traceIds must contain at least one trace ID.");
@@ -4066,7 +4195,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4066
4195
  } catch {
4067
4196
  }
4068
4197
  }
4069
- const attempts = resolveAttempts(options?.attempts);
4198
+ const requestedAttempts = resolveAttempts(options?.attempts);
4070
4199
  const maxConcurrency = options?.maxConcurrency ?? 10;
4071
4200
  if (!Number.isInteger(maxConcurrency) || maxConcurrency < 1) {
4072
4201
  throw new BitfabError(
@@ -4085,40 +4214,59 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4085
4214
  }
4086
4215
  }
4087
4216
  }
4088
- const {
4089
- experimentId,
4090
- experimentUrl,
4091
- items: serverItems
4092
- } = await httpClient.startReplay(
4093
- traceFunctionKey,
4094
- // limit is meaningless with explicit traceIds (the ID list determines
4095
- // the count), so it's omitted from the request entirely.
4096
- options?.traceIds ? void 0 : options?.limit ?? 5,
4097
- options?.traceIds,
4098
- options?.name,
4099
- codeChangeDescription,
4100
- codeChangeFiles,
4101
- // A dry run executes nothing, so a branch would be provisioned (and billed)
4102
- // for code that never runs, and a seeded source's refusal would fail the item
4103
- // before it could report its resolved inputs.
4104
- dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true,
4105
- options?.experimentGroupId,
4106
- datasetIds,
4107
- options?.graderIds,
4108
- resolveDbBranchSettings(options?.dbBranch),
4109
- attempts,
4110
- options?.adaptInputs !== void 0,
4111
- options?.onlyWithAssertions,
4112
- options?.judgeAssertions
4113
- );
4114
- if (serverItems.length === 0) {
4115
- try {
4116
- console.warn(
4117
- `Bitfab: no traces matched "${traceFunctionKey}", so this replay ran nothing. Capture a trace, or seed one with seedTrace, before replaying.`
4118
- );
4119
- } catch {
4217
+ const includeDbBranchLease = dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true;
4218
+ let session;
4219
+ if (options?.resume === void 0) {
4220
+ const started2 = await httpClient.startReplay(
4221
+ traceFunctionKey,
4222
+ // limit is meaningless with explicit traceIds (the ID list determines
4223
+ // the count), so it's omitted from the request entirely.
4224
+ options?.traceIds ? void 0 : options?.limit ?? 5,
4225
+ options?.traceIds,
4226
+ options?.name,
4227
+ codeChangeDescription,
4228
+ codeChangeFiles,
4229
+ // A dry run executes nothing, so a branch would be provisioned (and billed)
4230
+ // for code that never runs, and a seeded source's refusal would fail the item
4231
+ // before it could report its resolved inputs.
4232
+ includeDbBranchLease,
4233
+ options?.experimentGroupId,
4234
+ datasetIds,
4235
+ options?.graderIds,
4236
+ resolveDbBranchSettings(options?.dbBranch),
4237
+ requestedAttempts,
4238
+ options?.adaptInputs !== void 0,
4239
+ options?.onlyWithAssertions,
4240
+ options?.judgeAssertions
4241
+ );
4242
+ if (started2.items.length === 0) {
4243
+ try {
4244
+ console.warn(
4245
+ `Bitfab: no traces matched "${traceFunctionKey}", so this replay ran nothing. Capture a trace, or seed one with seedTrace, before replaying.`
4246
+ );
4247
+ } catch {
4248
+ }
4120
4249
  }
4250
+ session = {
4251
+ experimentId: started2.experimentId,
4252
+ experimentUrl: started2.experimentUrl,
4253
+ attempts: requestedAttempts,
4254
+ work: expandReplayWork(started2.items, requestedAttempts),
4255
+ finished: [],
4256
+ includeDbBranchLease,
4257
+ dbBranchSettings: resolveDbBranchSettings(options?.dbBranch)
4258
+ };
4259
+ } else {
4260
+ session = await resumeReplaySession(
4261
+ httpClient,
4262
+ options.resume,
4263
+ traceFunctionKey,
4264
+ codeChangeFiles,
4265
+ options.force,
4266
+ options.dbBranch
4267
+ );
4121
4268
  }
4269
+ const { experimentId, experimentUrl, attempts } = session;
4122
4270
  const mockStrategy = options?.mock ?? "marked";
4123
4271
  const fullExperimentUrl = `${serviceUrl}${experimentUrl}`;
4124
4272
  try {
@@ -4132,7 +4280,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4132
4280
  ...normalizeMockOverrides(options?.mockOverride),
4133
4281
  ...registeredOverrides
4134
4282
  ];
4135
- const workItems = expandReplayWork(serverItems, attempts);
4283
+ const workItems = session.work;
4284
+ const carriedOverItems = session.finished.map(
4285
+ (finished) => carriedOverReplayItem(finished)
4286
+ );
4136
4287
  const replayedTraceIds = workItems.map(() => randomUuid());
4137
4288
  httpClient.trackTraceDeliveries(replayedTraceIds);
4138
4289
  const tasks = workItems.map(
@@ -4145,18 +4296,18 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4145
4296
  resolvedOverrides,
4146
4297
  replayedTraceIds[index],
4147
4298
  attempt,
4148
- dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true,
4149
- resolveDbBranchSettings(options?.dbBranch),
4299
+ session.includeDbBranchLease,
4300
+ session.dbBranchSettings,
4150
4301
  options?.adaptInputs,
4151
4302
  options?.dryRun === true
4152
4303
  )
4153
4304
  );
4154
- const total = tasks.length;
4305
+ const total = tasks.length + carriedOverItems.length;
4155
4306
  const onItemFinish = options?.onItemFinish ?? options?.onProgress;
4156
- let completed = 0;
4157
- let started = 0;
4158
- let succeeded = 0;
4159
- let errored = 0;
4307
+ let completed = carriedOverItems.length;
4308
+ let started = carriedOverItems.length;
4309
+ let errored = carriedOverItems.filter((item) => item.error !== null).length;
4310
+ let succeeded = completed - errored;
4160
4311
  let flushInFlight = null;
4161
4312
  let flushPending = false;
4162
4313
  const flushFinishedItemTrace = () => {
@@ -4176,7 +4327,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4176
4327
  }
4177
4328
  return flushInFlight;
4178
4329
  };
4179
- const resultItems = await mapWithConcurrency2(
4330
+ const ranItems = await mapWithConcurrency2(
4180
4331
  tasks,
4181
4332
  maxConcurrency,
4182
4333
  async (item, index) => {
@@ -4263,6 +4414,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4263
4414
  }
4264
4415
  } : void 0
4265
4416
  );
4417
+ const resultItems = [...carriedOverItems, ...ranItems];
4266
4418
  if (options?.dryRun === true) {
4267
4419
  await httpClient.completeReplay(experimentId).catch(() => void 0);
4268
4420
  const dryResult = {
@@ -4282,11 +4434,11 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4282
4434
  experimentId,
4283
4435
  fullExperimentUrl
4284
4436
  );
4285
- for (let index = 0; index < resultItems.length; index += 1) {
4437
+ for (let index = 0; index < ranItems.length; index += 1) {
4286
4438
  const localId = replayedTraceIds[index];
4287
4439
  const readBack = localId ? deliveredTraceIds[localId] : void 0;
4288
4440
  if (readBack !== void 0) {
4289
- resultItems[index].traceId = readBack;
4441
+ ranItems[index].traceId = readBack;
4290
4442
  }
4291
4443
  }
4292
4444
  const completeResult = await preserveReplayFailure(
@@ -4300,8 +4452,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4300
4452
  if (serverTraceIds !== void 0) {
4301
4453
  const missing = [];
4302
4454
  let completedCount = 0;
4303
- for (let index = 0; index < resultItems.length; index += 1) {
4304
- const item = resultItems[index];
4455
+ for (let index = 0; index < ranItems.length; index += 1) {
4456
+ const item = ranItems[index];
4305
4457
  const localId = replayedTraceIds[index];
4306
4458
  const mapped = localId ? serverTraceIds[localId] : void 0;
4307
4459
  item.traceId = item.traceId ?? mapped ?? null;
@@ -4387,7 +4539,7 @@ async function writeReplayResultFile(result) {
4387
4539
  }
4388
4540
  }
4389
4541
  }
4390
- var REPLAY_PERSISTENCE_TIMEOUT_MS, MAX_REPLAY_ATTEMPTS, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
4542
+ var REPLAY_PERSISTENCE_TIMEOUT_MS, MAX_REPLAY_ATTEMPTS, RESUME_CONFLICTING_OPTIONS, CARRIED_OVER_ERROR, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
4391
4543
  var init_replay = __esm({
4392
4544
  "src/replay.ts"() {
4393
4545
  "use strict";
@@ -4401,6 +4553,17 @@ var init_replay = __esm({
4401
4553
  init_serialize();
4402
4554
  REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
4403
4555
  MAX_REPLAY_ATTEMPTS = 100;
4556
+ RESUME_CONFLICTING_OPTIONS = [
4557
+ "traceIds",
4558
+ "limit",
4559
+ "datasetId",
4560
+ "datasetIds",
4561
+ "attempts",
4562
+ "graderIds",
4563
+ "onlyWithAssertions",
4564
+ "dryRun"
4565
+ ];
4566
+ CARRIED_OVER_ERROR = "This item errored in an earlier run of this experiment, before it was resumed.";
4404
4567
  BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
4405
4568
  ReplayError = class extends BitfabError {
4406
4569
  constructor(message, items, experimentId, experimentUrl, cause) {