@bitfab/sdk 0.41.0 → 0.43.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.
package/dist/index.cjs CHANGED
@@ -42,7 +42,7 @@ var __version__, __packageName__;
42
42
  var init_version_generated = __esm({
43
43
  "src/version.generated.ts"() {
44
44
  "use strict";
45
- __version__ = "0.41.0";
45
+ __version__ = "0.43.0";
46
46
  __packageName__ = "@bitfab/sdk";
47
47
  }
48
48
  });
@@ -1337,7 +1337,7 @@ function sourceTraceIdOf(payload) {
1337
1337
  const id = rawTrace?.id;
1338
1338
  return typeof id === "string" ? id : void 0;
1339
1339
  }
1340
- var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, OTLP_TRACES_ENDPOINT, RETRYABLE_STATUSES, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCLE_TIMEOUT_MS2, pendingTracePromises, carrierSeq, HttpClient;
1340
+ var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, REPLAY_COMPLETE_REQUEST_TIMEOUT_MS, OTLP_TRACES_ENDPOINT, RETRYABLE_STATUSES, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCLE_TIMEOUT_MS2, pendingTracePromises, carrierSeq, HttpClient;
1341
1341
  var init_http = __esm({
1342
1342
  "src/http.ts"() {
1343
1343
  "use strict";
@@ -1351,6 +1351,7 @@ var init_http = __esm({
1351
1351
  init_unrefTimer();
1352
1352
  init_warnOnce();
1353
1353
  REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS = 3e5;
1354
+ REPLAY_COMPLETE_REQUEST_TIMEOUT_MS = 12e4;
1354
1355
  OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
1355
1356
  RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
1356
1357
  EXIT_FLUSH_TIMEOUT_MS = 5e3;
@@ -1840,7 +1841,7 @@ var init_http = __esm({
1840
1841
  * Start a replay session by fetching historical traces.
1841
1842
  * Blocking call - creates a test run and returns lightweight item references.
1842
1843
  */
1843
- async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetId, graderIds, dbBranchSettings) {
1844
+ async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetId, graderIds, dbBranchSettings, attempts, includeOriginalMetadata) {
1844
1845
  const payload = { traceFunctionKey };
1845
1846
  if (limit !== void 0) {
1846
1847
  payload.limit = limit;
@@ -1873,6 +1874,12 @@ var init_http = __esm({
1873
1874
  if (dbBranchSettings !== void 0) {
1874
1875
  payload.dbBranchSettings = dbBranchSettings;
1875
1876
  }
1877
+ if (attempts !== void 0 && attempts > 1) {
1878
+ payload.attempts = attempts;
1879
+ }
1880
+ if (includeOriginalMetadata) {
1881
+ payload.includeOriginalMetadata = true;
1882
+ }
1876
1883
  const timeout = includeDbBranchLease ? REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS : 3e4;
1877
1884
  return this.request("/api/sdk/replay/start", payload, {
1878
1885
  timeout
@@ -1988,7 +1995,7 @@ var init_http = __esm({
1988
1995
  return this.request(
1989
1996
  "/api/sdk/replay/complete",
1990
1997
  { testRunId },
1991
- { timeout: 3e4 }
1998
+ { timeout: REPLAY_COMPLETE_REQUEST_TIMEOUT_MS }
1992
1999
  );
1993
2000
  }
1994
2001
  /**
@@ -1997,10 +2004,15 @@ var init_http = __esm({
1997
2004
  * snapshot + preview branch and polls operations to readiness, which
1998
2005
  * can take seconds.
1999
2006
  */
2000
- async resolveDbBranchLease(testRunId, traceId, dbBranchSettings) {
2007
+ async resolveDbBranchLease(testRunId, traceId, dbBranchSettings, attempt) {
2001
2008
  return this.request(
2002
2009
  "/api/sdk/replay/resolveDbBranchLease",
2003
- { testRunId, traceId, dbBranchSettings },
2010
+ {
2011
+ testRunId,
2012
+ traceId,
2013
+ dbBranchSettings,
2014
+ ...attempt !== void 0 && attempt > 0 ? { attempt } : {}
2015
+ },
2004
2016
  { timeout: REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS }
2005
2017
  );
2006
2018
  }
@@ -2436,6 +2448,23 @@ __export(replay_exports, {
2436
2448
  sleepForReplayPersistence: () => sleepForReplayPersistence,
2437
2449
  waitForReplayPersistence: () => waitForReplayPersistence
2438
2450
  });
2451
+ function expandReplayWork(serverItems, attempts) {
2452
+ return Array.from(
2453
+ { length: attempts },
2454
+ (_, attempt) => serverItems.map((serverItem) => ({ serverItem, attempt }))
2455
+ ).flat();
2456
+ }
2457
+ function resolveAttempts(attempts) {
2458
+ if (attempts === void 0) {
2459
+ return 1;
2460
+ }
2461
+ if (!Number.isInteger(attempts) || attempts < 1 || attempts > MAX_REPLAY_ATTEMPTS) {
2462
+ throw new BitfabError(
2463
+ `attempts must be an integer from 1 to ${MAX_REPLAY_ATTEMPTS} (got ${attempts}).`
2464
+ );
2465
+ }
2466
+ return attempts;
2467
+ }
2439
2468
  function dbBranchEnabled(dbBranch) {
2440
2469
  return dbBranch !== void 0 && dbBranch !== false;
2441
2470
  }
@@ -2464,6 +2493,9 @@ function reportReplayProgress(progress) {
2464
2493
  } catch {
2465
2494
  }
2466
2495
  }
2496
+ function isPlainRecord(value) {
2497
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2498
+ }
2467
2499
  function errorMessage(error) {
2468
2500
  return error instanceof Error ? error.message : String(error);
2469
2501
  }
@@ -2559,7 +2591,7 @@ function buildMockTree(rootNode) {
2559
2591
  }
2560
2592
  return { spans };
2561
2593
  }
2562
- async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, resolvedOverrides, replayedTraceId, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
2594
+ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
2563
2595
  let lease = includeDbBranchLease ? serverItem.dbBranchLease : void 0;
2564
2596
  let leaseError = includeDbBranchLease ? serverItem.dbBranchLeaseError : void 0;
2565
2597
  let dbSnapshotRef = serverItem.dbSnapshotRef;
@@ -2581,7 +2613,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2581
2613
  resolved = await httpClient.resolveDbBranchLease(
2582
2614
  testRunId,
2583
2615
  originalTraceId,
2584
- dbBranchSettings
2616
+ dbBranchSettings,
2617
+ attempt
2585
2618
  );
2586
2619
  } catch (cause) {
2587
2620
  throw new DbBranchReplayError(
@@ -2610,12 +2643,14 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2610
2643
  inputs = deserializeInputs(spanData);
2611
2644
  originalOutput = deserializeOutput(spanData);
2612
2645
  if (adaptInputs) {
2646
+ const originalMetadata = serverItem.originalMetadata;
2613
2647
  inputs = adaptInputs(inputs, {
2614
2648
  originalTraceId,
2615
2649
  originalSpanId,
2616
2650
  // Deprecated aliases for originalTraceId/originalSpanId.
2617
2651
  sourceTraceId: originalTraceId,
2618
- sourceSpanId: originalSpanId
2652
+ sourceSpanId: originalSpanId,
2653
+ metadata: isPlainRecord(originalMetadata) ? { ...originalMetadata } : {}
2619
2654
  });
2620
2655
  }
2621
2656
  const hasOverrides = resolvedOverrides.length > 0;
@@ -2661,6 +2696,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2661
2696
  originalSpanId,
2662
2697
  sourceTraceId: originalTraceId,
2663
2698
  sourceSpanId: originalSpanId,
2699
+ attempt,
2664
2700
  input: inputs,
2665
2701
  result: void 0,
2666
2702
  originalOutput,
@@ -2689,6 +2725,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2689
2725
  inputSourceSpanId: span.id,
2690
2726
  inputSourceTraceId: span.externalTraceId,
2691
2727
  sourceBitfabTraceId: originalTraceId,
2728
+ replayAttempt: attempt,
2692
2729
  mockTree,
2693
2730
  callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
2694
2731
  mockStrategy,
@@ -2740,6 +2777,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2740
2777
  // Deprecated aliases for originalTraceId/originalSpanId.
2741
2778
  sourceTraceId: originalTraceId,
2742
2779
  sourceSpanId: originalSpanId,
2780
+ attempt,
2743
2781
  input: inputs,
2744
2782
  result,
2745
2783
  originalOutput,
@@ -2868,6 +2906,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2868
2906
  } catch {
2869
2907
  }
2870
2908
  }
2909
+ const attempts = resolveAttempts(options?.attempts);
2871
2910
  await replayContextReady;
2872
2911
  let codeChangeDescription = options?.codeChangeDescription;
2873
2912
  let codeChangeFiles = options?.codeChangeFiles;
@@ -2900,7 +2939,9 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2900
2939
  options?.experimentGroupId,
2901
2940
  options?.datasetId,
2902
2941
  options?.graderIds,
2903
- resolveDbBranchSettings(options?.dbBranch)
2942
+ resolveDbBranchSettings(options?.dbBranch),
2943
+ attempts,
2944
+ options?.adaptInputs !== void 0
2904
2945
  );
2905
2946
  if (serverItems.length === 0) {
2906
2947
  try {
@@ -2917,10 +2958,11 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2917
2958
  ...normalizeMockOverrides(options?.mockOverride),
2918
2959
  ...registeredOverrides
2919
2960
  ];
2920
- const replayedTraceIds = serverItems.map(() => randomUuid());
2961
+ const workItems = expandReplayWork(serverItems, attempts);
2962
+ const replayedTraceIds = workItems.map(() => randomUuid());
2921
2963
  httpClient.trackTraceDeliveries(replayedTraceIds);
2922
- const tasks = serverItems.map(
2923
- (serverItem, index) => () => processItem(
2964
+ const tasks = workItems.map(
2965
+ ({ serverItem, attempt }, index) => () => processItem(
2924
2966
  httpClient,
2925
2967
  serverItem,
2926
2968
  fn,
@@ -2928,6 +2970,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2928
2970
  mockStrategy,
2929
2971
  resolvedOverrides,
2930
2972
  replayedTraceIds[index],
2973
+ attempt,
2931
2974
  dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true,
2932
2975
  resolveDbBranchSettings(options?.dbBranch),
2933
2976
  options?.adaptInputs,
@@ -2995,6 +3038,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2995
3038
  // Deprecated aliases for originalTraceId/originalSpanId.
2996
3039
  sourceTraceId: item.originalTraceId ?? null,
2997
3040
  sourceSpanId: item.originalSpanId ?? null,
3041
+ attempt: item.attempt,
2998
3042
  input: item.input,
2999
3043
  result: item.result,
3000
3044
  originalOutput: item.originalOutput,
@@ -3016,7 +3060,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
3016
3060
  },
3017
3061
  options?.onItemStart ? (index) => {
3018
3062
  started += 1;
3019
- const serverItem = serverItems[index];
3063
+ const { serverItem, attempt } = workItems[index];
3020
3064
  const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
3021
3065
  const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
3022
3066
  try {
@@ -3032,7 +3076,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
3032
3076
  originalTraceId,
3033
3077
  originalSpanId,
3034
3078
  sourceTraceId: originalTraceId,
3035
- sourceSpanId: originalSpanId
3079
+ sourceSpanId: originalSpanId,
3080
+ attempt
3036
3081
  }
3037
3082
  });
3038
3083
  } catch {
@@ -3044,7 +3089,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
3044
3089
  const dryResult = {
3045
3090
  items: resultItems,
3046
3091
  testRunId,
3047
- testRunUrl: fullTestRunUrl
3092
+ testRunUrl: fullTestRunUrl,
3093
+ attempts
3048
3094
  };
3049
3095
  await writeReplayResultFile(dryResult);
3050
3096
  return dryResult;
@@ -3113,7 +3159,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
3113
3159
  const result = {
3114
3160
  items: resultItems,
3115
3161
  testRunId,
3116
- testRunUrl: fullTestRunUrl
3162
+ testRunUrl: fullTestRunUrl,
3163
+ attempts
3117
3164
  };
3118
3165
  await writeReplayResultFile(result);
3119
3166
  if (!options?.onItemFinish) {
@@ -3154,7 +3201,7 @@ async function writeReplayResultFile(result) {
3154
3201
  }
3155
3202
  }
3156
3203
  }
3157
- var REPLAY_PERSISTENCE_TIMEOUT_MS, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
3204
+ var REPLAY_PERSISTENCE_TIMEOUT_MS, MAX_REPLAY_ATTEMPTS, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
3158
3205
  var init_replay = __esm({
3159
3206
  "src/replay.ts"() {
3160
3207
  "use strict";
@@ -3166,6 +3213,7 @@ var init_replay = __esm({
3166
3213
  init_replayContext();
3167
3214
  init_serialize();
3168
3215
  REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
3216
+ MAX_REPLAY_ATTEMPTS = 100;
3169
3217
  BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
3170
3218
  ReplayError = class extends BitfabError {
3171
3219
  constructor(message, items, testRunId, testRunUrl, cause) {
@@ -5247,7 +5295,8 @@ var BitfabLangGraphIntegration = class {
5247
5295
  type: "function",
5248
5296
  captureWhen: "nested",
5249
5297
  mockOnReplay: shouldMock,
5250
- finalize: finalizeToolResult
5298
+ finalize: finalizeToolResult,
5299
+ surface: "inherit"
5251
5300
  },
5252
5301
  async (_args) => await originalInvoke(input, ...rest)
5253
5302
  );
@@ -5290,7 +5339,7 @@ var BitfabLangGraphIntegration = class {
5290
5339
  wrapInvoke(fn) {
5291
5340
  return this.client.withSpan(
5292
5341
  this.traceFunctionKey,
5293
- { name: this.traceFunctionKey, type: "agent" },
5342
+ { name: this.traceFunctionKey, type: "agent", surface: "inherit" },
5294
5343
  fn
5295
5344
  );
5296
5345
  }
@@ -5343,7 +5392,11 @@ var BitfabOpenAIAgentHandler = class {
5343
5392
  }
5344
5393
  return res?.finalOutput;
5345
5394
  };
5346
- const options_ = { type: "agent", finalize };
5395
+ const options_ = {
5396
+ type: "agent",
5397
+ finalize,
5398
+ surface: "inherit"
5399
+ };
5347
5400
  const traced = this.withSpanFn(
5348
5401
  this.traceFunctionKey,
5349
5402
  options_,
@@ -5407,6 +5460,38 @@ _context = new WeakMap();
5407
5460
 
5408
5461
  // src/client.ts
5409
5462
  init_replayContext();
5463
+
5464
+ // src/seedContext.ts
5465
+ init_asyncStorage();
5466
+ var seedContextStorage = null;
5467
+ var SEED_CONTEXT_STORAGE_SYMBOL = /* @__PURE__ */ Symbol.for("bitfab.seedContextStorage");
5468
+ var seedContextReady = asyncStorageReady.then(() => {
5469
+ const shared = globalThis;
5470
+ const existing = shared[SEED_CONTEXT_STORAGE_SYMBOL];
5471
+ if (existing) {
5472
+ seedContextStorage = existing;
5473
+ return;
5474
+ }
5475
+ const created = createAsyncLocalStorage();
5476
+ if (created) {
5477
+ shared[SEED_CONTEXT_STORAGE_SYMBOL] = created;
5478
+ seedContextStorage = created;
5479
+ }
5480
+ });
5481
+ function getSeedContext() {
5482
+ return seedContextStorage?.getStore() ?? null;
5483
+ }
5484
+ function inSeedScope() {
5485
+ return getSeedContext() !== null;
5486
+ }
5487
+ function runWithSeedContext(ctx, fn) {
5488
+ if (seedContextStorage) {
5489
+ return seedContextStorage.run(ctx, fn);
5490
+ }
5491
+ return fn();
5492
+ }
5493
+
5494
+ // src/client.ts
5410
5495
  init_serialize();
5411
5496
 
5412
5497
  // src/tracing.ts
@@ -5742,7 +5827,8 @@ var BitfabVercelAiHandler = class {
5742
5827
  key,
5743
5828
  {
5744
5829
  type: "llm",
5745
- finalize: (result) => summarizeGenerate(result ?? {}, label)
5830
+ finalize: (result) => summarizeGenerate(result ?? {}, label),
5831
+ surface: "inherit"
5746
5832
  },
5747
5833
  () => doGenerate()
5748
5834
  );
@@ -5760,7 +5846,7 @@ var BitfabVercelAiHandler = class {
5760
5846
  // The wrapped fn returns immediately with the live stream, so the span
5761
5847
  // output cannot be read from the return value. `finalize` instead
5762
5848
  // awaits the summary the accumulator resolves once the stream drains.
5763
- { type: "llm", finalize: () => summary },
5849
+ { type: "llm", finalize: () => summary, surface: "inherit" },
5764
5850
  async () => {
5765
5851
  const result = await doStream();
5766
5852
  const stream = result.stream.pipeThrough(
@@ -5777,6 +5863,18 @@ var BitfabVercelAiHandler = class {
5777
5863
 
5778
5864
  // src/client.ts
5779
5865
  init_warnOnce();
5866
+ var MIXED_SURFACE_REMEDY = {
5867
+ "opt-in": "Inside a withTrace/trace subtree, configure a discovered call with node()/withNode() instead, or trace this workflow with withSpan() only.",
5868
+ "opt-out": "Wrap the caller with withTrace()/trace() as well, or wrap this function with withSpan()."
5869
+ };
5870
+ function warnMixedTracingSurfaces(traceFunctionKey, entered, enclosing) {
5871
+ const enteredApi = entered === "opt-in" ? "withSpan()" : "withTrace()";
5872
+ const enclosingApi = enclosing === "opt-in" ? "withSpan()" : "withTrace()";
5873
+ warnOnce(
5874
+ `mixed-tracing-surface:${traceFunctionKey}:${entered}`,
5875
+ `opt-in and opt-out tracing are mixed in one call stack: ${enteredApi} (${entered}) for "${traceFunctionKey}" was entered inside a ${enclosingApi} call (${enclosing}). Both are recorded, and the spans nest as written. ${MIXED_SURFACE_REMEDY[entered]}`
5876
+ );
5877
+ }
5780
5878
  var activeTraceStates = /* @__PURE__ */ new Map();
5781
5879
  var asyncLocalStorage = null;
5782
5880
  var SPAN_CONTEXT_STORAGE_SYMBOL = /* @__PURE__ */ Symbol.for("bitfab.spanContextStorage");
@@ -6140,7 +6238,13 @@ var Bitfab = class {
6140
6238
  this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
6141
6239
  this.timeout = config.timeout ?? 12e4;
6142
6240
  this.envVars = config.envVars ?? {};
6143
- this.explicitlyEnabled = config.enabled ?? true;
6241
+ if (config.enabled !== void 0) {
6242
+ warnOnce(
6243
+ "deprecated-enabled-option",
6244
+ "Bitfab({ enabled }) is deprecated; pass captureEnabled instead."
6245
+ );
6246
+ }
6247
+ this.captureConfigured = (config.captureEnabled ?? true) && (config.enabled ?? true);
6144
6248
  this.strict = config.strict ?? false;
6145
6249
  this.bamlClient = config.bamlClient ?? null;
6146
6250
  if (config.dbSnapshot) {
@@ -6176,9 +6280,6 @@ var Bitfab = class {
6176
6280
  if (!descriptor || typeof descriptor.value !== "function") {
6177
6281
  throw new BitfabError("@bitfab.trace can only decorate methods");
6178
6282
  }
6179
- if (!this.explicitlyEnabled) {
6180
- return;
6181
- }
6182
6283
  descriptor.value = this.createAutoTraceRoot(
6183
6284
  traceFunctionKey,
6184
6285
  String(propertyKey),
@@ -6192,9 +6293,6 @@ var Bitfab = class {
6192
6293
  if (typeof method !== "function" || context?.kind !== "method" || context.name === void 0) {
6193
6294
  throw new BitfabError("@bitfab.trace can only decorate methods");
6194
6295
  }
6195
- if (!this.explicitlyEnabled) {
6196
- return method;
6197
- }
6198
6296
  return this.createAutoTraceRoot(
6199
6297
  traceFunctionKey,
6200
6298
  String(context.name),
@@ -6210,9 +6308,6 @@ var Bitfab = class {
6210
6308
  if (!fn) {
6211
6309
  throw new BitfabError("bitfab.withTrace requires a function");
6212
6310
  }
6213
- if (!this.explicitlyEnabled) {
6214
- return fn;
6215
- }
6216
6311
  const name = fn.name !== "" ? fn.name : traceFunctionKey;
6217
6312
  return this.createAutoTraceRoot(traceFunctionKey, name, options, fn);
6218
6313
  }
@@ -6236,9 +6331,6 @@ var Bitfab = class {
6236
6331
  if (!descriptor || typeof descriptor.value !== "function") {
6237
6332
  throw new BitfabError("@bitfab.node can only decorate methods");
6238
6333
  }
6239
- if (!this.explicitlyEnabled) {
6240
- return;
6241
- }
6242
6334
  descriptor.value = this.createAutoTraceNode(
6243
6335
  configuration,
6244
6336
  descriptor.value,
@@ -6251,9 +6343,6 @@ var Bitfab = class {
6251
6343
  if (typeof method !== "function" || context?.kind !== "method") {
6252
6344
  throw new BitfabError("@bitfab.node can only decorate methods");
6253
6345
  }
6254
- if (!this.explicitlyEnabled) {
6255
- return method;
6256
- }
6257
6346
  return this.createAutoTraceNode(
6258
6347
  configuration,
6259
6348
  method,
@@ -6269,9 +6358,6 @@ var Bitfab = class {
6269
6358
  throw new BitfabError("bitfab.withNode requires a function");
6270
6359
  }
6271
6360
  const configuration = this.resolveNodeConfiguration(options);
6272
- if (!this.explicitlyEnabled) {
6273
- return fn;
6274
- }
6275
6361
  const functionName = internalFunctionName ?? fn.name;
6276
6362
  if (functionName === "") {
6277
6363
  throw new BitfabError(
@@ -6324,9 +6410,14 @@ var Bitfab = class {
6324
6410
  );
6325
6411
  const excluded = new Set(options.exclude ?? []);
6326
6412
  const includeWrappers = options.includeWrappers ?? false;
6413
+ const rootOptions = {
6414
+ name: options.name ?? name,
6415
+ type: options.type ?? "custom",
6416
+ surface: "opt-out"
6417
+ };
6327
6418
  const tracedRoot = this.withSpan(
6328
6419
  traceFunctionKey,
6329
- { name: options.name ?? name, type: options.type ?? "custom" },
6420
+ rootOptions,
6330
6421
  function(...args) {
6331
6422
  const capturePolicy = getAutoTraceCapturePolicy(self, traceFunctionKey);
6332
6423
  self.refreshAutoTraceCapturePolicy(traceFunctionKey);
@@ -6370,6 +6461,7 @@ var Bitfab = class {
6370
6461
  name: nodeConfiguration?.name ?? definition.name,
6371
6462
  type: nodeConfiguration?.type ?? "function",
6372
6463
  captureWhen: "nested",
6464
+ surface: "opt-out",
6373
6465
  functionId: definition.id,
6374
6466
  captureContent: nodeConfiguration !== void 0 || capturePolicy === void 0 || capturePolicy.has(definition.id),
6375
6467
  autoTraceDefinition: definition,
@@ -6407,7 +6499,7 @@ var Bitfab = class {
6407
6499
  }
6408
6500
  );
6409
6501
  const autoTraceRoot = function(...args) {
6410
- if (!self.isTracingEnabled()) {
6502
+ if (!self.shouldRecord()) {
6411
6503
  return fn.apply(this, args);
6412
6504
  }
6413
6505
  return tracedRoot.apply(this, args);
@@ -6415,6 +6507,7 @@ var Bitfab = class {
6415
6507
  Object.defineProperty(autoTraceRoot, "_bitfabTraceFunctionKey", {
6416
6508
  value: traceFunctionKey
6417
6509
  });
6510
+ Object.defineProperty(autoTraceRoot, "_bitfabWrappedFn", { value: fn });
6418
6511
  return autoTraceRoot;
6419
6512
  }
6420
6513
  refreshAutoTraceCapturePolicy(traceFunctionKey) {
@@ -6498,7 +6591,7 @@ var Bitfab = class {
6498
6591
  "Bitfab: no API key resolved. Set BITFAB_API_KEY or pass apiKey to new Bitfab(). If a script loads env with dotenv, load it before the module that constructs the client is imported (e.g. `node --env-file=.env script.ts`), or pass `apiKey: () => process.env.BITFAB_API_KEY`."
6499
6592
  );
6500
6593
  }
6501
- if (this.explicitlyEnabled && !this.apiKeyWarned) {
6594
+ if (this.captureConfigured && !this.apiKeyWarned) {
6502
6595
  this.apiKeyWarned = true;
6503
6596
  console.warn(
6504
6597
  "Bitfab: apiKey is empty - tracing is disabled. Provide a valid API key to enable tracing."
@@ -6506,17 +6599,21 @@ var Bitfab = class {
6506
6599
  }
6507
6600
  return void 0;
6508
6601
  }
6509
- /**
6510
- * Whether tracing should run, decided lazily at call time (not frozen at
6511
- * construction). An explicit `enabled: false` short-circuits without ever
6512
- * touching the key.
6513
- */
6514
- isTracingEnabled() {
6515
- if (!this.explicitlyEnabled) {
6602
+ isCaptureEnabled() {
6603
+ if (!this.captureConfigured) {
6516
6604
  return false;
6517
6605
  }
6518
6606
  return this.resolveApiKey() !== void 0;
6519
6607
  }
6608
+ shouldRecord() {
6609
+ if (!this.captureConfigured && !getReplayContext() && !inSeedScope()) {
6610
+ return false;
6611
+ }
6612
+ return this.resolveApiKey() !== void 0;
6613
+ }
6614
+ get captureEnabled() {
6615
+ return this.isCaptureEnabled();
6616
+ }
6520
6617
  /**
6521
6618
  * Fetch the function with its current version and BAML prompt from the server.
6522
6619
  *
@@ -6928,9 +7025,6 @@ var Bitfab = class {
6928
7025
  * @returns A wrapped function with the same signature that creates spans for inputs and outputs
6929
7026
  */
6930
7027
  withSpan(traceFunctionKey, optionsOrFn, maybeFn) {
6931
- if (!this.explicitlyEnabled) {
6932
- return typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
6933
- }
6934
7028
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
6935
7029
  const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
6936
7030
  const self = this;
@@ -6944,7 +7038,7 @@ var Bitfab = class {
6944
7038
  }
6945
7039
  })();
6946
7040
  const wrappedFn = function(...args) {
6947
- if (!self.isTracingEnabled()) {
7041
+ if (!self.shouldRecord()) {
6948
7042
  return fn.apply(this, args);
6949
7043
  }
6950
7044
  initializeAsyncContext();
@@ -6992,11 +7086,26 @@ var Bitfab = class {
6992
7086
  const currentStack = getSpanStack();
6993
7087
  const parentContext = currentStack[currentStack.length - 1];
6994
7088
  const replayCtxForTraceId = parentContext ? null : getReplayContext();
6995
- const traceId = parentContext?.traceId ?? replayCtxForTraceId?.traceId ?? randomUuid();
7089
+ const seedCtxForTraceId = parentContext ? null : getSeedContext();
7090
+ const traceId = parentContext?.traceId ?? replayCtxForTraceId?.traceId ?? seedCtxForTraceId?.traceId ?? randomUuid();
6996
7091
  const spanId = randomUuid();
6997
7092
  const parentSpanId = parentContext?.spanId ?? null;
6998
7093
  const isRootSpan = parentSpanId === null;
6999
- const newContext = { traceId, spanId, contexts: [] };
7094
+ const requestedSurface = options.surface ?? "opt-in";
7095
+ const surface = requestedSurface === "inherit" ? parentContext?.surface ?? "opt-in" : requestedSurface;
7096
+ if (requestedSurface !== "inherit" && parentContext?.surface !== void 0 && parentContext.surface !== surface) {
7097
+ warnMixedTracingSurfaces(
7098
+ traceFunctionKey,
7099
+ surface,
7100
+ parentContext.surface
7101
+ );
7102
+ }
7103
+ const newContext = {
7104
+ traceId,
7105
+ spanId,
7106
+ contexts: [],
7107
+ surface
7108
+ };
7000
7109
  newStack = [...currentStack, newContext];
7001
7110
  const inputs = args;
7002
7111
  const startedAt = nowIsoTimestamp();
@@ -7012,6 +7121,9 @@ var Bitfab = class {
7012
7121
  ...replayCtxAtStart?.inputSourceTraceId && {
7013
7122
  inputSourceTraceId: replayCtxAtStart.inputSourceTraceId
7014
7123
  },
7124
+ ...replayCtxAtStart?.replayAttempt !== void 0 && {
7125
+ replayAttempt: replayCtxAtStart.replayAttempt
7126
+ },
7015
7127
  dbSnapshotRef
7016
7128
  });
7017
7129
  registeredTraceId = traceId;
@@ -7062,6 +7174,7 @@ var Bitfab = class {
7062
7174
  contexts: traceState?.contexts ?? [],
7063
7175
  testRunId: traceState?.testRunId,
7064
7176
  inputSourceTraceId: traceState?.inputSourceTraceId,
7177
+ replayAttempt: traceState?.replayAttempt,
7065
7178
  dbSnapshotRef: traceState?.dbSnapshotRef,
7066
7179
  dropped: traceState?.dropped,
7067
7180
  ingestionType: traceState?.ingestionType,
@@ -7274,7 +7387,7 @@ var Bitfab = class {
7274
7387
  if (registeredTraceId) {
7275
7388
  activeTraceStates.delete(registeredTraceId);
7276
7389
  }
7277
- if (getReplayContext()) {
7390
+ if (getReplayContext() || inSeedScope()) {
7278
7391
  throw setupError;
7279
7392
  }
7280
7393
  warnOnce(
@@ -7349,7 +7462,7 @@ var Bitfab = class {
7349
7462
  return {
7350
7463
  traceId,
7351
7464
  addContext: (context) => {
7352
- if (!this.isTracingEnabled()) {
7465
+ if (!this.shouldRecord()) {
7353
7466
  return Promise.resolve();
7354
7467
  }
7355
7468
  if (typeof context !== "object" || context === null) {
@@ -7360,7 +7473,7 @@ var Bitfab = class {
7360
7473
  });
7361
7474
  },
7362
7475
  setMetadata: (metadata) => {
7363
- if (!this.isTracingEnabled()) {
7476
+ if (!this.shouldRecord()) {
7364
7477
  return Promise.resolve();
7365
7478
  }
7366
7479
  if (typeof metadata !== "object" || metadata === null) {
@@ -7369,7 +7482,7 @@ var Bitfab = class {
7369
7482
  return this.httpClient.patchTrace(traceId, { mergeMetadata: metadata });
7370
7483
  },
7371
7484
  setSessionId: (sessionId) => {
7372
- if (!this.isTracingEnabled()) {
7485
+ if (!this.shouldRecord()) {
7373
7486
  return Promise.resolve();
7374
7487
  }
7375
7488
  if (typeof sessionId !== "string" || sessionId.length === 0) {
@@ -7378,7 +7491,7 @@ var Bitfab = class {
7378
7491
  return this.httpClient.patchTrace(traceId, { setSessionId: sessionId });
7379
7492
  },
7380
7493
  setName: (name) => {
7381
- if (!this.isTracingEnabled()) {
7494
+ if (!this.shouldRecord()) {
7382
7495
  return Promise.resolve();
7383
7496
  }
7384
7497
  if (typeof name !== "string" || name.length === 0) {
@@ -7459,6 +7572,9 @@ var Bitfab = class {
7459
7572
  if (params.inputSourceTraceId) {
7460
7573
  rawTrace.input_source_trace_id = params.inputSourceTraceId;
7461
7574
  }
7575
+ if (params.replayAttempt !== void 0) {
7576
+ rawTrace.replay_attempt = params.replayAttempt;
7577
+ }
7462
7578
  if (params.dbSnapshotRef) {
7463
7579
  rawTrace.db_snapshot_ref = params.dbSnapshotRef;
7464
7580
  }
@@ -7611,22 +7727,13 @@ var Bitfab = class {
7611
7727
  clearMockOverrides() {
7612
7728
  this.mockOverrides.length = 0;
7613
7729
  }
7614
- /**
7615
- * Write a replayable trace from a case, without running anything.
7616
- *
7617
- * Use this to turn a corpus you already hold (a Braintrust dataset, a
7618
- * spreadsheet, hand-written cases) into traces that {@link replay} can
7619
- * select. The recorded root span carries `input` as its input and `expected`
7620
- * as its output, so replay reports each item against the value you expected
7621
- * rather than against a previous run.
7622
- *
7623
- * A seeded trace has no child spans and no database pin, so replay mocking
7624
- * has nothing recorded to substitute and `dbBranch` refuses it. Pass
7625
- * `mockOverride` at replay time for calls that must not run.
7626
- *
7627
- * @returns The trace ID, usable with `replay({ traceIds: [...] })`.
7628
- */
7629
- seedTrace(traceFunctionKey, options) {
7730
+ seedTrace(traceFunctionKey, optionsOrFn, runOptions) {
7731
+ if (typeof optionsOrFn === "function") {
7732
+ return this.seedTraceByRunning(traceFunctionKey, optionsOrFn, runOptions);
7733
+ }
7734
+ return this.seedTraceFromCase(traceFunctionKey, optionsOrFn);
7735
+ }
7736
+ seedTraceFromCase(traceFunctionKey, options) {
7630
7737
  const { input } = options;
7631
7738
  const fn = options.fn?._bitfabWrappedFn ?? options.fn;
7632
7739
  if (fn && input.length < fn.length) {
@@ -7675,6 +7782,50 @@ var Bitfab = class {
7675
7782
  }
7676
7783
  return traceId;
7677
7784
  }
7785
+ async seedTraceByRunning(traceFunctionKey, fn, options) {
7786
+ const wrappedKey = fn._bitfabTraceFunctionKey;
7787
+ let target = fn;
7788
+ if (wrappedKey === void 0) {
7789
+ target = this.withSpan(
7790
+ traceFunctionKey,
7791
+ { name: traceFunctionKey, type: "agent" },
7792
+ fn
7793
+ );
7794
+ } else if (wrappedKey !== traceFunctionKey) {
7795
+ throw new BitfabError(
7796
+ `Function is wrapped with trace function key '${wrappedKey}' but seedTrace was called with '${traceFunctionKey}'. Pass matching keys, or pass the unwrapped function to seed it under the explicit key.`
7797
+ );
7798
+ }
7799
+ await seedContextReady;
7800
+ const traceId = randomUuid();
7801
+ activeTraceStates.set(traceId, {
7802
+ traceId,
7803
+ startedAt: nowIsoTimestamp(),
7804
+ contexts: [],
7805
+ ingestionType: "seeded",
7806
+ ...options?.sessionId !== void 0 && { sessionId: options.sessionId },
7807
+ ...options?.name !== void 0 && { name: options.name },
7808
+ ...options?.metadata !== void 0 && { metadata: options.metadata }
7809
+ });
7810
+ const args = options?.args ?? [];
7811
+ let unrecorded = true;
7812
+ try {
7813
+ await runWithSeedContext({ traceId }, async () => target(...args));
7814
+ } finally {
7815
+ try {
7816
+ const { flushTraces: flushTraces2 } = await Promise.resolve().then(() => (init_http(), http_exports));
7817
+ await flushTraces2(3e4);
7818
+ } finally {
7819
+ unrecorded = activeTraceStates.delete(traceId);
7820
+ }
7821
+ }
7822
+ if (unrecorded) {
7823
+ throw new BitfabError(
7824
+ `seedTrace recorded nothing for '${traceFunctionKey}': the call finished without a root span. Check that an API key resolves (BITFAB_API_KEY or apiKey), and that fn is a regular or async function rather than a generator.`
7825
+ );
7826
+ }
7827
+ return traceId;
7828
+ }
7678
7829
  async replay(traceFunctionKey, fn, options) {
7679
7830
  const wrappedKey = fn._bitfabTraceFunctionKey;
7680
7831
  let replayFn = fn;
@@ -7935,7 +8086,7 @@ init_replay();
7935
8086
  function defineReplayRegistry(registry) {
7936
8087
  return registry;
7937
8088
  }
7938
- async function seedFromRegistry(registry, pipeline, cases) {
8089
+ async function seedFromRegistry(registry, pipeline, cases, options = {}) {
7939
8090
  const registration = registry[pipeline];
7940
8091
  if (registration === void 0) {
7941
8092
  throw new BitfabError(
@@ -7943,6 +8094,19 @@ async function seedFromRegistry(registry, pipeline, cases) {
7943
8094
  );
7944
8095
  }
7945
8096
  const traceFunctionKey = resolveTraceFunctionKey(registration);
8097
+ if (options.run === true) {
8098
+ const traceIds2 = [];
8099
+ for (const seedCase of cases) {
8100
+ traceIds2.push(
8101
+ await registration.client.seedTrace(traceFunctionKey, registration.fn, {
8102
+ args: seedCase.input,
8103
+ metadata: seedCase.metadata,
8104
+ sessionId: seedCase.sessionId
8105
+ })
8106
+ );
8107
+ }
8108
+ return { pipeline, traceFunctionKey, traceIds: traceIds2 };
8109
+ }
7946
8110
  const traceIds = cases.map(
7947
8111
  (seedCase) => registration.client.seedTrace(traceFunctionKey, {
7948
8112
  input: seedCase.input,