bitfab 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
@@ -51,7 +51,7 @@ var __version__, __packageName__;
51
51
  var init_version_generated = __esm({
52
52
  "src/version.generated.ts"() {
53
53
  "use strict";
54
- __version__ = "0.41.0";
54
+ __version__ = "0.43.0";
55
55
  __packageName__ = "bitfab";
56
56
  }
57
57
  });
@@ -1346,7 +1346,7 @@ function sourceTraceIdOf(payload) {
1346
1346
  const id = rawTrace?.id;
1347
1347
  return typeof id === "string" ? id : void 0;
1348
1348
  }
1349
- var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, OTLP_TRACES_ENDPOINT, RETRYABLE_STATUSES, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCLE_TIMEOUT_MS2, pendingTracePromises, carrierSeq, HttpClient;
1349
+ 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;
1350
1350
  var init_http = __esm({
1351
1351
  "src/http.ts"() {
1352
1352
  "use strict";
@@ -1360,6 +1360,7 @@ var init_http = __esm({
1360
1360
  init_unrefTimer();
1361
1361
  init_warnOnce();
1362
1362
  REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS = 3e5;
1363
+ REPLAY_COMPLETE_REQUEST_TIMEOUT_MS = 12e4;
1363
1364
  OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
1364
1365
  RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
1365
1366
  EXIT_FLUSH_TIMEOUT_MS = 5e3;
@@ -1849,7 +1850,7 @@ var init_http = __esm({
1849
1850
  * Start a replay session by fetching historical traces.
1850
1851
  * Blocking call - creates a test run and returns lightweight item references.
1851
1852
  */
1852
- async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetId, graderIds, dbBranchSettings) {
1853
+ async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetId, graderIds, dbBranchSettings, attempts, includeOriginalMetadata) {
1853
1854
  const payload = { traceFunctionKey };
1854
1855
  if (limit !== void 0) {
1855
1856
  payload.limit = limit;
@@ -1882,6 +1883,12 @@ var init_http = __esm({
1882
1883
  if (dbBranchSettings !== void 0) {
1883
1884
  payload.dbBranchSettings = dbBranchSettings;
1884
1885
  }
1886
+ if (attempts !== void 0 && attempts > 1) {
1887
+ payload.attempts = attempts;
1888
+ }
1889
+ if (includeOriginalMetadata) {
1890
+ payload.includeOriginalMetadata = true;
1891
+ }
1885
1892
  const timeout = includeDbBranchLease ? REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS : 3e4;
1886
1893
  return this.request("/api/sdk/replay/start", payload, {
1887
1894
  timeout
@@ -1997,7 +2004,7 @@ var init_http = __esm({
1997
2004
  return this.request(
1998
2005
  "/api/sdk/replay/complete",
1999
2006
  { testRunId },
2000
- { timeout: 3e4 }
2007
+ { timeout: REPLAY_COMPLETE_REQUEST_TIMEOUT_MS }
2001
2008
  );
2002
2009
  }
2003
2010
  /**
@@ -2006,10 +2013,15 @@ var init_http = __esm({
2006
2013
  * snapshot + preview branch and polls operations to readiness, which
2007
2014
  * can take seconds.
2008
2015
  */
2009
- async resolveDbBranchLease(testRunId, traceId, dbBranchSettings) {
2016
+ async resolveDbBranchLease(testRunId, traceId, dbBranchSettings, attempt) {
2010
2017
  return this.request(
2011
2018
  "/api/sdk/replay/resolveDbBranchLease",
2012
- { testRunId, traceId, dbBranchSettings },
2019
+ {
2020
+ testRunId,
2021
+ traceId,
2022
+ dbBranchSettings,
2023
+ ...attempt !== void 0 && attempt > 0 ? { attempt } : {}
2024
+ },
2013
2025
  { timeout: REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS }
2014
2026
  );
2015
2027
  }
@@ -2445,6 +2457,23 @@ __export(replay_exports, {
2445
2457
  sleepForReplayPersistence: () => sleepForReplayPersistence,
2446
2458
  waitForReplayPersistence: () => waitForReplayPersistence
2447
2459
  });
2460
+ function expandReplayWork(serverItems, attempts) {
2461
+ return Array.from(
2462
+ { length: attempts },
2463
+ (_, attempt) => serverItems.map((serverItem) => ({ serverItem, attempt }))
2464
+ ).flat();
2465
+ }
2466
+ function resolveAttempts(attempts) {
2467
+ if (attempts === void 0) {
2468
+ return 1;
2469
+ }
2470
+ if (!Number.isInteger(attempts) || attempts < 1 || attempts > MAX_REPLAY_ATTEMPTS) {
2471
+ throw new BitfabError(
2472
+ `attempts must be an integer from 1 to ${MAX_REPLAY_ATTEMPTS} (got ${attempts}).`
2473
+ );
2474
+ }
2475
+ return attempts;
2476
+ }
2448
2477
  function dbBranchEnabled(dbBranch) {
2449
2478
  return dbBranch !== void 0 && dbBranch !== false;
2450
2479
  }
@@ -2473,6 +2502,9 @@ function reportReplayProgress(progress) {
2473
2502
  } catch {
2474
2503
  }
2475
2504
  }
2505
+ function isPlainRecord(value) {
2506
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2507
+ }
2476
2508
  function errorMessage(error) {
2477
2509
  return error instanceof Error ? error.message : String(error);
2478
2510
  }
@@ -2568,7 +2600,7 @@ function buildMockTree(rootNode) {
2568
2600
  }
2569
2601
  return { spans };
2570
2602
  }
2571
- async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, resolvedOverrides, replayedTraceId, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
2603
+ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
2572
2604
  let lease = includeDbBranchLease ? serverItem.dbBranchLease : void 0;
2573
2605
  let leaseError = includeDbBranchLease ? serverItem.dbBranchLeaseError : void 0;
2574
2606
  let dbSnapshotRef = serverItem.dbSnapshotRef;
@@ -2590,7 +2622,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2590
2622
  resolved = await httpClient.resolveDbBranchLease(
2591
2623
  testRunId,
2592
2624
  originalTraceId,
2593
- dbBranchSettings
2625
+ dbBranchSettings,
2626
+ attempt
2594
2627
  );
2595
2628
  } catch (cause) {
2596
2629
  throw new DbBranchReplayError(
@@ -2619,12 +2652,14 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2619
2652
  inputs = deserializeInputs(spanData);
2620
2653
  originalOutput = deserializeOutput(spanData);
2621
2654
  if (adaptInputs) {
2655
+ const originalMetadata = serverItem.originalMetadata;
2622
2656
  inputs = adaptInputs(inputs, {
2623
2657
  originalTraceId,
2624
2658
  originalSpanId,
2625
2659
  // Deprecated aliases for originalTraceId/originalSpanId.
2626
2660
  sourceTraceId: originalTraceId,
2627
- sourceSpanId: originalSpanId
2661
+ sourceSpanId: originalSpanId,
2662
+ metadata: isPlainRecord(originalMetadata) ? { ...originalMetadata } : {}
2628
2663
  });
2629
2664
  }
2630
2665
  const hasOverrides = resolvedOverrides.length > 0;
@@ -2670,6 +2705,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2670
2705
  originalSpanId,
2671
2706
  sourceTraceId: originalTraceId,
2672
2707
  sourceSpanId: originalSpanId,
2708
+ attempt,
2673
2709
  input: inputs,
2674
2710
  result: void 0,
2675
2711
  originalOutput,
@@ -2698,6 +2734,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2698
2734
  inputSourceSpanId: span.id,
2699
2735
  inputSourceTraceId: span.externalTraceId,
2700
2736
  sourceBitfabTraceId: originalTraceId,
2737
+ replayAttempt: attempt,
2701
2738
  mockTree,
2702
2739
  callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
2703
2740
  mockStrategy,
@@ -2749,6 +2786,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2749
2786
  // Deprecated aliases for originalTraceId/originalSpanId.
2750
2787
  sourceTraceId: originalTraceId,
2751
2788
  sourceSpanId: originalSpanId,
2789
+ attempt,
2752
2790
  input: inputs,
2753
2791
  result,
2754
2792
  originalOutput,
@@ -2877,6 +2915,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2877
2915
  } catch {
2878
2916
  }
2879
2917
  }
2918
+ const attempts = resolveAttempts(options?.attempts);
2880
2919
  await replayContextReady;
2881
2920
  let codeChangeDescription = options?.codeChangeDescription;
2882
2921
  let codeChangeFiles = options?.codeChangeFiles;
@@ -2909,7 +2948,9 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2909
2948
  options?.experimentGroupId,
2910
2949
  options?.datasetId,
2911
2950
  options?.graderIds,
2912
- resolveDbBranchSettings(options?.dbBranch)
2951
+ resolveDbBranchSettings(options?.dbBranch),
2952
+ attempts,
2953
+ options?.adaptInputs !== void 0
2913
2954
  );
2914
2955
  if (serverItems.length === 0) {
2915
2956
  try {
@@ -2926,10 +2967,11 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2926
2967
  ...normalizeMockOverrides(options?.mockOverride),
2927
2968
  ...registeredOverrides
2928
2969
  ];
2929
- const replayedTraceIds = serverItems.map(() => randomUuid());
2970
+ const workItems = expandReplayWork(serverItems, attempts);
2971
+ const replayedTraceIds = workItems.map(() => randomUuid());
2930
2972
  httpClient.trackTraceDeliveries(replayedTraceIds);
2931
- const tasks = serverItems.map(
2932
- (serverItem, index) => () => processItem(
2973
+ const tasks = workItems.map(
2974
+ ({ serverItem, attempt }, index) => () => processItem(
2933
2975
  httpClient,
2934
2976
  serverItem,
2935
2977
  fn,
@@ -2937,6 +2979,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2937
2979
  mockStrategy,
2938
2980
  resolvedOverrides,
2939
2981
  replayedTraceIds[index],
2982
+ attempt,
2940
2983
  dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true,
2941
2984
  resolveDbBranchSettings(options?.dbBranch),
2942
2985
  options?.adaptInputs,
@@ -3004,6 +3047,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
3004
3047
  // Deprecated aliases for originalTraceId/originalSpanId.
3005
3048
  sourceTraceId: item.originalTraceId ?? null,
3006
3049
  sourceSpanId: item.originalSpanId ?? null,
3050
+ attempt: item.attempt,
3007
3051
  input: item.input,
3008
3052
  result: item.result,
3009
3053
  originalOutput: item.originalOutput,
@@ -3025,7 +3069,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
3025
3069
  },
3026
3070
  options?.onItemStart ? (index) => {
3027
3071
  started += 1;
3028
- const serverItem = serverItems[index];
3072
+ const { serverItem, attempt } = workItems[index];
3029
3073
  const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
3030
3074
  const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
3031
3075
  try {
@@ -3041,7 +3085,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
3041
3085
  originalTraceId,
3042
3086
  originalSpanId,
3043
3087
  sourceTraceId: originalTraceId,
3044
- sourceSpanId: originalSpanId
3088
+ sourceSpanId: originalSpanId,
3089
+ attempt
3045
3090
  }
3046
3091
  });
3047
3092
  } catch {
@@ -3053,7 +3098,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
3053
3098
  const dryResult = {
3054
3099
  items: resultItems,
3055
3100
  testRunId,
3056
- testRunUrl: fullTestRunUrl
3101
+ testRunUrl: fullTestRunUrl,
3102
+ attempts
3057
3103
  };
3058
3104
  await writeReplayResultFile(dryResult);
3059
3105
  return dryResult;
@@ -3122,7 +3168,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
3122
3168
  const result = {
3123
3169
  items: resultItems,
3124
3170
  testRunId,
3125
- testRunUrl: fullTestRunUrl
3171
+ testRunUrl: fullTestRunUrl,
3172
+ attempts
3126
3173
  };
3127
3174
  await writeReplayResultFile(result);
3128
3175
  if (!options?.onItemFinish) {
@@ -3163,7 +3210,7 @@ async function writeReplayResultFile(result) {
3163
3210
  }
3164
3211
  }
3165
3212
  }
3166
- var REPLAY_PERSISTENCE_TIMEOUT_MS, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
3213
+ var REPLAY_PERSISTENCE_TIMEOUT_MS, MAX_REPLAY_ATTEMPTS, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
3167
3214
  var init_replay = __esm({
3168
3215
  "src/replay.ts"() {
3169
3216
  "use strict";
@@ -3175,6 +3222,7 @@ var init_replay = __esm({
3175
3222
  init_replayContext();
3176
3223
  init_serialize();
3177
3224
  REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
3225
+ MAX_REPLAY_ATTEMPTS = 100;
3178
3226
  BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
3179
3227
  ReplayError = class extends BitfabError {
3180
3228
  constructor(message, items, testRunId, testRunUrl, cause) {
@@ -5256,7 +5304,8 @@ var BitfabLangGraphIntegration = class {
5256
5304
  type: "function",
5257
5305
  captureWhen: "nested",
5258
5306
  mockOnReplay: shouldMock,
5259
- finalize: finalizeToolResult
5307
+ finalize: finalizeToolResult,
5308
+ surface: "inherit"
5260
5309
  },
5261
5310
  async (_args) => await originalInvoke(input, ...rest)
5262
5311
  );
@@ -5299,7 +5348,7 @@ var BitfabLangGraphIntegration = class {
5299
5348
  wrapInvoke(fn) {
5300
5349
  return this.client.withSpan(
5301
5350
  this.traceFunctionKey,
5302
- { name: this.traceFunctionKey, type: "agent" },
5351
+ { name: this.traceFunctionKey, type: "agent", surface: "inherit" },
5303
5352
  fn
5304
5353
  );
5305
5354
  }
@@ -5352,7 +5401,11 @@ var BitfabOpenAIAgentHandler = class {
5352
5401
  }
5353
5402
  return res?.finalOutput;
5354
5403
  };
5355
- const options_ = { type: "agent", finalize };
5404
+ const options_ = {
5405
+ type: "agent",
5406
+ finalize,
5407
+ surface: "inherit"
5408
+ };
5356
5409
  const traced = this.withSpanFn(
5357
5410
  this.traceFunctionKey,
5358
5411
  options_,
@@ -5416,6 +5469,38 @@ _context = new WeakMap();
5416
5469
 
5417
5470
  // src/client.ts
5418
5471
  init_replayContext();
5472
+
5473
+ // src/seedContext.ts
5474
+ init_asyncStorage();
5475
+ var seedContextStorage = null;
5476
+ var SEED_CONTEXT_STORAGE_SYMBOL = /* @__PURE__ */ Symbol.for("bitfab.seedContextStorage");
5477
+ var seedContextReady = asyncStorageReady.then(() => {
5478
+ const shared = globalThis;
5479
+ const existing = shared[SEED_CONTEXT_STORAGE_SYMBOL];
5480
+ if (existing) {
5481
+ seedContextStorage = existing;
5482
+ return;
5483
+ }
5484
+ const created = createAsyncLocalStorage();
5485
+ if (created) {
5486
+ shared[SEED_CONTEXT_STORAGE_SYMBOL] = created;
5487
+ seedContextStorage = created;
5488
+ }
5489
+ });
5490
+ function getSeedContext() {
5491
+ return seedContextStorage?.getStore() ?? null;
5492
+ }
5493
+ function inSeedScope() {
5494
+ return getSeedContext() !== null;
5495
+ }
5496
+ function runWithSeedContext(ctx, fn) {
5497
+ if (seedContextStorage) {
5498
+ return seedContextStorage.run(ctx, fn);
5499
+ }
5500
+ return fn();
5501
+ }
5502
+
5503
+ // src/client.ts
5419
5504
  init_serialize();
5420
5505
 
5421
5506
  // src/tracing.ts
@@ -5751,7 +5836,8 @@ var BitfabVercelAiHandler = class {
5751
5836
  key,
5752
5837
  {
5753
5838
  type: "llm",
5754
- finalize: (result) => summarizeGenerate(result ?? {}, label)
5839
+ finalize: (result) => summarizeGenerate(result ?? {}, label),
5840
+ surface: "inherit"
5755
5841
  },
5756
5842
  () => doGenerate()
5757
5843
  );
@@ -5769,7 +5855,7 @@ var BitfabVercelAiHandler = class {
5769
5855
  // The wrapped fn returns immediately with the live stream, so the span
5770
5856
  // output cannot be read from the return value. `finalize` instead
5771
5857
  // awaits the summary the accumulator resolves once the stream drains.
5772
- { type: "llm", finalize: () => summary },
5858
+ { type: "llm", finalize: () => summary, surface: "inherit" },
5773
5859
  async () => {
5774
5860
  const result = await doStream();
5775
5861
  const stream = result.stream.pipeThrough(
@@ -5786,6 +5872,18 @@ var BitfabVercelAiHandler = class {
5786
5872
 
5787
5873
  // src/client.ts
5788
5874
  init_warnOnce();
5875
+ var MIXED_SURFACE_REMEDY = {
5876
+ "opt-in": "Inside a withTrace/trace subtree, configure a discovered call with node()/withNode() instead, or trace this workflow with withSpan() only.",
5877
+ "opt-out": "Wrap the caller with withTrace()/trace() as well, or wrap this function with withSpan()."
5878
+ };
5879
+ function warnMixedTracingSurfaces(traceFunctionKey, entered, enclosing) {
5880
+ const enteredApi = entered === "opt-in" ? "withSpan()" : "withTrace()";
5881
+ const enclosingApi = enclosing === "opt-in" ? "withSpan()" : "withTrace()";
5882
+ warnOnce(
5883
+ `mixed-tracing-surface:${traceFunctionKey}:${entered}`,
5884
+ `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]}`
5885
+ );
5886
+ }
5789
5887
  var activeTraceStates = /* @__PURE__ */ new Map();
5790
5888
  var asyncLocalStorage = null;
5791
5889
  var SPAN_CONTEXT_STORAGE_SYMBOL = /* @__PURE__ */ Symbol.for("bitfab.spanContextStorage");
@@ -6149,7 +6247,13 @@ var Bitfab = class {
6149
6247
  this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
6150
6248
  this.timeout = config.timeout ?? 12e4;
6151
6249
  this.envVars = config.envVars ?? {};
6152
- this.explicitlyEnabled = config.enabled ?? true;
6250
+ if (config.enabled !== void 0) {
6251
+ warnOnce(
6252
+ "deprecated-enabled-option",
6253
+ "Bitfab({ enabled }) is deprecated; pass captureEnabled instead."
6254
+ );
6255
+ }
6256
+ this.captureConfigured = (config.captureEnabled ?? true) && (config.enabled ?? true);
6153
6257
  this.strict = config.strict ?? false;
6154
6258
  this.bamlClient = config.bamlClient ?? null;
6155
6259
  if (config.dbSnapshot) {
@@ -6185,9 +6289,6 @@ var Bitfab = class {
6185
6289
  if (!descriptor || typeof descriptor.value !== "function") {
6186
6290
  throw new BitfabError("@bitfab.trace can only decorate methods");
6187
6291
  }
6188
- if (!this.explicitlyEnabled) {
6189
- return;
6190
- }
6191
6292
  descriptor.value = this.createAutoTraceRoot(
6192
6293
  traceFunctionKey,
6193
6294
  String(propertyKey),
@@ -6201,9 +6302,6 @@ var Bitfab = class {
6201
6302
  if (typeof method !== "function" || context?.kind !== "method" || context.name === void 0) {
6202
6303
  throw new BitfabError("@bitfab.trace can only decorate methods");
6203
6304
  }
6204
- if (!this.explicitlyEnabled) {
6205
- return method;
6206
- }
6207
6305
  return this.createAutoTraceRoot(
6208
6306
  traceFunctionKey,
6209
6307
  String(context.name),
@@ -6219,9 +6317,6 @@ var Bitfab = class {
6219
6317
  if (!fn) {
6220
6318
  throw new BitfabError("bitfab.withTrace requires a function");
6221
6319
  }
6222
- if (!this.explicitlyEnabled) {
6223
- return fn;
6224
- }
6225
6320
  const name = fn.name !== "" ? fn.name : traceFunctionKey;
6226
6321
  return this.createAutoTraceRoot(traceFunctionKey, name, options, fn);
6227
6322
  }
@@ -6245,9 +6340,6 @@ var Bitfab = class {
6245
6340
  if (!descriptor || typeof descriptor.value !== "function") {
6246
6341
  throw new BitfabError("@bitfab.node can only decorate methods");
6247
6342
  }
6248
- if (!this.explicitlyEnabled) {
6249
- return;
6250
- }
6251
6343
  descriptor.value = this.createAutoTraceNode(
6252
6344
  configuration,
6253
6345
  descriptor.value,
@@ -6260,9 +6352,6 @@ var Bitfab = class {
6260
6352
  if (typeof method !== "function" || context?.kind !== "method") {
6261
6353
  throw new BitfabError("@bitfab.node can only decorate methods");
6262
6354
  }
6263
- if (!this.explicitlyEnabled) {
6264
- return method;
6265
- }
6266
6355
  return this.createAutoTraceNode(
6267
6356
  configuration,
6268
6357
  method,
@@ -6278,9 +6367,6 @@ var Bitfab = class {
6278
6367
  throw new BitfabError("bitfab.withNode requires a function");
6279
6368
  }
6280
6369
  const configuration = this.resolveNodeConfiguration(options);
6281
- if (!this.explicitlyEnabled) {
6282
- return fn;
6283
- }
6284
6370
  const functionName = internalFunctionName ?? fn.name;
6285
6371
  if (functionName === "") {
6286
6372
  throw new BitfabError(
@@ -6333,9 +6419,14 @@ var Bitfab = class {
6333
6419
  );
6334
6420
  const excluded = new Set(options.exclude ?? []);
6335
6421
  const includeWrappers = options.includeWrappers ?? false;
6422
+ const rootOptions = {
6423
+ name: options.name ?? name,
6424
+ type: options.type ?? "custom",
6425
+ surface: "opt-out"
6426
+ };
6336
6427
  const tracedRoot = this.withSpan(
6337
6428
  traceFunctionKey,
6338
- { name: options.name ?? name, type: options.type ?? "custom" },
6429
+ rootOptions,
6339
6430
  function(...args) {
6340
6431
  const capturePolicy = getAutoTraceCapturePolicy(self, traceFunctionKey);
6341
6432
  self.refreshAutoTraceCapturePolicy(traceFunctionKey);
@@ -6379,6 +6470,7 @@ var Bitfab = class {
6379
6470
  name: nodeConfiguration?.name ?? definition.name,
6380
6471
  type: nodeConfiguration?.type ?? "function",
6381
6472
  captureWhen: "nested",
6473
+ surface: "opt-out",
6382
6474
  functionId: definition.id,
6383
6475
  captureContent: nodeConfiguration !== void 0 || capturePolicy === void 0 || capturePolicy.has(definition.id),
6384
6476
  autoTraceDefinition: definition,
@@ -6416,7 +6508,7 @@ var Bitfab = class {
6416
6508
  }
6417
6509
  );
6418
6510
  const autoTraceRoot = function(...args) {
6419
- if (!self.isTracingEnabled()) {
6511
+ if (!self.shouldRecord()) {
6420
6512
  return fn.apply(this, args);
6421
6513
  }
6422
6514
  return tracedRoot.apply(this, args);
@@ -6424,6 +6516,7 @@ var Bitfab = class {
6424
6516
  Object.defineProperty(autoTraceRoot, "_bitfabTraceFunctionKey", {
6425
6517
  value: traceFunctionKey
6426
6518
  });
6519
+ Object.defineProperty(autoTraceRoot, "_bitfabWrappedFn", { value: fn });
6427
6520
  return autoTraceRoot;
6428
6521
  }
6429
6522
  refreshAutoTraceCapturePolicy(traceFunctionKey) {
@@ -6507,7 +6600,7 @@ var Bitfab = class {
6507
6600
  "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`."
6508
6601
  );
6509
6602
  }
6510
- if (this.explicitlyEnabled && !this.apiKeyWarned) {
6603
+ if (this.captureConfigured && !this.apiKeyWarned) {
6511
6604
  this.apiKeyWarned = true;
6512
6605
  console.warn(
6513
6606
  "Bitfab: apiKey is empty - tracing is disabled. Provide a valid API key to enable tracing."
@@ -6515,17 +6608,21 @@ var Bitfab = class {
6515
6608
  }
6516
6609
  return void 0;
6517
6610
  }
6518
- /**
6519
- * Whether tracing should run, decided lazily at call time (not frozen at
6520
- * construction). An explicit `enabled: false` short-circuits without ever
6521
- * touching the key.
6522
- */
6523
- isTracingEnabled() {
6524
- if (!this.explicitlyEnabled) {
6611
+ isCaptureEnabled() {
6612
+ if (!this.captureConfigured) {
6525
6613
  return false;
6526
6614
  }
6527
6615
  return this.resolveApiKey() !== void 0;
6528
6616
  }
6617
+ shouldRecord() {
6618
+ if (!this.captureConfigured && !getReplayContext() && !inSeedScope()) {
6619
+ return false;
6620
+ }
6621
+ return this.resolveApiKey() !== void 0;
6622
+ }
6623
+ get captureEnabled() {
6624
+ return this.isCaptureEnabled();
6625
+ }
6529
6626
  /**
6530
6627
  * Fetch the function with its current version and BAML prompt from the server.
6531
6628
  *
@@ -6937,9 +7034,6 @@ var Bitfab = class {
6937
7034
  * @returns A wrapped function with the same signature that creates spans for inputs and outputs
6938
7035
  */
6939
7036
  withSpan(traceFunctionKey, optionsOrFn, maybeFn) {
6940
- if (!this.explicitlyEnabled) {
6941
- return typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
6942
- }
6943
7037
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
6944
7038
  const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
6945
7039
  const self = this;
@@ -6953,7 +7047,7 @@ var Bitfab = class {
6953
7047
  }
6954
7048
  })();
6955
7049
  const wrappedFn = function(...args) {
6956
- if (!self.isTracingEnabled()) {
7050
+ if (!self.shouldRecord()) {
6957
7051
  return fn.apply(this, args);
6958
7052
  }
6959
7053
  initializeAsyncContext();
@@ -7001,11 +7095,26 @@ var Bitfab = class {
7001
7095
  const currentStack = getSpanStack();
7002
7096
  const parentContext = currentStack[currentStack.length - 1];
7003
7097
  const replayCtxForTraceId = parentContext ? null : getReplayContext();
7004
- const traceId = parentContext?.traceId ?? replayCtxForTraceId?.traceId ?? randomUuid();
7098
+ const seedCtxForTraceId = parentContext ? null : getSeedContext();
7099
+ const traceId = parentContext?.traceId ?? replayCtxForTraceId?.traceId ?? seedCtxForTraceId?.traceId ?? randomUuid();
7005
7100
  const spanId = randomUuid();
7006
7101
  const parentSpanId = parentContext?.spanId ?? null;
7007
7102
  const isRootSpan = parentSpanId === null;
7008
- const newContext = { traceId, spanId, contexts: [] };
7103
+ const requestedSurface = options.surface ?? "opt-in";
7104
+ const surface = requestedSurface === "inherit" ? parentContext?.surface ?? "opt-in" : requestedSurface;
7105
+ if (requestedSurface !== "inherit" && parentContext?.surface !== void 0 && parentContext.surface !== surface) {
7106
+ warnMixedTracingSurfaces(
7107
+ traceFunctionKey,
7108
+ surface,
7109
+ parentContext.surface
7110
+ );
7111
+ }
7112
+ const newContext = {
7113
+ traceId,
7114
+ spanId,
7115
+ contexts: [],
7116
+ surface
7117
+ };
7009
7118
  newStack = [...currentStack, newContext];
7010
7119
  const inputs = args;
7011
7120
  const startedAt = nowIsoTimestamp();
@@ -7021,6 +7130,9 @@ var Bitfab = class {
7021
7130
  ...replayCtxAtStart?.inputSourceTraceId && {
7022
7131
  inputSourceTraceId: replayCtxAtStart.inputSourceTraceId
7023
7132
  },
7133
+ ...replayCtxAtStart?.replayAttempt !== void 0 && {
7134
+ replayAttempt: replayCtxAtStart.replayAttempt
7135
+ },
7024
7136
  dbSnapshotRef
7025
7137
  });
7026
7138
  registeredTraceId = traceId;
@@ -7071,6 +7183,7 @@ var Bitfab = class {
7071
7183
  contexts: traceState?.contexts ?? [],
7072
7184
  testRunId: traceState?.testRunId,
7073
7185
  inputSourceTraceId: traceState?.inputSourceTraceId,
7186
+ replayAttempt: traceState?.replayAttempt,
7074
7187
  dbSnapshotRef: traceState?.dbSnapshotRef,
7075
7188
  dropped: traceState?.dropped,
7076
7189
  ingestionType: traceState?.ingestionType,
@@ -7283,7 +7396,7 @@ var Bitfab = class {
7283
7396
  if (registeredTraceId) {
7284
7397
  activeTraceStates.delete(registeredTraceId);
7285
7398
  }
7286
- if (getReplayContext()) {
7399
+ if (getReplayContext() || inSeedScope()) {
7287
7400
  throw setupError;
7288
7401
  }
7289
7402
  warnOnce(
@@ -7358,7 +7471,7 @@ var Bitfab = class {
7358
7471
  return {
7359
7472
  traceId,
7360
7473
  addContext: (context) => {
7361
- if (!this.isTracingEnabled()) {
7474
+ if (!this.shouldRecord()) {
7362
7475
  return Promise.resolve();
7363
7476
  }
7364
7477
  if (typeof context !== "object" || context === null) {
@@ -7369,7 +7482,7 @@ var Bitfab = class {
7369
7482
  });
7370
7483
  },
7371
7484
  setMetadata: (metadata) => {
7372
- if (!this.isTracingEnabled()) {
7485
+ if (!this.shouldRecord()) {
7373
7486
  return Promise.resolve();
7374
7487
  }
7375
7488
  if (typeof metadata !== "object" || metadata === null) {
@@ -7378,7 +7491,7 @@ var Bitfab = class {
7378
7491
  return this.httpClient.patchTrace(traceId, { mergeMetadata: metadata });
7379
7492
  },
7380
7493
  setSessionId: (sessionId) => {
7381
- if (!this.isTracingEnabled()) {
7494
+ if (!this.shouldRecord()) {
7382
7495
  return Promise.resolve();
7383
7496
  }
7384
7497
  if (typeof sessionId !== "string" || sessionId.length === 0) {
@@ -7387,7 +7500,7 @@ var Bitfab = class {
7387
7500
  return this.httpClient.patchTrace(traceId, { setSessionId: sessionId });
7388
7501
  },
7389
7502
  setName: (name) => {
7390
- if (!this.isTracingEnabled()) {
7503
+ if (!this.shouldRecord()) {
7391
7504
  return Promise.resolve();
7392
7505
  }
7393
7506
  if (typeof name !== "string" || name.length === 0) {
@@ -7468,6 +7581,9 @@ var Bitfab = class {
7468
7581
  if (params.inputSourceTraceId) {
7469
7582
  rawTrace.input_source_trace_id = params.inputSourceTraceId;
7470
7583
  }
7584
+ if (params.replayAttempt !== void 0) {
7585
+ rawTrace.replay_attempt = params.replayAttempt;
7586
+ }
7471
7587
  if (params.dbSnapshotRef) {
7472
7588
  rawTrace.db_snapshot_ref = params.dbSnapshotRef;
7473
7589
  }
@@ -7620,22 +7736,13 @@ var Bitfab = class {
7620
7736
  clearMockOverrides() {
7621
7737
  this.mockOverrides.length = 0;
7622
7738
  }
7623
- /**
7624
- * Write a replayable trace from a case, without running anything.
7625
- *
7626
- * Use this to turn a corpus you already hold (a Braintrust dataset, a
7627
- * spreadsheet, hand-written cases) into traces that {@link replay} can
7628
- * select. The recorded root span carries `input` as its input and `expected`
7629
- * as its output, so replay reports each item against the value you expected
7630
- * rather than against a previous run.
7631
- *
7632
- * A seeded trace has no child spans and no database pin, so replay mocking
7633
- * has nothing recorded to substitute and `dbBranch` refuses it. Pass
7634
- * `mockOverride` at replay time for calls that must not run.
7635
- *
7636
- * @returns The trace ID, usable with `replay({ traceIds: [...] })`.
7637
- */
7638
- seedTrace(traceFunctionKey, options) {
7739
+ seedTrace(traceFunctionKey, optionsOrFn, runOptions) {
7740
+ if (typeof optionsOrFn === "function") {
7741
+ return this.seedTraceByRunning(traceFunctionKey, optionsOrFn, runOptions);
7742
+ }
7743
+ return this.seedTraceFromCase(traceFunctionKey, optionsOrFn);
7744
+ }
7745
+ seedTraceFromCase(traceFunctionKey, options) {
7639
7746
  const { input } = options;
7640
7747
  const fn = options.fn?._bitfabWrappedFn ?? options.fn;
7641
7748
  if (fn && input.length < fn.length) {
@@ -7684,6 +7791,50 @@ var Bitfab = class {
7684
7791
  }
7685
7792
  return traceId;
7686
7793
  }
7794
+ async seedTraceByRunning(traceFunctionKey, fn, options) {
7795
+ const wrappedKey = fn._bitfabTraceFunctionKey;
7796
+ let target = fn;
7797
+ if (wrappedKey === void 0) {
7798
+ target = this.withSpan(
7799
+ traceFunctionKey,
7800
+ { name: traceFunctionKey, type: "agent" },
7801
+ fn
7802
+ );
7803
+ } else if (wrappedKey !== traceFunctionKey) {
7804
+ throw new BitfabError(
7805
+ `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.`
7806
+ );
7807
+ }
7808
+ await seedContextReady;
7809
+ const traceId = randomUuid();
7810
+ activeTraceStates.set(traceId, {
7811
+ traceId,
7812
+ startedAt: nowIsoTimestamp(),
7813
+ contexts: [],
7814
+ ingestionType: "seeded",
7815
+ ...options?.sessionId !== void 0 && { sessionId: options.sessionId },
7816
+ ...options?.name !== void 0 && { name: options.name },
7817
+ ...options?.metadata !== void 0 && { metadata: options.metadata }
7818
+ });
7819
+ const args = options?.args ?? [];
7820
+ let unrecorded = true;
7821
+ try {
7822
+ await runWithSeedContext({ traceId }, async () => target(...args));
7823
+ } finally {
7824
+ try {
7825
+ const { flushTraces: flushTraces2 } = await Promise.resolve().then(() => (init_http(), http_exports));
7826
+ await flushTraces2(3e4);
7827
+ } finally {
7828
+ unrecorded = activeTraceStates.delete(traceId);
7829
+ }
7830
+ }
7831
+ if (unrecorded) {
7832
+ throw new BitfabError(
7833
+ `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.`
7834
+ );
7835
+ }
7836
+ return traceId;
7837
+ }
7687
7838
  async replay(traceFunctionKey, fn, options) {
7688
7839
  const wrappedKey = fn._bitfabTraceFunctionKey;
7689
7840
  let replayFn = fn;
@@ -7944,7 +8095,7 @@ init_replay();
7944
8095
  function defineReplayRegistry(registry) {
7945
8096
  return registry;
7946
8097
  }
7947
- async function seedFromRegistry(registry, pipeline, cases) {
8098
+ async function seedFromRegistry(registry, pipeline, cases, options = {}) {
7948
8099
  const registration = registry[pipeline];
7949
8100
  if (registration === void 0) {
7950
8101
  throw new BitfabError(
@@ -7952,6 +8103,19 @@ async function seedFromRegistry(registry, pipeline, cases) {
7952
8103
  );
7953
8104
  }
7954
8105
  const traceFunctionKey = resolveTraceFunctionKey(registration);
8106
+ if (options.run === true) {
8107
+ const traceIds2 = [];
8108
+ for (const seedCase of cases) {
8109
+ traceIds2.push(
8110
+ await registration.client.seedTrace(traceFunctionKey, registration.fn, {
8111
+ args: seedCase.input,
8112
+ metadata: seedCase.metadata,
8113
+ sessionId: seedCase.sessionId
8114
+ })
8115
+ );
8116
+ }
8117
+ return { pipeline, traceFunctionKey, traceIds: traceIds2 };
8118
+ }
7955
8119
  const traceIds = cases.map(
7956
8120
  (seedCase) => registration.client.seedTrace(traceFunctionKey, {
7957
8121
  input: seedCase.input,