@bitfab/sdk 0.28.10 → 0.29.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.
@@ -6,14 +6,15 @@ import {
6
6
  getReplayContext,
7
7
  isAsyncStorageInitDone,
8
8
  randomUuid,
9
+ resolveMockValue,
9
10
  serializeValue,
10
11
  toJsonSafe,
11
12
  toJsonSafeReport,
12
13
  warnOnce
13
- } from "./chunk-5E4BUIYA.js";
14
+ } from "./chunk-V3XORTWI.js";
14
15
 
15
16
  // src/version.generated.ts
16
- var __version__ = "0.28.10";
17
+ var __version__ = "0.29.0";
17
18
 
18
19
  // src/constants.ts
19
20
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -249,6 +250,50 @@ var HttpClient = class {
249
250
  async lookupFunction(name) {
250
251
  return this.request("/api/sdk/functions/lookup", { name });
251
252
  }
253
+ async getTraceSpan(traceId, lookup) {
254
+ const searchParams = new URLSearchParams();
255
+ if (lookup.id !== void 0) {
256
+ searchParams.set("id", lookup.id);
257
+ } else {
258
+ searchParams.set("name", lookup.name);
259
+ searchParams.set("occurrence", String(lookup.occurrence ?? "last"));
260
+ }
261
+ const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}/span?${searchParams.toString()}`;
262
+ const response = await this.get(endpoint);
263
+ return response.span;
264
+ }
265
+ async get(endpoint) {
266
+ const url = `${this.serviceUrl}${endpoint}`;
267
+ const controller = new AbortController();
268
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
269
+ try {
270
+ const response = await fetch(url, {
271
+ method: "GET",
272
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
273
+ signal: controller.signal
274
+ });
275
+ if (!response.ok) {
276
+ const errorText = await response.text();
277
+ throw new BitfabError(
278
+ `HTTP ${response.status}: ${errorText.slice(0, 500)}`
279
+ );
280
+ }
281
+ return await response.json();
282
+ } catch (error) {
283
+ if (error instanceof BitfabError) {
284
+ throw error;
285
+ }
286
+ if (error instanceof Error) {
287
+ if (error.name === "AbortError") {
288
+ throw new BitfabError(`Request timed out after ${this.timeout}ms`);
289
+ }
290
+ throw new BitfabError(error.message);
291
+ }
292
+ throw new BitfabError("Unknown error occurred");
293
+ } finally {
294
+ clearTimeout(timeoutId);
295
+ }
296
+ }
252
297
  /**
253
298
  * Send an internal trace (from BAML execution).
254
299
  * Fire-and-forget with awaitOnExit - doesn't block the caller.
@@ -305,12 +350,12 @@ var HttpClient = class {
305
350
  });
306
351
  }
307
352
  /**
308
- * Partial update of an existing external trace identified by sourceTraceId.
353
+ * Partial update of an existing trace identified by its Bitfab trace ID.
309
354
  * Used by the detached `client.getTrace(id)` handle. Fire-and-forget;
310
355
  * returns a tracked promise that callers may optionally await.
311
356
  */
312
- patchTrace(sourceTraceId, payload) {
313
- const endpoint = `/api/sdk/externalTraces/${encodeURIComponent(sourceTraceId)}`;
357
+ patchTrace(traceId, payload) {
358
+ const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}`;
314
359
  return awaitOnExit(
315
360
  this.request(endpoint, payload, { method: "PATCH" })
316
361
  ).catch((error) => {
@@ -394,9 +439,14 @@ var HttpClient = class {
394
439
  /**
395
440
  * Fetch the span tree for a root span.
396
441
  * Blocking GET request.
442
+ *
443
+ * Pass `includeOutputs: false` for a payload-free tree (structure +
444
+ * `externalSpanId` only), so recorded outputs are fetched lazily per mocked
445
+ * span instead of all up front. Omit it (default eager) for `mock: "all"`.
397
446
  */
398
- async getSpanTree(externalSpanId) {
399
- const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}`;
447
+ async getSpanTree(externalSpanId, options) {
448
+ const query = options?.includeOutputs === false ? "?includeOutputs=false" : "";
449
+ const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}${query}`;
400
450
  const controller = new AbortController();
401
451
  const timeoutId = setTimeout(() => controller.abort(), 3e4);
402
452
  try {
@@ -644,6 +694,7 @@ var BitfabClaudeAgentHandler = class {
644
694
  const traceId = this.ensureTrace();
645
695
  const { safe: safeInput, dropped: inputDropped } = toJsonSafeReport(inputData);
646
696
  const spanInfo = {
697
+ id: randomUuid(),
647
698
  spanId,
648
699
  traceId,
649
700
  parentId: parentId ?? null,
@@ -707,6 +758,8 @@ var BitfabClaudeAgentHandler = class {
707
758
  rawSpan.parent_id = spanInfo.parentId;
708
759
  }
709
760
  const payload = {
761
+ id: spanInfo.id,
762
+ traceId: spanInfo.traceId,
710
763
  type: "sdk-function",
711
764
  source: "typescript-sdk-claude-agent-sdk",
712
765
  traceFunctionKey: this.traceFunctionKey,
@@ -736,6 +789,7 @@ var BitfabClaudeAgentHandler = class {
736
789
  externalTrace.metadata = metadata;
737
790
  }
738
791
  const traceData = {
792
+ id: traceId,
739
793
  type: "sdk-function",
740
794
  source: "typescript-sdk-claude-agent-sdk",
741
795
  traceFunctionKey: this.traceFunctionKey,
@@ -1004,6 +1058,7 @@ var BitfabClaudeAgentHandler = class {
1004
1058
  }
1005
1059
  Object.assign(llmContext, this.currentLlmUsage);
1006
1060
  const spanInfo = {
1061
+ id: randomUuid(),
1007
1062
  spanId,
1008
1063
  traceId,
1009
1064
  parentId,
@@ -1644,6 +1699,7 @@ var BitfabLangGraphCallbackHandler = class {
1644
1699
  const contexts = Object.keys(lgMetadata).length > 0 ? [lgMetadata] : [];
1645
1700
  const { safe: safeInput, dropped: inputDropped } = toJsonSafeReport(inputData);
1646
1701
  const spanInfo = {
1702
+ id: randomUuid(),
1647
1703
  spanId: runId,
1648
1704
  traceId: invocation.traceId,
1649
1705
  rootRunId: invocation.rootRunId,
@@ -1722,6 +1778,8 @@ var BitfabLangGraphCallbackHandler = class {
1722
1778
  rawSpan.parent_id = spanInfo.parentId;
1723
1779
  }
1724
1780
  const payload = {
1781
+ id: spanInfo.id,
1782
+ traceId: spanInfo.traceId,
1725
1783
  type: "sdk-function",
1726
1784
  source: "typescript-sdk-langgraph",
1727
1785
  traceFunctionKey: this.traceFunctionKey,
@@ -1737,6 +1795,7 @@ var BitfabLangGraphCallbackHandler = class {
1737
1795
  sendTraceCompletion(rootSpan, activeContext) {
1738
1796
  const completed = activeContext === null;
1739
1797
  const traceData = {
1798
+ id: rootSpan.traceId,
1740
1799
  type: "sdk-function",
1741
1800
  source: "typescript-sdk-langgraph",
1742
1801
  traceFunctionKey: this.traceFunctionKey,
@@ -1756,6 +1815,7 @@ var BitfabLangGraphCallbackHandler = class {
1756
1815
  }
1757
1816
  sendTraceStart(rootSpan) {
1758
1817
  const traceData = {
1818
+ id: rootSpan.traceId,
1759
1819
  type: "sdk-function",
1760
1820
  source: "typescript-sdk-langgraph",
1761
1821
  traceFunctionKey: this.traceFunctionKey,
@@ -2115,6 +2175,7 @@ var BitfabOpenAITracingProcessor = class {
2115
2175
  constructor(config) {
2116
2176
  this.activeTraces = {};
2117
2177
  this.activeSpanMappings = {};
2178
+ this.canonicalTraceIds = {};
2118
2179
  this.httpClient = new HttpClient({
2119
2180
  apiKey: config.apiKey,
2120
2181
  serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
@@ -2122,6 +2183,15 @@ var BitfabOpenAITracingProcessor = class {
2122
2183
  });
2123
2184
  this.getActiveSpanContext = config.getActiveSpanContext ?? null;
2124
2185
  }
2186
+ getCanonicalTraceId(sourceTraceId) {
2187
+ const existing = this.canonicalTraceIds[sourceTraceId];
2188
+ if (existing) {
2189
+ return existing;
2190
+ }
2191
+ const created = randomUuid();
2192
+ this.canonicalTraceIds[sourceTraceId] = created;
2193
+ return created;
2194
+ }
2125
2195
  /**
2126
2196
  * Called when a trace is started.
2127
2197
  * If there's an active withSpan context, the trace ID is remapped to the
@@ -2133,7 +2203,12 @@ var BitfabOpenAITracingProcessor = class {
2133
2203
  if (activeContext) {
2134
2204
  this.activeSpanMappings[trace.traceId] = activeContext;
2135
2205
  }
2136
- this.sendTrace(trace, activeContext ? { id: activeContext.traceId } : {});
2206
+ const canonicalTraceId = activeContext?.traceId ?? this.getCanonicalTraceId(trace.traceId);
2207
+ this.canonicalTraceIds[trace.traceId] = canonicalTraceId;
2208
+ this.sendTrace(trace, {
2209
+ id: canonicalTraceId,
2210
+ sourceTraceId: activeContext?.traceId
2211
+ });
2137
2212
  }
2138
2213
  /**
2139
2214
  * Called when a trace is ended.
@@ -2142,11 +2217,13 @@ var BitfabOpenAITracingProcessor = class {
2142
2217
  */
2143
2218
  async onTraceEnd(trace) {
2144
2219
  const mapping = this.activeSpanMappings[trace.traceId];
2145
- this.sendTrace(
2146
- trace,
2147
- mapping ? { id: mapping.traceId } : { completed: true }
2148
- );
2220
+ this.sendTrace(trace, {
2221
+ completed: mapping === void 0,
2222
+ id: mapping?.traceId ?? this.getCanonicalTraceId(trace.traceId),
2223
+ sourceTraceId: mapping?.traceId
2224
+ });
2149
2225
  delete this.activeSpanMappings[trace.traceId];
2226
+ delete this.canonicalTraceIds[trace.traceId];
2150
2227
  delete this.activeTraces[trace.traceId];
2151
2228
  }
2152
2229
  /**
@@ -2176,22 +2253,25 @@ var BitfabOpenAITracingProcessor = class {
2176
2253
  async shutdown(_timeout) {
2177
2254
  this.activeTraces = {};
2178
2255
  this.activeSpanMappings = {};
2256
+ this.canonicalTraceIds = {};
2179
2257
  }
2180
2258
  /**
2181
2259
  * Send trace to Bitfab API (fire-and-forget).
2182
2260
  * When traceIdOverride is provided, the trace ID is remapped to link
2183
2261
  * the OpenAI trace into an outer withSpan trace.
2184
2262
  */
2185
- sendTrace(trace, overrides = {}) {
2263
+ sendTrace(trace, options = {}) {
2186
2264
  try {
2187
- const { completed, ...traceOverrides } = overrides;
2188
2265
  const traceData = trace.toJSON();
2189
- Object.assign(traceData, traceOverrides);
2266
+ if (options.sourceTraceId) {
2267
+ traceData.id = options.sourceTraceId;
2268
+ }
2190
2269
  this.httpClient.sendExternalTrace({
2270
+ ...options.id && { id: options.id },
2191
2271
  type: "openai",
2192
2272
  source: "typescript-sdk-openai-tracing",
2193
2273
  externalTrace: traceData,
2194
- completed: completed ?? false
2274
+ completed: options.completed ?? false
2195
2275
  });
2196
2276
  } catch {
2197
2277
  }
@@ -2277,6 +2357,7 @@ var BitfabOpenAITracingProcessor = class {
2277
2357
  */
2278
2358
  buildSpanPayload(serializedSpan, errors) {
2279
2359
  const payload = {
2360
+ id: randomUuid(),
2280
2361
  type: "openai",
2281
2362
  source: "typescript-sdk-openai-tracing",
2282
2363
  sourceTraceId: serializedSpan.trace_id ?? "unknown",
@@ -2299,6 +2380,10 @@ var BitfabOpenAITracingProcessor = class {
2299
2380
  this.extractSpanInputResponse(span, serializedSpan, errors);
2300
2381
  this.applySpanOverrides(serializedSpan, span.traceId ?? "");
2301
2382
  const payload = this.buildSpanPayload(serializedSpan, errors);
2383
+ const canonicalTraceId = span.traceId ? this.getCanonicalTraceId(span.traceId) : void 0;
2384
+ if (canonicalTraceId) {
2385
+ payload.traceId = canonicalTraceId;
2386
+ }
2302
2387
  this.httpClient.sendExternalSpan(payload);
2303
2388
  }
2304
2389
  };
@@ -2614,24 +2699,19 @@ function extractContextFromCollector(collector) {
2614
2699
  return null;
2615
2700
  }
2616
2701
  }
2617
- var TRACE_ID_PATTERN = /^[a-zA-Z0-9_\-.:]+$/;
2618
- var TRACE_ID_MAX_LENGTH = 256;
2702
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
2619
2703
  function validateTraceId(traceId) {
2620
- if (typeof traceId !== "string" || traceId.length === 0) {
2621
- throw new BitfabError("traceId is required and must be a non-empty string");
2704
+ if (typeof traceId !== "string" || !UUID_PATTERN.test(traceId)) {
2705
+ throw new BitfabError("traceId must be a valid Bitfab trace ID");
2622
2706
  }
2623
- if (traceId.length > TRACE_ID_MAX_LENGTH) {
2624
- throw new BitfabError(
2625
- `traceId must be ${TRACE_ID_MAX_LENGTH} characters or fewer`
2626
- );
2627
- }
2628
- if (!TRACE_ID_PATTERN.test(traceId)) {
2629
- throw new BitfabError(
2630
- `traceId may only contain letters, digits, "_", "-", ".", ":"`
2631
- );
2707
+ }
2708
+ function validateSpanId(id) {
2709
+ if (typeof id !== "string" || !UUID_PATTERN.test(id)) {
2710
+ throw new BitfabError("id must be a valid Bitfab span ID");
2632
2711
  }
2633
2712
  }
2634
2713
  var noOpSpan = {
2714
+ id: "",
2635
2715
  traceId: "",
2636
2716
  addContext() {
2637
2717
  },
@@ -2655,6 +2735,7 @@ function getCurrentSpan() {
2655
2735
  return noOpSpan;
2656
2736
  }
2657
2737
  return {
2738
+ id: current.spanId,
2658
2739
  traceId: current.traceId,
2659
2740
  addContext(context) {
2660
2741
  try {
@@ -2746,6 +2827,12 @@ var Bitfab = class {
2746
2827
  constructor(config) {
2747
2828
  /** Gate the empty-key warning to fire at most once. */
2748
2829
  this.apiKeyWarned = false;
2830
+ /**
2831
+ * Mock overrides registered via {@link Bitfab.registerMockOverride}, applied
2832
+ * to every `replay` on this client (after any per-call `mockOverride`). In
2833
+ * registration order; first matcher wins within this list.
2834
+ */
2835
+ this.mockOverrides = [];
2749
2836
  this.apiKeyConfig = config.apiKey;
2750
2837
  this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
2751
2838
  this.timeout = config.timeout ?? 12e4;
@@ -3358,25 +3445,78 @@ var Bitfab = class {
3358
3445
  const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
3359
3446
  const callIndex = counters.get(counterKey) ?? 0;
3360
3447
  counters.set(counterKey, callIndex + 1);
3361
- const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
3362
- if (shouldMock) {
3363
- const mockKey = `${counterKey}:${callIndex}`;
3364
- const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
3365
- if (mockSpan) {
3366
- let output = mockSpan.output;
3367
- if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
3368
- output = deserializeValue({
3369
- json: mockSpan.output,
3370
- meta: mockSpan.outputMeta
3371
- });
3372
- }
3448
+ const mockKey = `${counterKey}:${callIndex}`;
3449
+ const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
3450
+ const emitMock = (output) => {
3451
+ void sendSpan({ result: output, mocked: true });
3452
+ if (fnReturnsPromise) {
3453
+ return Promise.resolve(output);
3454
+ }
3455
+ return output;
3456
+ };
3457
+ const emitMockAsync = (pending) => {
3458
+ if (!fnReturnsPromise) {
3459
+ throw new BitfabError(
3460
+ `Cannot mock synchronous span "${traceFunctionKey}" with an asynchronously-resolved value (lazy recorded-output fetch or an async value function). Make the wrapped function async, or use mock: "all" so recorded outputs are fetched eagerly.`
3461
+ );
3462
+ }
3463
+ return (async () => {
3464
+ const output = await pending;
3373
3465
  void sendSpan({ result: output, mocked: true });
3374
- if (fnReturnsPromise) {
3375
- return Promise.resolve(output);
3376
- }
3377
3466
  return output;
3467
+ })();
3468
+ };
3469
+ const resolveRecordedOutput = () => {
3470
+ const hasInlineOutput = mockSpan?.output !== void 0 || mockSpan?.outputMeta !== void 0;
3471
+ if (!hasInlineOutput && replayCtxForMock.fetchSpanOutput && mockSpan?.externalSpanId) {
3472
+ return replayCtxForMock.fetchSpanOutput(mockSpan.externalSpanId);
3473
+ }
3474
+ if (!mockSpan) {
3475
+ return Promise.reject(
3476
+ new BitfabError(
3477
+ `No recorded span to source output for "${traceFunctionKey}".`
3478
+ )
3479
+ );
3480
+ }
3481
+ let output = mockSpan.output;
3482
+ if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
3483
+ output = deserializeValue({
3484
+ json: mockSpan.output,
3485
+ meta: mockSpan.outputMeta
3486
+ });
3487
+ }
3488
+ return output;
3489
+ };
3490
+ if (replayCtxForMock.mockOverrides?.length) {
3491
+ const nodeMeta = {
3492
+ traceFunctionKey,
3493
+ spanName: baseSpanParams.spanName,
3494
+ type: options.type ?? "custom",
3495
+ originalSpanId: mockSpan?.sourceSpanId
3496
+ };
3497
+ const override = replayCtxForMock.mockOverrides.find(
3498
+ (o) => o.match(nodeMeta)
3499
+ );
3500
+ if (override) {
3501
+ const injected = resolveMockValue(override.value, {
3502
+ node: nodeMeta,
3503
+ inputs: args,
3504
+ getOriginalOutput: () => Promise.resolve(resolveRecordedOutput())
3505
+ });
3506
+ if (injected instanceof Promise) {
3507
+ return emitMockAsync(injected);
3508
+ }
3509
+ return emitMock(injected);
3378
3510
  }
3379
3511
  }
3512
+ const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
3513
+ if (shouldMock && mockSpan) {
3514
+ const recorded = resolveRecordedOutput();
3515
+ if (recorded instanceof Promise) {
3516
+ return emitMockAsync(recorded);
3517
+ }
3518
+ return emitMock(recorded);
3519
+ }
3380
3520
  }
3381
3521
  const recordSpan = (result) => {
3382
3522
  if (options.finalize) {
@@ -3451,20 +3591,19 @@ var Bitfab = class {
3451
3591
  }
3452
3592
  /**
3453
3593
  * Get a detached handle to a previously-created trace, looked up by the
3454
- * caller-supplied id (the same id passed at trace creation).
3594
+ * canonical Bitfab trace ID.
3455
3595
  *
3456
3596
  * The returned handle is not tied to AsyncLocalStorage - each method sends
3457
3597
  * to the server immediately. Useful for adding context to a trace from a
3458
3598
  * different process or thread than the one that created it.
3459
3599
  *
3460
- * Throws synchronously if `traceId` is malformed (empty, too long, or
3461
- * contains characters outside `[a-zA-Z0-9_\-.:]`). Server returns 404 if
3462
- * no trace exists with that id in the org; the failure surfaces as a
3600
+ * Throws synchronously if `traceId` is not a valid Bitfab trace ID. The
3601
+ * server returns 404 if no trace exists with that ID in the org; the failure surfaces as a
3463
3602
  * logged warning (fire-and-forget) or via the awaited promise.
3464
3603
  *
3465
3604
  * Example:
3466
3605
  * ```typescript
3467
- * const trace = client.getTrace("order_abc_123");
3606
+ * const trace = client.getTrace(traceId);
3468
3607
  * await trace.addContext({ refund_status: "approved" });
3469
3608
  * await trace.setMetadata({ region: "us-west" });
3470
3609
  * ```
@@ -3504,6 +3643,33 @@ var Bitfab = class {
3504
3643
  }
3505
3644
  };
3506
3645
  }
3646
+ /**
3647
+ * Fetch one persisted span from a trace without loading the full trace.
3648
+ * Name lookups return the last matching span by default. Pass `occurrence`
3649
+ * as `"first"` or a zero-based index to select a different match.
3650
+ */
3651
+ async getTraceSpan(traceId, lookup) {
3652
+ validateTraceId(traceId);
3653
+ const hasId = lookup.id !== void 0;
3654
+ const hasName = lookup.name !== void 0;
3655
+ if (hasId === hasName) {
3656
+ throw new BitfabError("Provide exactly one of id or name");
3657
+ }
3658
+ if (hasId) {
3659
+ validateSpanId(lookup.id);
3660
+ } else {
3661
+ if (lookup.name.length === 0) {
3662
+ throw new BitfabError("name must be a non-empty string");
3663
+ }
3664
+ const occurrence = lookup.occurrence ?? "last";
3665
+ if (occurrence !== "first" && occurrence !== "last" && (!Number.isInteger(occurrence) || occurrence < 0)) {
3666
+ throw new BitfabError(
3667
+ 'occurrence must be "first", "last", or a non-negative integer'
3668
+ );
3669
+ }
3670
+ }
3671
+ return this.httpClient.getTraceSpan(traceId, lookup);
3672
+ }
3507
3673
  /**
3508
3674
  * Get a function wrapper for a specific trace function key.
3509
3675
  *
@@ -3562,6 +3728,7 @@ var Bitfab = class {
3562
3728
  };
3563
3729
  }
3564
3730
  return this.httpClient.sendExternalTrace({
3731
+ id: params.traceId,
3565
3732
  type: "sdk-function",
3566
3733
  source: "typescript-sdk-function",
3567
3734
  traceFunctionKey: params.traceFunctionKey,
@@ -3617,6 +3784,8 @@ var Bitfab = class {
3617
3784
  externalSpan.input_source_span_id = params.inputSourceSpanId;
3618
3785
  }
3619
3786
  return this.httpClient.sendExternalSpan({
3787
+ id: params.spanId,
3788
+ traceId: params.traceId,
3620
3789
  type: "sdk-function",
3621
3790
  source: "typescript-sdk-function",
3622
3791
  sourceTraceId: params.traceId,
@@ -3626,26 +3795,14 @@ var Bitfab = class {
3626
3795
  ...params.mocked && { mocked: true }
3627
3796
  });
3628
3797
  }
3629
- /**
3630
- * Replay historical traces through a function and create a test run.
3631
- *
3632
- * Fetches the last N traces for the given trace function key, re-runs each
3633
- * through the provided function, and returns comparison data.
3634
- *
3635
- * Accepts either a `withSpan`-wrapped function (under the same key) or any
3636
- * plain callable: plain callables are wrapped internally so each replayed
3637
- * invocation records a trace tied to the test run. The plain-callable form
3638
- * is how handler-instrumented workflows (LangGraph/LangChain, Claude Agent
3639
- * SDK) replay - those record traces under a key with no `withSpan`-wrapped
3640
- * root in the app.
3641
- *
3642
- * @param traceFunctionKey - The trace function key to replay
3643
- * @param fn - The function to run recorded inputs through
3644
- * @param options - Optional replay options. When `traceIds` is passed,
3645
- * `limit` is ignored (with a warning): an explicit ID list already
3646
- * determines how many traces replay.
3647
- * @returns ReplayResult with items, testRunId, and testRunUrl
3648
- */
3798
+ registerMockOverride(overrideOrMatch, value) {
3799
+ const override = typeof overrideOrMatch === "function" ? { match: overrideOrMatch, value } : overrideOrMatch;
3800
+ this.mockOverrides.push(override);
3801
+ }
3802
+ /** Remove all overrides registered via {@link registerMockOverride}. */
3803
+ clearMockOverrides() {
3804
+ this.mockOverrides.length = 0;
3805
+ }
3649
3806
  async replay(traceFunctionKey, fn, options) {
3650
3807
  const wrappedKey = fn._bitfabTraceFunctionKey;
3651
3808
  let replayFn = fn;
@@ -3660,13 +3817,14 @@ var Bitfab = class {
3660
3817
  `Function is wrapped with trace function key '${wrappedKey}' but replay was called with '${traceFunctionKey}'. Pass matching keys, or pass the unwrapped function to replay it under the explicit key.`
3661
3818
  );
3662
3819
  }
3663
- const { replay: doReplay } = await import("./replay-IEE4RY57.js");
3820
+ const { replay: doReplay } = await import("./replay-HEGU3YU2.js");
3664
3821
  return doReplay(
3665
3822
  this.httpClient,
3666
3823
  this.serviceUrl,
3667
3824
  traceFunctionKey,
3668
3825
  replayFn,
3669
- options
3826
+ options,
3827
+ this.mockOverrides
3670
3828
  );
3671
3829
  }
3672
3830
  };
@@ -3885,4 +4043,4 @@ export {
3885
4043
  BitfabFunction,
3886
4044
  finalizers
3887
4045
  };
3888
- //# sourceMappingURL=chunk-SK4TNXRC.js.map
4046
+ //# sourceMappingURL=chunk-2M5AWVVQ.js.map