bitfab 0.28.2 → 0.28.4

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.
@@ -55,7 +55,7 @@ function registerAsyncLocalStorageClass(cls) {
55
55
  function assertAsyncStorageRegistered() {
56
56
  if (!AsyncLocalStorageClass) {
57
57
  console.warn(
58
- "Bitfab: AsyncLocalStorage not available \u2014 nested span context will not propagate."
58
+ "Bitfab: AsyncLocalStorage not available - nested span context will not propagate."
59
59
  );
60
60
  }
61
61
  }
@@ -323,10 +323,25 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
323
323
  }
324
324
  let mockTree;
325
325
  if (mockStrategy === "all" || mockStrategy === "marked") {
326
- const treeResponse = await httpClient.getSpanTree(
327
- serverItem.externalSpanId
328
- );
329
- mockTree = buildMockTree(treeResponse.root);
326
+ try {
327
+ const treeResponse = await httpClient.getSpanTree(
328
+ serverItem.externalSpanId
329
+ );
330
+ if (treeResponse.root) {
331
+ mockTree = buildMockTree(treeResponse.root);
332
+ } else if (mockStrategy === "all") {
333
+ throw new BitfabError(
334
+ `Replay mock strategy "all" requires a span tree root for source span ${serverItem.externalSpanId}.`
335
+ );
336
+ } else {
337
+ mockTree = void 0;
338
+ }
339
+ } catch (e) {
340
+ if (mockStrategy === "all") {
341
+ throw e;
342
+ }
343
+ mockTree = void 0;
344
+ }
330
345
  }
331
346
  const maybePromise = runWithReplayContext(
332
347
  {
@@ -432,7 +447,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
432
447
  options?.experimentGroupId,
433
448
  options?.datasetId
434
449
  );
435
- const mockStrategy = options?.mock ?? "none";
450
+ const mockStrategy = options?.mock ?? "marked";
436
451
  const maxConcurrency = options?.maxConcurrency ?? 10;
437
452
  const tasks = serverItems.map(
438
453
  (serverItem) => () => processItem(
@@ -557,4 +572,4 @@ export {
557
572
  reportReplayProgress,
558
573
  replay
559
574
  };
560
- //# sourceMappingURL=chunk-3PCOUZZP.js.map
575
+ //# sourceMappingURL=chunk-SAGZ674W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/warnOnce.ts","../src/randomUuid.ts","../src/asyncStorage.ts","../src/replayContext.ts","../src/serialize.ts","../src/replay.ts"],"sourcesContent":["/**\n * Shared error type for Bitfab SDK runtime errors. Lives in its own\n * module to avoid import cycles between `http.ts` and modules that need\n * to throw structured errors (e.g. `dbSnapshot.ts` validation).\n */\n\nexport class BitfabError extends Error {\n constructor(\n message: string,\n public readonly url?: string,\n ) {\n super(message)\n this.name = \"BitfabError\"\n }\n}\n","/**\n * Emit a `console.warn` at most once per distinct `key` for the life of the\n * process.\n *\n * The SDK must NEVER crash a host app, so every failure on the user's path\n * degrades silently (a span is dropped, a call runs untraced, a payload is\n * stubbed). Silent is safe but undebuggable: a user who suddenly has no traces,\n * or sees `<unserializable>` in a span, has no signal as to why. A one-time\n * warning per distinct issue restores that signal without spamming the console\n * from a hot path.\n *\n * Keys should identify the specific degradation (e.g. include the traced\n * function key) so each distinct issue warns once, not just the first one seen.\n */\nconst warned = new Set<string>()\n\nexport function warnOnce(key: string, message: string): void {\n if (warned.has(key)) {\n return\n }\n warned.add(key)\n try {\n console.warn(`[bitfab] ${message}`)\n } catch {\n // Logging must never crash the host app (e.g. a closed/replaced console).\n }\n}\n\n/** Test-only: clear the dedup set so a warning can fire again. */\nexport function _resetWarnOnce(): void {\n warned.clear()\n}\n","/**\n * Generate a UUID v4 without assuming a global `crypto`.\n *\n * `crypto.randomUUID()` is the fast path and exists in every mainstream modern\n * runtime (Node >= 19, all browsers, Deno, Vercel edge, Cloudflare Workers).\n * But the SDK must NEVER crash a host app, and a few runtimes it can legitimately\n * run in lack a global `crypto` or `crypto.randomUUID` (Node < 19 without a\n * polyfill, older React Native, some sandboxes). There, an unguarded\n * `crypto.randomUUID()` throws on the user's call path. This helper falls back\n * to a `Math.random()`-based v4 string instead.\n *\n * The fallback is NOT cryptographically secure. That is fine: trace and span\n * IDs only need to be unique enough to correlate the spans of one trace; they\n * are never security-sensitive.\n */\nimport { warnOnce } from \"./warnOnce.js\"\n\nexport function randomUuid(): string {\n const globalCrypto = (\n globalThis as { crypto?: { randomUUID?: () => string } }\n ).crypto\n if (typeof globalCrypto?.randomUUID === \"function\") {\n try {\n return globalCrypto.randomUUID()\n } catch {\n // Fall through to the manual generator below.\n }\n }\n warnOnce(\n \"crypto-unavailable\",\n \"global crypto.randomUUID is unavailable; using a non-cryptographic fallback for trace/span ids. Tracing works normally (ids are correlation-only, not security-sensitive).\",\n )\n return fallbackUuidV4()\n}\n\n/**\n * RFC 4122 version 4 layout filled from `Math.random()`. Used only when a usable\n * global `crypto.randomUUID` is unavailable.\n */\nfunction fallbackUuidV4(): string {\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (char) => {\n const rand = (Math.random() * 16) | 0\n const value = char === \"x\" ? rand : (rand & 0x3) | 0x8\n return value.toString(16)\n })\n}\n","/**\n * Shared AsyncLocalStorage loader.\n *\n * Provides two ways to initialize AsyncLocalStorage:\n *\n * 1. **Synchronous registration** (preferred for Node.js):\n * `asyncStorageNode.ts` calls `registerAsyncLocalStorageClass()` at module\n * evaluation time, so the class is available immediately - no async gap.\n * The `node.ts` entry point imports it before anything else.\n *\n * 2. **Async dynamic import** (fallback for the default entry point):\n * Loads `node:async_hooks` via a bundler-safe dynamic import. This is used\n * by the default `index.ts` entry point so the SDK works in browsers\n * (where the import silently fails) and in Node.js when imported via the\n * default entry point.\n *\n * ## Why the dynamic import looks like this\n *\n * We need to handle three environments:\n *\n * 1. **Pure Node.js** - `import(\"node:async_hooks\")` works natively.\n * 2. **Webpack/Turbopack (Next.js server)** - The bundler processes\n * `import()` calls at build time. The `webpackIgnore` magic comment tells\n * webpack (and turbopack) to emit a native `import()` call instead of\n * trying to resolve it, so Node.js handles it at runtime.\n * 3. **Browsers / Edge** - The `process.versions?.node` guard prevents\n * execution entirely. If it somehow runs, `.catch(() => {})` swallows\n * the failure.\n */\n\nexport interface AsyncLocalStorageLike<T> {\n getStore(): T | undefined\n run<R>(store: T, fn: () => R): R\n}\n\nlet AsyncLocalStorageClass: (new () => AsyncLocalStorageLike<unknown>) | null =\n null\nlet initDone = false\n\n/**\n * Register the AsyncLocalStorage class synchronously.\n *\n * Called by `asyncStorageNode.ts` at module evaluation time so the class\n * is available before any span is created - no async gap, no race condition.\n *\n * Safe to call multiple times; subsequent calls are no-ops.\n */\nexport function registerAsyncLocalStorageClass(\n cls: new () => AsyncLocalStorageLike<unknown>,\n): void {\n if (!AsyncLocalStorageClass) {\n AsyncLocalStorageClass = cls\n }\n initDone = true\n}\n\n/**\n * Assert that AsyncLocalStorage was registered successfully.\n *\n * Called by `node.ts` after importing `asyncStorageNode.ts` to catch\n * import-order bugs at startup rather than silently degrading to the\n * browser fallback (flat spans with no nesting).\n *\n * This should ONLY be called from the Node.js entry point where we\n * know `node:async_hooks` must be available.\n */\nexport function assertAsyncStorageRegistered(): void {\n if (!AsyncLocalStorageClass) {\n console.warn(\n \"Bitfab: AsyncLocalStorage not available - nested span context will not propagate.\",\n )\n }\n}\n\nexport const asyncStorageReady: Promise<void> = (\n typeof process !== \"undefined\" && process.versions?.node\n ? // The join trick hides \"node:async_hooks\" from static analysis so\n // bundlers that ban Node.js built-ins don't fail at build time.\n // webpackIgnore tells webpack/turbopack to emit a native import()\n // so Node.js can resolve the module at runtime.\n import(\n /* webpackIgnore: true */\n [\"node\", \"async_hooks\"].join(\":\")\n )\n .then(\n (mod: {\n AsyncLocalStorage: new () => AsyncLocalStorageLike<unknown>\n }) => {\n registerAsyncLocalStorageClass(mod.AsyncLocalStorage)\n },\n )\n .catch(() => {})\n : Promise.resolve()\n).then(() => {\n initDone = true\n})\n\nexport function isAsyncStorageInitDone(): boolean {\n return initDone\n}\n\nexport function createAsyncLocalStorage<T>(): AsyncLocalStorageLike<T> | null {\n return AsyncLocalStorageClass\n ? (new AsyncLocalStorageClass() as AsyncLocalStorageLike<T>)\n : null\n}\n","/**\n * Replay context propagation via AsyncLocalStorage.\n *\n * When set, the withSpan wrapper injects testRunId into the span payload\n * so that new spans created during replay are linked to the test run.\n * Optionally carries a mock tree so child spans can return historical\n * outputs instead of executing.\n */\n\nimport {\n type AsyncLocalStorageLike,\n asyncStorageReady,\n createAsyncLocalStorage,\n} from \"./asyncStorage.js\"\n\n/** A single span entry in the mock tree with its historical output. */\nexport interface MockSpan {\n sourceSpanId: string\n output: unknown\n outputMeta?: unknown\n}\n\n/**\n * Per-item DB branch resolved by the Bitfab service from the source\n * trace's `dbSnapshotRef`. Carried on the replay context so that\n * customer code reads `databaseUrl` through `ReplayEnvironment`, and so\n * the process-isolated replay runner can materialize it into a `.env`\n * overlay file before customer code initializes its DB client.\n *\n * `neonBranchId` is the literal Neon branch id; passing it to\n * `releaseDbBranchLease` deletes that branch.\n */\nexport interface DbBranchLease {\n neonBranchId: string\n /** Env var name the customer's app reads, e.g. \"DATABASE_URL\". */\n envKey: string\n databaseUrl: string\n expiresAt: string\n /**\n * The instant the branch was pinned to (the source trace's wall clock).\n * Echoed back in `db_snapshot_usage` on the replayed trace's completion.\n */\n snapshotTimestamp?: string\n providerConsoleUrl?: string\n readOnly?: boolean\n}\n\n/**\n * Pre-built lookup table of historical span outputs.\n * Keys are `${traceFunctionKey}:${spanName}:${callIndex}` so that repeated\n * calls with the same (key, name) are matched by call order, but spans\n * sharing only the traceFunctionKey (different name) do not collide.\n */\nexport interface MockTree {\n spans: Map<string, MockSpan>\n}\n\nexport interface ReplayContext {\n testRunId: string\n traceId?: string\n inputSourceSpanId?: string\n /**\n * External trace ID from `external_traces.id`. Used for span-chain\n * lookup against the source platform's trace tree (Braintrust, etc.).\n * NOT the same as the Bitfab `traceId` - see `sourceBitfabTraceId`.\n */\n inputSourceTraceId?: string\n /**\n * The Bitfab `traces.id` of the historical trace that produced this\n * replay item's input. This is what customer-facing surfaces (e.g.\n * `ReplayEnvironment.traceId`) should expose, since it's the ID the\n * customer sees in the Bitfab dashboard.\n */\n sourceBitfabTraceId?: string\n mockTree?: MockTree\n callCounters?: Map<string, number>\n mockStrategy?: \"none\" | \"all\" | \"marked\"\n dbBranchLease?: DbBranchLease\n /**\n * Set to true by `ReplayEnvironment` the first time customer code\n * actually obtains `databaseUrl` for this item (via the getter or\n * `snapshot()`). Reported on the trace completion inside\n * `db_snapshot_usage` so the server can distinguish \"branch was\n * provisioned and exposed\" from \"branch URL was actually consumed\".\n * Any future consumption path that hands the URL to customer code by\n * other means (e.g. a process-isolated runner writing an env overlay)\n * must also set this.\n */\n dbSnapshotAccessed?: boolean\n /**\n * Collector for the replay item's trace-persistence work. When present,\n * the root span's send path pushes a promise that resolves only after\n * every span upload AND the trace completion have been sent (registered\n * synchronously at send time, so the replay runner can await it after\n * the wrapped fn resolves). This is what lets replay guarantee traces\n * are persisted server-side before `completeReplay` builds the\n * trace-ID mapping. Absent outside replay, where sends stay\n * fire-and-forget.\n */\n pendingPersistence?: Promise<unknown>[]\n}\n\nlet replayContextStorage: AsyncLocalStorageLike<ReplayContext | null> | null =\n null\n\nexport const replayContextReady: Promise<void> = asyncStorageReady.then(() => {\n replayContextStorage = createAsyncLocalStorage<ReplayContext | null>()\n})\n\n/** Get the current replay context, if any. */\nexport function getReplayContext(): ReplayContext | null {\n return replayContextStorage?.getStore() ?? null\n}\n\n/** Run a function within a replay context. */\nexport function runWithReplayContext<T>(ctx: ReplayContext, fn: () => T): T {\n if (replayContextStorage) {\n return replayContextStorage.run(ctx, fn)\n }\n return fn()\n}\n","/**\n * Serialization utilities for Bitfab SDK.\n *\n * This module provides serialization with type metadata preservation,\n * using superjson for handling special JavaScript types like Date, Map,\n * Set, BigInt, undefined, etc.\n */\n\nimport superjson from \"superjson\"\nimport { warnOnce } from \"./warnOnce.js\"\n\n/**\n * Serialized value with JSON data and optional superjson meta for type preservation.\n *\n * The json field contains the JSON-serializable data.\n * The meta field (if present) contains superjson type information for deserializing\n * special types like Date, Map, Set, BigInt, etc.\n */\nexport interface SerializedValue {\n json: unknown\n meta?: unknown\n}\n\n// Cap on serialized payload size. superjson can succeed on values like SDK\n// client instances (OpenAI, etc.) and produce hundreds of KB to MB of useless\n// internal state. Anything beyond this is replaced with a stub so the span\n// still ships and the trace isn't dropped server-side.\nconst MAX_SERIALIZED_BYTES = 512_000\n\n// A higher cap for the framework-capture path (toJsonSafe). Agent/graph states\n// (LangGraph, Claude Agent SDK) are legitimately larger than a single\n// function's args/return, so this ceiling preserves real detail while still\n// bounding the payload so the wire-side JSON.stringify can't blow up or get the\n// span rejected server-side.\nconst MAX_FRAMEWORK_SERIALIZED_BYTES = 2_000_000\n\nfunction describeValue(value: unknown): string {\n try {\n const ctorName = (value as { constructor?: { name?: string } })?.constructor\n ?.name\n if (ctorName && ctorName !== \"Object\") {\n return ctorName\n }\n } catch {\n // Property access on `value` can throw (Proxy, poisoned getter).\n }\n return typeof value\n}\n\nfunction unserializableStub(value: unknown, reason: string): SerializedValue {\n // Normalize the byte count out of the key so a too_large warning dedups\n // across differently-sized payloads instead of warning once per size.\n warnOnce(\n `serialize:${reason.replace(/\\d+/g, \"N\")}`,\n `a value could not be fully serialized for a span (${reason}); it was replaced with a placeholder. The span still ships, but its captured input/output is incomplete.`,\n )\n let summary: string\n try {\n summary = `<unserializable: ${describeValue(value)} (${reason})>`\n } catch {\n summary = `<unserializable (${reason})>`\n }\n return { json: summary }\n}\n\n/**\n * Serialize a value using superjson for trace storage.\n *\n * Handles arbitrary JavaScript values including:\n * - Date, RegExp, Error\n * - Map, Set\n * - BigInt\n * - undefined (in objects/arrays)\n * - Circular references\n *\n * Guarantees:\n * - Never throws. Pathological inputs (SDK clients, proxies, poisoned\n * getters, circular graphs that defeat superjson) return a stub string.\n * - Never returns a payload larger than MAX_SERIALIZED_BYTES; oversized\n * inputs are replaced with a stub. Without this the wire-side\n * `JSON.stringify` in http.ts can produce a request that times out or\n * gets rejected, leaving a trace with zero spans.\n *\n * @param value - Any JavaScript value to serialize\n * @returns SerializedValue with 'json' field containing the data.\n * If type metadata is needed for reconstruction, includes 'meta' field.\n *\n * @example\n * ```typescript\n * const result = serializeValue(new Date('2024-01-15T10:30:00Z'))\n * // result.json contains the ISO string\n * // result.meta contains type info for Date reconstruction\n * ```\n */\nexport function serializeValue(value: unknown): SerializedValue {\n try {\n const { json, meta } = superjson.serialize(value)\n\n let size: number\n try {\n size = JSON.stringify(json).length\n } catch {\n return unserializableStub(value, \"stringify_failed_after_superjson\")\n }\n if (size > MAX_SERIALIZED_BYTES) {\n return unserializableStub(value, `too_large_${size}_bytes`)\n }\n\n return meta ? { json, meta } : { json }\n } catch {\n try {\n return { json: JSON.parse(JSON.stringify(value)) }\n } catch {\n return unserializableStub(value, \"json_stringify_failed\")\n }\n }\n}\n\n/**\n * Deserialize a value that was serialized with serializeValue.\n *\n * @param serialized - A SerializedValue object with 'json' and optional 'meta'\n * @returns The reconstructed JavaScript value\n *\n * @example\n * ```typescript\n * const serialized = serializeValue(new Date('2024-01-15'))\n * const date = deserializeValue(serialized)\n * // date is a Date object\n * ```\n */\nexport function deserializeValue(serialized: SerializedValue): unknown {\n if (serialized.meta === undefined) {\n // No metadata, return as-is\n return serialized.json\n }\n\n // Use superjson to deserialize with type reconstruction\n // Cast json to the expected superjson type\n type SuperJSONResult = Parameters<typeof superjson.deserialize>[0]\n return superjson.deserialize({\n json: serialized.json as SuperJSONResult[\"json\"],\n meta: serialized.meta as SuperJSONResult[\"meta\"],\n })\n}\n\nconst MAX_SAFE_DEPTH = 6\n\n/**\n * Convert any value to JSON-safe primitives, never throwing.\n *\n * Produces plain objects/arrays/scalars, recursing through `toJSON()` and\n * own-enumerable properties so no raw non-serializable value (a class, a\n * BigInt-bearing object) survives into a span payload. Cycles collapse to a\n * `<cycle ...>` marker; depth is capped.\n *\n * This is the single shared \"safe serialize\" used by the framework\n * integrations that capture raw objects (LangGraph, Claude Agent SDK). Keeping\n * the recurse-the-dump logic here, in one place, is what stops a new\n * integration from reintroducing the \"dump without recursing\" bug - see\n * `serializationInvariant.test.ts`.\n */\nexport function toJsonSafe(value: unknown): unknown {\n return toJsonSafeReport(value).safe\n}\n\n/**\n * Like {@link toJsonSafe}, but also reports what could not be faithfully\n * captured.\n *\n * Returns `{ safe, dropped }` where `dropped` lists the type name behind every\n * placeholder the walker had to emit: a cycle, a max-depth cut, an oversized\n * payload, or a value that could only be stringified (a function/symbol) or\n * stubbed after a throw. A non-empty `dropped` means the captured input/output\n * is lossy. Framework handlers carry it to the send boundary so a degraded\n * capture is marked non-replayable (`serialization_degraded`) instead of being\n * shipped as if it round-trips - mirrors the Python SDK's `to_json_safe_report`\n * + `finalize_span_payload`.\n */\nexport function toJsonSafeReport(value: unknown): {\n safe: unknown\n dropped: string[]\n} {\n const dropped: string[] = []\n const safe = toJsonSafeInner(value, 0, new WeakSet(), dropped)\n // Cap output size for parity with serializeValue. A multi-MB framework\n // payload (a large LangGraph state, a long message history) would otherwise\n // be JSON.stringify'd synchronously on the user's thread in http.ts and may\n // be rejected server-side, leaving a trace with zero spans. Stub it instead\n // so the span still ships. toJsonSafeInner produces only plain\n // objects/arrays/scalars/strings, so JSON.stringify here cannot throw; the\n // try is belt-and-suspenders.\n try {\n const size = JSON.stringify(safe)?.length ?? 0\n if (size > MAX_FRAMEWORK_SERIALIZED_BYTES) {\n warnOnce(\n \"toJsonSafe:too_large\",\n `a framework payload exceeded ${MAX_FRAMEWORK_SERIALIZED_BYTES} bytes and was replaced with a placeholder so the span still ships. The captured state for this span is incomplete.`,\n )\n // Keep any drops already accumulated by the walk (cycles, functions,\n // depth cuts); a payload can be both lossy AND oversized, and the\n // non-replayable marking needs the real types, not just the size stub.\n return {\n safe: `<unserializable: too_large_${size}_bytes>`,\n dropped: [...dropped, `too_large_${size}_bytes`],\n }\n }\n } catch {\n // Keep the recursed value; the http-layer sanitizer is the final backstop.\n }\n return { safe, dropped }\n}\n\nfunction toJsonSafeInner(\n value: unknown,\n depth: number,\n seen: WeakSet<object>,\n dropped: string[],\n): unknown {\n if (value === null || value === undefined) {\n return value\n }\n if (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return value\n }\n\n const className =\n (value as { constructor?: { name?: string } })?.constructor?.name ??\n typeof value\n if (depth > MAX_SAFE_DEPTH) {\n dropped.push(className)\n return `<${className}>`\n }\n\n // Non-object composites (bigint, function, symbol) stringify directly. A\n // bigint stringifies faithfully; a function/symbol becomes a lossy summary,\n // so those are reported as dropped.\n if (typeof value !== \"object\") {\n if (typeof value === \"function\" || typeof value === \"symbol\") {\n dropped.push(className)\n }\n try {\n return String(value)\n } catch {\n dropped.push(className)\n return `<${className}>`\n }\n }\n\n if (seen.has(value as object)) {\n dropped.push(className)\n return `<cycle ${className}>`\n }\n seen.add(value as object)\n\n let result: unknown\n if (Array.isArray(value)) {\n result = value.map((item) =>\n toJsonSafeInner(item, depth + 1, seen, dropped),\n )\n } else if (typeof (value as Record<string, unknown>).toJSON === \"function\") {\n // Recurse toJSON() output: it can still hold non-serializable values (e.g.\n // a LangChain tool whose schema is a class) that would otherwise survive\n // into the span payload and crash the wire-side JSON.stringify.\n try {\n result = toJsonSafeInner(\n (value as { toJSON(): unknown }).toJSON(),\n depth + 1,\n seen,\n dropped,\n )\n } catch {\n dropped.push(className)\n result = `<${className}>`\n }\n } else {\n try {\n const obj: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value)) {\n if (!k.startsWith(\"_\")) {\n obj[k] = toJsonSafeInner(v, depth + 1, seen, dropped)\n }\n }\n result = obj\n } catch {\n dropped.push(className)\n result = `<${className}>`\n }\n }\n\n // Backtrack: keep only ancestors on the current path in `seen`, so a shared\n // (DAG) reference under sibling keys is serialized again rather than stubbed\n // as a false cycle. Real cycles (an ancestor referencing itself) are still\n // caught above.\n seen.delete(value as object)\n return result\n}\n","/**\n * Replay historical traces through a function and create a test run.\n *\n * The replay flow has three phases:\n * 1. Start: fetches historical traces from the server and creates a test run\n * 2. Execute: re-runs each trace's inputs through the provided function locally\n * 3. Complete: marks the test run as completed on the server\n */\n\nimport type { DbSnapshotRef } from \"./dbSnapshot.js\"\nimport { BitfabError } from \"./errors.js\"\nimport type {\n CodeChangeFile,\n HttpClient,\n SpanTreeNode,\n TokenUsage,\n} from \"./http.js\"\nimport { randomUuid } from \"./randomUuid.js\"\nimport type { DbBranchLease, MockTree } from \"./replayContext.js\"\nimport { replayContextReady, runWithReplayContext } from \"./replayContext.js\"\nimport type { ReplayEnvironment } from \"./replayEnvironment.js\"\nimport { deserializeValue } from \"./serialize.js\"\n\nexport type MockStrategy = \"none\" | \"all\" | \"marked\"\n\nexport interface ReplayOptions {\n /**\n * Maximum number of traces to replay (1-100, default 5). Ignored when\n * `traceIds` is passed (with a warning): an explicit ID list already\n * determines how many traces replay.\n */\n limit?: number\n /** Optional list of specific trace IDs to replay (max 100). */\n traceIds?: string[]\n /** Optional display name for the resulting experiment/test run. */\n name?: string\n /** Maximum number of items to process in parallel. Set to 1 for sequential. Default 10. */\n maxConcurrency?: number\n /**\n * Description of the code change being tested in this replay. Stored on\n * the resulting experiment so the change can be reviewed alongside results.\n */\n codeChangeDescription?: string\n /**\n * Files edited as part of this code change. Each entry holds the file path\n * and the full `before`/`after` contents. The agent reads each file before\n * and after editing and passes the two strings. Use `\"\"` for newly created\n * files (`before`) or deleted files (`after`).\n */\n codeChangeFiles?: CodeChangeFile[]\n /**\n * Mock strategy for child spans during replay.\n * - \"marked\": only spans tagged with { mockOnReplay: true } in SpanOptions are mocked (default)\n * - \"none\": everything runs real code\n * - \"all\": every child withSpan returns historical output\n */\n mock?: MockStrategy\n /**\n * Per-trace environment. When the source trace carries a DB branching\n * snapshot, the SDK populates `environment.databaseUrl` before invoking\n * `fn` for that item and resets it after. Customer code reads from the\n * environment to pick up the per-trace branch URL.\n */\n environment?: ReplayEnvironment\n /** Group ID to associate this replay with an experiment group for live streaming in Studio. */\n experimentGroupId?: string\n /**\n * Dataset this replay runs against. When set, the resulting experiment is\n * durably attributed to the dataset (stored on the test run), so it appears\n * under the dataset's experiments even if the trace lineage can't be\n * reconstructed. Validated server-side against the org.\n */\n datasetId?: string\n /**\n * Reshape recorded inputs before they are spread into `fn`.\n *\n * Replay pulls each trace's inputs exactly as they were captured against the\n * function's signature AT TRACE TIME. When the function's shape has since\n * changed (params renamed, reordered, collapsed into an options object, etc.),\n * the recorded inputs no longer line up and `fn(...inputs)` throws. This hook\n * lets a caller map the recorded inputs onto the current signature so replay\n * can still run.\n *\n * Receives the deserialized recorded inputs and a per-trace {@link AdaptContext}\n * (so a table-driven adapter can look up the adapted inputs by `traceId`), and\n * returns the array actually spread into `fn`. The returned array is also what\n * `ReplayItem.input` reports, so the experiment shows what was really run.\n *\n * Runs per item, inside the same try/catch as `fn`: if the adapter throws, the\n * failure is surfaced on that item's `error` rather than crashing the run.\n * Omit it to spread the recorded inputs unchanged.\n */\n adaptInputs?: (inputs: unknown[], ctx: AdaptContext) => unknown[]\n /**\n * Called once per item as it finishes, in completion order (not input\n * order), with running totals for the whole run. Use it to render replay\n * progress, for example a terminal progress bar. Replay does not know\n * pass/fail at this point (verdicts are assigned later), so the totals only\n * distinguish items whose function ran (`succeeded`) from items that threw\n * (`errored`).\n *\n * Invoked synchronously right after each item settles, so keep it cheap. A\n * throwing callback never crashes the run: the error is swallowed so progress\n * UI can't break replay.\n */\n onProgress?: (progress: ReplayProgress) => void\n}\n\n/** Running totals reported to {@link ReplayOptions.onProgress} as replay proceeds. */\nexport interface ReplayProgress {\n /** Test run ID created for this replay. */\n testRunId?: string\n /** Items that have finished so far, whether they succeeded or errored. */\n completed: number\n /** Total number of items in this replay run. */\n total: number\n /** Of the completed items, how many ran the function without throwing. */\n succeeded: number\n /** Of the completed items, how many threw (their `item.error` is set). */\n errored: number\n /**\n * The single item that just settled to produce this event. `traceId` is the\n * source (historical) trace that was replayed (so a UI can identify or link\n * it); `error` is its replay error, or null when it ran ok; `durationMs` is how\n * long this one trace took to replay. Lets a progress UI show per-trace\n * pass/fail and timing as the run streams, without waiting for the full\n * {@link ReplayResult}.\n */\n item?: {\n /** Source (historical) trace ID being replayed. */\n traceId: string | null\n /** Local SDK replay trace ID, before the server maps it to a row ID. */\n replayTraceId?: string | null\n /** Deserialized inputs from the original trace. */\n input?: unknown[]\n /** The result returned by the replayed function, or undefined on error. */\n result?: unknown\n /** The original output from the historical trace. */\n originalOutput?: unknown\n error: string | null\n durationMs: number | null\n tokens?: TokenUsage | null\n model?: string | null\n dbSnapshotRef?: DbSnapshotRef | null\n }\n}\n\n/**\n * Wire prefix the Bitfab plugin scans for. Each line {@link reportReplayProgress}\n * writes is this prefix followed by the JSON of the running totals (plus the\n * settled `item`). The plugin polls these lines to report live progress while a\n * replay runs in the background; keep the SDK emitter and the plugin parser in\n * sync.\n */\nexport const BITFAB_PROGRESS_PREFIX = \"@@bitfab:progress \"\n\n/**\n * A ready-made {@link ReplayOptions.onProgress} callback for replay scripts.\n * Pass it straight in:\n *\n * ```ts\n * await bitfab.replay(\"my-fn\", fn, { limit, onProgress: reportReplayProgress })\n * ```\n *\n * It writes one `@@bitfab:progress` line per trace to stderr, which the Bitfab\n * plugin polls to report live progress while the replay runs in the background,\n * so scripts never hand-format the wire protocol.\n * stdout is left untouched for the ReplayResult JSON. Outside Node (no\n * `process.stderr`, e.g. a browser) it is a no-op, and a write failure is\n * swallowed so progress can never crash a run.\n */\nexport function reportReplayProgress(progress: ReplayProgress): void {\n const stderr = typeof process !== \"undefined\" ? process.stderr : undefined\n if (!stderr) {\n return\n }\n try {\n stderr.write(`${BITFAB_PROGRESS_PREFIX}${JSON.stringify(progress)}\\n`)\n } catch {\n // A progress reporter must never crash the replay run.\n }\n}\n\n/** Per-trace context passed to {@link ReplayOptions.adaptInputs}. */\nexport interface AdaptContext {\n /** Bitfab trace ID of the historical trace being replayed. */\n traceId: string\n /** External span ID the recorded inputs were read from. */\n sourceSpanId: string\n}\n\n/**\n * The shape an adapter module must export as `adaptInputs`.\n *\n * Author an adapter in its own file (e.g. `scripts/replay-adapters/<name>.ts`),\n * import it in your replay script, and pass it to `replay({ adaptInputs })`:\n *\n * ```ts\n * import type { AdaptInputsFn } from \"@bitfab/sdk\"\n * export const adaptInputs: AdaptInputsFn = (inputs, ctx) => [reshape(inputs)]\n * ```\n */\nexport type AdaptInputsFn = (inputs: unknown[], ctx: AdaptContext) => unknown[]\n\nexport interface ReplayItem<T> {\n /** Trace ID of the new trace created during replay. */\n traceId: string | null\n /** Deserialized inputs from the original trace. */\n input: unknown[]\n /** The result returned by the function during replay, or undefined on error. */\n result: T | undefined\n /** The original output from the historical trace. */\n originalOutput: unknown\n /** Error message if the function threw, or null on success. */\n error: string | null\n /** Original trace duration in milliseconds, or null if timestamps are missing. */\n durationMs: number | null\n /**\n * Token usage from the REPLAYED run (this item's new execution), aggregated\n * server-side from the spans it produced, or null if the run captured no\n * token data. This is the \"new\" side of a token delta: compare it against\n * the original trace's recorded usage to see how the code change moved cost.\n * Matches what Studio's experiments view shows.\n */\n tokens: TokenUsage | null\n /** Model name from the original trace, or null if not captured. */\n model: string | null\n /**\n * The DB snapshot ref the SDK captured at trace open. Useful for debugging\n * (\"what state was this trace pinned to?\") and for customers building\n * their own resolvers. Undefined when the source trace was captured\n * without `dbSnapshot` configured.\n */\n dbSnapshotRef: DbSnapshotRef | null\n}\n\nexport type { CodeChangeFile, TokenUsage }\n\nexport interface ReplayResult<T> {\n /** Individual replay items with inputs, results, and comparison data. */\n items: ReplayItem<T>[]\n /** The test run ID created on the server. */\n testRunId: string\n /** Full URL to view the test run in the dashboard. */\n testRunUrl: string\n}\n\n/**\n * Deserialize inputs from a historical span's rawData.\n *\n * Prefers superjson-serialized `input_meta` for type preservation,\n * falls back to the raw `input` field.\n */\nfunction deserializeInputs(spanData: Record<string, unknown>): unknown[] {\n const inputMeta = spanData.input_meta as unknown\n const rawInput = spanData.input\n\n // If superjson meta is available, deserialize with type reconstruction\n if (inputMeta !== undefined && inputMeta !== null) {\n const deserialized = deserializeValue({ json: rawInput, meta: inputMeta })\n if (Array.isArray(deserialized)) {\n return deserialized\n }\n return deserialized !== undefined && deserialized !== null\n ? [deserialized]\n : []\n }\n\n // Fall back to raw input\n if (Array.isArray(rawInput)) {\n return rawInput\n }\n return rawInput !== undefined && rawInput !== null ? [rawInput] : []\n}\n\n/**\n * Deserialize the original output from a historical span's rawData.\n */\nfunction deserializeOutput(spanData: Record<string, unknown>): unknown {\n const outputMeta = spanData.output_meta as unknown\n const rawOutput = spanData.output\n\n if (outputMeta !== undefined && outputMeta !== null) {\n return deserializeValue({ json: rawOutput, meta: outputMeta })\n }\n\n return rawOutput\n}\n\n/**\n * Walk the children of a root span tree node in depth-first order and build\n * a MockTree keyed by `${traceFunctionKey}:${spanName}:${callIndex}`.\n *\n * The historical root itself is NOT walked. At replay time the runtime root\n * span has `isRootSpan === true` and never queries the mockTree (mock\n * interception is skipped for root spans by design), so the root has nothing\n * to look up. Walking it would just leave an unreachable entry in the table.\n *\n * The (key, name) compound match is what disambiguates same-key spans:\n * - A wrapped function's children commonly share its traceFunctionKey via\n * the fluent `getFunction(key).withSpan({ name }, ...)` pattern. They\n * disambiguate by `name`, never colliding with each other.\n * - Recursion: same (key, name) at every depth. callIndex per (key, name)\n * orders them correctly without leaking the historical root into the\n * nested call's slot.\n * - Outer-wrapper replay scripts: the outer wrapper's `name` is distinct\n * from anything in the historical tree (it only exists at replay), so\n * its presence never disturbs counters or lookups for spans that do\n * exist in the historical tree.\n */\nfunction buildMockTree(rootNode: SpanTreeNode): MockTree {\n const spans = new Map<\n string,\n { sourceSpanId: string; output: unknown; outputMeta?: unknown }\n >()\n const counters = new Map<string, number>()\n\n function walk(node: SpanTreeNode): void {\n const key = node.traceFunctionKey\n if (key) {\n const name = node.spanName || key\n const counterKey = `${key}:${name}`\n const index = counters.get(counterKey) ?? 0\n counters.set(counterKey, index + 1)\n spans.set(`${counterKey}:${index}`, {\n sourceSpanId: node.sourceSpanId,\n output: node.output,\n outputMeta: node.outputMeta,\n })\n }\n for (const child of node.children) {\n walk(child)\n }\n }\n\n for (const child of rootNode.children) {\n walk(child)\n }\n\n return { spans }\n}\n\n/**\n * Execute a single replay item: fetch span data, deserialize inputs, call\n * the function within a replay context that injects testRunId into new spans.\n */\nasync function processItem<TReturn>(\n httpClient: HttpClient,\n serverItem: {\n traceId: string\n externalSpanId: string\n durationMs: number | null\n model: string | null\n dbSnapshotRef?: DbSnapshotRef\n dbBranchLease?: DbBranchLease\n },\n // biome-ignore lint/suspicious/noExplicitAny: replay deserializes inputs from historical data\n fn: (...args: any[]) => TReturn | Promise<TReturn>,\n testRunId: string,\n mockStrategy: MockStrategy,\n environment: ReplayEnvironment | undefined,\n adaptInputs:\n | ((inputs: unknown[], ctx: AdaptContext) => unknown[])\n | undefined,\n): Promise<ReplayItem<TReturn>> {\n // The server-side resolver materializes a Neon preview branch per item\n // during `/api/sdk/replay/start` (when the customer passed `environment`,\n // which triggers `includeDbBranchLease: true`). The lease arrives on the\n // server item; we just attach it to the replay context and release the\n // branch in `finally` so any throw (in fetch, mock-tree build, or the\n // customer fn) frees the Neon resource. Items whose source trace had\n // no snapshot ref, or whose resolve failed server-side, arrive without\n // a lease; `env.active` will be `false` for those.\n const lease = environment ? serverItem.dbBranchLease : undefined\n\n let inputs: unknown[] = []\n let originalOutput: unknown\n let result: TReturn | undefined\n let error: string | null = null\n const replayedTraceId = randomUuid()\n // Collects the root span's full persistence chain (span uploads + trace\n // completion). Awaited below so this item's trace is on the server before\n // replay() calls completeReplay. Otherwise the server's trace-ID mapping\n // races the uploads and item.traceId nulls out.\n const pendingPersistence: Promise<unknown>[] = []\n\n try {\n const span = await httpClient.getExternalSpan(serverItem.externalSpanId)\n const spanData = (span.rawData?.span_data ?? {}) as Record<string, unknown>\n\n inputs = deserializeInputs(spanData)\n originalOutput = deserializeOutput(spanData)\n\n // Reshape the recorded inputs onto the current signature when an adapter\n // is supplied. Runs before the mock tree / fn call so the adapted array is\n // what fn receives and what `item.input` reports.\n if (adaptInputs) {\n inputs = adaptInputs(inputs, {\n traceId: serverItem.traceId,\n sourceSpanId: serverItem.externalSpanId,\n })\n }\n\n // Build mock tree when mocking is active\n let mockTree: MockTree | undefined\n if (mockStrategy === \"all\" || mockStrategy === \"marked\") {\n try {\n const treeResponse = await httpClient.getSpanTree(\n serverItem.externalSpanId,\n )\n if (treeResponse.root) {\n mockTree = buildMockTree(treeResponse.root)\n } else if (mockStrategy === \"all\") {\n throw new BitfabError(\n `Replay mock strategy \"all\" requires a span tree root for source span ${serverItem.externalSpanId}.`,\n )\n } else {\n mockTree = undefined\n }\n } catch (e) {\n if (mockStrategy === \"all\") {\n throw e\n }\n mockTree = undefined\n }\n }\n\n const maybePromise = runWithReplayContext(\n {\n testRunId,\n traceId: replayedTraceId,\n inputSourceSpanId: span.id,\n inputSourceTraceId: span.externalTraceId,\n sourceBitfabTraceId: serverItem.traceId,\n mockTree,\n callCounters: mockTree ? new Map() : undefined,\n mockStrategy,\n dbBranchLease: lease,\n pendingPersistence,\n },\n () => fn(...inputs),\n )\n result = maybePromise instanceof Promise ? await maybePromise : maybePromise\n } catch (e) {\n error = e instanceof Error ? e.message : String(e)\n } finally {\n // Wait for this item's trace (spans + completion) to be fully persisted\n // before the item resolves. Runs on the error path too. A throwing fn\n // still emits a root span whose trace must land before completeReplay.\n await Promise.allSettled(pendingPersistence)\n if (lease) {\n try {\n await httpClient.releaseDbBranchLease(lease.neonBranchId)\n } catch (e) {\n try {\n console.warn(\n `Bitfab: failed to release DB branch ${lease.neonBranchId} (TTL janitor will catch it): ${\n e instanceof Error ? e.message : String(e)\n }`,\n )\n } catch {\n // Never crash the host\n }\n }\n }\n }\n\n return {\n traceId: replayedTraceId,\n input: inputs,\n result,\n originalOutput,\n error,\n durationMs: serverItem.durationMs ?? null,\n // Filled in by replay() from the complete-replay response once the\n // replay traces are persisted and their spans aggregated server-side.\n // Null here (and on older servers) means \"replay tokens not known\".\n tokens: null,\n model: serverItem.model ?? null,\n dbSnapshotRef: serverItem.dbSnapshotRef ?? null,\n }\n}\n\n/**\n * Run async tasks with a concurrency limit.\n * Each task factory is called when a slot opens; results preserve input order.\n */\nasync function mapWithConcurrency<T>(\n tasks: Array<() => Promise<T>>,\n maxConcurrency: number,\n onSettled?: (result: T, index: number) => void,\n): Promise<T[]> {\n const results: T[] = new Array(tasks.length)\n let nextIndex = 0\n\n async function worker(): Promise<void> {\n while (nextIndex < tasks.length) {\n const index = nextIndex++\n const result = await tasks[index]()\n results[index] = result\n onSettled?.(result, index)\n }\n }\n\n const workers = Array.from(\n { length: Math.min(maxConcurrency, tasks.length) },\n () => worker(),\n )\n await Promise.all(workers)\n return results\n}\n\n/**\n * Replay historical traces through a function and create a test run.\n *\n * @internal Called by Bitfab.replay, not part of the public API.\n */\nexport async function replay<TReturn>(\n httpClient: HttpClient,\n serviceUrl: string,\n traceFunctionKey: string,\n // biome-ignore lint/suspicious/noExplicitAny: replay deserializes inputs from historical data\n fn: (...args: any[]) => TReturn | Promise<TReturn>,\n options?: ReplayOptions,\n): Promise<ReplayResult<TReturn>> {\n if (options?.traceIds !== undefined) {\n if (options.traceIds.length === 0) {\n throw new BitfabError(\"traceIds must contain at least one trace ID.\")\n }\n if (options.traceIds.length > 100) {\n throw new BitfabError(\n `traceIds supports at most 100 trace IDs per replay (got ${options.traceIds.length}).`,\n )\n }\n }\n if (options?.limit !== undefined && options?.traceIds !== undefined) {\n try {\n console.warn(\n \"Bitfab: limit is ignored when traceIds is passed: the explicit trace ID list already determines how many traces replay.\",\n )\n } catch {\n // Never crash the host app\n }\n }\n\n await replayContextReady\n\n const {\n testRunId,\n testRunUrl,\n items: serverItems,\n } = await httpClient.startReplay(\n traceFunctionKey,\n // limit is meaningless with explicit traceIds (the ID list determines\n // the count), so it's omitted from the request entirely.\n options?.traceIds ? undefined : (options?.limit ?? 5),\n options?.traceIds,\n options?.name,\n options?.codeChangeDescription,\n options?.codeChangeFiles,\n options?.environment !== undefined, // includeDbBranchLease\n options?.experimentGroupId,\n options?.datasetId,\n )\n\n const mockStrategy: MockStrategy = options?.mock ?? \"marked\"\n const maxConcurrency = options?.maxConcurrency ?? 10\n\n const tasks = serverItems.map(\n (serverItem) => () =>\n processItem(\n httpClient,\n serverItem,\n fn,\n testRunId,\n mockStrategy,\n options?.environment,\n options?.adaptInputs,\n ),\n )\n const total = tasks.length\n let completed = 0\n let succeeded = 0\n let errored = 0\n const resultItems = await mapWithConcurrency(\n tasks,\n maxConcurrency,\n options?.onProgress\n ? (item, index) => {\n completed += 1\n if (item.error === null) {\n succeeded += 1\n } else {\n errored += 1\n }\n try {\n options?.onProgress?.({\n testRunId,\n completed,\n total,\n succeeded,\n errored,\n item: {\n // Source (historical) trace id, so a UI can identify the trace\n // that just settled. The item's own traceId is the new replay\n // trace and is assigned later (below), so use the server item.\n traceId: serverItems[index]?.traceId ?? null,\n replayTraceId: item.traceId,\n input: item.input,\n result: item.result,\n originalOutput: item.originalOutput,\n error: item.error,\n durationMs: item.durationMs,\n tokens: item.tokens,\n model: item.model,\n dbSnapshotRef: item.dbSnapshotRef,\n },\n })\n } catch {\n // Progress UI must never crash the replay run.\n }\n }\n : undefined,\n )\n\n // Every item awaited its own trace persistence (spans + completion) in\n // processItem, so all replay traces are on the server by now. No flush\n // needed, and completeReplay's trace-ID mapping is deterministic.\n // completeReplay failures propagate: a missing mapping means verdicts\n // can't be persisted, which callers must hear about loudly.\n const completeResult = await httpClient.completeReplay(testRunId)\n const serverTraceIds = completeResult.traceIds\n // Per-replay-trace token usage, keyed by server trace id. The REPLAYED run's\n // tokens (span-aggregated server-side), used below to fill each item.tokens.\n const replayTokens = completeResult.tokens\n\n if (serverTraceIds === undefined) {\n // Older servers don't return the mapping. Preserve the legacy\n // null-traceId behavior but say why.\n try {\n console.warn(\n \"Bitfab: server did not return replay trace IDs; item.traceId will be null (server upgrade required for verdict persistence)\",\n )\n } catch {\n // Never crash the host app\n }\n for (const item of resultItems) {\n item.traceId = null\n }\n } else {\n // Map each item's locally-generated trace ID to the server's trace row\n // ID. A completed item with no mapping means its trace was sent but the\n // server has no record. A null traceId blocks verdict persistence and\n // the Studio experiments view downstream, so this must never be silent.\n //\n // Severity splits on scope:\n // - ALL completed items missing → systemic (the replayed function isn't\n // wrapped with withSpan, or uploads are wholesale broken). Throw; the\n // run's results are unusable for persistence and silence here is the\n // exact bug this guarantee exists to prevent.\n // - SOME completed items missing → per-item upload failure (transient\n // network blip, one oversized payload). Null those items and log an\n // unmissable error, but return the run; callers can persist verdicts\n // for the items that landed instead of losing all the compute.\n const missing: string[] = []\n let completedCount = 0\n for (const item of resultItems) {\n if (item.traceId) {\n const mapped = serverTraceIds[item.traceId]\n if (item.error === null) {\n completedCount += 1\n if (mapped === undefined) {\n missing.push(item.traceId)\n }\n }\n // Pull this item's replayed-run tokens by its server trace id, before\n // item.traceId is overwritten with that id below.\n if (mapped !== undefined) {\n item.tokens = replayTokens?.[mapped] ?? null\n }\n item.traceId = mapped ?? null\n }\n }\n if (missing.length > 0) {\n const serverCount =\n completeResult.traceCount !== undefined\n ? ` The server persisted ${completeResult.traceCount} trace(s) for this run.`\n : \"\"\n if (missing.length === completedCount) {\n throw new BitfabError(\n `Replay completed but the server has no persisted trace for any of the ${completedCount} completed item(s) (testRunId ${testRunId}).${serverCount} ` +\n `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.`,\n )\n }\n try {\n console.error(\n `Bitfab: server has no persisted trace for ${missing.length} of ${completedCount} completed replay item(s) (testRunId ${testRunId}).${serverCount} ` +\n `Their traceId is null and verdicts cannot be persisted for them. Missing: ${missing.join(\", \")}`,\n )\n } catch {\n // Never crash the host app\n }\n }\n }\n\n return {\n items: resultItems,\n testRunId,\n testRunUrl: `${serviceUrl}${testRunUrl}`,\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -400,10 +400,25 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
400
400
  }
401
401
  let mockTree;
402
402
  if (mockStrategy === "all" || mockStrategy === "marked") {
403
- const treeResponse = await httpClient.getSpanTree(
404
- serverItem.externalSpanId
405
- );
406
- mockTree = buildMockTree(treeResponse.root);
403
+ try {
404
+ const treeResponse = await httpClient.getSpanTree(
405
+ serverItem.externalSpanId
406
+ );
407
+ if (treeResponse.root) {
408
+ mockTree = buildMockTree(treeResponse.root);
409
+ } else if (mockStrategy === "all") {
410
+ throw new BitfabError(
411
+ `Replay mock strategy "all" requires a span tree root for source span ${serverItem.externalSpanId}.`
412
+ );
413
+ } else {
414
+ mockTree = void 0;
415
+ }
416
+ } catch (e) {
417
+ if (mockStrategy === "all") {
418
+ throw e;
419
+ }
420
+ mockTree = void 0;
421
+ }
407
422
  }
408
423
  const maybePromise = runWithReplayContext(
409
424
  {
@@ -509,7 +524,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
509
524
  options?.experimentGroupId,
510
525
  options?.datasetId
511
526
  );
512
- const mockStrategy = options?.mock ?? "none";
527
+ const mockStrategy = options?.mock ?? "marked";
513
528
  const maxConcurrency = options?.maxConcurrency ?? 10;
514
529
  const tasks = serverItems.map(
515
530
  (serverItem) => () => processItem(
@@ -653,7 +668,7 @@ __export(index_exports, {
653
668
  module.exports = __toCommonJS(index_exports);
654
669
 
655
670
  // src/version.generated.ts
656
- var __version__ = "0.28.2";
671
+ var __version__ = "0.28.4";
657
672
 
658
673
  // src/constants.ts
659
674
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -966,7 +981,7 @@ var HttpClient = class {
966
981
  }
967
982
  /**
968
983
  * Start a replay session by fetching historical traces.
969
- * Blocking call creates a test run and returns lightweight item references.
984
+ * Blocking call - creates a test run and returns lightweight item references.
970
985
  */
971
986
  async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetId) {
972
987
  const payload = { traceFunctionKey };
@@ -1084,7 +1099,7 @@ var HttpClient = class {
1084
1099
  }
1085
1100
  /**
1086
1101
  * Ask the server to materialize a per-trace DB branch lease from a
1087
- * captured `dbSnapshotRef`. Blocking the resolver creates a Neon
1102
+ * captured `dbSnapshotRef`. Blocking - the resolver creates a Neon
1088
1103
  * snapshot + preview branch and polls operations to readiness, which
1089
1104
  * can take seconds.
1090
1105
  */
@@ -1224,7 +1239,7 @@ var BitfabClaudeAgentHandler = class {
1224
1239
  // Synthetic root span (handler-only replay). When an `input` is supplied to
1225
1240
  // wrapQuery/wrapResponse, the handler emits a root `agent` span carrying that
1226
1241
  // input, so a handler-instrumented run is replayable WITHOUT an enclosing
1227
- // withSpan matching the LangGraph handler, which records the graph input as
1242
+ // withSpan - matching the LangGraph handler, which records the graph input as
1228
1243
  // its root. The prompt is not present anywhere in the message stream, so it
1229
1244
  // must be handed in explicitly.
1230
1245
  this.hasRootInput = false;
@@ -1498,7 +1513,7 @@ var BitfabClaudeAgentHandler = class {
1498
1513
  * SDK's `wrapResponse` (which wraps `ClaudeSDKClient.receiveResponse()`);
1499
1514
  * in TypeScript, prefer `wrapQuery` around `query()`.
1500
1515
  *
1501
- * Pass `{ input }` (the prompt) to record a replayable root span see
1516
+ * Pass `{ input }` (the prompt) to record a replayable root span - see
1502
1517
  * `wrapQuery`.
1503
1518
  */
1504
1519
  async *wrapResponse(stream, opts) {
@@ -1511,8 +1526,8 @@ var BitfabClaudeAgentHandler = class {
1511
1526
  * Tool and subagent spans are captured separately via the hooks injected
1512
1527
  * by `instrumentOptions` into the `options` passed to `query()`.
1513
1528
  *
1514
- * Pass `{ input }` the prompt (or the serializable args that produced it)
1515
- * to make a handler-only run replayable: the handler records a root `agent`
1529
+ * Pass `{ input }` - the prompt (or the serializable args that produced it)
1530
+ * - to make a handler-only run replayable: the handler records a root `agent`
1516
1531
  * span with that input, so `replay(key, fn)` can re-feed it. Omit it only
1517
1532
  * when an enclosing `withSpan` already supplies the replayable root.
1518
1533
  *
@@ -2624,7 +2639,7 @@ var BitfabOpenAIAgentHandler = class {
2624
2639
  * argument, so `replay(key, fn)` re-feeds it), and the run's `finalOutput`
2625
2640
  * is recorded as the root output. For streaming runs (`{ stream: true }`),
2626
2641
  * the result is handed back immediately and the final output is recorded
2627
- * once the stream completes first-byte latency is untouched.
2642
+ * once the stream completes - first-byte latency is untouched.
2628
2643
  *
2629
2644
  * The process-wide tracing processor (`getOpenAiTracingProcessor`) must still
2630
2645
  * be registered: it captures the LLM/tool/handoff spans that nest beneath
@@ -2717,7 +2732,7 @@ var ReplayEnvironment = class {
2717
2732
  }
2718
2733
  /**
2719
2734
  * Record on the replay context that customer code obtained the branch
2720
- * URL. Only `databaseUrl` and `snapshot()` count `active`, `readOnly`
2735
+ * URL. Only `databaseUrl` and `snapshot()` count - `active`, `readOnly`
2721
2736
  * and friends inspect the lease without exposing the connection string,
2722
2737
  * so they don't prove the replayed code could have connected to the
2723
2738
  * branch.
@@ -3442,7 +3457,7 @@ var Bitfab = class {
3442
3457
  if (this.explicitlyEnabled && !this.apiKeyWarned) {
3443
3458
  this.apiKeyWarned = true;
3444
3459
  console.warn(
3445
- "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3460
+ "Bitfab: apiKey is empty - tracing is disabled. Provide a valid API key to enable tracing."
3446
3461
  );
3447
3462
  }
3448
3463
  return void 0;
@@ -3706,7 +3721,7 @@ var Bitfab = class {
3706
3721
  * Wrap a BAML client method to automatically capture prompt and LLM metadata.
3707
3722
  *
3708
3723
  * Creates a BAML Collector, calls the method through a tracked client,
3709
- * then extracts rendered messages and token usage calling setPrompt()
3724
+ * then extracts rendered messages and token usage - calling setPrompt()
3710
3725
  * and addContext() on the current span automatically.
3711
3726
  *
3712
3727
  * The BAML client can be provided in the constructor or passed explicitly:
@@ -4093,7 +4108,7 @@ var Bitfab = class {
4093
4108
  * Get a detached handle to a previously-created trace, looked up by the
4094
4109
  * caller-supplied id (the same id passed at trace creation).
4095
4110
  *
4096
- * The returned handle is not tied to AsyncLocalStorage each method sends
4111
+ * The returned handle is not tied to AsyncLocalStorage - each method sends
4097
4112
  * to the server immediately. Useful for adding context to a trace from a
4098
4113
  * different process or thread than the one that created it.
4099
4114
  *
@@ -4274,7 +4289,7 @@ var Bitfab = class {
4274
4289
  * plain callable: plain callables are wrapped internally so each replayed
4275
4290
  * invocation records a trace tied to the test run. The plain-callable form
4276
4291
  * is how handler-instrumented workflows (LangGraph/LangChain, Claude Agent
4277
- * SDK) replay those record traces under a key with no `withSpan`-wrapped
4292
+ * SDK) replay - those record traces under a key with no `withSpan`-wrapped
4278
4293
  * root in the app.
4279
4294
  *
4280
4295
  * @param traceFunctionKey - The trace function key to replay
@@ -4426,7 +4441,7 @@ var BitfabFunction = class {
4426
4441
  return this.client.getLangGraphCallbackHandler(this.traceFunctionKey);
4427
4442
  }
4428
4443
  /**
4429
- * Alias of {@link getLangGraphCallbackHandler} LangChain and LangGraph
4444
+ * Alias of {@link getLangGraphCallbackHandler} - LangChain and LangGraph
4430
4445
  * share one callback system, so the same bound handler serves both.
4431
4446
  *
4432
4447
  * @returns A LangChain callback handler for this client and key
@@ -4441,7 +4456,7 @@ var BitfabFunction = class {
4441
4456
  * Unlike the other methods on this handle, `wrapBAML` does NOT use the bound
4442
4457
  * key: it opens no span of its own. It enriches the *current* span (via
4443
4458
  * `getCurrentSpan().setPrompt()` / `addContext()`), so call it inside a
4444
- * function wrapped by this handle's `withSpan` the bound key keys that
4459
+ * function wrapped by this handle's `withSpan` - the bound key keys that
4445
4460
  * wrapper, and the BAML prompt/metadata attach to it.
4446
4461
  *
4447
4462
  * @param methodOrClient - Either a BAML method (uses constructor bamlClient) or the BAML client instance