bitfab 0.36.1 → 0.36.3

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__;
51
51
  var init_version_generated = __esm({
52
52
  "src/version.generated.ts"() {
53
53
  "use strict";
54
- __version__ = "0.36.1";
54
+ __version__ = "0.36.3";
55
55
  }
56
56
  });
57
57
 
@@ -1530,9 +1530,11 @@ var init_http = __esm({
1530
1530
  /**
1531
1531
  * Fetch an external span by ID.
1532
1532
  * Blocking GET request.
1533
+ * The replay view limits rawData to input/output serialization fields.
1533
1534
  */
1534
- async getExternalSpan(spanId) {
1535
- const url = `${this.serviceUrl}/api/sdk/externalSpans/${spanId}`;
1535
+ async getExternalSpan(spanId, options) {
1536
+ const query = options?.view === "replay" ? "?view=replay" : "";
1537
+ const url = `${this.serviceUrl}/api/sdk/externalSpans/${spanId}${query}`;
1536
1538
  const controller = new AbortController();
1537
1539
  const timeoutId = setTimeout(() => controller.abort(), 3e4);
1538
1540
  try {
@@ -1570,9 +1572,18 @@ var init_http = __esm({
1570
1572
  * Pass `includeOutputs: false` for a payload-free tree (structure +
1571
1573
  * `externalSpanId` only), so recorded outputs are fetched lazily per mocked
1572
1574
  * span instead of all up front. Omit it (default eager) for `mock: "all"`.
1575
+ * Pass `includeRootOutput: false` when the root was already fetched.
1573
1576
  */
1574
1577
  async getSpanTree(externalSpanId, options) {
1575
- const query = options?.includeOutputs === false ? "?includeOutputs=false" : "";
1578
+ const searchParams = new URLSearchParams();
1579
+ if (options?.includeOutputs === false) {
1580
+ searchParams.set("includeOutputs", "false");
1581
+ }
1582
+ if (options?.includeRootOutput === false) {
1583
+ searchParams.set("includeRootOutput", "false");
1584
+ }
1585
+ const encodedQuery = searchParams.toString();
1586
+ const query = encodedQuery ? `?${encodedQuery}` : "";
1576
1587
  const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}${query}`;
1577
1588
  const controller = new AbortController();
1578
1589
  const timeoutId = setTimeout(() => controller.abort(), 3e4);
@@ -2051,8 +2062,11 @@ var init_codeChange = __esm({
2051
2062
  var replay_exports = {};
2052
2063
  __export(replay_exports, {
2053
2064
  BITFAB_PROGRESS_PREFIX: () => BITFAB_PROGRESS_PREFIX,
2065
+ DbBranchReplayError: () => DbBranchReplayError,
2066
+ ReplayError: () => ReplayError,
2054
2067
  replay: () => replay,
2055
- reportReplayProgress: () => reportReplayProgress
2068
+ reportReplayProgress: () => reportReplayProgress,
2069
+ serializeReplayResult: () => serializeReplayResult
2056
2070
  });
2057
2071
  function dbBranchEnabled(dbBranch) {
2058
2072
  return dbBranch !== void 0 && dbBranch !== false;
@@ -2075,11 +2089,59 @@ function reportReplayProgress(progress) {
2075
2089
  return;
2076
2090
  }
2077
2091
  try {
2078
- stderr.write(`${BITFAB_PROGRESS_PREFIX}${JSON.stringify(progress)}
2079
- `);
2092
+ stderr.write(
2093
+ `${BITFAB_PROGRESS_PREFIX}${JSON.stringify(progress, replayJsonReplacer)}
2094
+ `
2095
+ );
2080
2096
  } catch {
2081
2097
  }
2082
2098
  }
2099
+ function errorMessage(error) {
2100
+ return error instanceof Error ? error.message : String(error);
2101
+ }
2102
+ function replayItemErrorMessage(error) {
2103
+ if (error instanceof DbBranchReplayError) {
2104
+ return `Replay requested a database branch for trace ${error.originalTraceId} but it could not be resolved (${error.code}): ${error.message}. The function was not run, because replaying it against the live database would produce a result that looks valid but did not use the historical data you asked for.`;
2105
+ }
2106
+ return errorMessage(error);
2107
+ }
2108
+ function replayJsonReplacer(_key, value) {
2109
+ if (value instanceof Error) {
2110
+ const serialized = {
2111
+ name: value.name,
2112
+ message: value.message,
2113
+ stack: value.stack
2114
+ };
2115
+ if (value instanceof DbBranchReplayError) {
2116
+ serialized.code = value.code;
2117
+ serialized.originalTraceId = value.originalTraceId;
2118
+ if (value.cause !== void 0) {
2119
+ serialized.cause = value.cause;
2120
+ }
2121
+ }
2122
+ return serialized;
2123
+ }
2124
+ return value;
2125
+ }
2126
+ function serializeReplayResult(result) {
2127
+ return JSON.stringify(result, replayJsonReplacer, 2);
2128
+ }
2129
+ async function preserveReplayFailure(operation, items, testRunId, testRunUrl) {
2130
+ try {
2131
+ return await operation();
2132
+ } catch (cause) {
2133
+ if (cause instanceof ReplayError) {
2134
+ throw cause;
2135
+ }
2136
+ throw new ReplayError(
2137
+ errorMessage(cause),
2138
+ items,
2139
+ testRunId,
2140
+ testRunUrl,
2141
+ cause
2142
+ );
2143
+ }
2144
+ }
2083
2145
  function deserializeInputs(spanData) {
2084
2146
  const inputMeta = spanData.input_meta;
2085
2147
  const rawInput = spanData.input;
@@ -2137,25 +2199,41 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2137
2199
  let originalOutput;
2138
2200
  let result;
2139
2201
  let error = null;
2202
+ let traceError = null;
2203
+ let replayError = null;
2140
2204
  const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
2141
2205
  const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
2142
2206
  try {
2143
2207
  if (includeDbBranchLease && !lease && !leaseError) {
2144
- const resolved = await httpClient.resolveDbBranchLease(
2145
- testRunId,
2146
- originalTraceId,
2147
- dbBranchSettings
2148
- );
2208
+ let resolved;
2209
+ try {
2210
+ resolved = await httpClient.resolveDbBranchLease(
2211
+ testRunId,
2212
+ originalTraceId,
2213
+ dbBranchSettings
2214
+ );
2215
+ } catch (cause) {
2216
+ throw new DbBranchReplayError(
2217
+ "lease_request_failed",
2218
+ `Bitfab could not request the database branch: ${errorMessage(cause)}`,
2219
+ originalTraceId,
2220
+ cause
2221
+ );
2222
+ }
2149
2223
  lease = resolved.lease ?? void 0;
2150
2224
  leaseError = resolved.leaseError ?? void 0;
2151
2225
  dbSnapshotRef = resolved.dbSnapshotRef ?? dbSnapshotRef;
2152
2226
  }
2153
2227
  if (leaseError) {
2154
- throw new BitfabError(
2155
- `Replay requested a database branch for trace ${originalTraceId} but it could not be resolved (${leaseError.code}): ${leaseError.message}. The function was not run, because replaying it against the live database would produce a result that looks valid but did not use the historical data you asked for.`
2228
+ throw new DbBranchReplayError(
2229
+ leaseError.code,
2230
+ leaseError.message,
2231
+ originalTraceId
2156
2232
  );
2157
2233
  }
2158
- const span = await httpClient.getExternalSpan(originalSpanId);
2234
+ const span = await httpClient.getExternalSpan(originalSpanId, {
2235
+ view: "replay"
2236
+ });
2159
2237
  const spanData = span.rawData?.span_data ?? {};
2160
2238
  inputs = deserializeInputs(spanData);
2161
2239
  originalOutput = deserializeOutput(spanData);
@@ -2175,7 +2253,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2175
2253
  if (needTree) {
2176
2254
  try {
2177
2255
  const treeResponse = await httpClient.getSpanTree(originalSpanId, {
2178
- includeOutputs
2256
+ includeOutputs,
2257
+ includeRootOutput: false
2179
2258
  });
2180
2259
  if (treeResponse.root) {
2181
2260
  mockTree = buildMockTree(treeResponse.root);
@@ -2197,7 +2276,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2197
2276
  const fetchSpanOutput = mockTree && !includeOutputs ? (externalSpanId) => {
2198
2277
  let pending = outputCache.get(externalSpanId);
2199
2278
  if (!pending) {
2200
- pending = httpClient.getExternalSpan(externalSpanId).then(
2279
+ pending = httpClient.getExternalSpan(externalSpanId, { view: "replay" }).then(
2201
2280
  (s) => deserializeOutput(
2202
2281
  s.rawData?.span_data ?? {}
2203
2282
  )
@@ -2206,25 +2285,31 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2206
2285
  }
2207
2286
  return pending;
2208
2287
  } : void 0;
2209
- const maybePromise = runWithReplayContext(
2210
- {
2211
- testRunId,
2212
- traceId: replayedTraceId,
2213
- inputSourceSpanId: span.id,
2214
- inputSourceTraceId: span.externalTraceId,
2215
- sourceBitfabTraceId: originalTraceId,
2216
- mockTree,
2217
- callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
2218
- mockStrategy,
2219
- mockOverrides: hasOverrides ? resolvedOverrides : void 0,
2220
- fetchSpanOutput,
2221
- dbBranchLease: lease
2222
- },
2223
- () => fn(...inputs)
2224
- );
2225
- result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
2288
+ try {
2289
+ const maybePromise = runWithReplayContext(
2290
+ {
2291
+ testRunId,
2292
+ traceId: replayedTraceId,
2293
+ inputSourceSpanId: span.id,
2294
+ inputSourceTraceId: span.externalTraceId,
2295
+ sourceBitfabTraceId: originalTraceId,
2296
+ mockTree,
2297
+ callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
2298
+ mockStrategy,
2299
+ mockOverrides: hasOverrides ? resolvedOverrides : void 0,
2300
+ fetchSpanOutput,
2301
+ dbBranchLease: lease
2302
+ },
2303
+ () => fn(...inputs)
2304
+ );
2305
+ result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
2306
+ } catch (e) {
2307
+ traceError = e;
2308
+ error = errorMessage(e);
2309
+ }
2226
2310
  } catch (e) {
2227
- error = e instanceof Error ? e.message : String(e);
2311
+ replayError = e;
2312
+ error = replayItemErrorMessage(e);
2228
2313
  } finally {
2229
2314
  if (lease) {
2230
2315
  try {
@@ -2253,6 +2338,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2253
2338
  result,
2254
2339
  originalOutput,
2255
2340
  error,
2341
+ traceError,
2342
+ replayError,
2256
2343
  durationMs: serverItem.durationMs ?? null,
2257
2344
  // Filled in by replay() from the complete-replay response once the
2258
2345
  // replay traces are persisted and their spans aggregated server-side.
@@ -2377,6 +2464,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2377
2464
  );
2378
2465
  const mockStrategy = options?.mock ?? "marked";
2379
2466
  const maxConcurrency = options?.maxConcurrency ?? 10;
2467
+ const fullTestRunUrl = `${serviceUrl}${testRunUrl}`;
2380
2468
  const resolvedOverrides = [
2381
2469
  ...normalizeMockOverrides(options?.mockOverride),
2382
2470
  ...registeredOverrides
@@ -2433,6 +2521,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2433
2521
  result: item.result,
2434
2522
  originalOutput: item.originalOutput,
2435
2523
  error: item.error,
2524
+ traceError: item.traceError,
2525
+ replayError: item.replayError,
2436
2526
  durationMs: item.durationMs,
2437
2527
  tokens: item.tokens,
2438
2528
  model: item.model,
@@ -2443,8 +2533,18 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2443
2533
  }
2444
2534
  } : void 0
2445
2535
  );
2446
- await waitForReplayPersistence(httpClient, testRunId, replayedTraceIds);
2447
- const completeResult = await httpClient.completeReplay(testRunId);
2536
+ await preserveReplayFailure(
2537
+ () => waitForReplayPersistence(httpClient, testRunId, replayedTraceIds),
2538
+ resultItems,
2539
+ testRunId,
2540
+ fullTestRunUrl
2541
+ );
2542
+ const completeResult = await preserveReplayFailure(
2543
+ () => httpClient.completeReplay(testRunId),
2544
+ resultItems,
2545
+ testRunId,
2546
+ fullTestRunUrl
2547
+ );
2448
2548
  const serverTraceIds = completeResult.traceIds;
2449
2549
  const replayTokens = completeResult.tokens;
2450
2550
  if (serverTraceIds !== void 0) {
@@ -2467,9 +2567,16 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2467
2567
  }
2468
2568
  if (completedCount > 0 && missing.length === completedCount) {
2469
2569
  const serverCount = completeResult.traceCount !== void 0 ? ` The server persisted ${completeResult.traceCount} trace(s) for this run.` : "";
2470
- throw new BitfabError(
2570
+ const cause = new BitfabError(
2471
2571
  `Replay completed but the server has no persisted trace for any of the ${completedCount} completed item(s) (testRunId ${testRunId}).${serverCount} Trace uploads were awaited, so either the uploads failed (check for "Bitfab: Failed to create" errors above) or the replayed function is not wrapped with withSpan.`
2472
2572
  );
2573
+ throw new ReplayError(
2574
+ cause.message,
2575
+ resultItems,
2576
+ testRunId,
2577
+ fullTestRunUrl,
2578
+ cause
2579
+ );
2473
2580
  }
2474
2581
  if (missing.length > 0) {
2475
2582
  try {
@@ -2483,7 +2590,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2483
2590
  const result = {
2484
2591
  items: resultItems,
2485
2592
  testRunId,
2486
- testRunUrl: `${serviceUrl}${testRunUrl}`
2593
+ testRunUrl: fullTestRunUrl
2487
2594
  };
2488
2595
  await writeReplayResultFile(result);
2489
2596
  try {
@@ -2511,7 +2618,7 @@ async function writeReplayResultFile(result) {
2511
2618
  import("fs/promises")
2512
2619
  ]);
2513
2620
  await mkdir(dirname(resultPath), { recursive: true });
2514
- await writeFile(resultPath, `${JSON.stringify(result, null, 2)}
2621
+ await writeFile(resultPath, `${serializeReplayResult(result)}
2515
2622
  `);
2516
2623
  } catch (err) {
2517
2624
  try {
@@ -2522,7 +2629,7 @@ async function writeReplayResultFile(result) {
2522
2629
  }
2523
2630
  }
2524
2631
  }
2525
- var REPLAY_PERSISTENCE_TIMEOUT_MS, BITFAB_PROGRESS_PREFIX;
2632
+ var REPLAY_PERSISTENCE_TIMEOUT_MS, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
2526
2633
  var init_replay = __esm({
2527
2634
  "src/replay.ts"() {
2528
2635
  "use strict";
@@ -2537,6 +2644,25 @@ var init_replay = __esm({
2537
2644
  init_unrefTimer();
2538
2645
  REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
2539
2646
  BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
2647
+ ReplayError = class extends BitfabError {
2648
+ constructor(message, items, testRunId, testRunUrl, cause) {
2649
+ super(message, testRunUrl);
2650
+ this.items = items;
2651
+ this.testRunId = testRunId;
2652
+ this.testRunUrl = testRunUrl;
2653
+ this.cause = cause;
2654
+ this.name = "ReplayError";
2655
+ }
2656
+ };
2657
+ DbBranchReplayError = class extends BitfabError {
2658
+ constructor(code, message, originalTraceId, cause) {
2659
+ super(message);
2660
+ this.code = code;
2661
+ this.originalTraceId = originalTraceId;
2662
+ this.cause = cause;
2663
+ this.name = "DbBranchReplayError";
2664
+ }
2665
+ };
2540
2666
  }
2541
2667
  });
2542
2668
 
@@ -2554,7 +2680,9 @@ __export(index_exports, {
2554
2680
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
2555
2681
  BitfabVercelAiHandler: () => BitfabVercelAiHandler,
2556
2682
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
2683
+ DbBranchReplayError: () => DbBranchReplayError,
2557
2684
  HttpClient: () => HttpClient,
2685
+ ReplayError: () => ReplayError,
2558
2686
  SUPPORTED_PROVIDERS: () => SUPPORTED_PROVIDERS,
2559
2687
  __version__: () => __version__,
2560
2688
  finalizers: () => finalizers,
@@ -2562,7 +2690,8 @@ __export(index_exports, {
2562
2690
  getCurrentReplayBranch: () => getCurrentReplayBranch,
2563
2691
  getCurrentSpan: () => getCurrentSpan,
2564
2692
  getCurrentTrace: () => getCurrentTrace,
2565
- reportReplayProgress: () => reportReplayProgress
2693
+ reportReplayProgress: () => reportReplayProgress,
2694
+ serializeReplayResult: () => serializeReplayResult
2566
2695
  });
2567
2696
  module.exports = __toCommonJS(index_exports);
2568
2697
 
@@ -6143,7 +6272,9 @@ init_replay();
6143
6272
  BitfabOpenAITracingProcessor,
6144
6273
  BitfabVercelAiHandler,
6145
6274
  DEFAULT_SERVICE_URL,
6275
+ DbBranchReplayError,
6146
6276
  HttpClient,
6277
+ ReplayError,
6147
6278
  SUPPORTED_PROVIDERS,
6148
6279
  __version__,
6149
6280
  finalizers,
@@ -6151,6 +6282,7 @@ init_replay();
6151
6282
  getCurrentReplayBranch,
6152
6283
  getCurrentSpan,
6153
6284
  getCurrentTrace,
6154
- reportReplayProgress
6285
+ reportReplayProgress,
6286
+ serializeReplayResult
6155
6287
  });
6156
6288
  //# sourceMappingURL=index.cjs.map