bitfab 0.54.13 → 0.57.0

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 (34) hide show
  1. package/dist/{chunk-VW7MA7DS.js → chunk-3KWRBX56.js} +29 -17
  2. package/dist/chunk-3KWRBX56.js.map +1 -0
  3. package/dist/{chunk-Q7HDNYYY.js → chunk-56USYDIX.js} +143 -34
  4. package/dist/chunk-56USYDIX.js.map +1 -0
  5. package/dist/{chunk-KPALXSAF.js → chunk-FZNBGX2R.js} +37 -7
  6. package/dist/chunk-FZNBGX2R.js.map +1 -0
  7. package/dist/{chunk-FZNRSEMN.js → chunk-JEN5MX7A.js} +9 -3
  8. package/dist/chunk-JEN5MX7A.js.map +1 -0
  9. package/dist/{chunk-YKOD4GWI.js → chunk-YE75OWXI.js} +136 -110
  10. package/dist/chunk-YE75OWXI.js.map +1 -0
  11. package/dist/{http-7PF6XCJR.js → http-33ALYZUK.js} +2 -2
  12. package/dist/{http-42KAFBUN.js → http-HKMIFNAR.js} +2 -2
  13. package/dist/index.cjs +276 -146
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +12 -5
  16. package/dist/index.d.ts +12 -5
  17. package/dist/index.js +3 -3
  18. package/dist/node.cjs +276 -146
  19. package/dist/node.cjs.map +1 -1
  20. package/dist/node.d.cts +1 -1
  21. package/dist/node.d.ts +1 -1
  22. package/dist/node.js +3 -3
  23. package/dist/{replay-5RHBDIEK.js → replay-JCY64EHP.js} +3 -3
  24. package/dist/replayCli.js +2 -2
  25. package/dist/seedCli.js +2 -2
  26. package/package.json +1 -1
  27. package/dist/chunk-FZNRSEMN.js.map +0 -1
  28. package/dist/chunk-KPALXSAF.js.map +0 -1
  29. package/dist/chunk-Q7HDNYYY.js.map +0 -1
  30. package/dist/chunk-VW7MA7DS.js.map +0 -1
  31. package/dist/chunk-YKOD4GWI.js.map +0 -1
  32. /package/dist/{http-42KAFBUN.js.map → http-33ALYZUK.js.map} +0 -0
  33. /package/dist/{http-7PF6XCJR.js.map → http-HKMIFNAR.js.map} +0 -0
  34. /package/dist/{replay-5RHBDIEK.js.map → replay-JCY64EHP.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.54.13";
54
+ __version__ = "0.57.0";
55
55
  __packageName__ = "bitfab";
56
56
  }
57
57
  });
@@ -972,19 +972,6 @@ var init_spanOrigin = __esm({
972
972
  }
973
973
  });
974
974
 
975
- // src/unrefTimer.ts
976
- function unrefTimer(timer) {
977
- const handle = timer;
978
- if (typeof handle.unref === "function") {
979
- handle.unref();
980
- }
981
- }
982
- var init_unrefTimer = __esm({
983
- "src/unrefTimer.ts"() {
984
- "use strict";
985
- }
986
- });
987
-
988
975
  // src/simulationPlan.ts
989
976
  function planWaitSliceMs(timeoutMs) {
990
977
  return Math.min(SIMULATION_PLAN_READ_TIMEOUT_MS, Math.max(timeoutMs, 0) / 2);
@@ -1057,6 +1044,10 @@ function simulationPlanEnvDisabled() {
1057
1044
  function missingSimulationPlanEndpoint(error) {
1058
1045
  return error instanceof BitfabError && error.status === 404;
1059
1046
  }
1047
+ function isTraceRootSpan(payload) {
1048
+ const parentId = rawSpanOf(payload)?.parent_id;
1049
+ return parentId === void 0 || parentId === null;
1050
+ }
1060
1051
  var SIMULATION_PLAN_REFRESH_MS, SIMULATION_PLAN_RETRY_MS, SIMULATION_PLAN_READ_TIMEOUT_MS, SIMULATION_PLAN_MAX_HELD, DISABLE_SIMULATION_PLAN_ENV, ROOT_TRACE_FUNCTION_KEY_FIELD, CONTENT_OFF_KEY, CONTENT_KEYS, SimulationPlan;
1061
1052
  var init_simulationPlan = __esm({
1062
1053
  "src/simulationPlan.ts"() {
@@ -1065,7 +1056,6 @@ var init_simulationPlan = __esm({
1065
1056
  init_policyRefresh();
1066
1057
  init_readEnv();
1067
1058
  init_spanOrigin();
1068
- init_unrefTimer();
1069
1059
  init_warnOnce();
1070
1060
  SIMULATION_PLAN_REFRESH_MS = 6e4;
1071
1061
  SIMULATION_PLAN_RETRY_MS = 1e4;
@@ -1080,14 +1070,16 @@ var init_simulationPlan = __esm({
1080
1070
  this.source = source;
1081
1071
  this.enabled = enabled;
1082
1072
  this.policyRefresh = new PolicyRefresh();
1083
- this.retryPending = false;
1084
- this.waitingForFirstLoad = 0;
1073
+ this.firstReadOver = false;
1074
+ this.rootWaitOver = false;
1085
1075
  this.held = [];
1086
- this.droppedWhileHolding = 0;
1087
1076
  this.stopped = false;
1088
1077
  this.loaded = new Promise((resolve) => {
1089
1078
  this.resolveLoaded = resolve;
1090
1079
  });
1080
+ this.firstReadEnded = new Promise((resolve) => {
1081
+ this.resolveFirstReadEnded = resolve;
1082
+ });
1091
1083
  }
1092
1084
  disabled() {
1093
1085
  return !this.enabled || simulationPlanEnvDisabled();
@@ -1105,19 +1097,21 @@ var init_simulationPlan = __esm({
1105
1097
  "sim-plan-unreadable",
1106
1098
  "the sim plan response was not understood"
1107
1099
  );
1108
- return;
1100
+ } else {
1101
+ this.markLoaded(parsed);
1109
1102
  }
1110
- this.markLoaded(parsed);
1103
+ this.endFirstRead();
1111
1104
  },
1112
1105
  (error) => {
1113
1106
  if (missingSimulationPlanEndpoint(error)) {
1114
1107
  this.markLoaded(/* @__PURE__ */ new Map());
1115
- return;
1108
+ } else {
1109
+ this.scheduleRetry(
1110
+ "sim-plan-unavailable",
1111
+ `could not read the sim plan: ${error instanceof Error ? error.message : String(error)}`
1112
+ );
1116
1113
  }
1117
- this.scheduleRetry(
1118
- "sim-plan-unavailable",
1119
- `could not read the sim plan: ${error instanceof Error ? error.message : String(error)}`
1120
- );
1114
+ this.endFirstRead();
1121
1115
  }
1122
1116
  );
1123
1117
  }
@@ -1127,11 +1121,8 @@ var init_simulationPlan = __esm({
1127
1121
  }
1128
1122
  this.contentOffByKey = parsed;
1129
1123
  this.policyRefresh.hold(SIMULATION_PLAN_REFRESH_MS);
1130
- this.retryPending = false;
1131
- this.clearRetryTimer();
1132
1124
  this.resolveLoaded?.();
1133
1125
  this.resolveLoaded = void 0;
1134
- this.releaseHeld();
1135
1126
  }
1136
1127
  scheduleRetry(warnKey, reason) {
1137
1128
  if (this.stopped) {
@@ -1141,39 +1132,50 @@ var init_simulationPlan = __esm({
1141
1132
  if (this.contentOffByKey !== void 0) {
1142
1133
  return;
1143
1134
  }
1144
- this.retryPending = true;
1145
1135
  warnOnce(
1146
1136
  warnKey,
1147
- `${reason}. Spans are held until the first read succeeds; while a record is held it is retried every ${SIMULATION_PLAN_RETRY_MS / 1e3}s, otherwise on the next span.`
1137
+ `${reason}. Until a read succeeds, spans are sent without their inputs and outputs, except each trace's root span and spans recorded by a framework integration. The read is retried on the next span after ${SIMULATION_PLAN_RETRY_MS / 1e3}s.`
1148
1138
  );
1149
- this.armRetry();
1150
1139
  }
1151
- holdsWork() {
1152
- return this.held.length > 0 || this.waitingForFirstLoad > 0;
1153
- }
1154
- armRetry() {
1155
- if (this.stopped || !this.retryPending || this.retryTimer !== void 0 || !this.holdsWork()) {
1156
- return;
1140
+ endFirstRead() {
1141
+ this.endRootWait();
1142
+ if (!this.firstReadOver) {
1143
+ this.firstReadOver = true;
1144
+ this.resolveFirstReadEnded?.();
1145
+ this.resolveFirstReadEnded = void 0;
1157
1146
  }
1158
- const timer = setTimeout(() => {
1159
- this.retryTimer = void 0;
1160
- this.policyRefresh.releaseHold();
1161
- this.refresh();
1162
- }, SIMULATION_PLAN_RETRY_MS);
1163
- unrefTimer(timer);
1164
- this.retryTimer = timer;
1147
+ this.releaseHeld();
1165
1148
  }
1166
- clearRetryTimer() {
1167
- if (this.retryTimer !== void 0) {
1168
- clearTimeout(this.retryTimer);
1169
- this.retryTimer = void 0;
1149
+ endRootWait() {
1150
+ this.rootWaitOver = true;
1151
+ if (this.rootWaitTimer !== void 0) {
1152
+ clearTimeout(this.rootWaitTimer);
1153
+ this.rootWaitTimer = void 0;
1170
1154
  }
1155
+ this.resolveRootWait?.();
1156
+ this.resolveRootWait = void 0;
1171
1157
  }
1172
- syncRetryTimer() {
1173
- if (this.holdsWork()) {
1174
- return;
1158
+ waitForFirstRead() {
1159
+ this.refresh();
1160
+ if (this.rootWaitOver || this.disabled() || !this.holdingForFirstRead()) {
1161
+ return void 0;
1175
1162
  }
1176
- this.clearRetryTimer();
1163
+ if (this.rootWait === void 0) {
1164
+ this.rootWait = new Promise((resolve) => {
1165
+ this.resolveRootWait = resolve;
1166
+ });
1167
+ this.rootWaitTimer = setTimeout(
1168
+ () => this.endRootWait(),
1169
+ SIMULATION_PLAN_READ_TIMEOUT_MS
1170
+ );
1171
+ }
1172
+ return this.rootWait;
1173
+ }
1174
+ holdingForFirstRead() {
1175
+ return this.contentOffByKey === void 0 && !this.firstReadOver && !this.stopped;
1176
+ }
1177
+ unavailableNow() {
1178
+ return !this.disabled() && this.contentOffByKey === void 0 && this.firstReadOver;
1177
1179
  }
1178
1180
  firstLoad() {
1179
1181
  if (this.contentOffByKey !== void 0 || this.stopped || this.disabled()) {
@@ -1183,19 +1185,20 @@ var init_simulationPlan = __esm({
1183
1185
  }
1184
1186
  send(payload, traceFunctionKey, submit) {
1185
1187
  this.refresh();
1186
- if (this.stopped || this.disabled() || traceFunctionKey === void 0 || spanNameOf(payload) === void 0 || recordedByFramework(payload)) {
1188
+ if (this.disabled() || traceFunctionKey === void 0 || spanNameOf(payload) === void 0 || recordedByFramework(payload)) {
1187
1189
  submit(payload);
1188
1190
  return;
1189
1191
  }
1190
- if (this.contentOffByKey === void 0) {
1191
- this.hold({ payload, traceFunctionKey, submit });
1192
+ const entry = { payload, traceFunctionKey, submit };
1193
+ if (this.holdingForFirstRead()) {
1194
+ this.hold(entry);
1192
1195
  return;
1193
1196
  }
1194
- this.submitEntry({ payload, traceFunctionKey, submit });
1197
+ this.submitEntry(entry);
1195
1198
  }
1196
1199
  sendTrace(payload, submit) {
1197
1200
  this.refresh();
1198
- if (this.stopped || this.disabled() || this.contentOffByKey !== void 0 || !this.holdsSpanOfTrace(traceIdOf(payload))) {
1201
+ if (this.disabled() || !this.holdingForFirstRead() || !this.holdsSpanOfTrace(traceIdOf(payload))) {
1199
1202
  submit(payload);
1200
1203
  return;
1201
1204
  }
@@ -1211,17 +1214,34 @@ var init_simulationPlan = __esm({
1211
1214
  }
1212
1215
  hold(entry) {
1213
1216
  this.held.push(entry);
1217
+ let overflowed = false;
1214
1218
  while (this.held.length > SIMULATION_PLAN_MAX_HELD) {
1215
- this.held.shift();
1216
- this.droppedWhileHolding += 1;
1219
+ const oldest = this.held.shift();
1220
+ this.submitEntry(oldest);
1221
+ overflowed = true;
1222
+ }
1223
+ if (overflowed) {
1224
+ warnOnce(
1225
+ "sim-plan-held-overflow",
1226
+ `more than ${SIMULATION_PLAN_MAX_HELD} records were waiting for the sim plan, so the oldest were sent without their inputs and outputs.`
1227
+ );
1228
+ }
1229
+ }
1230
+ payloadToSend(entry) {
1231
+ if (entry.traceFunctionKey === void 0) {
1232
+ return entry.payload;
1233
+ }
1234
+ if (this.contentOffByKey !== void 0) {
1235
+ return this.apply(entry.payload, entry.traceFunctionKey);
1217
1236
  }
1218
- this.armRetry();
1237
+ if (recordedByFramework(entry.payload) || isTraceRootSpan(entry.payload)) {
1238
+ return entry.payload;
1239
+ }
1240
+ return stripContent(entry.payload);
1219
1241
  }
1220
1242
  submitEntry(entry) {
1221
1243
  try {
1222
- entry.submit(
1223
- entry.traceFunctionKey === void 0 ? entry.payload : this.apply(entry.payload, entry.traceFunctionKey)
1224
- );
1244
+ entry.submit(this.payloadToSend(entry));
1225
1245
  } catch (error) {
1226
1246
  warnOnce(
1227
1247
  "sim-plan-held-span-dropped",
@@ -1232,62 +1252,38 @@ var init_simulationPlan = __esm({
1232
1252
  releaseHeld() {
1233
1253
  const held = this.held;
1234
1254
  this.held = [];
1235
- this.syncRetryTimer();
1236
- this.warnDropped();
1237
1255
  for (const entry of held) {
1238
1256
  this.submitEntry(entry);
1239
1257
  }
1240
1258
  }
1241
- warnDropped() {
1242
- if (this.droppedWhileHolding === 0) {
1243
- return;
1244
- }
1245
- const dropped = this.droppedWhileHolding;
1246
- this.droppedWhileHolding = 0;
1247
- warnOnce(
1248
- "sim-plan-held-overflow",
1249
- `${dropped} record(s) were dropped while the sim plan was still loading; at most ${SIMULATION_PLAN_MAX_HELD} are held per client.`
1250
- );
1251
- }
1252
- warnNeverLoaded() {
1259
+ releaseHeldWithoutPlan() {
1253
1260
  if (this.held.length === 0) {
1254
1261
  return;
1255
1262
  }
1256
- warnOnce(
1257
- "sim-plan-never-loaded",
1258
- `${this.held.length} record(s) were not sent because the sim plan never loaded`
1259
- );
1263
+ if (this.contentOffByKey === void 0) {
1264
+ warnOnce(
1265
+ "sim-plan-not-loaded-at-release",
1266
+ `${this.held.length} record(s) were sent without their inputs and outputs because the sim plan had not loaded`
1267
+ );
1268
+ }
1269
+ this.releaseHeld();
1260
1270
  }
1261
1271
  async release(timeoutMs) {
1262
1272
  this.refresh();
1263
- if (this.holdsWork()) {
1264
- await this.waitForFirstLoad(timeoutMs);
1273
+ if (this.held.length > 0) {
1274
+ await this.waitForFirstReadEnd(timeoutMs);
1265
1275
  }
1266
- this.warnNeverLoaded();
1267
- this.warnDropped();
1276
+ this.releaseHeldWithoutPlan();
1268
1277
  }
1269
- waitForFirstLoad(timeoutMs) {
1270
- const firstLoad = this.firstLoad();
1271
- if (firstLoad === void 0) {
1278
+ waitForFirstReadEnd(timeoutMs) {
1279
+ if (this.firstReadOver) {
1272
1280
  return Promise.resolve();
1273
1281
  }
1274
- this.waitingForFirstLoad += 1;
1275
- this.armRetry();
1276
1282
  return new Promise((resolve) => {
1277
- let settled2 = false;
1278
- const finish = () => {
1279
- if (settled2) {
1280
- return;
1281
- }
1282
- settled2 = true;
1283
- this.waitingForFirstLoad -= 1;
1284
- this.syncRetryTimer();
1285
- resolve();
1286
- };
1287
- const timer = setTimeout(finish, Math.max(timeoutMs, 0));
1288
- void firstLoad.then(() => {
1283
+ const timer = setTimeout(resolve, Math.max(timeoutMs, 0));
1284
+ void this.firstReadEnded.then(() => {
1289
1285
  clearTimeout(timer);
1290
- finish();
1286
+ resolve();
1291
1287
  });
1292
1288
  });
1293
1289
  }
@@ -1295,16 +1291,16 @@ var init_simulationPlan = __esm({
1295
1291
  if (this.stopped) {
1296
1292
  return;
1297
1293
  }
1294
+ this.releaseHeldWithoutPlan();
1298
1295
  this.stopped = true;
1299
- this.retryPending = false;
1300
- this.clearRetryTimer();
1301
- this.warnNeverLoaded();
1302
- this.held = [];
1303
- this.warnDropped();
1296
+ this.endFirstRead();
1304
1297
  }
1305
1298
  contentOff(traceFunctionKey, spanName) {
1306
1299
  return this.contentOffByKey?.get(traceFunctionKey)?.has(spanName) === true;
1307
1300
  }
1301
+ contentOffNow(traceFunctionKey, spanName) {
1302
+ return !this.stopped && !this.disabled() && this.contentOff(traceFunctionKey, spanName);
1303
+ }
1308
1304
  apply(payload, traceFunctionKey) {
1309
1305
  if (recordedByFramework(payload)) {
1310
1306
  return payload;
@@ -1435,6 +1431,19 @@ var init_transportTypes = __esm({
1435
1431
  }
1436
1432
  });
1437
1433
 
1434
+ // src/unrefTimer.ts
1435
+ function unrefTimer(timer) {
1436
+ const handle = timer;
1437
+ if (typeof handle.unref === "function") {
1438
+ handle.unref();
1439
+ }
1440
+ }
1441
+ var init_unrefTimer = __esm({
1442
+ "src/unrefTimer.ts"() {
1443
+ "use strict";
1444
+ }
1445
+ });
1446
+
1438
1447
  // src/otel.ts
1439
1448
  function readBoundedIntEnv(name, max, fallback, warnKey) {
1440
1449
  const raw = readEnv(name);
@@ -2087,9 +2096,14 @@ __export(http_exports, {
2087
2096
  });
2088
2097
  async function releaseHeldExternalSpans(timeoutMs) {
2089
2098
  const deadline = Date.now() + Math.max(timeoutMs, 0);
2099
+ let released = true;
2090
2100
  for (const client of [...liveHttpClients]) {
2091
- await client.releaseHeldExternalSpans?.(Math.max(0, deadline - Date.now()));
2101
+ const result = await client.releaseHeldExternalSpans?.(
2102
+ Math.max(0, deadline - Date.now())
2103
+ );
2104
+ released = result !== false && released;
2092
2105
  }
2106
+ return released;
2093
2107
  }
2094
2108
  function stopSimulationPlans() {
2095
2109
  for (const client of [...liveHttpClients]) {
@@ -2106,14 +2120,15 @@ function awaitOnExit(promise) {
2106
2120
  }
2107
2121
  async function flushTraces(timeoutMs = 5e3) {
2108
2122
  const deadline = Date.now() + Math.max(timeoutMs, 0);
2109
- await releaseHeldExternalSpans(planWaitSliceMs(timeoutMs));
2123
+ const released = await releaseHeldExternalSpans(planWaitSliceMs(timeoutMs));
2110
2124
  const requestsFlushed = await awaitPendingRequests(
2111
2125
  Math.max(0, deadline - Date.now())
2112
2126
  );
2127
+ const releasedAfterDeferred = await releaseHeldExternalSpans(0);
2113
2128
  const transportsFlushed = await flushTraceTransports(
2114
2129
  Math.max(0, deadline - Date.now())
2115
2130
  );
2116
- return requestsFlushed && transportsFlushed;
2131
+ return released && releasedAfterDeferred && requestsFlushed && transportsFlushed;
2117
2132
  }
2118
2133
  async function awaitPendingRequests(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS2) {
2119
2134
  await replayContextReady.catch(() => {
@@ -2523,13 +2538,17 @@ var init_http = __esm({
2523
2538
  */
2524
2539
  async settleDeferredWork(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS2) {
2525
2540
  const deadline = Date.now() + Math.max(timeoutMs, 0);
2526
- await this.releaseHeldExternalSpans?.(planWaitSliceMs(timeoutMs));
2541
+ const released = await this.releaseHeldExternalSpans?.(
2542
+ planWaitSliceMs(timeoutMs)
2543
+ );
2527
2544
  await replayContextReady.catch(() => {
2528
2545
  });
2529
- return waitForPromises(
2546
+ const settled2 = await waitForPromises(
2530
2547
  Array.from(this.deferredWork),
2531
2548
  Math.max(0, deadline - Date.now())
2532
2549
  );
2550
+ const releasedAfterDeferred = await this.releaseHeldExternalSpans?.(0);
2551
+ return released !== false && releasedAfterDeferred !== false && settled2;
2533
2552
  }
2534
2553
  /**
2535
2554
  * Wait for spans queued by this client to be delivered, within one deadline.
@@ -2554,7 +2573,9 @@ var init_http = __esm({
2554
2573
  }
2555
2574
  const deadline = Date.now() + Math.max(timeoutMs, 0);
2556
2575
  this.closing = (async () => {
2557
- await this.releaseHeldExternalSpans?.(planWaitSliceMs(timeoutMs));
2576
+ const released = await this.releaseHeldExternalSpans?.(
2577
+ planWaitSliceMs(timeoutMs)
2578
+ );
2558
2579
  this.stopSimulationPlan?.();
2559
2580
  this.externalSpanTransform = void 0;
2560
2581
  this.externalTraceTransform = void 0;
@@ -2568,7 +2589,7 @@ var init_http = __esm({
2568
2589
  const transport = this.traceTransport;
2569
2590
  this.traceTransport = void 0;
2570
2591
  const shutdownOk = await transport?.shutdown(Math.max(0, deadline - Date.now())) ?? true;
2571
- return settled2 && shutdownOk;
2592
+ return released !== false && settled2 && shutdownOk;
2572
2593
  })();
2573
2594
  return this.closing;
2574
2595
  }
@@ -4002,6 +4023,12 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4002
4023
  }
4003
4024
  }
4004
4025
  const attempts = resolveAttempts(options?.attempts);
4026
+ const maxConcurrency = options?.maxConcurrency ?? 10;
4027
+ if (!Number.isInteger(maxConcurrency) || maxConcurrency < 1) {
4028
+ throw new BitfabError(
4029
+ `maxConcurrency must be a positive integer (got ${maxConcurrency}).`
4030
+ );
4031
+ }
4005
4032
  await replayContextReady;
4006
4033
  let codeChangeDescription = options?.codeChangeDescription;
4007
4034
  let codeChangeFiles = options?.codeChangeFiles;
@@ -4048,7 +4075,6 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4048
4075
  }
4049
4076
  }
4050
4077
  const mockStrategy = options?.mock ?? "marked";
4051
- const maxConcurrency = options?.maxConcurrency ?? 10;
4052
4078
  const fullTestRunUrl = `${serviceUrl}${testRunUrl}`;
4053
4079
  const resolvedOverrides = [
4054
4080
  ...normalizeMockOverrides(options?.mockOverride),
@@ -4145,6 +4171,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4145
4171
  originalDurationMs: item.originalDurationMs,
4146
4172
  originalTokens: item.originalTokens,
4147
4173
  originalModel: item.originalModel,
4174
+ ingestionType: item.ingestionType,
4148
4175
  tokens: item.tokens,
4149
4176
  model: item.model,
4150
4177
  dbSnapshotRef: item.dbSnapshotRef,
@@ -5978,6 +6005,14 @@ var LabelsClient = class {
5978
6005
  );
5979
6006
  return response.labels;
5980
6007
  }
6008
+ /** Withhold a verdict for a trace or replay attempt whose check did not run. */
6009
+ async skip(target, testRunId) {
6010
+ return this.save({ ...target, skip: true }, testRunId);
6011
+ }
6012
+ /** Clear a previous verdict for a trace, replay attempt, or assertion. */
6013
+ async archive(target, testRunId) {
6014
+ return this.save({ ...target, archive: true }, testRunId);
6015
+ }
5981
6016
  async saveHuman(update) {
5982
6017
  const [outcome] = await this.saveHumanAll([update]);
5983
6018
  return outcome;
@@ -6896,6 +6931,15 @@ var BitfabLangGraphIntegration = class {
6896
6931
  // src/client.ts
6897
6932
  init_mockOverride();
6898
6933
 
6934
+ // src/streamFinalizationError.ts
6935
+ var StreamFinalizationError = class extends Error {
6936
+ constructor(message, output) {
6937
+ super(message);
6938
+ this.output = output;
6939
+ this.name = "StreamFinalizationError";
6940
+ }
6941
+ };
6942
+
6899
6943
  // src/openaiAgentSdk.ts
6900
6944
  var BitfabOpenAIAgentHandler = class {
6901
6945
  constructor(config) {
@@ -6935,7 +6979,11 @@ var BitfabOpenAIAgentHandler = class {
6935
6979
  if (isStreaming && res?.completed) {
6936
6980
  try {
6937
6981
  await res.completed;
6938
- } catch {
6982
+ } catch (error) {
6983
+ throw new StreamFinalizationError(
6984
+ error instanceof Error ? error.message : String(error),
6985
+ res.finalOutput
6986
+ );
6939
6987
  }
6940
6988
  }
6941
6989
  return res?.finalOutput;
@@ -7693,17 +7741,6 @@ function runWithSeedContext(ctx, fn) {
7693
7741
  init_serialize();
7694
7742
  init_simulationPlan();
7695
7743
  init_spanOrigin();
7696
-
7697
- // src/streamFinalizationError.ts
7698
- var StreamFinalizationError = class extends Error {
7699
- constructor(message, output) {
7700
- super(message);
7701
- this.output = output;
7702
- this.name = "StreamFinalizationError";
7703
- }
7704
- };
7705
-
7706
- // src/client.ts
7707
7744
  init_traceMetadata();
7708
7745
 
7709
7746
  // src/traces.ts
@@ -8243,6 +8280,25 @@ var BitfabVercelAiHandler = class {
8243
8280
  // src/client.ts
8244
8281
  init_warnOnce();
8245
8282
  var activeTraceStates = /* @__PURE__ */ new Map();
8283
+ function fitsTraceSpanBudget(payload) {
8284
+ if (!recordedByFramework(payload)) {
8285
+ return true;
8286
+ }
8287
+ const traceId = traceIdOf(payload);
8288
+ const spanBudget = traceId === void 0 ? void 0 : activeTraceStates.get(traceId)?.spanBudget;
8289
+ if (spanBudget === void 0) {
8290
+ return true;
8291
+ }
8292
+ if (spanBudget.spansUsed >= spanBudget.maxSpans) {
8293
+ spanBudget.truncate();
8294
+ return false;
8295
+ }
8296
+ spanBudget.spansUsed += 1;
8297
+ return true;
8298
+ }
8299
+ function rootTraceFunctionKeyOfTrace(traceId, fallback) {
8300
+ return activeTraceStates.get(traceId)?.traceFunctionKey ?? fallback;
8301
+ }
8246
8302
  function rootTraceFunctionKeyOf(payload) {
8247
8303
  const captured = payload[ROOT_TRACE_FUNCTION_KEY_FIELD];
8248
8304
  if (typeof captured === "string") {
@@ -8618,7 +8674,8 @@ function readBooleanEnv(name) {
8618
8674
  return void 0;
8619
8675
  }
8620
8676
  var DEFAULT_AUTO_TRACE_MAX_DEPTH = 30;
8621
- var DEFAULT_AUTO_TRACE_MAX_SPANS = 500;
8677
+ var DEFAULT_AUTO_TRACE_MAX_SPANS = 2048;
8678
+ var DEFAULT_AUTO_TRACE_MAX_CAPTURED_SUBTREE_SPANS = 512;
8622
8679
  var AUTO_TRACE_PROTOCOL = "ts-auto-v1";
8623
8680
  function spanOwnerName(receiver) {
8624
8681
  if (typeof receiver === "function") {
@@ -8689,6 +8746,9 @@ var Bitfab = class {
8689
8746
  this.httpClient.externalTraceTransform = (payload, submit) => this.simulationPlan.sendTrace(payload, submit);
8690
8747
  this.httpClient.releaseHeldExternalSpans = (timeoutMs) => this.simulationPlan.release(timeoutMs);
8691
8748
  this.httpClient.stopSimulationPlan = () => this.simulationPlan.stop();
8749
+ if (this.findApiKey(false) !== void 0) {
8750
+ this.simulationPlan.refresh();
8751
+ }
8692
8752
  this.datasets = new DatasetsClient(this.httpClient);
8693
8753
  this.assertionCategories = new AssertionCategoriesClient(this.httpClient);
8694
8754
  this.traces = new TracesClient(this.httpClient);
@@ -8845,10 +8905,23 @@ var Bitfab = class {
8845
8905
  options.maxDepth,
8846
8906
  DEFAULT_AUTO_TRACE_MAX_DEPTH
8847
8907
  );
8908
+ if (options.maxSpans !== void 0 && options.maxSpans < 1) {
8909
+ throw new BitfabError(
8910
+ `maxSpans must be at least 1 because the root span counts toward it (got ${options.maxSpans})`
8911
+ );
8912
+ }
8848
8913
  const maxSpans = autoTraceLimit(
8849
8914
  options.maxSpans,
8850
8915
  DEFAULT_AUTO_TRACE_MAX_SPANS
8851
8916
  );
8917
+ const maxCapturedSubtreeSpans = Math.min(
8918
+ autoTraceLimit(
8919
+ options.maxCapturedSubtreeSpans,
8920
+ DEFAULT_AUTO_TRACE_MAX_CAPTURED_SUBTREE_SPANS
8921
+ ),
8922
+ maxSpans
8923
+ );
8924
+ const rootIsAsync = fn.constructor.name === "AsyncFunction";
8852
8925
  const excluded = new Set(options.exclude ?? []);
8853
8926
  const includeWrappers = options.includeWrappers ?? false;
8854
8927
  const rootNameFor = (receiver) => options.name ?? (fn.name !== "" ? qualifiedSpanName(receiver, name) ?? name : name);
@@ -8864,7 +8937,7 @@ var Bitfab = class {
8864
8937
  spanOptions,
8865
8938
  function(...args) {
8866
8939
  self.simulationPlan.refresh();
8867
- let spansUsed = 0;
8940
+ let capturedSubtreeCount = 0;
8868
8941
  let truncated = false;
8869
8942
  const warnTruncated = () => {
8870
8943
  if (!truncated) {
@@ -8874,15 +8947,27 @@ var Bitfab = class {
8874
8947
  protocol: AUTO_TRACE_PROTOCOL,
8875
8948
  truncated: true,
8876
8949
  maxDepth,
8877
- maxSpans
8950
+ maxSpans,
8951
+ maxCapturedSubtreeSpans
8878
8952
  }
8879
8953
  });
8880
8954
  }
8881
8955
  warnOnce(
8882
8956
  `auto-trace-truncated:${traceFunctionKey}`,
8883
- `"${traceFunctionKey}" hit an automatic subtree capture limit (maxDepth=${maxDepth}, maxSpans=${maxSpans}); its trace is incomplete. Raise the limits or narrow the subtree with exclude.`
8957
+ `"${traceFunctionKey}" hit an automatic subtree capture limit (maxDepth=${maxDepth}, maxCapturedSubtreeSpans=${maxCapturedSubtreeSpans}, maxSpans=${maxSpans}); its trace is incomplete. Raise the limits, declare the calls you need with node(), or narrow the subtree with exclude.`
8884
8958
  );
8885
8959
  };
8960
+ const rootSpanStack = getSpanStack();
8961
+ const spanBudget = {
8962
+ spansUsed: 1,
8963
+ maxSpans,
8964
+ truncate: () => runWithSpanStack(rootSpanStack, warnTruncated)
8965
+ };
8966
+ const rootSpan = rootSpanStack[rootSpanStack.length - 1];
8967
+ const rootTraceState = rootSpan === void 0 ? void 0 : activeTraceStates.get(rootSpan.traceId);
8968
+ if (rootTraceState !== void 0 && rootTraceState.spanBudget === void 0) {
8969
+ rootTraceState.spanBudget = spanBudget;
8970
+ }
8886
8971
  const spanStackOf = (entry) => entry.spanStack;
8887
8972
  const openChild = (entry, childOptions, inputs, isAsync, invokeFn) => {
8888
8973
  const runInside = () => runWithAutoTraceEntry(
@@ -8911,12 +8996,25 @@ var Bitfab = class {
8911
8996
  return tracedChild(...inputs);
8912
8997
  });
8913
8998
  };
8914
- const overBudget = (entry) => {
8915
- if (entry.depth < maxDepth && spansUsed < maxSpans) {
8916
- return false;
8999
+ const rootTraceFunctionKeyOfEntry = (entry) => {
9000
+ const stack = spanStackOf(entry);
9001
+ const innermost = stack[stack.length - 1];
9002
+ return innermost === void 0 ? traceFunctionKey : rootTraceFunctionKeyOfTrace(innermost.traceId, traceFunctionKey);
9003
+ };
9004
+ const recordsSpan = (entry, spanName, declared) => {
9005
+ const countsTowardCapturedSubtree = !declared && !self.simulationPlan.contentOffNow(
9006
+ rootTraceFunctionKeyOfEntry(entry),
9007
+ spanName
9008
+ );
9009
+ if (entry.depth < maxDepth && spanBudget.spansUsed < maxSpans && (!countsTowardCapturedSubtree || capturedSubtreeCount < maxCapturedSubtreeSpans)) {
9010
+ spanBudget.spansUsed += 1;
9011
+ if (countsTowardCapturedSubtree) {
9012
+ capturedSubtreeCount += 1;
9013
+ }
9014
+ return true;
8917
9015
  }
8918
9016
  runWithSpanStack(spanStackOf(entry), warnTruncated);
8919
- return true;
9017
+ return false;
8920
9018
  };
8921
9019
  const autoTraceContext = {
8922
9020
  traceFunctionKey,
@@ -8930,13 +9028,13 @@ var Bitfab = class {
8930
9028
  if (nodeConfiguration?.capture === false) {
8931
9029
  return clearAutoTraceNodeConfiguration(invokeFn);
8932
9030
  }
8933
- if (overBudget(entry)) {
9031
+ const spanName = nodeConfiguration?.name ?? definition.name;
9032
+ if (!recordsSpan(entry, spanName, nodeConfiguration !== void 0)) {
8934
9033
  return invokeWithoutNode();
8935
9034
  }
8936
- spansUsed += 1;
8937
9035
  const mockOnReplay = nodeConfiguration?.mockOnReplay ?? options.mockOnReplayDefault;
8938
9036
  const childOptions = {
8939
- name: nodeConfiguration?.name ?? definition.name,
9037
+ name: spanName,
8940
9038
  type: nodeConfiguration?.type ?? "function",
8941
9039
  captureWhen: "nested",
8942
9040
  surface: "opt-out",
@@ -8963,10 +9061,9 @@ var Bitfab = class {
8963
9061
  );
8964
9062
  },
8965
9063
  invokeNestedRoot(nestedName, nestedFunctionName, inputs, invokeFn, entry, link) {
8966
- if (excluded.has(nestedName) || excluded.has(nestedFunctionName) || overBudget(entry)) {
9064
+ if (excluded.has(nestedName) || excluded.has(nestedFunctionName) || !recordsSpan(entry, nestedName, true)) {
8967
9065
  return invokeFn();
8968
9066
  }
8969
- spansUsed += 1;
8970
9067
  const childOptions = {
8971
9068
  name: nestedName,
8972
9069
  type: "function",
@@ -8993,6 +9090,12 @@ var Bitfab = class {
8993
9090
  }
8994
9091
  const enclosing = currentAutoTraceEntries();
8995
9092
  if (enclosing.length === 0) {
9093
+ const firstPlanRead = rootIsAsync ? self.simulationPlan.waitForFirstRead() : void 0;
9094
+ if (firstPlanRead !== void 0) {
9095
+ return firstPlanRead.then(
9096
+ () => tracedRoot.apply(this, args)
9097
+ );
9098
+ }
8996
9099
  return tracedRoot.apply(this, args);
8997
9100
  }
8998
9101
  const link = getReplayContext() || inSeedScope() ? void 0 : { traceFunctionKey };
@@ -9039,6 +9142,9 @@ var Bitfab = class {
9039
9142
  return autoTraceRoot;
9040
9143
  }
9041
9144
  applySimulationPlan(payload, submit) {
9145
+ if (!fitsTraceSpanBudget(payload)) {
9146
+ return;
9147
+ }
9042
9148
  this.simulationPlan.send(payload, rootTraceFunctionKeyOf(payload), submit);
9043
9149
  }
9044
9150
  /**
@@ -9073,15 +9179,24 @@ var Bitfab = class {
9073
9179
  * from the environment. Once a non-empty key is found it is cached, so an
9074
9180
  * early resolve that found nothing never poisons a later one.
9075
9181
  */
9076
- resolveApiKey() {
9182
+ findApiKey(callsKeyFunction) {
9077
9183
  if (this.resolvedApiKey !== void 0) {
9078
9184
  return this.resolvedApiKey;
9079
9185
  }
9186
+ if (typeof this.apiKeyConfig === "function" && !callsKeyFunction) {
9187
+ return void 0;
9188
+ }
9080
9189
  const fromConfig = typeof this.apiKeyConfig === "function" ? this.apiKeyConfig() : this.apiKeyConfig;
9081
9190
  const candidate = fromConfig && fromConfig.trim() !== "" ? fromConfig : readEnv2("BITFAB_API_KEY");
9082
9191
  const key = candidate && candidate.trim() !== "" ? candidate : void 0;
9083
9192
  if (key) {
9084
9193
  this.resolvedApiKey = key;
9194
+ }
9195
+ return key;
9196
+ }
9197
+ resolveApiKey() {
9198
+ const key = this.findApiKey(true);
9199
+ if (key) {
9085
9200
  return key;
9086
9201
  }
9087
9202
  if (this.strict) {
@@ -9701,7 +9816,10 @@ var Bitfab = class {
9701
9816
  self.httpClient.traceCompletion.start(traceId, spanId);
9702
9817
  startedSpan = { traceId, spanId };
9703
9818
  const functionName = fn.name !== "" ? fn.name : void 0;
9704
- const rootTraceFunctionKey = activeTraceStates.get(traceId)?.traceFunctionKey ?? traceFunctionKey;
9819
+ const rootTraceFunctionKey = rootTraceFunctionKeyOfTrace(
9820
+ traceId,
9821
+ traceFunctionKey
9822
+ );
9705
9823
  const baseSpanParams = {
9706
9824
  traceFunctionKey,
9707
9825
  rootTraceFunctionKey,
@@ -10223,14 +10341,25 @@ var Bitfab = class {
10223
10341
  ...params.testRunId && { testRunId: params.testRunId }
10224
10342
  });
10225
10343
  }
10344
+ contentOffBySimulationPlan(params) {
10345
+ if (params.rootTraceFunctionKey === void 0 || isFrameworkInstrumentation(params.instrumentation)) {
10346
+ return false;
10347
+ }
10348
+ return this.simulationPlan.contentOffNow(
10349
+ params.rootTraceFunctionKey,
10350
+ params.spanName
10351
+ ) || params.parentSpanId !== null && this.simulationPlan.unavailableNow();
10352
+ }
10226
10353
  /**
10227
10354
  * Send a wrapper span from function execution.
10228
10355
  * Internal method to record spans when using withSpan.
10229
10356
  * Queued on the client's span transport; delivery is the transport's job.
10230
10357
  */
10231
10358
  sendWrapperSpan(params) {
10232
- const serializedInputs = params.captureContent ? serializeValue(params.inputs) : void 0;
10233
- const serializedResult = params.captureContent ? serializeValue(params.result) : void 0;
10359
+ const contentOff = this.contentOffBySimulationPlan(params);
10360
+ const buildsContent = params.captureContent && !contentOff;
10361
+ const serializedInputs = buildsContent ? serializeValue(params.inputs) : void 0;
10362
+ const serializedResult = buildsContent ? serializeValue(params.result) : void 0;
10234
10363
  const externalSpan = {
10235
10364
  id: params.spanId,
10236
10365
  trace_id: params.traceId,
@@ -10281,7 +10410,8 @@ var Bitfab = class {
10281
10410
  ...params.captureContent && params.contexts && params.contexts.length > 0 && {
10282
10411
  contexts: params.contexts
10283
10412
  },
10284
- ...params.captureContent && params.prompt !== void 0 && { prompt: params.prompt }
10413
+ ...params.captureContent && params.prompt !== void 0 && { prompt: params.prompt },
10414
+ ...contentOff && { [CONTENT_OFF_KEY]: true }
10285
10415
  }
10286
10416
  };
10287
10417
  if (params.parentSpanId) {