bitfab 0.11.6 → 0.12.1

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.d.cts CHANGED
@@ -258,6 +258,7 @@ declare class BitfabLangGraphCallbackHandler {
258
258
  * 3. Complete — marks the test run as completed on the server
259
259
  */
260
260
 
261
+ type MockStrategy = "none" | "all" | "marked";
261
262
  interface ReplayOptions {
262
263
  /** Maximum number of traces to replay (1–100, default 5). */
263
264
  limit?: number;
@@ -277,6 +278,13 @@ interface ReplayOptions {
277
278
  * files (`before`) or deleted files (`after`).
278
279
  */
279
280
  codeChangeFiles?: CodeChangeFile[];
281
+ /**
282
+ * Mock strategy for child spans during replay.
283
+ * - "none": everything runs real code (default)
284
+ * - "all": every child withSpan returns historical output
285
+ * - "marked": only spans tagged with { mockOnReplay: true } in SpanOptions are mocked
286
+ */
287
+ mock?: MockStrategy;
280
288
  }
281
289
  interface ReplayItem<T> {
282
290
  /** Deserialized inputs from the original trace. */
@@ -533,6 +541,16 @@ interface SpanOptions {
533
541
  * The type of span. Defaults to "custom" if not specified.
534
542
  */
535
543
  type?: SpanType;
544
+ /**
545
+ * When true, replay will reuse this span's historical output instead of
546
+ * executing the wrapped function. Read by the "marked" replay strategy;
547
+ * ignored outside replay and under the "all"/"none" strategies.
548
+ *
549
+ * Use this for child spans that are expensive (paid LLM/API calls),
550
+ * slow, or non-deterministic — the root function still runs real code,
551
+ * only the marked descendants return their recorded output.
552
+ */
553
+ mockOnReplay?: boolean;
536
554
  }
537
555
 
538
556
  /**
@@ -740,6 +758,7 @@ declare class Bitfab {
740
758
  maxConcurrency?: number;
741
759
  codeChangeDescription?: string;
742
760
  codeChangeFiles?: CodeChangeFile[];
761
+ mock?: "none" | "all" | "marked";
743
762
  }): Promise<ReplayResult<TReturn>>;
744
763
  }
745
764
  /**
@@ -803,7 +822,7 @@ declare class BitfabFunction {
803
822
  /**
804
823
  * SDK version from package.json (injected at build time)
805
824
  */
806
- declare const __version__ = "0.11.6";
825
+ declare const __version__ = "0.12.1";
807
826
 
808
827
  /**
809
828
  * Constants for the Bitfab SDK.
@@ -813,4 +832,4 @@ declare const __version__ = "0.11.6";
813
832
  */
814
833
  declare const DEFAULT_SERVICE_URL = "https://bitfab.ai";
815
834
 
816
- export { type ActiveSpanContext, type AllowedEnvVars, type BamlExecutionResult, Bitfab, BitfabClaudeAgentHandler, type BitfabConfig, BitfabError, BitfabFunction, BitfabLangGraphCallbackHandler, BitfabOpenAITracingProcessor, type CodeChangeFile, type CurrentSpan, type CurrentTrace, DEFAULT_SERVICE_URL, type ProviderDefinition, type ReplayItem, type ReplayOptions, type ReplayResult, type SpanOptions, type SpanType, type TokenUsage, type TraceResponse, type TracingProcessor, type WrapBAMLOptions, type WrappedBamlFn, __version__, flushTraces, getCurrentSpan, getCurrentTrace };
835
+ export { type ActiveSpanContext, type AllowedEnvVars, type BamlExecutionResult, Bitfab, BitfabClaudeAgentHandler, type BitfabConfig, BitfabError, BitfabFunction, BitfabLangGraphCallbackHandler, BitfabOpenAITracingProcessor, type CodeChangeFile, type CurrentSpan, type CurrentTrace, DEFAULT_SERVICE_URL, type MockStrategy, type ProviderDefinition, type ReplayItem, type ReplayOptions, type ReplayResult, type SpanOptions, type SpanType, type TokenUsage, type TraceResponse, type TracingProcessor, type WrapBAMLOptions, type WrappedBamlFn, __version__, flushTraces, getCurrentSpan, getCurrentTrace };
package/dist/index.d.ts CHANGED
@@ -258,6 +258,7 @@ declare class BitfabLangGraphCallbackHandler {
258
258
  * 3. Complete — marks the test run as completed on the server
259
259
  */
260
260
 
261
+ type MockStrategy = "none" | "all" | "marked";
261
262
  interface ReplayOptions {
262
263
  /** Maximum number of traces to replay (1–100, default 5). */
263
264
  limit?: number;
@@ -277,6 +278,13 @@ interface ReplayOptions {
277
278
  * files (`before`) or deleted files (`after`).
278
279
  */
279
280
  codeChangeFiles?: CodeChangeFile[];
281
+ /**
282
+ * Mock strategy for child spans during replay.
283
+ * - "none": everything runs real code (default)
284
+ * - "all": every child withSpan returns historical output
285
+ * - "marked": only spans tagged with { mockOnReplay: true } in SpanOptions are mocked
286
+ */
287
+ mock?: MockStrategy;
280
288
  }
281
289
  interface ReplayItem<T> {
282
290
  /** Deserialized inputs from the original trace. */
@@ -533,6 +541,16 @@ interface SpanOptions {
533
541
  * The type of span. Defaults to "custom" if not specified.
534
542
  */
535
543
  type?: SpanType;
544
+ /**
545
+ * When true, replay will reuse this span's historical output instead of
546
+ * executing the wrapped function. Read by the "marked" replay strategy;
547
+ * ignored outside replay and under the "all"/"none" strategies.
548
+ *
549
+ * Use this for child spans that are expensive (paid LLM/API calls),
550
+ * slow, or non-deterministic — the root function still runs real code,
551
+ * only the marked descendants return their recorded output.
552
+ */
553
+ mockOnReplay?: boolean;
536
554
  }
537
555
 
538
556
  /**
@@ -740,6 +758,7 @@ declare class Bitfab {
740
758
  maxConcurrency?: number;
741
759
  codeChangeDescription?: string;
742
760
  codeChangeFiles?: CodeChangeFile[];
761
+ mock?: "none" | "all" | "marked";
743
762
  }): Promise<ReplayResult<TReturn>>;
744
763
  }
745
764
  /**
@@ -803,7 +822,7 @@ declare class BitfabFunction {
803
822
  /**
804
823
  * SDK version from package.json (injected at build time)
805
824
  */
806
- declare const __version__ = "0.11.6";
825
+ declare const __version__ = "0.12.1";
807
826
 
808
827
  /**
809
828
  * Constants for the Bitfab SDK.
@@ -813,4 +832,4 @@ declare const __version__ = "0.11.6";
813
832
  */
814
833
  declare const DEFAULT_SERVICE_URL = "https://bitfab.ai";
815
834
 
816
- export { type ActiveSpanContext, type AllowedEnvVars, type BamlExecutionResult, Bitfab, BitfabClaudeAgentHandler, type BitfabConfig, BitfabError, BitfabFunction, BitfabLangGraphCallbackHandler, BitfabOpenAITracingProcessor, type CodeChangeFile, type CurrentSpan, type CurrentTrace, DEFAULT_SERVICE_URL, type ProviderDefinition, type ReplayItem, type ReplayOptions, type ReplayResult, type SpanOptions, type SpanType, type TokenUsage, type TraceResponse, type TracingProcessor, type WrapBAMLOptions, type WrappedBamlFn, __version__, flushTraces, getCurrentSpan, getCurrentTrace };
835
+ export { type ActiveSpanContext, type AllowedEnvVars, type BamlExecutionResult, Bitfab, BitfabClaudeAgentHandler, type BitfabConfig, BitfabError, BitfabFunction, BitfabLangGraphCallbackHandler, BitfabOpenAITracingProcessor, type CodeChangeFile, type CurrentSpan, type CurrentTrace, DEFAULT_SERVICE_URL, type MockStrategy, type ProviderDefinition, type ReplayItem, type ReplayOptions, type ReplayResult, type SpanOptions, type SpanType, type TokenUsage, type TraceResponse, type TracingProcessor, type WrapBAMLOptions, type WrappedBamlFn, __version__, flushTraces, getCurrentSpan, getCurrentTrace };
package/dist/index.js CHANGED
@@ -6,13 +6,13 @@ import {
6
6
  BitfabOpenAITracingProcessor,
7
7
  getCurrentSpan,
8
8
  getCurrentTrace
9
- } from "./chunk-MTHBHBOD.js";
9
+ } from "./chunk-NU52P7HA.js";
10
10
  import {
11
11
  BitfabError,
12
12
  DEFAULT_SERVICE_URL,
13
13
  __version__,
14
14
  flushTraces
15
- } from "./chunk-VOSASRED.js";
15
+ } from "./chunk-OOKZ6S64.js";
16
16
  export {
17
17
  Bitfab,
18
18
  BitfabClaudeAgentHandler,
package/dist/node.cjs CHANGED
@@ -81,7 +81,7 @@ var __version__;
81
81
  var init_version_generated = __esm({
82
82
  "src/version.generated.ts"() {
83
83
  "use strict";
84
- __version__ = "0.11.6";
84
+ __version__ = "0.12.1";
85
85
  }
86
86
  });
87
87
 
@@ -336,6 +336,42 @@ var init_http = __esm({
336
336
  clearTimeout(timeoutId);
337
337
  }
338
338
  }
339
+ /**
340
+ * Fetch the span tree for a root span.
341
+ * Blocking GET request.
342
+ */
343
+ async getSpanTree(externalSpanId) {
344
+ const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}`;
345
+ const controller = new AbortController();
346
+ const timeoutId = setTimeout(() => controller.abort(), 3e4);
347
+ try {
348
+ const response = await fetch(url, {
349
+ method: "GET",
350
+ headers: { Authorization: `Bearer ${this.apiKey}` },
351
+ signal: controller.signal
352
+ });
353
+ if (!response.ok) {
354
+ const errorText = await response.text();
355
+ throw new BitfabError(
356
+ `HTTP ${response.status}: ${errorText.slice(0, 500)}`
357
+ );
358
+ }
359
+ return await response.json();
360
+ } catch (error) {
361
+ if (error instanceof BitfabError) {
362
+ throw error;
363
+ }
364
+ if (error instanceof Error) {
365
+ if (error.name === "AbortError") {
366
+ throw new BitfabError("Request timed out after 30000ms");
367
+ }
368
+ throw new BitfabError(error.message);
369
+ }
370
+ throw new BitfabError("Unknown error occurred");
371
+ } finally {
372
+ clearTimeout(timeoutId);
373
+ }
374
+ }
339
375
  /**
340
376
  * Mark a replay test run as completed.
341
377
  * Blocking call.
@@ -431,11 +467,37 @@ function deserializeOutput(spanData) {
431
467
  }
432
468
  return rawOutput;
433
469
  }
434
- async function processItem(httpClient, serverItem, fn, testRunId) {
470
+ function buildMockTree(rootNode) {
471
+ const spans = /* @__PURE__ */ new Map();
472
+ const counters = /* @__PURE__ */ new Map();
473
+ function walk(node) {
474
+ const key = node.traceFunctionKey;
475
+ if (key) {
476
+ const index = counters.get(key) ?? 0;
477
+ counters.set(key, index + 1);
478
+ spans.set(`${key}:${index}`, {
479
+ sourceSpanId: node.sourceSpanId,
480
+ output: node.output,
481
+ outputMeta: node.outputMeta
482
+ });
483
+ }
484
+ for (const child of node.children) {
485
+ walk(child);
486
+ }
487
+ }
488
+ walk(rootNode);
489
+ return { spans };
490
+ }
491
+ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy) {
435
492
  const span = await httpClient.getExternalSpan(serverItem.externalSpanId);
436
493
  const spanData = span.rawData?.span_data ?? {};
437
494
  const inputs = deserializeInputs(spanData);
438
495
  const originalOutput = deserializeOutput(spanData);
496
+ let mockTree;
497
+ if (mockStrategy === "all" || mockStrategy === "marked") {
498
+ const treeResponse = await httpClient.getSpanTree(serverItem.externalSpanId);
499
+ mockTree = buildMockTree(treeResponse.root);
500
+ }
439
501
  let result;
440
502
  let error = null;
441
503
  try {
@@ -443,7 +505,10 @@ async function processItem(httpClient, serverItem, fn, testRunId) {
443
505
  {
444
506
  testRunId,
445
507
  inputSourceSpanId: span.id,
446
- inputSourceTraceId: span.externalTraceId
508
+ inputSourceTraceId: span.externalTraceId,
509
+ mockTree,
510
+ callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
511
+ mockStrategy
447
512
  },
448
513
  () => fn(...inputs)
449
514
  );
@@ -490,9 +555,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
490
555
  options?.codeChangeDescription,
491
556
  options?.codeChangeFiles
492
557
  );
558
+ const mockStrategy = options?.mock ?? "none";
493
559
  const maxConcurrency = options?.maxConcurrency ?? 10;
494
560
  const tasks = serverItems.map(
495
- (serverItem) => () => processItem(httpClient, serverItem, fn, testRunId)
561
+ (serverItem) => () => processItem(httpClient, serverItem, fn, testRunId, mockStrategy)
496
562
  );
497
563
  const resultItems = await mapWithConcurrency(tasks, maxConcurrency);
498
564
  await flushTraces();
@@ -2639,6 +2705,31 @@ var Bitfab = class {
2639
2705
  } catch {
2640
2706
  }
2641
2707
  };
2708
+ const replayCtxForMock = getReplayContext();
2709
+ if (replayCtxForMock?.mockTree && !isRootSpan) {
2710
+ const counters = replayCtxForMock.callCounters;
2711
+ const callIndex = counters.get(traceFunctionKey) ?? 0;
2712
+ counters.set(traceFunctionKey, callIndex + 1);
2713
+ const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
2714
+ if (shouldMock) {
2715
+ const mockKey = `${traceFunctionKey}:${callIndex}`;
2716
+ const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
2717
+ if (mockSpan) {
2718
+ let output = mockSpan.output;
2719
+ if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
2720
+ output = deserializeValue({
2721
+ json: mockSpan.output,
2722
+ meta: mockSpan.outputMeta
2723
+ });
2724
+ }
2725
+ void sendSpan({ result: output });
2726
+ if (fn.constructor.name === "AsyncFunction") {
2727
+ return Promise.resolve(output);
2728
+ }
2729
+ return output;
2730
+ }
2731
+ }
2732
+ }
2642
2733
  const executeWithContext = () => {
2643
2734
  const result = fn(...args);
2644
2735
  if (result instanceof Promise) {