@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/node.cjs CHANGED
@@ -88,7 +88,7 @@ var __version__, __packageName__;
88
88
  var init_version_generated = __esm({
89
89
  "src/version.generated.ts"() {
90
90
  "use strict";
91
- __version__ = "0.41.0";
91
+ __version__ = "0.43.0";
92
92
  __packageName__ = "@bitfab/sdk";
93
93
  }
94
94
  });
@@ -1344,7 +1344,7 @@ function sourceTraceIdOf(payload) {
1344
1344
  const id = rawTrace?.id;
1345
1345
  return typeof id === "string" ? id : void 0;
1346
1346
  }
1347
- var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, OTLP_TRACES_ENDPOINT, RETRYABLE_STATUSES, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCLE_TIMEOUT_MS2, pendingTracePromises, carrierSeq, HttpClient;
1347
+ 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;
1348
1348
  var init_http = __esm({
1349
1349
  "src/http.ts"() {
1350
1350
  "use strict";
@@ -1358,6 +1358,7 @@ var init_http = __esm({
1358
1358
  init_unrefTimer();
1359
1359
  init_warnOnce();
1360
1360
  REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS = 3e5;
1361
+ REPLAY_COMPLETE_REQUEST_TIMEOUT_MS = 12e4;
1361
1362
  OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
1362
1363
  RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
1363
1364
  EXIT_FLUSH_TIMEOUT_MS = 5e3;
@@ -1847,7 +1848,7 @@ var init_http = __esm({
1847
1848
  * Start a replay session by fetching historical traces.
1848
1849
  * Blocking call - creates a test run and returns lightweight item references.
1849
1850
  */
1850
- async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetId, graderIds, dbBranchSettings) {
1851
+ async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetId, graderIds, dbBranchSettings, attempts, includeOriginalMetadata) {
1851
1852
  const payload = { traceFunctionKey };
1852
1853
  if (limit !== void 0) {
1853
1854
  payload.limit = limit;
@@ -1880,6 +1881,12 @@ var init_http = __esm({
1880
1881
  if (dbBranchSettings !== void 0) {
1881
1882
  payload.dbBranchSettings = dbBranchSettings;
1882
1883
  }
1884
+ if (attempts !== void 0 && attempts > 1) {
1885
+ payload.attempts = attempts;
1886
+ }
1887
+ if (includeOriginalMetadata) {
1888
+ payload.includeOriginalMetadata = true;
1889
+ }
1883
1890
  const timeout = includeDbBranchLease ? REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS : 3e4;
1884
1891
  return this.request("/api/sdk/replay/start", payload, {
1885
1892
  timeout
@@ -1995,7 +2002,7 @@ var init_http = __esm({
1995
2002
  return this.request(
1996
2003
  "/api/sdk/replay/complete",
1997
2004
  { testRunId },
1998
- { timeout: 3e4 }
2005
+ { timeout: REPLAY_COMPLETE_REQUEST_TIMEOUT_MS }
1999
2006
  );
2000
2007
  }
2001
2008
  /**
@@ -2004,10 +2011,15 @@ var init_http = __esm({
2004
2011
  * snapshot + preview branch and polls operations to readiness, which
2005
2012
  * can take seconds.
2006
2013
  */
2007
- async resolveDbBranchLease(testRunId, traceId, dbBranchSettings) {
2014
+ async resolveDbBranchLease(testRunId, traceId, dbBranchSettings, attempt) {
2008
2015
  return this.request(
2009
2016
  "/api/sdk/replay/resolveDbBranchLease",
2010
- { testRunId, traceId, dbBranchSettings },
2017
+ {
2018
+ testRunId,
2019
+ traceId,
2020
+ dbBranchSettings,
2021
+ ...attempt !== void 0 && attempt > 0 ? { attempt } : {}
2022
+ },
2011
2023
  { timeout: REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS }
2012
2024
  );
2013
2025
  }
@@ -2443,6 +2455,23 @@ __export(replay_exports, {
2443
2455
  sleepForReplayPersistence: () => sleepForReplayPersistence,
2444
2456
  waitForReplayPersistence: () => waitForReplayPersistence
2445
2457
  });
2458
+ function expandReplayWork(serverItems, attempts) {
2459
+ return Array.from(
2460
+ { length: attempts },
2461
+ (_, attempt) => serverItems.map((serverItem) => ({ serverItem, attempt }))
2462
+ ).flat();
2463
+ }
2464
+ function resolveAttempts(attempts) {
2465
+ if (attempts === void 0) {
2466
+ return 1;
2467
+ }
2468
+ if (!Number.isInteger(attempts) || attempts < 1 || attempts > MAX_REPLAY_ATTEMPTS) {
2469
+ throw new BitfabError(
2470
+ `attempts must be an integer from 1 to ${MAX_REPLAY_ATTEMPTS} (got ${attempts}).`
2471
+ );
2472
+ }
2473
+ return attempts;
2474
+ }
2446
2475
  function dbBranchEnabled(dbBranch) {
2447
2476
  return dbBranch !== void 0 && dbBranch !== false;
2448
2477
  }
@@ -2471,6 +2500,9 @@ function reportReplayProgress(progress) {
2471
2500
  } catch {
2472
2501
  }
2473
2502
  }
2503
+ function isPlainRecord(value) {
2504
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2505
+ }
2474
2506
  function errorMessage(error) {
2475
2507
  return error instanceof Error ? error.message : String(error);
2476
2508
  }
@@ -2566,7 +2598,7 @@ function buildMockTree(rootNode) {
2566
2598
  }
2567
2599
  return { spans };
2568
2600
  }
2569
- async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, resolvedOverrides, replayedTraceId, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
2601
+ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
2570
2602
  let lease = includeDbBranchLease ? serverItem.dbBranchLease : void 0;
2571
2603
  let leaseError = includeDbBranchLease ? serverItem.dbBranchLeaseError : void 0;
2572
2604
  let dbSnapshotRef = serverItem.dbSnapshotRef;
@@ -2588,7 +2620,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2588
2620
  resolved = await httpClient.resolveDbBranchLease(
2589
2621
  testRunId,
2590
2622
  originalTraceId,
2591
- dbBranchSettings
2623
+ dbBranchSettings,
2624
+ attempt
2592
2625
  );
2593
2626
  } catch (cause) {
2594
2627
  throw new DbBranchReplayError(
@@ -2617,12 +2650,14 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2617
2650
  inputs = deserializeInputs(spanData);
2618
2651
  originalOutput = deserializeOutput(spanData);
2619
2652
  if (adaptInputs) {
2653
+ const originalMetadata = serverItem.originalMetadata;
2620
2654
  inputs = adaptInputs(inputs, {
2621
2655
  originalTraceId,
2622
2656
  originalSpanId,
2623
2657
  // Deprecated aliases for originalTraceId/originalSpanId.
2624
2658
  sourceTraceId: originalTraceId,
2625
- sourceSpanId: originalSpanId
2659
+ sourceSpanId: originalSpanId,
2660
+ metadata: isPlainRecord(originalMetadata) ? { ...originalMetadata } : {}
2626
2661
  });
2627
2662
  }
2628
2663
  const hasOverrides = resolvedOverrides.length > 0;
@@ -2668,6 +2703,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2668
2703
  originalSpanId,
2669
2704
  sourceTraceId: originalTraceId,
2670
2705
  sourceSpanId: originalSpanId,
2706
+ attempt,
2671
2707
  input: inputs,
2672
2708
  result: void 0,
2673
2709
  originalOutput,
@@ -2696,6 +2732,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2696
2732
  inputSourceSpanId: span.id,
2697
2733
  inputSourceTraceId: span.externalTraceId,
2698
2734
  sourceBitfabTraceId: originalTraceId,
2735
+ replayAttempt: attempt,
2699
2736
  mockTree,
2700
2737
  callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
2701
2738
  mockStrategy,
@@ -2747,6 +2784,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2747
2784
  // Deprecated aliases for originalTraceId/originalSpanId.
2748
2785
  sourceTraceId: originalTraceId,
2749
2786
  sourceSpanId: originalSpanId,
2787
+ attempt,
2750
2788
  input: inputs,
2751
2789
  result,
2752
2790
  originalOutput,
@@ -2875,6 +2913,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2875
2913
  } catch {
2876
2914
  }
2877
2915
  }
2916
+ const attempts = resolveAttempts(options?.attempts);
2878
2917
  await replayContextReady;
2879
2918
  let codeChangeDescription = options?.codeChangeDescription;
2880
2919
  let codeChangeFiles = options?.codeChangeFiles;
@@ -2907,7 +2946,9 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2907
2946
  options?.experimentGroupId,
2908
2947
  options?.datasetId,
2909
2948
  options?.graderIds,
2910
- resolveDbBranchSettings(options?.dbBranch)
2949
+ resolveDbBranchSettings(options?.dbBranch),
2950
+ attempts,
2951
+ options?.adaptInputs !== void 0
2911
2952
  );
2912
2953
  if (serverItems.length === 0) {
2913
2954
  try {
@@ -2924,10 +2965,11 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2924
2965
  ...normalizeMockOverrides(options?.mockOverride),
2925
2966
  ...registeredOverrides
2926
2967
  ];
2927
- const replayedTraceIds = serverItems.map(() => randomUuid());
2968
+ const workItems = expandReplayWork(serverItems, attempts);
2969
+ const replayedTraceIds = workItems.map(() => randomUuid());
2928
2970
  httpClient.trackTraceDeliveries(replayedTraceIds);
2929
- const tasks = serverItems.map(
2930
- (serverItem, index) => () => processItem(
2971
+ const tasks = workItems.map(
2972
+ ({ serverItem, attempt }, index) => () => processItem(
2931
2973
  httpClient,
2932
2974
  serverItem,
2933
2975
  fn,
@@ -2935,6 +2977,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2935
2977
  mockStrategy,
2936
2978
  resolvedOverrides,
2937
2979
  replayedTraceIds[index],
2980
+ attempt,
2938
2981
  dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true,
2939
2982
  resolveDbBranchSettings(options?.dbBranch),
2940
2983
  options?.adaptInputs,
@@ -3002,6 +3045,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
3002
3045
  // Deprecated aliases for originalTraceId/originalSpanId.
3003
3046
  sourceTraceId: item.originalTraceId ?? null,
3004
3047
  sourceSpanId: item.originalSpanId ?? null,
3048
+ attempt: item.attempt,
3005
3049
  input: item.input,
3006
3050
  result: item.result,
3007
3051
  originalOutput: item.originalOutput,
@@ -3023,7 +3067,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
3023
3067
  },
3024
3068
  options?.onItemStart ? (index) => {
3025
3069
  started += 1;
3026
- const serverItem = serverItems[index];
3070
+ const { serverItem, attempt } = workItems[index];
3027
3071
  const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
3028
3072
  const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
3029
3073
  try {
@@ -3039,7 +3083,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
3039
3083
  originalTraceId,
3040
3084
  originalSpanId,
3041
3085
  sourceTraceId: originalTraceId,
3042
- sourceSpanId: originalSpanId
3086
+ sourceSpanId: originalSpanId,
3087
+ attempt
3043
3088
  }
3044
3089
  });
3045
3090
  } catch {
@@ -3051,7 +3096,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
3051
3096
  const dryResult = {
3052
3097
  items: resultItems,
3053
3098
  testRunId,
3054
- testRunUrl: fullTestRunUrl
3099
+ testRunUrl: fullTestRunUrl,
3100
+ attempts
3055
3101
  };
3056
3102
  await writeReplayResultFile(dryResult);
3057
3103
  return dryResult;
@@ -3120,7 +3166,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
3120
3166
  const result = {
3121
3167
  items: resultItems,
3122
3168
  testRunId,
3123
- testRunUrl: fullTestRunUrl
3169
+ testRunUrl: fullTestRunUrl,
3170
+ attempts
3124
3171
  };
3125
3172
  await writeReplayResultFile(result);
3126
3173
  if (!options?.onItemFinish) {
@@ -3161,7 +3208,7 @@ async function writeReplayResultFile(result) {
3161
3208
  }
3162
3209
  }
3163
3210
  }
3164
- var REPLAY_PERSISTENCE_TIMEOUT_MS, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
3211
+ var REPLAY_PERSISTENCE_TIMEOUT_MS, MAX_REPLAY_ATTEMPTS, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
3165
3212
  var init_replay = __esm({
3166
3213
  "src/replay.ts"() {
3167
3214
  "use strict";
@@ -3173,6 +3220,7 @@ var init_replay = __esm({
3173
3220
  init_replayContext();
3174
3221
  init_serialize();
3175
3222
  REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
3223
+ MAX_REPLAY_ATTEMPTS = 100;
3176
3224
  BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
3177
3225
  ReplayError = class extends BitfabError {
3178
3226
  constructor(message, items, testRunId, testRunUrl, cause) {
@@ -5261,7 +5309,8 @@ var BitfabLangGraphIntegration = class {
5261
5309
  type: "function",
5262
5310
  captureWhen: "nested",
5263
5311
  mockOnReplay: shouldMock,
5264
- finalize: finalizeToolResult
5312
+ finalize: finalizeToolResult,
5313
+ surface: "inherit"
5265
5314
  },
5266
5315
  async (_args) => await originalInvoke(input, ...rest)
5267
5316
  );
@@ -5304,7 +5353,7 @@ var BitfabLangGraphIntegration = class {
5304
5353
  wrapInvoke(fn) {
5305
5354
  return this.client.withSpan(
5306
5355
  this.traceFunctionKey,
5307
- { name: this.traceFunctionKey, type: "agent" },
5356
+ { name: this.traceFunctionKey, type: "agent", surface: "inherit" },
5308
5357
  fn
5309
5358
  );
5310
5359
  }
@@ -5357,7 +5406,11 @@ var BitfabOpenAIAgentHandler = class {
5357
5406
  }
5358
5407
  return res?.finalOutput;
5359
5408
  };
5360
- const options_ = { type: "agent", finalize };
5409
+ const options_ = {
5410
+ type: "agent",
5411
+ finalize,
5412
+ surface: "inherit"
5413
+ };
5361
5414
  const traced = this.withSpanFn(
5362
5415
  this.traceFunctionKey,
5363
5416
  options_,
@@ -5421,6 +5474,38 @@ _context = new WeakMap();
5421
5474
 
5422
5475
  // src/client.ts
5423
5476
  init_replayContext();
5477
+
5478
+ // src/seedContext.ts
5479
+ init_asyncStorage();
5480
+ var seedContextStorage = null;
5481
+ var SEED_CONTEXT_STORAGE_SYMBOL = /* @__PURE__ */ Symbol.for("bitfab.seedContextStorage");
5482
+ var seedContextReady = asyncStorageReady.then(() => {
5483
+ const shared = globalThis;
5484
+ const existing = shared[SEED_CONTEXT_STORAGE_SYMBOL];
5485
+ if (existing) {
5486
+ seedContextStorage = existing;
5487
+ return;
5488
+ }
5489
+ const created = createAsyncLocalStorage();
5490
+ if (created) {
5491
+ shared[SEED_CONTEXT_STORAGE_SYMBOL] = created;
5492
+ seedContextStorage = created;
5493
+ }
5494
+ });
5495
+ function getSeedContext() {
5496
+ return seedContextStorage?.getStore() ?? null;
5497
+ }
5498
+ function inSeedScope() {
5499
+ return getSeedContext() !== null;
5500
+ }
5501
+ function runWithSeedContext(ctx, fn) {
5502
+ if (seedContextStorage) {
5503
+ return seedContextStorage.run(ctx, fn);
5504
+ }
5505
+ return fn();
5506
+ }
5507
+
5508
+ // src/client.ts
5424
5509
  init_serialize();
5425
5510
 
5426
5511
  // src/tracing.ts
@@ -5756,7 +5841,8 @@ var BitfabVercelAiHandler = class {
5756
5841
  key,
5757
5842
  {
5758
5843
  type: "llm",
5759
- finalize: (result) => summarizeGenerate(result ?? {}, label)
5844
+ finalize: (result) => summarizeGenerate(result ?? {}, label),
5845
+ surface: "inherit"
5760
5846
  },
5761
5847
  () => doGenerate()
5762
5848
  );
@@ -5774,7 +5860,7 @@ var BitfabVercelAiHandler = class {
5774
5860
  // The wrapped fn returns immediately with the live stream, so the span
5775
5861
  // output cannot be read from the return value. `finalize` instead
5776
5862
  // awaits the summary the accumulator resolves once the stream drains.
5777
- { type: "llm", finalize: () => summary },
5863
+ { type: "llm", finalize: () => summary, surface: "inherit" },
5778
5864
  async () => {
5779
5865
  const result = await doStream();
5780
5866
  const stream = result.stream.pipeThrough(
@@ -5791,6 +5877,18 @@ var BitfabVercelAiHandler = class {
5791
5877
 
5792
5878
  // src/client.ts
5793
5879
  init_warnOnce();
5880
+ var MIXED_SURFACE_REMEDY = {
5881
+ "opt-in": "Inside a withTrace/trace subtree, configure a discovered call with node()/withNode() instead, or trace this workflow with withSpan() only.",
5882
+ "opt-out": "Wrap the caller with withTrace()/trace() as well, or wrap this function with withSpan()."
5883
+ };
5884
+ function warnMixedTracingSurfaces(traceFunctionKey, entered, enclosing) {
5885
+ const enteredApi = entered === "opt-in" ? "withSpan()" : "withTrace()";
5886
+ const enclosingApi = enclosing === "opt-in" ? "withSpan()" : "withTrace()";
5887
+ warnOnce(
5888
+ `mixed-tracing-surface:${traceFunctionKey}:${entered}`,
5889
+ `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]}`
5890
+ );
5891
+ }
5794
5892
  var activeTraceStates = /* @__PURE__ */ new Map();
5795
5893
  var asyncLocalStorage = null;
5796
5894
  var SPAN_CONTEXT_STORAGE_SYMBOL = /* @__PURE__ */ Symbol.for("bitfab.spanContextStorage");
@@ -6154,7 +6252,13 @@ var Bitfab = class {
6154
6252
  this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
6155
6253
  this.timeout = config.timeout ?? 12e4;
6156
6254
  this.envVars = config.envVars ?? {};
6157
- this.explicitlyEnabled = config.enabled ?? true;
6255
+ if (config.enabled !== void 0) {
6256
+ warnOnce(
6257
+ "deprecated-enabled-option",
6258
+ "Bitfab({ enabled }) is deprecated; pass captureEnabled instead."
6259
+ );
6260
+ }
6261
+ this.captureConfigured = (config.captureEnabled ?? true) && (config.enabled ?? true);
6158
6262
  this.strict = config.strict ?? false;
6159
6263
  this.bamlClient = config.bamlClient ?? null;
6160
6264
  if (config.dbSnapshot) {
@@ -6190,9 +6294,6 @@ var Bitfab = class {
6190
6294
  if (!descriptor || typeof descriptor.value !== "function") {
6191
6295
  throw new BitfabError("@bitfab.trace can only decorate methods");
6192
6296
  }
6193
- if (!this.explicitlyEnabled) {
6194
- return;
6195
- }
6196
6297
  descriptor.value = this.createAutoTraceRoot(
6197
6298
  traceFunctionKey,
6198
6299
  String(propertyKey),
@@ -6206,9 +6307,6 @@ var Bitfab = class {
6206
6307
  if (typeof method !== "function" || context?.kind !== "method" || context.name === void 0) {
6207
6308
  throw new BitfabError("@bitfab.trace can only decorate methods");
6208
6309
  }
6209
- if (!this.explicitlyEnabled) {
6210
- return method;
6211
- }
6212
6310
  return this.createAutoTraceRoot(
6213
6311
  traceFunctionKey,
6214
6312
  String(context.name),
@@ -6224,9 +6322,6 @@ var Bitfab = class {
6224
6322
  if (!fn) {
6225
6323
  throw new BitfabError("bitfab.withTrace requires a function");
6226
6324
  }
6227
- if (!this.explicitlyEnabled) {
6228
- return fn;
6229
- }
6230
6325
  const name = fn.name !== "" ? fn.name : traceFunctionKey;
6231
6326
  return this.createAutoTraceRoot(traceFunctionKey, name, options, fn);
6232
6327
  }
@@ -6250,9 +6345,6 @@ var Bitfab = class {
6250
6345
  if (!descriptor || typeof descriptor.value !== "function") {
6251
6346
  throw new BitfabError("@bitfab.node can only decorate methods");
6252
6347
  }
6253
- if (!this.explicitlyEnabled) {
6254
- return;
6255
- }
6256
6348
  descriptor.value = this.createAutoTraceNode(
6257
6349
  configuration,
6258
6350
  descriptor.value,
@@ -6265,9 +6357,6 @@ var Bitfab = class {
6265
6357
  if (typeof method !== "function" || context?.kind !== "method") {
6266
6358
  throw new BitfabError("@bitfab.node can only decorate methods");
6267
6359
  }
6268
- if (!this.explicitlyEnabled) {
6269
- return method;
6270
- }
6271
6360
  return this.createAutoTraceNode(
6272
6361
  configuration,
6273
6362
  method,
@@ -6283,9 +6372,6 @@ var Bitfab = class {
6283
6372
  throw new BitfabError("bitfab.withNode requires a function");
6284
6373
  }
6285
6374
  const configuration = this.resolveNodeConfiguration(options);
6286
- if (!this.explicitlyEnabled) {
6287
- return fn;
6288
- }
6289
6375
  const functionName = internalFunctionName ?? fn.name;
6290
6376
  if (functionName === "") {
6291
6377
  throw new BitfabError(
@@ -6338,9 +6424,14 @@ var Bitfab = class {
6338
6424
  );
6339
6425
  const excluded = new Set(options.exclude ?? []);
6340
6426
  const includeWrappers = options.includeWrappers ?? false;
6427
+ const rootOptions = {
6428
+ name: options.name ?? name,
6429
+ type: options.type ?? "custom",
6430
+ surface: "opt-out"
6431
+ };
6341
6432
  const tracedRoot = this.withSpan(
6342
6433
  traceFunctionKey,
6343
- { name: options.name ?? name, type: options.type ?? "custom" },
6434
+ rootOptions,
6344
6435
  function(...args) {
6345
6436
  const capturePolicy = getAutoTraceCapturePolicy(self, traceFunctionKey);
6346
6437
  self.refreshAutoTraceCapturePolicy(traceFunctionKey);
@@ -6384,6 +6475,7 @@ var Bitfab = class {
6384
6475
  name: nodeConfiguration?.name ?? definition.name,
6385
6476
  type: nodeConfiguration?.type ?? "function",
6386
6477
  captureWhen: "nested",
6478
+ surface: "opt-out",
6387
6479
  functionId: definition.id,
6388
6480
  captureContent: nodeConfiguration !== void 0 || capturePolicy === void 0 || capturePolicy.has(definition.id),
6389
6481
  autoTraceDefinition: definition,
@@ -6421,7 +6513,7 @@ var Bitfab = class {
6421
6513
  }
6422
6514
  );
6423
6515
  const autoTraceRoot = function(...args) {
6424
- if (!self.isTracingEnabled()) {
6516
+ if (!self.shouldRecord()) {
6425
6517
  return fn.apply(this, args);
6426
6518
  }
6427
6519
  return tracedRoot.apply(this, args);
@@ -6429,6 +6521,7 @@ var Bitfab = class {
6429
6521
  Object.defineProperty(autoTraceRoot, "_bitfabTraceFunctionKey", {
6430
6522
  value: traceFunctionKey
6431
6523
  });
6524
+ Object.defineProperty(autoTraceRoot, "_bitfabWrappedFn", { value: fn });
6432
6525
  return autoTraceRoot;
6433
6526
  }
6434
6527
  refreshAutoTraceCapturePolicy(traceFunctionKey) {
@@ -6512,7 +6605,7 @@ var Bitfab = class {
6512
6605
  "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`."
6513
6606
  );
6514
6607
  }
6515
- if (this.explicitlyEnabled && !this.apiKeyWarned) {
6608
+ if (this.captureConfigured && !this.apiKeyWarned) {
6516
6609
  this.apiKeyWarned = true;
6517
6610
  console.warn(
6518
6611
  "Bitfab: apiKey is empty - tracing is disabled. Provide a valid API key to enable tracing."
@@ -6520,17 +6613,21 @@ var Bitfab = class {
6520
6613
  }
6521
6614
  return void 0;
6522
6615
  }
6523
- /**
6524
- * Whether tracing should run, decided lazily at call time (not frozen at
6525
- * construction). An explicit `enabled: false` short-circuits without ever
6526
- * touching the key.
6527
- */
6528
- isTracingEnabled() {
6529
- if (!this.explicitlyEnabled) {
6616
+ isCaptureEnabled() {
6617
+ if (!this.captureConfigured) {
6530
6618
  return false;
6531
6619
  }
6532
6620
  return this.resolveApiKey() !== void 0;
6533
6621
  }
6622
+ shouldRecord() {
6623
+ if (!this.captureConfigured && !getReplayContext() && !inSeedScope()) {
6624
+ return false;
6625
+ }
6626
+ return this.resolveApiKey() !== void 0;
6627
+ }
6628
+ get captureEnabled() {
6629
+ return this.isCaptureEnabled();
6630
+ }
6534
6631
  /**
6535
6632
  * Fetch the function with its current version and BAML prompt from the server.
6536
6633
  *
@@ -6942,9 +7039,6 @@ var Bitfab = class {
6942
7039
  * @returns A wrapped function with the same signature that creates spans for inputs and outputs
6943
7040
  */
6944
7041
  withSpan(traceFunctionKey, optionsOrFn, maybeFn) {
6945
- if (!this.explicitlyEnabled) {
6946
- return typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
6947
- }
6948
7042
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
6949
7043
  const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
6950
7044
  const self = this;
@@ -6958,7 +7052,7 @@ var Bitfab = class {
6958
7052
  }
6959
7053
  })();
6960
7054
  const wrappedFn = function(...args) {
6961
- if (!self.isTracingEnabled()) {
7055
+ if (!self.shouldRecord()) {
6962
7056
  return fn.apply(this, args);
6963
7057
  }
6964
7058
  initializeAsyncContext();
@@ -7006,11 +7100,26 @@ var Bitfab = class {
7006
7100
  const currentStack = getSpanStack();
7007
7101
  const parentContext = currentStack[currentStack.length - 1];
7008
7102
  const replayCtxForTraceId = parentContext ? null : getReplayContext();
7009
- const traceId = parentContext?.traceId ?? replayCtxForTraceId?.traceId ?? randomUuid();
7103
+ const seedCtxForTraceId = parentContext ? null : getSeedContext();
7104
+ const traceId = parentContext?.traceId ?? replayCtxForTraceId?.traceId ?? seedCtxForTraceId?.traceId ?? randomUuid();
7010
7105
  const spanId = randomUuid();
7011
7106
  const parentSpanId = parentContext?.spanId ?? null;
7012
7107
  const isRootSpan = parentSpanId === null;
7013
- const newContext = { traceId, spanId, contexts: [] };
7108
+ const requestedSurface = options.surface ?? "opt-in";
7109
+ const surface = requestedSurface === "inherit" ? parentContext?.surface ?? "opt-in" : requestedSurface;
7110
+ if (requestedSurface !== "inherit" && parentContext?.surface !== void 0 && parentContext.surface !== surface) {
7111
+ warnMixedTracingSurfaces(
7112
+ traceFunctionKey,
7113
+ surface,
7114
+ parentContext.surface
7115
+ );
7116
+ }
7117
+ const newContext = {
7118
+ traceId,
7119
+ spanId,
7120
+ contexts: [],
7121
+ surface
7122
+ };
7014
7123
  newStack = [...currentStack, newContext];
7015
7124
  const inputs = args;
7016
7125
  const startedAt = nowIsoTimestamp();
@@ -7026,6 +7135,9 @@ var Bitfab = class {
7026
7135
  ...replayCtxAtStart?.inputSourceTraceId && {
7027
7136
  inputSourceTraceId: replayCtxAtStart.inputSourceTraceId
7028
7137
  },
7138
+ ...replayCtxAtStart?.replayAttempt !== void 0 && {
7139
+ replayAttempt: replayCtxAtStart.replayAttempt
7140
+ },
7029
7141
  dbSnapshotRef
7030
7142
  });
7031
7143
  registeredTraceId = traceId;
@@ -7076,6 +7188,7 @@ var Bitfab = class {
7076
7188
  contexts: traceState?.contexts ?? [],
7077
7189
  testRunId: traceState?.testRunId,
7078
7190
  inputSourceTraceId: traceState?.inputSourceTraceId,
7191
+ replayAttempt: traceState?.replayAttempt,
7079
7192
  dbSnapshotRef: traceState?.dbSnapshotRef,
7080
7193
  dropped: traceState?.dropped,
7081
7194
  ingestionType: traceState?.ingestionType,
@@ -7288,7 +7401,7 @@ var Bitfab = class {
7288
7401
  if (registeredTraceId) {
7289
7402
  activeTraceStates.delete(registeredTraceId);
7290
7403
  }
7291
- if (getReplayContext()) {
7404
+ if (getReplayContext() || inSeedScope()) {
7292
7405
  throw setupError;
7293
7406
  }
7294
7407
  warnOnce(
@@ -7363,7 +7476,7 @@ var Bitfab = class {
7363
7476
  return {
7364
7477
  traceId,
7365
7478
  addContext: (context) => {
7366
- if (!this.isTracingEnabled()) {
7479
+ if (!this.shouldRecord()) {
7367
7480
  return Promise.resolve();
7368
7481
  }
7369
7482
  if (typeof context !== "object" || context === null) {
@@ -7374,7 +7487,7 @@ var Bitfab = class {
7374
7487
  });
7375
7488
  },
7376
7489
  setMetadata: (metadata) => {
7377
- if (!this.isTracingEnabled()) {
7490
+ if (!this.shouldRecord()) {
7378
7491
  return Promise.resolve();
7379
7492
  }
7380
7493
  if (typeof metadata !== "object" || metadata === null) {
@@ -7383,7 +7496,7 @@ var Bitfab = class {
7383
7496
  return this.httpClient.patchTrace(traceId, { mergeMetadata: metadata });
7384
7497
  },
7385
7498
  setSessionId: (sessionId) => {
7386
- if (!this.isTracingEnabled()) {
7499
+ if (!this.shouldRecord()) {
7387
7500
  return Promise.resolve();
7388
7501
  }
7389
7502
  if (typeof sessionId !== "string" || sessionId.length === 0) {
@@ -7392,7 +7505,7 @@ var Bitfab = class {
7392
7505
  return this.httpClient.patchTrace(traceId, { setSessionId: sessionId });
7393
7506
  },
7394
7507
  setName: (name) => {
7395
- if (!this.isTracingEnabled()) {
7508
+ if (!this.shouldRecord()) {
7396
7509
  return Promise.resolve();
7397
7510
  }
7398
7511
  if (typeof name !== "string" || name.length === 0) {
@@ -7473,6 +7586,9 @@ var Bitfab = class {
7473
7586
  if (params.inputSourceTraceId) {
7474
7587
  rawTrace.input_source_trace_id = params.inputSourceTraceId;
7475
7588
  }
7589
+ if (params.replayAttempt !== void 0) {
7590
+ rawTrace.replay_attempt = params.replayAttempt;
7591
+ }
7476
7592
  if (params.dbSnapshotRef) {
7477
7593
  rawTrace.db_snapshot_ref = params.dbSnapshotRef;
7478
7594
  }
@@ -7625,22 +7741,13 @@ var Bitfab = class {
7625
7741
  clearMockOverrides() {
7626
7742
  this.mockOverrides.length = 0;
7627
7743
  }
7628
- /**
7629
- * Write a replayable trace from a case, without running anything.
7630
- *
7631
- * Use this to turn a corpus you already hold (a Braintrust dataset, a
7632
- * spreadsheet, hand-written cases) into traces that {@link replay} can
7633
- * select. The recorded root span carries `input` as its input and `expected`
7634
- * as its output, so replay reports each item against the value you expected
7635
- * rather than against a previous run.
7636
- *
7637
- * A seeded trace has no child spans and no database pin, so replay mocking
7638
- * has nothing recorded to substitute and `dbBranch` refuses it. Pass
7639
- * `mockOverride` at replay time for calls that must not run.
7640
- *
7641
- * @returns The trace ID, usable with `replay({ traceIds: [...] })`.
7642
- */
7643
- seedTrace(traceFunctionKey, options) {
7744
+ seedTrace(traceFunctionKey, optionsOrFn, runOptions) {
7745
+ if (typeof optionsOrFn === "function") {
7746
+ return this.seedTraceByRunning(traceFunctionKey, optionsOrFn, runOptions);
7747
+ }
7748
+ return this.seedTraceFromCase(traceFunctionKey, optionsOrFn);
7749
+ }
7750
+ seedTraceFromCase(traceFunctionKey, options) {
7644
7751
  const { input } = options;
7645
7752
  const fn = options.fn?._bitfabWrappedFn ?? options.fn;
7646
7753
  if (fn && input.length < fn.length) {
@@ -7689,6 +7796,50 @@ var Bitfab = class {
7689
7796
  }
7690
7797
  return traceId;
7691
7798
  }
7799
+ async seedTraceByRunning(traceFunctionKey, fn, options) {
7800
+ const wrappedKey = fn._bitfabTraceFunctionKey;
7801
+ let target = fn;
7802
+ if (wrappedKey === void 0) {
7803
+ target = this.withSpan(
7804
+ traceFunctionKey,
7805
+ { name: traceFunctionKey, type: "agent" },
7806
+ fn
7807
+ );
7808
+ } else if (wrappedKey !== traceFunctionKey) {
7809
+ throw new BitfabError(
7810
+ `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.`
7811
+ );
7812
+ }
7813
+ await seedContextReady;
7814
+ const traceId = randomUuid();
7815
+ activeTraceStates.set(traceId, {
7816
+ traceId,
7817
+ startedAt: nowIsoTimestamp(),
7818
+ contexts: [],
7819
+ ingestionType: "seeded",
7820
+ ...options?.sessionId !== void 0 && { sessionId: options.sessionId },
7821
+ ...options?.name !== void 0 && { name: options.name },
7822
+ ...options?.metadata !== void 0 && { metadata: options.metadata }
7823
+ });
7824
+ const args = options?.args ?? [];
7825
+ let unrecorded = true;
7826
+ try {
7827
+ await runWithSeedContext({ traceId }, async () => target(...args));
7828
+ } finally {
7829
+ try {
7830
+ const { flushTraces: flushTraces2 } = await Promise.resolve().then(() => (init_http(), http_exports));
7831
+ await flushTraces2(3e4);
7832
+ } finally {
7833
+ unrecorded = activeTraceStates.delete(traceId);
7834
+ }
7835
+ }
7836
+ if (unrecorded) {
7837
+ throw new BitfabError(
7838
+ `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.`
7839
+ );
7840
+ }
7841
+ return traceId;
7842
+ }
7692
7843
  async replay(traceFunctionKey, fn, options) {
7693
7844
  const wrappedKey = fn._bitfabTraceFunctionKey;
7694
7845
  let replayFn = fn;
@@ -7949,7 +8100,7 @@ init_replay();
7949
8100
  function defineReplayRegistry(registry) {
7950
8101
  return registry;
7951
8102
  }
7952
- async function seedFromRegistry(registry, pipeline, cases) {
8103
+ async function seedFromRegistry(registry, pipeline, cases, options = {}) {
7953
8104
  const registration = registry[pipeline];
7954
8105
  if (registration === void 0) {
7955
8106
  throw new BitfabError(
@@ -7957,6 +8108,19 @@ async function seedFromRegistry(registry, pipeline, cases) {
7957
8108
  );
7958
8109
  }
7959
8110
  const traceFunctionKey = resolveTraceFunctionKey(registration);
8111
+ if (options.run === true) {
8112
+ const traceIds2 = [];
8113
+ for (const seedCase of cases) {
8114
+ traceIds2.push(
8115
+ await registration.client.seedTrace(traceFunctionKey, registration.fn, {
8116
+ args: seedCase.input,
8117
+ metadata: seedCase.metadata,
8118
+ sessionId: seedCase.sessionId
8119
+ })
8120
+ );
8121
+ }
8122
+ return { pipeline, traceFunctionKey, traceIds: traceIds2 };
8123
+ }
7960
8124
  const traceIds = cases.map(
7961
8125
  (seedCase) => registration.client.seedTrace(traceFunctionKey, {
7962
8126
  input: seedCase.input,