@bitfab/sdk 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
@@ -42,7 +42,7 @@ var __version__;
42
42
  var init_version_generated = __esm({
43
43
  "src/version.generated.ts"() {
44
44
  "use strict";
45
- __version__ = "0.36.1";
45
+ __version__ = "0.36.3";
46
46
  }
47
47
  });
48
48
 
@@ -1521,9 +1521,11 @@ var init_http = __esm({
1521
1521
  /**
1522
1522
  * Fetch an external span by ID.
1523
1523
  * Blocking GET request.
1524
+ * The replay view limits rawData to input/output serialization fields.
1524
1525
  */
1525
- async getExternalSpan(spanId) {
1526
- const url = `${this.serviceUrl}/api/sdk/externalSpans/${spanId}`;
1526
+ async getExternalSpan(spanId, options) {
1527
+ const query = options?.view === "replay" ? "?view=replay" : "";
1528
+ const url = `${this.serviceUrl}/api/sdk/externalSpans/${spanId}${query}`;
1527
1529
  const controller = new AbortController();
1528
1530
  const timeoutId = setTimeout(() => controller.abort(), 3e4);
1529
1531
  try {
@@ -1561,9 +1563,18 @@ var init_http = __esm({
1561
1563
  * Pass `includeOutputs: false` for a payload-free tree (structure +
1562
1564
  * `externalSpanId` only), so recorded outputs are fetched lazily per mocked
1563
1565
  * span instead of all up front. Omit it (default eager) for `mock: "all"`.
1566
+ * Pass `includeRootOutput: false` when the root was already fetched.
1564
1567
  */
1565
1568
  async getSpanTree(externalSpanId, options) {
1566
- const query = options?.includeOutputs === false ? "?includeOutputs=false" : "";
1569
+ const searchParams = new URLSearchParams();
1570
+ if (options?.includeOutputs === false) {
1571
+ searchParams.set("includeOutputs", "false");
1572
+ }
1573
+ if (options?.includeRootOutput === false) {
1574
+ searchParams.set("includeRootOutput", "false");
1575
+ }
1576
+ const encodedQuery = searchParams.toString();
1577
+ const query = encodedQuery ? `?${encodedQuery}` : "";
1567
1578
  const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}${query}`;
1568
1579
  const controller = new AbortController();
1569
1580
  const timeoutId = setTimeout(() => controller.abort(), 3e4);
@@ -2042,8 +2053,11 @@ var init_codeChange = __esm({
2042
2053
  var replay_exports = {};
2043
2054
  __export(replay_exports, {
2044
2055
  BITFAB_PROGRESS_PREFIX: () => BITFAB_PROGRESS_PREFIX,
2056
+ DbBranchReplayError: () => DbBranchReplayError,
2057
+ ReplayError: () => ReplayError,
2045
2058
  replay: () => replay,
2046
- reportReplayProgress: () => reportReplayProgress
2059
+ reportReplayProgress: () => reportReplayProgress,
2060
+ serializeReplayResult: () => serializeReplayResult
2047
2061
  });
2048
2062
  function dbBranchEnabled(dbBranch) {
2049
2063
  return dbBranch !== void 0 && dbBranch !== false;
@@ -2066,11 +2080,59 @@ function reportReplayProgress(progress) {
2066
2080
  return;
2067
2081
  }
2068
2082
  try {
2069
- stderr.write(`${BITFAB_PROGRESS_PREFIX}${JSON.stringify(progress)}
2070
- `);
2083
+ stderr.write(
2084
+ `${BITFAB_PROGRESS_PREFIX}${JSON.stringify(progress, replayJsonReplacer)}
2085
+ `
2086
+ );
2071
2087
  } catch {
2072
2088
  }
2073
2089
  }
2090
+ function errorMessage(error) {
2091
+ return error instanceof Error ? error.message : String(error);
2092
+ }
2093
+ function replayItemErrorMessage(error) {
2094
+ if (error instanceof DbBranchReplayError) {
2095
+ 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.`;
2096
+ }
2097
+ return errorMessage(error);
2098
+ }
2099
+ function replayJsonReplacer(_key, value) {
2100
+ if (value instanceof Error) {
2101
+ const serialized = {
2102
+ name: value.name,
2103
+ message: value.message,
2104
+ stack: value.stack
2105
+ };
2106
+ if (value instanceof DbBranchReplayError) {
2107
+ serialized.code = value.code;
2108
+ serialized.originalTraceId = value.originalTraceId;
2109
+ if (value.cause !== void 0) {
2110
+ serialized.cause = value.cause;
2111
+ }
2112
+ }
2113
+ return serialized;
2114
+ }
2115
+ return value;
2116
+ }
2117
+ function serializeReplayResult(result) {
2118
+ return JSON.stringify(result, replayJsonReplacer, 2);
2119
+ }
2120
+ async function preserveReplayFailure(operation, items, testRunId, testRunUrl) {
2121
+ try {
2122
+ return await operation();
2123
+ } catch (cause) {
2124
+ if (cause instanceof ReplayError) {
2125
+ throw cause;
2126
+ }
2127
+ throw new ReplayError(
2128
+ errorMessage(cause),
2129
+ items,
2130
+ testRunId,
2131
+ testRunUrl,
2132
+ cause
2133
+ );
2134
+ }
2135
+ }
2074
2136
  function deserializeInputs(spanData) {
2075
2137
  const inputMeta = spanData.input_meta;
2076
2138
  const rawInput = spanData.input;
@@ -2128,25 +2190,41 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2128
2190
  let originalOutput;
2129
2191
  let result;
2130
2192
  let error = null;
2193
+ let traceError = null;
2194
+ let replayError = null;
2131
2195
  const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
2132
2196
  const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
2133
2197
  try {
2134
2198
  if (includeDbBranchLease && !lease && !leaseError) {
2135
- const resolved = await httpClient.resolveDbBranchLease(
2136
- testRunId,
2137
- originalTraceId,
2138
- dbBranchSettings
2139
- );
2199
+ let resolved;
2200
+ try {
2201
+ resolved = await httpClient.resolveDbBranchLease(
2202
+ testRunId,
2203
+ originalTraceId,
2204
+ dbBranchSettings
2205
+ );
2206
+ } catch (cause) {
2207
+ throw new DbBranchReplayError(
2208
+ "lease_request_failed",
2209
+ `Bitfab could not request the database branch: ${errorMessage(cause)}`,
2210
+ originalTraceId,
2211
+ cause
2212
+ );
2213
+ }
2140
2214
  lease = resolved.lease ?? void 0;
2141
2215
  leaseError = resolved.leaseError ?? void 0;
2142
2216
  dbSnapshotRef = resolved.dbSnapshotRef ?? dbSnapshotRef;
2143
2217
  }
2144
2218
  if (leaseError) {
2145
- throw new BitfabError(
2146
- `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.`
2219
+ throw new DbBranchReplayError(
2220
+ leaseError.code,
2221
+ leaseError.message,
2222
+ originalTraceId
2147
2223
  );
2148
2224
  }
2149
- const span = await httpClient.getExternalSpan(originalSpanId);
2225
+ const span = await httpClient.getExternalSpan(originalSpanId, {
2226
+ view: "replay"
2227
+ });
2150
2228
  const spanData = span.rawData?.span_data ?? {};
2151
2229
  inputs = deserializeInputs(spanData);
2152
2230
  originalOutput = deserializeOutput(spanData);
@@ -2166,7 +2244,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2166
2244
  if (needTree) {
2167
2245
  try {
2168
2246
  const treeResponse = await httpClient.getSpanTree(originalSpanId, {
2169
- includeOutputs
2247
+ includeOutputs,
2248
+ includeRootOutput: false
2170
2249
  });
2171
2250
  if (treeResponse.root) {
2172
2251
  mockTree = buildMockTree(treeResponse.root);
@@ -2188,7 +2267,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2188
2267
  const fetchSpanOutput = mockTree && !includeOutputs ? (externalSpanId) => {
2189
2268
  let pending = outputCache.get(externalSpanId);
2190
2269
  if (!pending) {
2191
- pending = httpClient.getExternalSpan(externalSpanId).then(
2270
+ pending = httpClient.getExternalSpan(externalSpanId, { view: "replay" }).then(
2192
2271
  (s) => deserializeOutput(
2193
2272
  s.rawData?.span_data ?? {}
2194
2273
  )
@@ -2197,25 +2276,31 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2197
2276
  }
2198
2277
  return pending;
2199
2278
  } : void 0;
2200
- const maybePromise = runWithReplayContext(
2201
- {
2202
- testRunId,
2203
- traceId: replayedTraceId,
2204
- inputSourceSpanId: span.id,
2205
- inputSourceTraceId: span.externalTraceId,
2206
- sourceBitfabTraceId: originalTraceId,
2207
- mockTree,
2208
- callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
2209
- mockStrategy,
2210
- mockOverrides: hasOverrides ? resolvedOverrides : void 0,
2211
- fetchSpanOutput,
2212
- dbBranchLease: lease
2213
- },
2214
- () => fn(...inputs)
2215
- );
2216
- result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
2279
+ try {
2280
+ const maybePromise = runWithReplayContext(
2281
+ {
2282
+ testRunId,
2283
+ traceId: replayedTraceId,
2284
+ inputSourceSpanId: span.id,
2285
+ inputSourceTraceId: span.externalTraceId,
2286
+ sourceBitfabTraceId: originalTraceId,
2287
+ mockTree,
2288
+ callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
2289
+ mockStrategy,
2290
+ mockOverrides: hasOverrides ? resolvedOverrides : void 0,
2291
+ fetchSpanOutput,
2292
+ dbBranchLease: lease
2293
+ },
2294
+ () => fn(...inputs)
2295
+ );
2296
+ result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
2297
+ } catch (e) {
2298
+ traceError = e;
2299
+ error = errorMessage(e);
2300
+ }
2217
2301
  } catch (e) {
2218
- error = e instanceof Error ? e.message : String(e);
2302
+ replayError = e;
2303
+ error = replayItemErrorMessage(e);
2219
2304
  } finally {
2220
2305
  if (lease) {
2221
2306
  try {
@@ -2244,6 +2329,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2244
2329
  result,
2245
2330
  originalOutput,
2246
2331
  error,
2332
+ traceError,
2333
+ replayError,
2247
2334
  durationMs: serverItem.durationMs ?? null,
2248
2335
  // Filled in by replay() from the complete-replay response once the
2249
2336
  // replay traces are persisted and their spans aggregated server-side.
@@ -2368,6 +2455,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2368
2455
  );
2369
2456
  const mockStrategy = options?.mock ?? "marked";
2370
2457
  const maxConcurrency = options?.maxConcurrency ?? 10;
2458
+ const fullTestRunUrl = `${serviceUrl}${testRunUrl}`;
2371
2459
  const resolvedOverrides = [
2372
2460
  ...normalizeMockOverrides(options?.mockOverride),
2373
2461
  ...registeredOverrides
@@ -2424,6 +2512,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2424
2512
  result: item.result,
2425
2513
  originalOutput: item.originalOutput,
2426
2514
  error: item.error,
2515
+ traceError: item.traceError,
2516
+ replayError: item.replayError,
2427
2517
  durationMs: item.durationMs,
2428
2518
  tokens: item.tokens,
2429
2519
  model: item.model,
@@ -2434,8 +2524,18 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2434
2524
  }
2435
2525
  } : void 0
2436
2526
  );
2437
- await waitForReplayPersistence(httpClient, testRunId, replayedTraceIds);
2438
- const completeResult = await httpClient.completeReplay(testRunId);
2527
+ await preserveReplayFailure(
2528
+ () => waitForReplayPersistence(httpClient, testRunId, replayedTraceIds),
2529
+ resultItems,
2530
+ testRunId,
2531
+ fullTestRunUrl
2532
+ );
2533
+ const completeResult = await preserveReplayFailure(
2534
+ () => httpClient.completeReplay(testRunId),
2535
+ resultItems,
2536
+ testRunId,
2537
+ fullTestRunUrl
2538
+ );
2439
2539
  const serverTraceIds = completeResult.traceIds;
2440
2540
  const replayTokens = completeResult.tokens;
2441
2541
  if (serverTraceIds !== void 0) {
@@ -2458,9 +2558,16 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2458
2558
  }
2459
2559
  if (completedCount > 0 && missing.length === completedCount) {
2460
2560
  const serverCount = completeResult.traceCount !== void 0 ? ` The server persisted ${completeResult.traceCount} trace(s) for this run.` : "";
2461
- throw new BitfabError(
2561
+ const cause = new BitfabError(
2462
2562
  `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.`
2463
2563
  );
2564
+ throw new ReplayError(
2565
+ cause.message,
2566
+ resultItems,
2567
+ testRunId,
2568
+ fullTestRunUrl,
2569
+ cause
2570
+ );
2464
2571
  }
2465
2572
  if (missing.length > 0) {
2466
2573
  try {
@@ -2474,7 +2581,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2474
2581
  const result = {
2475
2582
  items: resultItems,
2476
2583
  testRunId,
2477
- testRunUrl: `${serviceUrl}${testRunUrl}`
2584
+ testRunUrl: fullTestRunUrl
2478
2585
  };
2479
2586
  await writeReplayResultFile(result);
2480
2587
  try {
@@ -2502,7 +2609,7 @@ async function writeReplayResultFile(result) {
2502
2609
  import("fs/promises")
2503
2610
  ]);
2504
2611
  await mkdir(dirname(resultPath), { recursive: true });
2505
- await writeFile(resultPath, `${JSON.stringify(result, null, 2)}
2612
+ await writeFile(resultPath, `${serializeReplayResult(result)}
2506
2613
  `);
2507
2614
  } catch (err) {
2508
2615
  try {
@@ -2513,7 +2620,7 @@ async function writeReplayResultFile(result) {
2513
2620
  }
2514
2621
  }
2515
2622
  }
2516
- var REPLAY_PERSISTENCE_TIMEOUT_MS, BITFAB_PROGRESS_PREFIX;
2623
+ var REPLAY_PERSISTENCE_TIMEOUT_MS, BITFAB_PROGRESS_PREFIX, ReplayError, DbBranchReplayError;
2517
2624
  var init_replay = __esm({
2518
2625
  "src/replay.ts"() {
2519
2626
  "use strict";
@@ -2528,6 +2635,25 @@ var init_replay = __esm({
2528
2635
  init_unrefTimer();
2529
2636
  REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
2530
2637
  BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
2638
+ ReplayError = class extends BitfabError {
2639
+ constructor(message, items, testRunId, testRunUrl, cause) {
2640
+ super(message, testRunUrl);
2641
+ this.items = items;
2642
+ this.testRunId = testRunId;
2643
+ this.testRunUrl = testRunUrl;
2644
+ this.cause = cause;
2645
+ this.name = "ReplayError";
2646
+ }
2647
+ };
2648
+ DbBranchReplayError = class extends BitfabError {
2649
+ constructor(code, message, originalTraceId, cause) {
2650
+ super(message);
2651
+ this.code = code;
2652
+ this.originalTraceId = originalTraceId;
2653
+ this.cause = cause;
2654
+ this.name = "DbBranchReplayError";
2655
+ }
2656
+ };
2531
2657
  }
2532
2658
  });
2533
2659
 
@@ -2545,7 +2671,9 @@ __export(index_exports, {
2545
2671
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
2546
2672
  BitfabVercelAiHandler: () => BitfabVercelAiHandler,
2547
2673
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
2674
+ DbBranchReplayError: () => DbBranchReplayError,
2548
2675
  HttpClient: () => HttpClient,
2676
+ ReplayError: () => ReplayError,
2549
2677
  SUPPORTED_PROVIDERS: () => SUPPORTED_PROVIDERS,
2550
2678
  __version__: () => __version__,
2551
2679
  finalizers: () => finalizers,
@@ -2553,7 +2681,8 @@ __export(index_exports, {
2553
2681
  getCurrentReplayBranch: () => getCurrentReplayBranch,
2554
2682
  getCurrentSpan: () => getCurrentSpan,
2555
2683
  getCurrentTrace: () => getCurrentTrace,
2556
- reportReplayProgress: () => reportReplayProgress
2684
+ reportReplayProgress: () => reportReplayProgress,
2685
+ serializeReplayResult: () => serializeReplayResult
2557
2686
  });
2558
2687
  module.exports = __toCommonJS(index_exports);
2559
2688
 
@@ -6134,7 +6263,9 @@ init_replay();
6134
6263
  BitfabOpenAITracingProcessor,
6135
6264
  BitfabVercelAiHandler,
6136
6265
  DEFAULT_SERVICE_URL,
6266
+ DbBranchReplayError,
6137
6267
  HttpClient,
6268
+ ReplayError,
6138
6269
  SUPPORTED_PROVIDERS,
6139
6270
  __version__,
6140
6271
  finalizers,
@@ -6142,6 +6273,7 @@ init_replay();
6142
6273
  getCurrentReplayBranch,
6143
6274
  getCurrentSpan,
6144
6275
  getCurrentTrace,
6145
- reportReplayProgress
6276
+ reportReplayProgress,
6277
+ serializeReplayResult
6146
6278
  });
6147
6279
  //# sourceMappingURL=index.cjs.map