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/node.cjs CHANGED
@@ -97,7 +97,7 @@ var __version__, __packageName__;
97
97
  var init_version_generated = __esm({
98
98
  "src/version.generated.ts"() {
99
99
  "use strict";
100
- __version__ = "0.54.13";
100
+ __version__ = "0.57.0";
101
101
  __packageName__ = "bitfab";
102
102
  }
103
103
  });
@@ -979,19 +979,6 @@ var init_spanOrigin = __esm({
979
979
  }
980
980
  });
981
981
 
982
- // src/unrefTimer.ts
983
- function unrefTimer(timer) {
984
- const handle = timer;
985
- if (typeof handle.unref === "function") {
986
- handle.unref();
987
- }
988
- }
989
- var init_unrefTimer = __esm({
990
- "src/unrefTimer.ts"() {
991
- "use strict";
992
- }
993
- });
994
-
995
982
  // src/simulationPlan.ts
996
983
  function planWaitSliceMs(timeoutMs) {
997
984
  return Math.min(SIMULATION_PLAN_READ_TIMEOUT_MS, Math.max(timeoutMs, 0) / 2);
@@ -1064,6 +1051,10 @@ function simulationPlanEnvDisabled() {
1064
1051
  function missingSimulationPlanEndpoint(error) {
1065
1052
  return error instanceof BitfabError && error.status === 404;
1066
1053
  }
1054
+ function isTraceRootSpan(payload) {
1055
+ const parentId = rawSpanOf(payload)?.parent_id;
1056
+ return parentId === void 0 || parentId === null;
1057
+ }
1067
1058
  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;
1068
1059
  var init_simulationPlan = __esm({
1069
1060
  "src/simulationPlan.ts"() {
@@ -1072,7 +1063,6 @@ var init_simulationPlan = __esm({
1072
1063
  init_policyRefresh();
1073
1064
  init_readEnv();
1074
1065
  init_spanOrigin();
1075
- init_unrefTimer();
1076
1066
  init_warnOnce();
1077
1067
  SIMULATION_PLAN_REFRESH_MS = 6e4;
1078
1068
  SIMULATION_PLAN_RETRY_MS = 1e4;
@@ -1087,14 +1077,16 @@ var init_simulationPlan = __esm({
1087
1077
  this.source = source;
1088
1078
  this.enabled = enabled;
1089
1079
  this.policyRefresh = new PolicyRefresh();
1090
- this.retryPending = false;
1091
- this.waitingForFirstLoad = 0;
1080
+ this.firstReadOver = false;
1081
+ this.rootWaitOver = false;
1092
1082
  this.held = [];
1093
- this.droppedWhileHolding = 0;
1094
1083
  this.stopped = false;
1095
1084
  this.loaded = new Promise((resolve) => {
1096
1085
  this.resolveLoaded = resolve;
1097
1086
  });
1087
+ this.firstReadEnded = new Promise((resolve) => {
1088
+ this.resolveFirstReadEnded = resolve;
1089
+ });
1098
1090
  }
1099
1091
  disabled() {
1100
1092
  return !this.enabled || simulationPlanEnvDisabled();
@@ -1112,19 +1104,21 @@ var init_simulationPlan = __esm({
1112
1104
  "sim-plan-unreadable",
1113
1105
  "the sim plan response was not understood"
1114
1106
  );
1115
- return;
1107
+ } else {
1108
+ this.markLoaded(parsed);
1116
1109
  }
1117
- this.markLoaded(parsed);
1110
+ this.endFirstRead();
1118
1111
  },
1119
1112
  (error) => {
1120
1113
  if (missingSimulationPlanEndpoint(error)) {
1121
1114
  this.markLoaded(/* @__PURE__ */ new Map());
1122
- return;
1115
+ } else {
1116
+ this.scheduleRetry(
1117
+ "sim-plan-unavailable",
1118
+ `could not read the sim plan: ${error instanceof Error ? error.message : String(error)}`
1119
+ );
1123
1120
  }
1124
- this.scheduleRetry(
1125
- "sim-plan-unavailable",
1126
- `could not read the sim plan: ${error instanceof Error ? error.message : String(error)}`
1127
- );
1121
+ this.endFirstRead();
1128
1122
  }
1129
1123
  );
1130
1124
  }
@@ -1134,11 +1128,8 @@ var init_simulationPlan = __esm({
1134
1128
  }
1135
1129
  this.contentOffByKey = parsed;
1136
1130
  this.policyRefresh.hold(SIMULATION_PLAN_REFRESH_MS);
1137
- this.retryPending = false;
1138
- this.clearRetryTimer();
1139
1131
  this.resolveLoaded?.();
1140
1132
  this.resolveLoaded = void 0;
1141
- this.releaseHeld();
1142
1133
  }
1143
1134
  scheduleRetry(warnKey, reason) {
1144
1135
  if (this.stopped) {
@@ -1148,39 +1139,50 @@ var init_simulationPlan = __esm({
1148
1139
  if (this.contentOffByKey !== void 0) {
1149
1140
  return;
1150
1141
  }
1151
- this.retryPending = true;
1152
1142
  warnOnce(
1153
1143
  warnKey,
1154
- `${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.`
1144
+ `${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.`
1155
1145
  );
1156
- this.armRetry();
1157
1146
  }
1158
- holdsWork() {
1159
- return this.held.length > 0 || this.waitingForFirstLoad > 0;
1160
- }
1161
- armRetry() {
1162
- if (this.stopped || !this.retryPending || this.retryTimer !== void 0 || !this.holdsWork()) {
1163
- return;
1147
+ endFirstRead() {
1148
+ this.endRootWait();
1149
+ if (!this.firstReadOver) {
1150
+ this.firstReadOver = true;
1151
+ this.resolveFirstReadEnded?.();
1152
+ this.resolveFirstReadEnded = void 0;
1164
1153
  }
1165
- const timer = setTimeout(() => {
1166
- this.retryTimer = void 0;
1167
- this.policyRefresh.releaseHold();
1168
- this.refresh();
1169
- }, SIMULATION_PLAN_RETRY_MS);
1170
- unrefTimer(timer);
1171
- this.retryTimer = timer;
1154
+ this.releaseHeld();
1172
1155
  }
1173
- clearRetryTimer() {
1174
- if (this.retryTimer !== void 0) {
1175
- clearTimeout(this.retryTimer);
1176
- this.retryTimer = void 0;
1156
+ endRootWait() {
1157
+ this.rootWaitOver = true;
1158
+ if (this.rootWaitTimer !== void 0) {
1159
+ clearTimeout(this.rootWaitTimer);
1160
+ this.rootWaitTimer = void 0;
1177
1161
  }
1162
+ this.resolveRootWait?.();
1163
+ this.resolveRootWait = void 0;
1178
1164
  }
1179
- syncRetryTimer() {
1180
- if (this.holdsWork()) {
1181
- return;
1165
+ waitForFirstRead() {
1166
+ this.refresh();
1167
+ if (this.rootWaitOver || this.disabled() || !this.holdingForFirstRead()) {
1168
+ return void 0;
1182
1169
  }
1183
- this.clearRetryTimer();
1170
+ if (this.rootWait === void 0) {
1171
+ this.rootWait = new Promise((resolve) => {
1172
+ this.resolveRootWait = resolve;
1173
+ });
1174
+ this.rootWaitTimer = setTimeout(
1175
+ () => this.endRootWait(),
1176
+ SIMULATION_PLAN_READ_TIMEOUT_MS
1177
+ );
1178
+ }
1179
+ return this.rootWait;
1180
+ }
1181
+ holdingForFirstRead() {
1182
+ return this.contentOffByKey === void 0 && !this.firstReadOver && !this.stopped;
1183
+ }
1184
+ unavailableNow() {
1185
+ return !this.disabled() && this.contentOffByKey === void 0 && this.firstReadOver;
1184
1186
  }
1185
1187
  firstLoad() {
1186
1188
  if (this.contentOffByKey !== void 0 || this.stopped || this.disabled()) {
@@ -1190,19 +1192,20 @@ var init_simulationPlan = __esm({
1190
1192
  }
1191
1193
  send(payload, traceFunctionKey, submit) {
1192
1194
  this.refresh();
1193
- if (this.stopped || this.disabled() || traceFunctionKey === void 0 || spanNameOf(payload) === void 0 || recordedByFramework(payload)) {
1195
+ if (this.disabled() || traceFunctionKey === void 0 || spanNameOf(payload) === void 0 || recordedByFramework(payload)) {
1194
1196
  submit(payload);
1195
1197
  return;
1196
1198
  }
1197
- if (this.contentOffByKey === void 0) {
1198
- this.hold({ payload, traceFunctionKey, submit });
1199
+ const entry = { payload, traceFunctionKey, submit };
1200
+ if (this.holdingForFirstRead()) {
1201
+ this.hold(entry);
1199
1202
  return;
1200
1203
  }
1201
- this.submitEntry({ payload, traceFunctionKey, submit });
1204
+ this.submitEntry(entry);
1202
1205
  }
1203
1206
  sendTrace(payload, submit) {
1204
1207
  this.refresh();
1205
- if (this.stopped || this.disabled() || this.contentOffByKey !== void 0 || !this.holdsSpanOfTrace(traceIdOf(payload))) {
1208
+ if (this.disabled() || !this.holdingForFirstRead() || !this.holdsSpanOfTrace(traceIdOf(payload))) {
1206
1209
  submit(payload);
1207
1210
  return;
1208
1211
  }
@@ -1218,17 +1221,34 @@ var init_simulationPlan = __esm({
1218
1221
  }
1219
1222
  hold(entry) {
1220
1223
  this.held.push(entry);
1224
+ let overflowed = false;
1221
1225
  while (this.held.length > SIMULATION_PLAN_MAX_HELD) {
1222
- this.held.shift();
1223
- this.droppedWhileHolding += 1;
1226
+ const oldest = this.held.shift();
1227
+ this.submitEntry(oldest);
1228
+ overflowed = true;
1229
+ }
1230
+ if (overflowed) {
1231
+ warnOnce(
1232
+ "sim-plan-held-overflow",
1233
+ `more than ${SIMULATION_PLAN_MAX_HELD} records were waiting for the sim plan, so the oldest were sent without their inputs and outputs.`
1234
+ );
1235
+ }
1236
+ }
1237
+ payloadToSend(entry) {
1238
+ if (entry.traceFunctionKey === void 0) {
1239
+ return entry.payload;
1240
+ }
1241
+ if (this.contentOffByKey !== void 0) {
1242
+ return this.apply(entry.payload, entry.traceFunctionKey);
1224
1243
  }
1225
- this.armRetry();
1244
+ if (recordedByFramework(entry.payload) || isTraceRootSpan(entry.payload)) {
1245
+ return entry.payload;
1246
+ }
1247
+ return stripContent(entry.payload);
1226
1248
  }
1227
1249
  submitEntry(entry) {
1228
1250
  try {
1229
- entry.submit(
1230
- entry.traceFunctionKey === void 0 ? entry.payload : this.apply(entry.payload, entry.traceFunctionKey)
1231
- );
1251
+ entry.submit(this.payloadToSend(entry));
1232
1252
  } catch (error) {
1233
1253
  warnOnce(
1234
1254
  "sim-plan-held-span-dropped",
@@ -1239,62 +1259,38 @@ var init_simulationPlan = __esm({
1239
1259
  releaseHeld() {
1240
1260
  const held = this.held;
1241
1261
  this.held = [];
1242
- this.syncRetryTimer();
1243
- this.warnDropped();
1244
1262
  for (const entry of held) {
1245
1263
  this.submitEntry(entry);
1246
1264
  }
1247
1265
  }
1248
- warnDropped() {
1249
- if (this.droppedWhileHolding === 0) {
1250
- return;
1251
- }
1252
- const dropped = this.droppedWhileHolding;
1253
- this.droppedWhileHolding = 0;
1254
- warnOnce(
1255
- "sim-plan-held-overflow",
1256
- `${dropped} record(s) were dropped while the sim plan was still loading; at most ${SIMULATION_PLAN_MAX_HELD} are held per client.`
1257
- );
1258
- }
1259
- warnNeverLoaded() {
1266
+ releaseHeldWithoutPlan() {
1260
1267
  if (this.held.length === 0) {
1261
1268
  return;
1262
1269
  }
1263
- warnOnce(
1264
- "sim-plan-never-loaded",
1265
- `${this.held.length} record(s) were not sent because the sim plan never loaded`
1266
- );
1270
+ if (this.contentOffByKey === void 0) {
1271
+ warnOnce(
1272
+ "sim-plan-not-loaded-at-release",
1273
+ `${this.held.length} record(s) were sent without their inputs and outputs because the sim plan had not loaded`
1274
+ );
1275
+ }
1276
+ this.releaseHeld();
1267
1277
  }
1268
1278
  async release(timeoutMs) {
1269
1279
  this.refresh();
1270
- if (this.holdsWork()) {
1271
- await this.waitForFirstLoad(timeoutMs);
1280
+ if (this.held.length > 0) {
1281
+ await this.waitForFirstReadEnd(timeoutMs);
1272
1282
  }
1273
- this.warnNeverLoaded();
1274
- this.warnDropped();
1283
+ this.releaseHeldWithoutPlan();
1275
1284
  }
1276
- waitForFirstLoad(timeoutMs) {
1277
- const firstLoad = this.firstLoad();
1278
- if (firstLoad === void 0) {
1285
+ waitForFirstReadEnd(timeoutMs) {
1286
+ if (this.firstReadOver) {
1279
1287
  return Promise.resolve();
1280
1288
  }
1281
- this.waitingForFirstLoad += 1;
1282
- this.armRetry();
1283
1289
  return new Promise((resolve) => {
1284
- let settled2 = false;
1285
- const finish = () => {
1286
- if (settled2) {
1287
- return;
1288
- }
1289
- settled2 = true;
1290
- this.waitingForFirstLoad -= 1;
1291
- this.syncRetryTimer();
1292
- resolve();
1293
- };
1294
- const timer = setTimeout(finish, Math.max(timeoutMs, 0));
1295
- void firstLoad.then(() => {
1290
+ const timer = setTimeout(resolve, Math.max(timeoutMs, 0));
1291
+ void this.firstReadEnded.then(() => {
1296
1292
  clearTimeout(timer);
1297
- finish();
1293
+ resolve();
1298
1294
  });
1299
1295
  });
1300
1296
  }
@@ -1302,16 +1298,16 @@ var init_simulationPlan = __esm({
1302
1298
  if (this.stopped) {
1303
1299
  return;
1304
1300
  }
1301
+ this.releaseHeldWithoutPlan();
1305
1302
  this.stopped = true;
1306
- this.retryPending = false;
1307
- this.clearRetryTimer();
1308
- this.warnNeverLoaded();
1309
- this.held = [];
1310
- this.warnDropped();
1303
+ this.endFirstRead();
1311
1304
  }
1312
1305
  contentOff(traceFunctionKey, spanName) {
1313
1306
  return this.contentOffByKey?.get(traceFunctionKey)?.has(spanName) === true;
1314
1307
  }
1308
+ contentOffNow(traceFunctionKey, spanName) {
1309
+ return !this.stopped && !this.disabled() && this.contentOff(traceFunctionKey, spanName);
1310
+ }
1315
1311
  apply(payload, traceFunctionKey) {
1316
1312
  if (recordedByFramework(payload)) {
1317
1313
  return payload;
@@ -1442,6 +1438,19 @@ var init_transportTypes = __esm({
1442
1438
  }
1443
1439
  });
1444
1440
 
1441
+ // src/unrefTimer.ts
1442
+ function unrefTimer(timer) {
1443
+ const handle = timer;
1444
+ if (typeof handle.unref === "function") {
1445
+ handle.unref();
1446
+ }
1447
+ }
1448
+ var init_unrefTimer = __esm({
1449
+ "src/unrefTimer.ts"() {
1450
+ "use strict";
1451
+ }
1452
+ });
1453
+
1445
1454
  // src/otel.ts
1446
1455
  function readBoundedIntEnv(name, max, fallback, warnKey) {
1447
1456
  const raw = readEnv(name);
@@ -2094,9 +2103,14 @@ __export(http_exports, {
2094
2103
  });
2095
2104
  async function releaseHeldExternalSpans(timeoutMs) {
2096
2105
  const deadline = Date.now() + Math.max(timeoutMs, 0);
2106
+ let released = true;
2097
2107
  for (const client of [...liveHttpClients]) {
2098
- await client.releaseHeldExternalSpans?.(Math.max(0, deadline - Date.now()));
2108
+ const result = await client.releaseHeldExternalSpans?.(
2109
+ Math.max(0, deadline - Date.now())
2110
+ );
2111
+ released = result !== false && released;
2099
2112
  }
2113
+ return released;
2100
2114
  }
2101
2115
  function stopSimulationPlans() {
2102
2116
  for (const client of [...liveHttpClients]) {
@@ -2113,14 +2127,15 @@ function awaitOnExit(promise) {
2113
2127
  }
2114
2128
  async function flushTraces(timeoutMs = 5e3) {
2115
2129
  const deadline = Date.now() + Math.max(timeoutMs, 0);
2116
- await releaseHeldExternalSpans(planWaitSliceMs(timeoutMs));
2130
+ const released = await releaseHeldExternalSpans(planWaitSliceMs(timeoutMs));
2117
2131
  const requestsFlushed = await awaitPendingRequests(
2118
2132
  Math.max(0, deadline - Date.now())
2119
2133
  );
2134
+ const releasedAfterDeferred = await releaseHeldExternalSpans(0);
2120
2135
  const transportsFlushed = await flushTraceTransports(
2121
2136
  Math.max(0, deadline - Date.now())
2122
2137
  );
2123
- return requestsFlushed && transportsFlushed;
2138
+ return released && releasedAfterDeferred && requestsFlushed && transportsFlushed;
2124
2139
  }
2125
2140
  async function awaitPendingRequests(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS2) {
2126
2141
  await replayContextReady.catch(() => {
@@ -2530,13 +2545,17 @@ var init_http = __esm({
2530
2545
  */
2531
2546
  async settleDeferredWork(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS2) {
2532
2547
  const deadline = Date.now() + Math.max(timeoutMs, 0);
2533
- await this.releaseHeldExternalSpans?.(planWaitSliceMs(timeoutMs));
2548
+ const released = await this.releaseHeldExternalSpans?.(
2549
+ planWaitSliceMs(timeoutMs)
2550
+ );
2534
2551
  await replayContextReady.catch(() => {
2535
2552
  });
2536
- return waitForPromises(
2553
+ const settled2 = await waitForPromises(
2537
2554
  Array.from(this.deferredWork),
2538
2555
  Math.max(0, deadline - Date.now())
2539
2556
  );
2557
+ const releasedAfterDeferred = await this.releaseHeldExternalSpans?.(0);
2558
+ return released !== false && releasedAfterDeferred !== false && settled2;
2540
2559
  }
2541
2560
  /**
2542
2561
  * Wait for spans queued by this client to be delivered, within one deadline.
@@ -2561,7 +2580,9 @@ var init_http = __esm({
2561
2580
  }
2562
2581
  const deadline = Date.now() + Math.max(timeoutMs, 0);
2563
2582
  this.closing = (async () => {
2564
- await this.releaseHeldExternalSpans?.(planWaitSliceMs(timeoutMs));
2583
+ const released = await this.releaseHeldExternalSpans?.(
2584
+ planWaitSliceMs(timeoutMs)
2585
+ );
2565
2586
  this.stopSimulationPlan?.();
2566
2587
  this.externalSpanTransform = void 0;
2567
2588
  this.externalTraceTransform = void 0;
@@ -2575,7 +2596,7 @@ var init_http = __esm({
2575
2596
  const transport = this.traceTransport;
2576
2597
  this.traceTransport = void 0;
2577
2598
  const shutdownOk = await transport?.shutdown(Math.max(0, deadline - Date.now())) ?? true;
2578
- return settled2 && shutdownOk;
2599
+ return released !== false && settled2 && shutdownOk;
2579
2600
  })();
2580
2601
  return this.closing;
2581
2602
  }
@@ -4009,6 +4030,12 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4009
4030
  }
4010
4031
  }
4011
4032
  const attempts = resolveAttempts(options?.attempts);
4033
+ const maxConcurrency = options?.maxConcurrency ?? 10;
4034
+ if (!Number.isInteger(maxConcurrency) || maxConcurrency < 1) {
4035
+ throw new BitfabError(
4036
+ `maxConcurrency must be a positive integer (got ${maxConcurrency}).`
4037
+ );
4038
+ }
4012
4039
  await replayContextReady;
4013
4040
  let codeChangeDescription = options?.codeChangeDescription;
4014
4041
  let codeChangeFiles = options?.codeChangeFiles;
@@ -4055,7 +4082,6 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4055
4082
  }
4056
4083
  }
4057
4084
  const mockStrategy = options?.mock ?? "marked";
4058
- const maxConcurrency = options?.maxConcurrency ?? 10;
4059
4085
  const fullTestRunUrl = `${serviceUrl}${testRunUrl}`;
4060
4086
  const resolvedOverrides = [
4061
4087
  ...normalizeMockOverrides(options?.mockOverride),
@@ -4152,6 +4178,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
4152
4178
  originalDurationMs: item.originalDurationMs,
4153
4179
  originalTokens: item.originalTokens,
4154
4180
  originalModel: item.originalModel,
4181
+ ingestionType: item.ingestionType,
4155
4182
  tokens: item.tokens,
4156
4183
  model: item.model,
4157
4184
  dbSnapshotRef: item.dbSnapshotRef,
@@ -5992,6 +6019,14 @@ var LabelsClient = class {
5992
6019
  );
5993
6020
  return response.labels;
5994
6021
  }
6022
+ /** Withhold a verdict for a trace or replay attempt whose check did not run. */
6023
+ async skip(target, testRunId) {
6024
+ return this.save({ ...target, skip: true }, testRunId);
6025
+ }
6026
+ /** Clear a previous verdict for a trace, replay attempt, or assertion. */
6027
+ async archive(target, testRunId) {
6028
+ return this.save({ ...target, archive: true }, testRunId);
6029
+ }
5995
6030
  async saveHuman(update) {
5996
6031
  const [outcome] = await this.saveHumanAll([update]);
5997
6032
  return outcome;
@@ -6910,6 +6945,15 @@ var BitfabLangGraphIntegration = class {
6910
6945
  // src/client.ts
6911
6946
  init_mockOverride();
6912
6947
 
6948
+ // src/streamFinalizationError.ts
6949
+ var StreamFinalizationError = class extends Error {
6950
+ constructor(message, output) {
6951
+ super(message);
6952
+ this.output = output;
6953
+ this.name = "StreamFinalizationError";
6954
+ }
6955
+ };
6956
+
6913
6957
  // src/openaiAgentSdk.ts
6914
6958
  var BitfabOpenAIAgentHandler = class {
6915
6959
  constructor(config) {
@@ -6949,7 +6993,11 @@ var BitfabOpenAIAgentHandler = class {
6949
6993
  if (isStreaming && res?.completed) {
6950
6994
  try {
6951
6995
  await res.completed;
6952
- } catch {
6996
+ } catch (error) {
6997
+ throw new StreamFinalizationError(
6998
+ error instanceof Error ? error.message : String(error),
6999
+ res.finalOutput
7000
+ );
6953
7001
  }
6954
7002
  }
6955
7003
  return res?.finalOutput;
@@ -7707,17 +7755,6 @@ function runWithSeedContext(ctx, fn) {
7707
7755
  init_serialize();
7708
7756
  init_simulationPlan();
7709
7757
  init_spanOrigin();
7710
-
7711
- // src/streamFinalizationError.ts
7712
- var StreamFinalizationError = class extends Error {
7713
- constructor(message, output) {
7714
- super(message);
7715
- this.output = output;
7716
- this.name = "StreamFinalizationError";
7717
- }
7718
- };
7719
-
7720
- // src/client.ts
7721
7758
  init_traceMetadata();
7722
7759
 
7723
7760
  // src/traces.ts
@@ -8257,6 +8294,25 @@ var BitfabVercelAiHandler = class {
8257
8294
  // src/client.ts
8258
8295
  init_warnOnce();
8259
8296
  var activeTraceStates = /* @__PURE__ */ new Map();
8297
+ function fitsTraceSpanBudget(payload) {
8298
+ if (!recordedByFramework(payload)) {
8299
+ return true;
8300
+ }
8301
+ const traceId = traceIdOf(payload);
8302
+ const spanBudget = traceId === void 0 ? void 0 : activeTraceStates.get(traceId)?.spanBudget;
8303
+ if (spanBudget === void 0) {
8304
+ return true;
8305
+ }
8306
+ if (spanBudget.spansUsed >= spanBudget.maxSpans) {
8307
+ spanBudget.truncate();
8308
+ return false;
8309
+ }
8310
+ spanBudget.spansUsed += 1;
8311
+ return true;
8312
+ }
8313
+ function rootTraceFunctionKeyOfTrace(traceId, fallback) {
8314
+ return activeTraceStates.get(traceId)?.traceFunctionKey ?? fallback;
8315
+ }
8260
8316
  function rootTraceFunctionKeyOf(payload) {
8261
8317
  const captured = payload[ROOT_TRACE_FUNCTION_KEY_FIELD];
8262
8318
  if (typeof captured === "string") {
@@ -8632,7 +8688,8 @@ function readBooleanEnv(name) {
8632
8688
  return void 0;
8633
8689
  }
8634
8690
  var DEFAULT_AUTO_TRACE_MAX_DEPTH = 30;
8635
- var DEFAULT_AUTO_TRACE_MAX_SPANS = 500;
8691
+ var DEFAULT_AUTO_TRACE_MAX_SPANS = 2048;
8692
+ var DEFAULT_AUTO_TRACE_MAX_CAPTURED_SUBTREE_SPANS = 512;
8636
8693
  var AUTO_TRACE_PROTOCOL = "ts-auto-v1";
8637
8694
  function spanOwnerName(receiver) {
8638
8695
  if (typeof receiver === "function") {
@@ -8703,6 +8760,9 @@ var Bitfab = class {
8703
8760
  this.httpClient.externalTraceTransform = (payload, submit) => this.simulationPlan.sendTrace(payload, submit);
8704
8761
  this.httpClient.releaseHeldExternalSpans = (timeoutMs) => this.simulationPlan.release(timeoutMs);
8705
8762
  this.httpClient.stopSimulationPlan = () => this.simulationPlan.stop();
8763
+ if (this.findApiKey(false) !== void 0) {
8764
+ this.simulationPlan.refresh();
8765
+ }
8706
8766
  this.datasets = new DatasetsClient(this.httpClient);
8707
8767
  this.assertionCategories = new AssertionCategoriesClient(this.httpClient);
8708
8768
  this.traces = new TracesClient(this.httpClient);
@@ -8859,10 +8919,23 @@ var Bitfab = class {
8859
8919
  options.maxDepth,
8860
8920
  DEFAULT_AUTO_TRACE_MAX_DEPTH
8861
8921
  );
8922
+ if (options.maxSpans !== void 0 && options.maxSpans < 1) {
8923
+ throw new BitfabError(
8924
+ `maxSpans must be at least 1 because the root span counts toward it (got ${options.maxSpans})`
8925
+ );
8926
+ }
8862
8927
  const maxSpans = autoTraceLimit(
8863
8928
  options.maxSpans,
8864
8929
  DEFAULT_AUTO_TRACE_MAX_SPANS
8865
8930
  );
8931
+ const maxCapturedSubtreeSpans = Math.min(
8932
+ autoTraceLimit(
8933
+ options.maxCapturedSubtreeSpans,
8934
+ DEFAULT_AUTO_TRACE_MAX_CAPTURED_SUBTREE_SPANS
8935
+ ),
8936
+ maxSpans
8937
+ );
8938
+ const rootIsAsync = fn.constructor.name === "AsyncFunction";
8866
8939
  const excluded = new Set(options.exclude ?? []);
8867
8940
  const includeWrappers = options.includeWrappers ?? false;
8868
8941
  const rootNameFor = (receiver) => options.name ?? (fn.name !== "" ? qualifiedSpanName(receiver, name) ?? name : name);
@@ -8878,7 +8951,7 @@ var Bitfab = class {
8878
8951
  spanOptions,
8879
8952
  function(...args) {
8880
8953
  self.simulationPlan.refresh();
8881
- let spansUsed = 0;
8954
+ let capturedSubtreeCount = 0;
8882
8955
  let truncated = false;
8883
8956
  const warnTruncated = () => {
8884
8957
  if (!truncated) {
@@ -8888,15 +8961,27 @@ var Bitfab = class {
8888
8961
  protocol: AUTO_TRACE_PROTOCOL,
8889
8962
  truncated: true,
8890
8963
  maxDepth,
8891
- maxSpans
8964
+ maxSpans,
8965
+ maxCapturedSubtreeSpans
8892
8966
  }
8893
8967
  });
8894
8968
  }
8895
8969
  warnOnce(
8896
8970
  `auto-trace-truncated:${traceFunctionKey}`,
8897
- `"${traceFunctionKey}" hit an automatic subtree capture limit (maxDepth=${maxDepth}, maxSpans=${maxSpans}); its trace is incomplete. Raise the limits or narrow the subtree with exclude.`
8971
+ `"${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.`
8898
8972
  );
8899
8973
  };
8974
+ const rootSpanStack = getSpanStack();
8975
+ const spanBudget = {
8976
+ spansUsed: 1,
8977
+ maxSpans,
8978
+ truncate: () => runWithSpanStack(rootSpanStack, warnTruncated)
8979
+ };
8980
+ const rootSpan = rootSpanStack[rootSpanStack.length - 1];
8981
+ const rootTraceState = rootSpan === void 0 ? void 0 : activeTraceStates.get(rootSpan.traceId);
8982
+ if (rootTraceState !== void 0 && rootTraceState.spanBudget === void 0) {
8983
+ rootTraceState.spanBudget = spanBudget;
8984
+ }
8900
8985
  const spanStackOf = (entry) => entry.spanStack;
8901
8986
  const openChild = (entry, childOptions, inputs, isAsync, invokeFn) => {
8902
8987
  const runInside = () => runWithAutoTraceEntry(
@@ -8925,12 +9010,25 @@ var Bitfab = class {
8925
9010
  return tracedChild(...inputs);
8926
9011
  });
8927
9012
  };
8928
- const overBudget = (entry) => {
8929
- if (entry.depth < maxDepth && spansUsed < maxSpans) {
8930
- return false;
9013
+ const rootTraceFunctionKeyOfEntry = (entry) => {
9014
+ const stack = spanStackOf(entry);
9015
+ const innermost = stack[stack.length - 1];
9016
+ return innermost === void 0 ? traceFunctionKey : rootTraceFunctionKeyOfTrace(innermost.traceId, traceFunctionKey);
9017
+ };
9018
+ const recordsSpan = (entry, spanName, declared) => {
9019
+ const countsTowardCapturedSubtree = !declared && !self.simulationPlan.contentOffNow(
9020
+ rootTraceFunctionKeyOfEntry(entry),
9021
+ spanName
9022
+ );
9023
+ if (entry.depth < maxDepth && spanBudget.spansUsed < maxSpans && (!countsTowardCapturedSubtree || capturedSubtreeCount < maxCapturedSubtreeSpans)) {
9024
+ spanBudget.spansUsed += 1;
9025
+ if (countsTowardCapturedSubtree) {
9026
+ capturedSubtreeCount += 1;
9027
+ }
9028
+ return true;
8931
9029
  }
8932
9030
  runWithSpanStack(spanStackOf(entry), warnTruncated);
8933
- return true;
9031
+ return false;
8934
9032
  };
8935
9033
  const autoTraceContext = {
8936
9034
  traceFunctionKey,
@@ -8944,13 +9042,13 @@ var Bitfab = class {
8944
9042
  if (nodeConfiguration?.capture === false) {
8945
9043
  return clearAutoTraceNodeConfiguration(invokeFn);
8946
9044
  }
8947
- if (overBudget(entry)) {
9045
+ const spanName = nodeConfiguration?.name ?? definition.name;
9046
+ if (!recordsSpan(entry, spanName, nodeConfiguration !== void 0)) {
8948
9047
  return invokeWithoutNode();
8949
9048
  }
8950
- spansUsed += 1;
8951
9049
  const mockOnReplay = nodeConfiguration?.mockOnReplay ?? options.mockOnReplayDefault;
8952
9050
  const childOptions = {
8953
- name: nodeConfiguration?.name ?? definition.name,
9051
+ name: spanName,
8954
9052
  type: nodeConfiguration?.type ?? "function",
8955
9053
  captureWhen: "nested",
8956
9054
  surface: "opt-out",
@@ -8977,10 +9075,9 @@ var Bitfab = class {
8977
9075
  );
8978
9076
  },
8979
9077
  invokeNestedRoot(nestedName, nestedFunctionName, inputs, invokeFn, entry, link) {
8980
- if (excluded.has(nestedName) || excluded.has(nestedFunctionName) || overBudget(entry)) {
9078
+ if (excluded.has(nestedName) || excluded.has(nestedFunctionName) || !recordsSpan(entry, nestedName, true)) {
8981
9079
  return invokeFn();
8982
9080
  }
8983
- spansUsed += 1;
8984
9081
  const childOptions = {
8985
9082
  name: nestedName,
8986
9083
  type: "function",
@@ -9007,6 +9104,12 @@ var Bitfab = class {
9007
9104
  }
9008
9105
  const enclosing = currentAutoTraceEntries();
9009
9106
  if (enclosing.length === 0) {
9107
+ const firstPlanRead = rootIsAsync ? self.simulationPlan.waitForFirstRead() : void 0;
9108
+ if (firstPlanRead !== void 0) {
9109
+ return firstPlanRead.then(
9110
+ () => tracedRoot.apply(this, args)
9111
+ );
9112
+ }
9010
9113
  return tracedRoot.apply(this, args);
9011
9114
  }
9012
9115
  const link = getReplayContext() || inSeedScope() ? void 0 : { traceFunctionKey };
@@ -9053,6 +9156,9 @@ var Bitfab = class {
9053
9156
  return autoTraceRoot;
9054
9157
  }
9055
9158
  applySimulationPlan(payload, submit) {
9159
+ if (!fitsTraceSpanBudget(payload)) {
9160
+ return;
9161
+ }
9056
9162
  this.simulationPlan.send(payload, rootTraceFunctionKeyOf(payload), submit);
9057
9163
  }
9058
9164
  /**
@@ -9087,15 +9193,24 @@ var Bitfab = class {
9087
9193
  * from the environment. Once a non-empty key is found it is cached, so an
9088
9194
  * early resolve that found nothing never poisons a later one.
9089
9195
  */
9090
- resolveApiKey() {
9196
+ findApiKey(callsKeyFunction) {
9091
9197
  if (this.resolvedApiKey !== void 0) {
9092
9198
  return this.resolvedApiKey;
9093
9199
  }
9200
+ if (typeof this.apiKeyConfig === "function" && !callsKeyFunction) {
9201
+ return void 0;
9202
+ }
9094
9203
  const fromConfig = typeof this.apiKeyConfig === "function" ? this.apiKeyConfig() : this.apiKeyConfig;
9095
9204
  const candidate = fromConfig && fromConfig.trim() !== "" ? fromConfig : readEnv2("BITFAB_API_KEY");
9096
9205
  const key = candidate && candidate.trim() !== "" ? candidate : void 0;
9097
9206
  if (key) {
9098
9207
  this.resolvedApiKey = key;
9208
+ }
9209
+ return key;
9210
+ }
9211
+ resolveApiKey() {
9212
+ const key = this.findApiKey(true);
9213
+ if (key) {
9099
9214
  return key;
9100
9215
  }
9101
9216
  if (this.strict) {
@@ -9715,7 +9830,10 @@ var Bitfab = class {
9715
9830
  self.httpClient.traceCompletion.start(traceId, spanId);
9716
9831
  startedSpan = { traceId, spanId };
9717
9832
  const functionName = fn.name !== "" ? fn.name : void 0;
9718
- const rootTraceFunctionKey = activeTraceStates.get(traceId)?.traceFunctionKey ?? traceFunctionKey;
9833
+ const rootTraceFunctionKey = rootTraceFunctionKeyOfTrace(
9834
+ traceId,
9835
+ traceFunctionKey
9836
+ );
9719
9837
  const baseSpanParams = {
9720
9838
  traceFunctionKey,
9721
9839
  rootTraceFunctionKey,
@@ -10237,14 +10355,25 @@ var Bitfab = class {
10237
10355
  ...params.testRunId && { testRunId: params.testRunId }
10238
10356
  });
10239
10357
  }
10358
+ contentOffBySimulationPlan(params) {
10359
+ if (params.rootTraceFunctionKey === void 0 || isFrameworkInstrumentation(params.instrumentation)) {
10360
+ return false;
10361
+ }
10362
+ return this.simulationPlan.contentOffNow(
10363
+ params.rootTraceFunctionKey,
10364
+ params.spanName
10365
+ ) || params.parentSpanId !== null && this.simulationPlan.unavailableNow();
10366
+ }
10240
10367
  /**
10241
10368
  * Send a wrapper span from function execution.
10242
10369
  * Internal method to record spans when using withSpan.
10243
10370
  * Queued on the client's span transport; delivery is the transport's job.
10244
10371
  */
10245
10372
  sendWrapperSpan(params) {
10246
- const serializedInputs = params.captureContent ? serializeValue(params.inputs) : void 0;
10247
- const serializedResult = params.captureContent ? serializeValue(params.result) : void 0;
10373
+ const contentOff = this.contentOffBySimulationPlan(params);
10374
+ const buildsContent = params.captureContent && !contentOff;
10375
+ const serializedInputs = buildsContent ? serializeValue(params.inputs) : void 0;
10376
+ const serializedResult = buildsContent ? serializeValue(params.result) : void 0;
10248
10377
  const externalSpan = {
10249
10378
  id: params.spanId,
10250
10379
  trace_id: params.traceId,
@@ -10295,7 +10424,8 @@ var Bitfab = class {
10295
10424
  ...params.captureContent && params.contexts && params.contexts.length > 0 && {
10296
10425
  contexts: params.contexts
10297
10426
  },
10298
- ...params.captureContent && params.prompt !== void 0 && { prompt: params.prompt }
10427
+ ...params.captureContent && params.prompt !== void 0 && { prompt: params.prompt },
10428
+ ...contentOff && { [CONTENT_OFF_KEY]: true }
10299
10429
  }
10300
10430
  };
10301
10431
  if (params.parentSpanId) {