@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.
@@ -16,7 +16,7 @@ import {
16
16
  toJsonSafe,
17
17
  toJsonSafeReport,
18
18
  warnOnce
19
- } from "./chunk-6ZBZBR5K.js";
19
+ } from "./chunk-M2GISXGV.js";
20
20
 
21
21
  // src/processorPayload.ts
22
22
  var SERIALIZATION_DEGRADED_STEP = "serialization_degraded";
@@ -3337,7 +3337,7 @@ var Bitfab = class {
3337
3337
  `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.`
3338
3338
  );
3339
3339
  }
3340
- const { replay: doReplay } = await import("./replay-ZR4KIZIB.js");
3340
+ const { replay: doReplay } = await import("./replay-ZH25RPKH.js");
3341
3341
  return doReplay(
3342
3342
  this.httpClient,
3343
3343
  this.serviceUrl,
@@ -3554,4 +3554,4 @@ export {
3554
3554
  BitfabFunction,
3555
3555
  finalizers
3556
3556
  };
3557
- //# sourceMappingURL=chunk-HD4DRDRE.js.map
3557
+ //# sourceMappingURL=chunk-4CTDZMG5.js.map
@@ -278,7 +278,7 @@ function encodeRequestBody(body) {
278
278
  }
279
279
 
280
280
  // src/version.generated.ts
281
- var __version__ = "0.36.1";
281
+ var __version__ = "0.36.3";
282
282
 
283
283
  // src/constants.ts
284
284
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -1598,9 +1598,11 @@ var HttpClient = class {
1598
1598
  /**
1599
1599
  * Fetch an external span by ID.
1600
1600
  * Blocking GET request.
1601
+ * The replay view limits rawData to input/output serialization fields.
1601
1602
  */
1602
- async getExternalSpan(spanId) {
1603
- const url = `${this.serviceUrl}/api/sdk/externalSpans/${spanId}`;
1603
+ async getExternalSpan(spanId, options) {
1604
+ const query = options?.view === "replay" ? "?view=replay" : "";
1605
+ const url = `${this.serviceUrl}/api/sdk/externalSpans/${spanId}${query}`;
1604
1606
  const controller = new AbortController();
1605
1607
  const timeoutId = setTimeout(() => controller.abort(), 3e4);
1606
1608
  try {
@@ -1638,9 +1640,18 @@ var HttpClient = class {
1638
1640
  * Pass `includeOutputs: false` for a payload-free tree (structure +
1639
1641
  * `externalSpanId` only), so recorded outputs are fetched lazily per mocked
1640
1642
  * span instead of all up front. Omit it (default eager) for `mock: "all"`.
1643
+ * Pass `includeRootOutput: false` when the root was already fetched.
1641
1644
  */
1642
1645
  async getSpanTree(externalSpanId, options) {
1643
- const query = options?.includeOutputs === false ? "?includeOutputs=false" : "";
1646
+ const searchParams = new URLSearchParams();
1647
+ if (options?.includeOutputs === false) {
1648
+ searchParams.set("includeOutputs", "false");
1649
+ }
1650
+ if (options?.includeRootOutput === false) {
1651
+ searchParams.set("includeRootOutput", "false");
1652
+ }
1653
+ const encodedQuery = searchParams.toString();
1654
+ const query = encodedQuery ? `?${encodedQuery}` : "";
1644
1655
  const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}${query}`;
1645
1656
  const controller = new AbortController();
1646
1657
  const timeoutId = setTimeout(() => controller.abort(), 3e4);
@@ -1921,11 +1932,78 @@ function reportReplayProgress(progress) {
1921
1932
  return;
1922
1933
  }
1923
1934
  try {
1924
- stderr.write(`${BITFAB_PROGRESS_PREFIX}${JSON.stringify(progress)}
1925
- `);
1935
+ stderr.write(
1936
+ `${BITFAB_PROGRESS_PREFIX}${JSON.stringify(progress, replayJsonReplacer)}
1937
+ `
1938
+ );
1926
1939
  } catch {
1927
1940
  }
1928
1941
  }
1942
+ var ReplayError = class extends BitfabError {
1943
+ constructor(message, items, testRunId, testRunUrl, cause) {
1944
+ super(message, testRunUrl);
1945
+ this.items = items;
1946
+ this.testRunId = testRunId;
1947
+ this.testRunUrl = testRunUrl;
1948
+ this.cause = cause;
1949
+ this.name = "ReplayError";
1950
+ }
1951
+ };
1952
+ var DbBranchReplayError = class extends BitfabError {
1953
+ constructor(code, message, originalTraceId, cause) {
1954
+ super(message);
1955
+ this.code = code;
1956
+ this.originalTraceId = originalTraceId;
1957
+ this.cause = cause;
1958
+ this.name = "DbBranchReplayError";
1959
+ }
1960
+ };
1961
+ function errorMessage(error) {
1962
+ return error instanceof Error ? error.message : String(error);
1963
+ }
1964
+ function replayItemErrorMessage(error) {
1965
+ if (error instanceof DbBranchReplayError) {
1966
+ 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.`;
1967
+ }
1968
+ return errorMessage(error);
1969
+ }
1970
+ function replayJsonReplacer(_key, value) {
1971
+ if (value instanceof Error) {
1972
+ const serialized = {
1973
+ name: value.name,
1974
+ message: value.message,
1975
+ stack: value.stack
1976
+ };
1977
+ if (value instanceof DbBranchReplayError) {
1978
+ serialized.code = value.code;
1979
+ serialized.originalTraceId = value.originalTraceId;
1980
+ if (value.cause !== void 0) {
1981
+ serialized.cause = value.cause;
1982
+ }
1983
+ }
1984
+ return serialized;
1985
+ }
1986
+ return value;
1987
+ }
1988
+ function serializeReplayResult(result) {
1989
+ return JSON.stringify(result, replayJsonReplacer, 2);
1990
+ }
1991
+ async function preserveReplayFailure(operation, items, testRunId, testRunUrl) {
1992
+ try {
1993
+ return await operation();
1994
+ } catch (cause) {
1995
+ if (cause instanceof ReplayError) {
1996
+ throw cause;
1997
+ }
1998
+ throw new ReplayError(
1999
+ errorMessage(cause),
2000
+ items,
2001
+ testRunId,
2002
+ testRunUrl,
2003
+ cause
2004
+ );
2005
+ }
2006
+ }
1929
2007
  function deserializeInputs(spanData) {
1930
2008
  const inputMeta = spanData.input_meta;
1931
2009
  const rawInput = spanData.input;
@@ -1983,25 +2061,41 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
1983
2061
  let originalOutput;
1984
2062
  let result;
1985
2063
  let error = null;
2064
+ let traceError = null;
2065
+ let replayError = null;
1986
2066
  const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
1987
2067
  const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
1988
2068
  try {
1989
2069
  if (includeDbBranchLease && !lease && !leaseError) {
1990
- const resolved = await httpClient.resolveDbBranchLease(
1991
- testRunId,
1992
- originalTraceId,
1993
- dbBranchSettings
1994
- );
2070
+ let resolved;
2071
+ try {
2072
+ resolved = await httpClient.resolveDbBranchLease(
2073
+ testRunId,
2074
+ originalTraceId,
2075
+ dbBranchSettings
2076
+ );
2077
+ } catch (cause) {
2078
+ throw new DbBranchReplayError(
2079
+ "lease_request_failed",
2080
+ `Bitfab could not request the database branch: ${errorMessage(cause)}`,
2081
+ originalTraceId,
2082
+ cause
2083
+ );
2084
+ }
1995
2085
  lease = resolved.lease ?? void 0;
1996
2086
  leaseError = resolved.leaseError ?? void 0;
1997
2087
  dbSnapshotRef = resolved.dbSnapshotRef ?? dbSnapshotRef;
1998
2088
  }
1999
2089
  if (leaseError) {
2000
- throw new BitfabError(
2001
- `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.`
2090
+ throw new DbBranchReplayError(
2091
+ leaseError.code,
2092
+ leaseError.message,
2093
+ originalTraceId
2002
2094
  );
2003
2095
  }
2004
- const span = await httpClient.getExternalSpan(originalSpanId);
2096
+ const span = await httpClient.getExternalSpan(originalSpanId, {
2097
+ view: "replay"
2098
+ });
2005
2099
  const spanData = span.rawData?.span_data ?? {};
2006
2100
  inputs = deserializeInputs(spanData);
2007
2101
  originalOutput = deserializeOutput(spanData);
@@ -2021,7 +2115,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2021
2115
  if (needTree) {
2022
2116
  try {
2023
2117
  const treeResponse = await httpClient.getSpanTree(originalSpanId, {
2024
- includeOutputs
2118
+ includeOutputs,
2119
+ includeRootOutput: false
2025
2120
  });
2026
2121
  if (treeResponse.root) {
2027
2122
  mockTree = buildMockTree(treeResponse.root);
@@ -2043,7 +2138,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2043
2138
  const fetchSpanOutput = mockTree && !includeOutputs ? (externalSpanId) => {
2044
2139
  let pending = outputCache.get(externalSpanId);
2045
2140
  if (!pending) {
2046
- pending = httpClient.getExternalSpan(externalSpanId).then(
2141
+ pending = httpClient.getExternalSpan(externalSpanId, { view: "replay" }).then(
2047
2142
  (s) => deserializeOutput(
2048
2143
  s.rawData?.span_data ?? {}
2049
2144
  )
@@ -2052,25 +2147,31 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2052
2147
  }
2053
2148
  return pending;
2054
2149
  } : void 0;
2055
- const maybePromise = runWithReplayContext(
2056
- {
2057
- testRunId,
2058
- traceId: replayedTraceId,
2059
- inputSourceSpanId: span.id,
2060
- inputSourceTraceId: span.externalTraceId,
2061
- sourceBitfabTraceId: originalTraceId,
2062
- mockTree,
2063
- callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
2064
- mockStrategy,
2065
- mockOverrides: hasOverrides ? resolvedOverrides : void 0,
2066
- fetchSpanOutput,
2067
- dbBranchLease: lease
2068
- },
2069
- () => fn(...inputs)
2070
- );
2071
- result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
2150
+ try {
2151
+ const maybePromise = runWithReplayContext(
2152
+ {
2153
+ testRunId,
2154
+ traceId: replayedTraceId,
2155
+ inputSourceSpanId: span.id,
2156
+ inputSourceTraceId: span.externalTraceId,
2157
+ sourceBitfabTraceId: originalTraceId,
2158
+ mockTree,
2159
+ callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
2160
+ mockStrategy,
2161
+ mockOverrides: hasOverrides ? resolvedOverrides : void 0,
2162
+ fetchSpanOutput,
2163
+ dbBranchLease: lease
2164
+ },
2165
+ () => fn(...inputs)
2166
+ );
2167
+ result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
2168
+ } catch (e) {
2169
+ traceError = e;
2170
+ error = errorMessage(e);
2171
+ }
2072
2172
  } catch (e) {
2073
- error = e instanceof Error ? e.message : String(e);
2173
+ replayError = e;
2174
+ error = replayItemErrorMessage(e);
2074
2175
  } finally {
2075
2176
  if (lease) {
2076
2177
  try {
@@ -2099,6 +2200,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
2099
2200
  result,
2100
2201
  originalOutput,
2101
2202
  error,
2203
+ traceError,
2204
+ replayError,
2102
2205
  durationMs: serverItem.durationMs ?? null,
2103
2206
  // Filled in by replay() from the complete-replay response once the
2104
2207
  // replay traces are persisted and their spans aggregated server-side.
@@ -2223,6 +2326,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2223
2326
  );
2224
2327
  const mockStrategy = options?.mock ?? "marked";
2225
2328
  const maxConcurrency = options?.maxConcurrency ?? 10;
2329
+ const fullTestRunUrl = `${serviceUrl}${testRunUrl}`;
2226
2330
  const resolvedOverrides = [
2227
2331
  ...normalizeMockOverrides(options?.mockOverride),
2228
2332
  ...registeredOverrides
@@ -2279,6 +2383,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2279
2383
  result: item.result,
2280
2384
  originalOutput: item.originalOutput,
2281
2385
  error: item.error,
2386
+ traceError: item.traceError,
2387
+ replayError: item.replayError,
2282
2388
  durationMs: item.durationMs,
2283
2389
  tokens: item.tokens,
2284
2390
  model: item.model,
@@ -2289,8 +2395,18 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2289
2395
  }
2290
2396
  } : void 0
2291
2397
  );
2292
- await waitForReplayPersistence(httpClient, testRunId, replayedTraceIds);
2293
- const completeResult = await httpClient.completeReplay(testRunId);
2398
+ await preserveReplayFailure(
2399
+ () => waitForReplayPersistence(httpClient, testRunId, replayedTraceIds),
2400
+ resultItems,
2401
+ testRunId,
2402
+ fullTestRunUrl
2403
+ );
2404
+ const completeResult = await preserveReplayFailure(
2405
+ () => httpClient.completeReplay(testRunId),
2406
+ resultItems,
2407
+ testRunId,
2408
+ fullTestRunUrl
2409
+ );
2294
2410
  const serverTraceIds = completeResult.traceIds;
2295
2411
  const replayTokens = completeResult.tokens;
2296
2412
  if (serverTraceIds !== void 0) {
@@ -2313,9 +2429,16 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2313
2429
  }
2314
2430
  if (completedCount > 0 && missing.length === completedCount) {
2315
2431
  const serverCount = completeResult.traceCount !== void 0 ? ` The server persisted ${completeResult.traceCount} trace(s) for this run.` : "";
2316
- throw new BitfabError(
2432
+ const cause = new BitfabError(
2317
2433
  `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.`
2318
2434
  );
2435
+ throw new ReplayError(
2436
+ cause.message,
2437
+ resultItems,
2438
+ testRunId,
2439
+ fullTestRunUrl,
2440
+ cause
2441
+ );
2319
2442
  }
2320
2443
  if (missing.length > 0) {
2321
2444
  try {
@@ -2329,7 +2452,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2329
2452
  const result = {
2330
2453
  items: resultItems,
2331
2454
  testRunId,
2332
- testRunUrl: `${serviceUrl}${testRunUrl}`
2455
+ testRunUrl: fullTestRunUrl
2333
2456
  };
2334
2457
  await writeReplayResultFile(result);
2335
2458
  try {
@@ -2357,7 +2480,7 @@ async function writeReplayResultFile(result) {
2357
2480
  import("fs/promises")
2358
2481
  ]);
2359
2482
  await mkdir(dirname(resultPath), { recursive: true });
2360
- await writeFile(resultPath, `${JSON.stringify(result, null, 2)}
2483
+ await writeFile(resultPath, `${serializeReplayResult(result)}
2361
2484
  `);
2362
2485
  } catch (err) {
2363
2486
  try {
@@ -2393,6 +2516,9 @@ export {
2393
2516
  resolveMockValue,
2394
2517
  BITFAB_PROGRESS_PREFIX,
2395
2518
  reportReplayProgress,
2519
+ ReplayError,
2520
+ DbBranchReplayError,
2521
+ serializeReplayResult,
2396
2522
  replay
2397
2523
  };
2398
- //# sourceMappingURL=chunk-6ZBZBR5K.js.map
2524
+ //# sourceMappingURL=chunk-M2GISXGV.js.map