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.
@@ -5,7 +5,7 @@ import {
5
5
  gitRunner,
6
6
  replayContextReady,
7
7
  runWithReplayContext
8
- } from "./chunk-HKUHPLAM.js";
8
+ } from "./chunk-INA6W7OB.js";
9
9
  import {
10
10
  runWithCaptureSuppressed,
11
11
  warnOnce
@@ -446,6 +446,99 @@ function expandReplayWork(serverItems, attempts) {
446
446
  (_, attempt) => serverItems.map((serverItem) => ({ serverItem, attempt }))
447
447
  ).flat();
448
448
  }
449
+ var RESUME_CONFLICTING_OPTIONS = [
450
+ "traceIds",
451
+ "limit",
452
+ "datasetId",
453
+ "datasetIds",
454
+ "attempts",
455
+ "graderIds",
456
+ "onlyWithAssertions",
457
+ "dryRun"
458
+ ];
459
+ var CARRIED_OVER_ERROR = "This item errored in an earlier run of this experiment, before it was resumed.";
460
+ function validateResume(resume, options) {
461
+ if (resume.trim().length === 0) {
462
+ throw new BitfabError("resume must be the ID of the experiment to resume.");
463
+ }
464
+ const conflicting = RESUME_CONFLICTING_OPTIONS.filter(
465
+ (name) => options[name] !== void 0 && options[name] !== false
466
+ );
467
+ if (conflicting.length > 0) {
468
+ throw new BitfabError(
469
+ `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.`
470
+ );
471
+ }
472
+ }
473
+ async function resumeReplaySession(httpClient, experimentId, traceFunctionKey, codeChangeFiles, force, requestedDbBranch) {
474
+ const response = await httpClient.resumeReplay(
475
+ experimentId,
476
+ traceFunctionKey,
477
+ codeChangeFiles,
478
+ force
479
+ );
480
+ const work = response.work.map(({ itemIndex, attempt }) => {
481
+ const serverItem = response.items[itemIndex];
482
+ if (serverItem === void 0) {
483
+ throw new BitfabError(
484
+ `Replay resume for experiment ${response.experimentId} named item ${itemIndex}, but the server returned only ${response.items.length} item(s).`
485
+ );
486
+ }
487
+ return { serverItem, attempt };
488
+ });
489
+ if (response.skippedOriginalTraceIds.length > 0) {
490
+ try {
491
+ console.warn(
492
+ `Bitfab: ${response.skippedOriginalTraceIds.length} trace(s) selected when experiment ${response.experimentId} started no longer exist, so resume skips them: ${response.skippedOriginalTraceIds.join(", ")}`
493
+ );
494
+ } catch {
495
+ }
496
+ }
497
+ if (requestedDbBranch !== void 0 && dbBranchEnabled(requestedDbBranch) !== response.includeDbBranchLease) {
498
+ try {
499
+ console.warn(
500
+ `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.`
501
+ );
502
+ } catch {
503
+ }
504
+ }
505
+ return {
506
+ experimentId: response.experimentId,
507
+ experimentUrl: response.experimentUrl,
508
+ attempts: response.attempts,
509
+ work,
510
+ finished: response.finished,
511
+ includeDbBranchLease: response.includeDbBranchLease,
512
+ dbBranchSettings: response.dbBranchSettings ?? void 0
513
+ };
514
+ }
515
+ function carriedOverReplayItem(finished) {
516
+ return {
517
+ traceId: finished.traceId,
518
+ originalTraceId: finished.originalTraceId,
519
+ originalSpanId: "",
520
+ sourceTraceId: finished.originalTraceId,
521
+ sourceSpanId: "",
522
+ attempt: finished.attempt,
523
+ input: [],
524
+ result: void 0,
525
+ originalOutput: void 0,
526
+ error: finished.hasError ? CARRIED_OVER_ERROR : null,
527
+ traceError: null,
528
+ replayError: null,
529
+ durationMs: null,
530
+ originalDurationMs: null,
531
+ originalTokens: null,
532
+ originalModel: null,
533
+ tokens: null,
534
+ model: null,
535
+ dbSnapshotRef: null,
536
+ dbBranchTimings: null,
537
+ traceOutline: null,
538
+ originalTraceOutline: null,
539
+ carriedOver: true
540
+ };
541
+ }
449
542
  function resolveAttempts(attempts) {
450
543
  if (attempts === void 0) {
451
544
  return 1;
@@ -920,6 +1013,13 @@ async function mapWithConcurrency(tasks, maxConcurrency, onSettled, onStarted) {
920
1013
  return results;
921
1014
  }
922
1015
  async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, registeredOverrides = []) {
1016
+ if (options?.resume !== void 0) {
1017
+ validateResume(options.resume, options);
1018
+ } else if (options?.force === true) {
1019
+ throw new BitfabError(
1020
+ "force only applies when resuming an experiment. Pass it together with resume."
1021
+ );
1022
+ }
923
1023
  if (options?.traceIds !== void 0) {
924
1024
  if (options.traceIds.length === 0) {
925
1025
  throw new BitfabError("traceIds must contain at least one trace ID.");
@@ -939,7 +1039,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
939
1039
  } catch {
940
1040
  }
941
1041
  }
942
- const attempts = resolveAttempts(options?.attempts);
1042
+ const requestedAttempts = resolveAttempts(options?.attempts);
943
1043
  const maxConcurrency = options?.maxConcurrency ?? 10;
944
1044
  if (!Number.isInteger(maxConcurrency) || maxConcurrency < 1) {
945
1045
  throw new BitfabError(
@@ -958,40 +1058,59 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
958
1058
  }
959
1059
  }
960
1060
  }
961
- const {
962
- experimentId,
963
- experimentUrl,
964
- items: serverItems
965
- } = await httpClient.startReplay(
966
- traceFunctionKey,
967
- // limit is meaningless with explicit traceIds (the ID list determines
968
- // the count), so it's omitted from the request entirely.
969
- options?.traceIds ? void 0 : options?.limit ?? 5,
970
- options?.traceIds,
971
- options?.name,
972
- codeChangeDescription,
973
- codeChangeFiles,
974
- // A dry run executes nothing, so a branch would be provisioned (and billed)
975
- // for code that never runs, and a seeded source's refusal would fail the item
976
- // before it could report its resolved inputs.
977
- dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true,
978
- options?.experimentGroupId,
979
- datasetIds,
980
- options?.graderIds,
981
- resolveDbBranchSettings(options?.dbBranch),
982
- attempts,
983
- options?.adaptInputs !== void 0,
984
- options?.onlyWithAssertions,
985
- options?.judgeAssertions
986
- );
987
- if (serverItems.length === 0) {
988
- try {
989
- console.warn(
990
- `Bitfab: no traces matched "${traceFunctionKey}", so this replay ran nothing. Capture a trace, or seed one with seedTrace, before replaying.`
991
- );
992
- } catch {
1061
+ const includeDbBranchLease = dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true;
1062
+ let session;
1063
+ if (options?.resume === void 0) {
1064
+ const started2 = await httpClient.startReplay(
1065
+ traceFunctionKey,
1066
+ // limit is meaningless with explicit traceIds (the ID list determines
1067
+ // the count), so it's omitted from the request entirely.
1068
+ options?.traceIds ? void 0 : options?.limit ?? 5,
1069
+ options?.traceIds,
1070
+ options?.name,
1071
+ codeChangeDescription,
1072
+ codeChangeFiles,
1073
+ // A dry run executes nothing, so a branch would be provisioned (and billed)
1074
+ // for code that never runs, and a seeded source's refusal would fail the item
1075
+ // before it could report its resolved inputs.
1076
+ includeDbBranchLease,
1077
+ options?.experimentGroupId,
1078
+ datasetIds,
1079
+ options?.graderIds,
1080
+ resolveDbBranchSettings(options?.dbBranch),
1081
+ requestedAttempts,
1082
+ options?.adaptInputs !== void 0,
1083
+ options?.onlyWithAssertions,
1084
+ options?.judgeAssertions
1085
+ );
1086
+ if (started2.items.length === 0) {
1087
+ try {
1088
+ console.warn(
1089
+ `Bitfab: no traces matched "${traceFunctionKey}", so this replay ran nothing. Capture a trace, or seed one with seedTrace, before replaying.`
1090
+ );
1091
+ } catch {
1092
+ }
993
1093
  }
1094
+ session = {
1095
+ experimentId: started2.experimentId,
1096
+ experimentUrl: started2.experimentUrl,
1097
+ attempts: requestedAttempts,
1098
+ work: expandReplayWork(started2.items, requestedAttempts),
1099
+ finished: [],
1100
+ includeDbBranchLease,
1101
+ dbBranchSettings: resolveDbBranchSettings(options?.dbBranch)
1102
+ };
1103
+ } else {
1104
+ session = await resumeReplaySession(
1105
+ httpClient,
1106
+ options.resume,
1107
+ traceFunctionKey,
1108
+ codeChangeFiles,
1109
+ options.force,
1110
+ options.dbBranch
1111
+ );
994
1112
  }
1113
+ const { experimentId, experimentUrl, attempts } = session;
995
1114
  const mockStrategy = options?.mock ?? "marked";
996
1115
  const fullExperimentUrl = `${serviceUrl}${experimentUrl}`;
997
1116
  try {
@@ -1005,7 +1124,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
1005
1124
  ...normalizeMockOverrides(options?.mockOverride),
1006
1125
  ...registeredOverrides
1007
1126
  ];
1008
- const workItems = expandReplayWork(serverItems, attempts);
1127
+ const workItems = session.work;
1128
+ const carriedOverItems = session.finished.map(
1129
+ (finished) => carriedOverReplayItem(finished)
1130
+ );
1009
1131
  const replayedTraceIds = workItems.map(() => randomUuid());
1010
1132
  httpClient.trackTraceDeliveries(replayedTraceIds);
1011
1133
  const tasks = workItems.map(
@@ -1018,18 +1140,18 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
1018
1140
  resolvedOverrides,
1019
1141
  replayedTraceIds[index],
1020
1142
  attempt,
1021
- dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true,
1022
- resolveDbBranchSettings(options?.dbBranch),
1143
+ session.includeDbBranchLease,
1144
+ session.dbBranchSettings,
1023
1145
  options?.adaptInputs,
1024
1146
  options?.dryRun === true
1025
1147
  )
1026
1148
  );
1027
- const total = tasks.length;
1149
+ const total = tasks.length + carriedOverItems.length;
1028
1150
  const onItemFinish = options?.onItemFinish ?? options?.onProgress;
1029
- let completed = 0;
1030
- let started = 0;
1031
- let succeeded = 0;
1032
- let errored = 0;
1151
+ let completed = carriedOverItems.length;
1152
+ let started = carriedOverItems.length;
1153
+ let errored = carriedOverItems.filter((item) => item.error !== null).length;
1154
+ let succeeded = completed - errored;
1033
1155
  let flushInFlight = null;
1034
1156
  let flushPending = false;
1035
1157
  const flushFinishedItemTrace = () => {
@@ -1049,7 +1171,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
1049
1171
  }
1050
1172
  return flushInFlight;
1051
1173
  };
1052
- const resultItems = await mapWithConcurrency(
1174
+ const ranItems = await mapWithConcurrency(
1053
1175
  tasks,
1054
1176
  maxConcurrency,
1055
1177
  async (item, index) => {
@@ -1136,6 +1258,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
1136
1258
  }
1137
1259
  } : void 0
1138
1260
  );
1261
+ const resultItems = [...carriedOverItems, ...ranItems];
1139
1262
  if (options?.dryRun === true) {
1140
1263
  await httpClient.completeReplay(experimentId).catch(() => void 0);
1141
1264
  const dryResult = {
@@ -1155,11 +1278,11 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
1155
1278
  experimentId,
1156
1279
  fullExperimentUrl
1157
1280
  );
1158
- for (let index = 0; index < resultItems.length; index += 1) {
1281
+ for (let index = 0; index < ranItems.length; index += 1) {
1159
1282
  const localId = replayedTraceIds[index];
1160
1283
  const readBack = localId ? deliveredTraceIds[localId] : void 0;
1161
1284
  if (readBack !== void 0) {
1162
- resultItems[index].traceId = readBack;
1285
+ ranItems[index].traceId = readBack;
1163
1286
  }
1164
1287
  }
1165
1288
  const completeResult = await preserveReplayFailure(
@@ -1173,8 +1296,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
1173
1296
  if (serverTraceIds !== void 0) {
1174
1297
  const missing = [];
1175
1298
  let completedCount = 0;
1176
- for (let index = 0; index < resultItems.length; index += 1) {
1177
- const item = resultItems[index];
1299
+ for (let index = 0; index < ranItems.length; index += 1) {
1300
+ const item = ranItems[index];
1178
1301
  const localId = replayedTraceIds[index];
1179
1302
  const mapped = localId ? serverTraceIds[localId] : void 0;
1180
1303
  item.traceId = item.traceId ?? mapped ?? null;
@@ -1280,4 +1403,4 @@ export {
1280
1403
  sleepForReplayPersistence,
1281
1404
  replay
1282
1405
  };
1283
- //# sourceMappingURL=chunk-VKGLQL7V.js.map
1406
+ //# sourceMappingURL=chunk-A4UVO6BQ.js.map