bitfab 0.13.3 → 0.13.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.
- package/dist/{chunk-LOZFAPCS.js → chunk-SKJWF5VX.js} +32 -5
- package/dist/chunk-SKJWF5VX.js.map +1 -0
- package/dist/{chunk-NHYPYRQB.js → chunk-XUW46356.js} +131 -6
- package/dist/chunk-XUW46356.js.map +1 -0
- package/dist/index.cjs +225 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +139 -8
- package/dist/index.d.ts +139 -8
- package/dist/index.js +6 -2
- package/dist/node.cjs +225 -28
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +6 -2
- package/dist/node.js.map +1 -1
- package/dist/{replay-JX33WJUT.js → replay-HGU5YAOK.js} +46 -16
- package/dist/replay-HGU5YAOK.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-LOZFAPCS.js.map +0 -1
- package/dist/chunk-NHYPYRQB.js.map +0 -1
- package/dist/replay-JX33WJUT.js.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// src/version.generated.ts
|
|
2
|
-
var __version__ = "0.13.
|
|
2
|
+
var __version__ = "0.13.4";
|
|
3
3
|
|
|
4
4
|
// src/constants.ts
|
|
5
5
|
var DEFAULT_SERVICE_URL = "https://bitfab.ai";
|
|
6
6
|
|
|
7
|
-
// src/
|
|
7
|
+
// src/errors.ts
|
|
8
8
|
var BitfabError = class extends Error {
|
|
9
9
|
constructor(message, url) {
|
|
10
10
|
super(message);
|
|
@@ -12,6 +12,8 @@ var BitfabError = class extends Error {
|
|
|
12
12
|
this.name = "BitfabError";
|
|
13
13
|
}
|
|
14
14
|
};
|
|
15
|
+
|
|
16
|
+
// src/http.ts
|
|
15
17
|
var pendingTracePromises = /* @__PURE__ */ new Set();
|
|
16
18
|
function awaitOnExit(promise) {
|
|
17
19
|
pendingTracePromises.add(promise);
|
|
@@ -207,7 +209,7 @@ var HttpClient = class {
|
|
|
207
209
|
* Start a replay session by fetching historical traces.
|
|
208
210
|
* Blocking call — creates a test run and returns lightweight item references.
|
|
209
211
|
*/
|
|
210
|
-
async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles) {
|
|
212
|
+
async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles, includeDbBranchLease) {
|
|
211
213
|
const payload = { traceFunctionKey, limit };
|
|
212
214
|
if (traceIds) {
|
|
213
215
|
payload.traceIds = traceIds;
|
|
@@ -218,8 +220,12 @@ var HttpClient = class {
|
|
|
218
220
|
if (codeChangeFiles !== void 0) {
|
|
219
221
|
payload.codeChangeFiles = codeChangeFiles;
|
|
220
222
|
}
|
|
223
|
+
if (includeDbBranchLease) {
|
|
224
|
+
payload.includeDbBranchLease = true;
|
|
225
|
+
}
|
|
226
|
+
const timeout = includeDbBranchLease ? 18e4 : 3e4;
|
|
221
227
|
return this.request("/api/sdk/replay/start", payload, {
|
|
222
|
-
timeout
|
|
228
|
+
timeout
|
|
223
229
|
});
|
|
224
230
|
}
|
|
225
231
|
/**
|
|
@@ -305,6 +311,27 @@ var HttpClient = class {
|
|
|
305
311
|
{ timeout: 3e4 }
|
|
306
312
|
);
|
|
307
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Ask the server to materialize a per-trace DB branch lease from a
|
|
316
|
+
* captured `dbSnapshotRef`. Blocking — the resolver creates a Neon
|
|
317
|
+
* snapshot + preview branch and polls operations to readiness, which
|
|
318
|
+
* can take seconds.
|
|
319
|
+
*/
|
|
320
|
+
async resolveDbBranchLease(testRunId, traceId, dbSnapshotRef) {
|
|
321
|
+
return this.request(
|
|
322
|
+
"/api/sdk/replay/resolveDbBranchLease",
|
|
323
|
+
{ testRunId, traceId, dbSnapshotRef },
|
|
324
|
+
{ timeout: 9e4 }
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
/** Release a previously-resolved DB branch lease. Idempotent server-side. */
|
|
328
|
+
async releaseDbBranchLease(leaseId) {
|
|
329
|
+
await this.request(
|
|
330
|
+
"/api/sdk/replay/releaseDbBranchLease",
|
|
331
|
+
{ leaseId },
|
|
332
|
+
{ timeout: 3e4 }
|
|
333
|
+
);
|
|
334
|
+
}
|
|
308
335
|
};
|
|
309
336
|
|
|
310
337
|
// src/asyncStorage.ts
|
|
@@ -432,4 +459,4 @@ export {
|
|
|
432
459
|
serializeValue,
|
|
433
460
|
deserializeValue
|
|
434
461
|
};
|
|
435
|
-
//# sourceMappingURL=chunk-
|
|
462
|
+
//# sourceMappingURL=chunk-SKJWF5VX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/version.generated.ts","../src/constants.ts","../src/errors.ts","../src/http.ts","../src/asyncStorage.ts","../src/replayContext.ts","../src/serialize.ts"],"sourcesContent":["/**\n * Auto-generated version file.\n * This file is generated by scripts/generate-version.ts during build.\n * DO NOT EDIT MANUALLY.\n */\n\n/**\n * SDK version from package.json (injected at build time)\n */\nexport const __version__ = \"0.13.4\"\n","/**\n * Constants for the Bitfab SDK.\n */\n\n/**\n * Default service URL for Bitfab API.\n */\nexport const DEFAULT_SERVICE_URL = \"https://bitfab.ai\"\n\n/**\n * SDK version from package.json (injected at build time)\n *\n * The version is generated at build time by scripts/generate-version.ts\n * to ensure compatibility with both Node.js and browser environments.\n */\nexport { __version__ } from \"./version.generated.js\"\n","/**\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 * HTTP client utilities for Bitfab API requests.\n *\n * This module provides:\n * - HttpClient class for making API requests\n * - awaitOnExit helper for fire-and-forget operations that must complete before process exit\n */\n\nimport { __version__ } from \"./constants.js\"\nimport type { DbSnapshotRef } from \"./dbSnapshot.js\"\nimport { BitfabError } from \"./errors.js\"\nimport type { DbBranchLease } from \"./replayContext.js\"\n\n// BitfabError lives in `errors.ts` to break the http ↔ dbSnapshot import\n// cycle. Re-exported here for backwards compatibility with existing\n// callers that import it from \"./http.js\".\nexport { BitfabError }\n\n// Global set to track pending trace creation promises\n// This prevents promises from being garbage collected before they complete\nconst pendingTracePromises = new Set<Promise<unknown>>()\n\n/**\n * Track a promise to prevent it from being garbage collected.\n * The promise will be removed from tracking when it completes (success or failure).\n * Useful for fire-and-forget operations that need to complete before process exit.\n *\n * @param promise - The promise to track\n * @returns The same promise (for chaining)\n */\nexport function awaitOnExit<T>(promise: Promise<T>): Promise<T> {\n pendingTracePromises.add(promise)\n // Use void to prevent unhandled rejection warnings from the .finally() chain\n // The actual error handling is done by the caller's .catch() on the returned promise\n void promise\n .finally(() => {\n pendingTracePromises.delete(promise)\n })\n .catch(() => {\n // Swallow rejection in this chain - the caller handles errors via their own .catch()\n })\n return promise\n}\n\n/**\n * Wait for all pending fire-and-forget operations (spans, traces) to complete.\n * Useful in tests and scripts to ensure all data has been sent before asserting or exiting.\n *\n * @param timeoutMs - Maximum time to wait in milliseconds (default: 5000)\n */\nexport async function flushTraces(timeoutMs: number = 5000): Promise<void> {\n if (pendingTracePromises.size === 0) {\n return\n }\n await Promise.race([\n Promise.allSettled(Array.from(pendingTracePromises)),\n new Promise<void>((resolve) => setTimeout(resolve, timeoutMs)),\n ])\n}\n\n// Register beforeExit handler to wait for pending traces (Node.js only)\n// This ensures traces are sent before the process exits (for scripts)\nif (\n typeof process !== \"undefined\" &&\n process.versions != null &&\n process.versions.node != null\n) {\n let isFlushing = false\n process.on(\"beforeExit\", () => {\n if (pendingTracePromises.size > 0 && !isFlushing) {\n isFlushing = true\n // Wait for all pending traces to complete\n // This keeps the event loop alive until promises resolve\n Promise.allSettled(\n Array.from(pendingTracePromises).map((p) =>\n p.catch(() => {\n // Silently ignore individual trace failures\n }),\n ),\n )\n .then(() => {\n isFlushing = false\n })\n .catch(() => {\n isFlushing = false\n })\n }\n })\n}\n\nexport interface HttpClientConfig {\n apiKey?: string\n serviceUrl: string\n timeout?: number\n}\n\n/**\n * HTTP client for Bitfab API requests.\n *\n * Provides methods for different API endpoints with proper error handling,\n * timeouts, and authentication.\n */\nexport class HttpClient {\n private readonly apiKey: string | undefined\n private readonly serviceUrl: string\n private readonly timeout: number\n\n constructor(config: HttpClientConfig) {\n this.apiKey = config.apiKey\n this.serviceUrl = config.serviceUrl\n this.timeout = config.timeout ?? 120000\n }\n\n /**\n * Make an HTTP request to the Bitfab API. Defaults to POST; pass\n * `options.method` to use a different verb (e.g. \"PATCH\").\n *\n * @param endpoint - The API endpoint (without base URL)\n * @param payload - The request body\n * @param options - Optional request options\n * @returns The parsed JSON response\n * @throws {BitfabError} If the request fails\n */\n async request<T>(\n endpoint: string,\n payload: Record<string, unknown>,\n options?: { timeout?: number; method?: \"POST\" | \"PATCH\" | \"PUT\" },\n ): Promise<T> {\n const url = `${this.serviceUrl}${endpoint}`\n const timeout = options?.timeout ?? this.timeout\n const method = options?.method ?? \"POST\"\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n // Serialize payload, handling circular references\n let body: string\n let serializationError: string | undefined\n try {\n body = JSON.stringify(payload)\n } catch (error) {\n serializationError =\n error instanceof Error ? error.message : String(error)\n // Create fallback payload with error info\n body = JSON.stringify({\n ...Object.fromEntries(\n Object.entries(payload).filter(\n ([, v]) => typeof v === \"string\" || typeof v === \"number\",\n ),\n ),\n rawSpan: {},\n errors: [{ step: \"json_serialize\", error: serializationError }],\n })\n }\n\n try {\n const response = await fetch(url, {\n method,\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body,\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new BitfabError(\n `HTTP ${response.status}: ${errorText.slice(0, 500)}`,\n )\n }\n\n const result = await response.json()\n\n // Check for errors in the response\n if (result.error) {\n if (result.url) {\n throw new BitfabError(\n `${result.error} Configure it at: ${this.serviceUrl}${result.url}`,\n result.url,\n )\n }\n throw new BitfabError(result.error)\n }\n\n return result as T\n } catch (error) {\n if (error instanceof BitfabError) {\n throw error\n }\n if (error instanceof Error) {\n if (error.name === \"AbortError\") {\n throw new BitfabError(`Request timed out after ${timeout}ms`)\n }\n throw new BitfabError(error.message)\n }\n throw new BitfabError(\"Unknown error occurred\")\n } finally {\n clearTimeout(timeoutId)\n }\n }\n\n /**\n * Look up a function by name.\n * Blocks until complete - needed for function execution.\n */\n async lookupFunction<T>(name: string): Promise<T> {\n return this.request<T>(\"/api/sdk/functions/lookup\", { name })\n }\n\n /**\n * Send an internal trace (from BAML execution).\n * Fire-and-forget with awaitOnExit - doesn't block the caller.\n */\n sendInternalTrace(\n functionId: string,\n payload: Record<string, unknown>,\n ): void {\n void awaitOnExit(\n this.request(`/api/sdk/functions/${functionId}/traces`, {\n ...payload,\n sdkVersion: __version__,\n }),\n ).catch((error) => {\n try {\n console.error(\"Bitfab: Failed to create trace:\", error)\n } catch {}\n })\n }\n\n /**\n * Send an external span (from withSpan wrapper or OpenAI tracing).\n * Fire-and-forget with awaitOnExit - doesn't block the caller.\n * Returns the tracked promise so callers can optionally await it.\n */\n sendExternalSpan(payload: Record<string, unknown>): Promise<unknown> {\n return awaitOnExit(\n this.request(\"/api/sdk/externalSpans\", {\n ...payload,\n sdkVersion: __version__,\n }),\n ).catch((error) => {\n try {\n console.error(\"Bitfab: Failed to create external span:\", error)\n } catch {}\n })\n }\n\n /**\n * Send an external trace (from OpenAI tracing).\n * Fire-and-forget with awaitOnExit - doesn't block the caller.\n */\n sendExternalTrace(payload: Record<string, unknown>): void {\n void awaitOnExit(\n this.request(\"/api/sdk/externalTraces\", {\n ...payload,\n sdkVersion: __version__,\n }),\n ).catch((error) => {\n try {\n console.error(\"Bitfab: Failed to create external trace:\", error)\n } catch {}\n })\n }\n\n /**\n * Partial update of an existing external trace identified by sourceTraceId.\n * Used by the detached `client.getTrace(id)` handle. Fire-and-forget;\n * returns a tracked promise that callers may optionally await.\n */\n patchTrace(\n sourceTraceId: string,\n payload: {\n appendContexts?: Record<string, unknown>[]\n mergeMetadata?: Record<string, unknown>\n setSessionId?: string\n },\n ): Promise<unknown> {\n const endpoint = `/api/sdk/externalTraces/${encodeURIComponent(sourceTraceId)}`\n return awaitOnExit(\n this.request(endpoint, payload, { method: \"PATCH\" }),\n ).catch((error) => {\n try {\n console.error(\"Bitfab: Failed to patch trace:\", error)\n } catch {}\n })\n }\n\n /**\n * Start a replay session by fetching historical traces.\n * Blocking call — creates a test run and returns lightweight item references.\n */\n async startReplay(\n traceFunctionKey: string,\n limit: number,\n traceIds?: string[],\n codeChangeDescription?: string,\n codeChangeFiles?: CodeChangeFile[],\n includeDbBranchLease?: boolean,\n ): Promise<StartReplayResponse> {\n const payload: Record<string, unknown> = { traceFunctionKey, limit }\n if (traceIds) {\n payload.traceIds = traceIds\n }\n if (codeChangeDescription !== undefined) {\n payload.codeChangeDescription = codeChangeDescription\n }\n if (codeChangeFiles !== undefined) {\n payload.codeChangeFiles = codeChangeFiles\n }\n if (includeDbBranchLease) {\n payload.includeDbBranchLease = true\n }\n // When DB branching is on, the server resolves a Neon preview branch\n // per item (snapshot + restore + poll), which can run ~5-10s each.\n // Use a generous timeout to cover the worst case; otherwise the SDK\n // would time out before a healthy server finished.\n const timeout = includeDbBranchLease ? 180_000 : 30_000\n return this.request<StartReplayResponse>(\"/api/sdk/replay/start\", payload, {\n timeout,\n })\n }\n\n /**\n * Fetch an external span by ID.\n * Blocking GET request.\n */\n async getExternalSpan(spanId: string): Promise<ExternalSpanResponse> {\n const url = `${this.serviceUrl}/api/sdk/externalSpans/${spanId}`\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), 30_000)\n\n try {\n const response = await fetch(url, {\n method: \"GET\",\n headers: { Authorization: `Bearer ${this.apiKey}` },\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new BitfabError(\n `HTTP ${response.status}: ${errorText.slice(0, 500)}`,\n )\n }\n\n return (await response.json()) as ExternalSpanResponse\n } catch (error) {\n if (error instanceof BitfabError) {\n throw error\n }\n if (error instanceof Error) {\n if (error.name === \"AbortError\") {\n throw new BitfabError(\"Request timed out after 30000ms\")\n }\n throw new BitfabError(error.message)\n }\n throw new BitfabError(\"Unknown error occurred\")\n } finally {\n clearTimeout(timeoutId)\n }\n }\n\n /**\n * Fetch the span tree for a root span.\n * Blocking GET request.\n */\n async getSpanTree(externalSpanId: string): Promise<SpanTreeResponse> {\n const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}`\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), 30_000)\n\n try {\n const response = await fetch(url, {\n method: \"GET\",\n headers: { Authorization: `Bearer ${this.apiKey}` },\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new BitfabError(\n `HTTP ${response.status}: ${errorText.slice(0, 500)}`,\n )\n }\n\n return (await response.json()) as SpanTreeResponse\n } catch (error) {\n if (error instanceof BitfabError) {\n throw error\n }\n if (error instanceof Error) {\n if (error.name === \"AbortError\") {\n throw new BitfabError(\"Request timed out after 30000ms\")\n }\n throw new BitfabError(error.message)\n }\n throw new BitfabError(\"Unknown error occurred\")\n } finally {\n clearTimeout(timeoutId)\n }\n }\n\n /**\n * Mark a replay test run as completed.\n * Blocking call.\n */\n async completeReplay(testRunId: string): Promise<CompleteReplayResponse> {\n return this.request<CompleteReplayResponse>(\n \"/api/sdk/replay/complete\",\n { testRunId },\n { timeout: 30_000 },\n )\n }\n\n /**\n * Ask the server to materialize a per-trace DB branch lease from a\n * captured `dbSnapshotRef`. Blocking — the resolver creates a Neon\n * snapshot + preview branch and polls operations to readiness, which\n * can take seconds.\n */\n async resolveDbBranchLease(\n testRunId: string,\n traceId: string,\n dbSnapshotRef: DbSnapshotRef,\n ): Promise<{ lease: DbBranchLease }> {\n return this.request<{ lease: DbBranchLease }>(\n \"/api/sdk/replay/resolveDbBranchLease\",\n { testRunId, traceId, dbSnapshotRef },\n { timeout: 90_000 },\n )\n }\n\n /** Release a previously-resolved DB branch lease. Idempotent server-side. */\n async releaseDbBranchLease(leaseId: string): Promise<void> {\n await this.request<{ released: true }>(\n \"/api/sdk/replay/releaseDbBranchLease\",\n { leaseId },\n { timeout: 30_000 },\n )\n }\n}\n\nexport interface TokenUsage {\n input: number | null\n output: number | null\n cached: number | null\n total: number | null\n}\n\n/**\n * Describes a single file edited as part of a code change.\n *\n * - `path`: file path (relative to the repo root, or any consistent root)\n * - `before`: file contents before the change (\"\" for newly created files)\n * - `after`: file contents after the change (\"\" for deleted files)\n */\nexport interface CodeChangeFile {\n path: string\n before: string\n after: string\n}\n\nexport interface StartReplayResponse {\n testRunId: string\n testRunUrl: string\n items: Array<{\n traceId: string\n externalSpanId: string\n durationMs: number | null\n tokens: TokenUsage | null\n model: string | null\n /**\n * The DB snapshot ref captured by the SDK at trace open. Surfaced so\n * the SDK can pass it to the lease-resolver step (or report when no\n * snapshot was captured for this trace).\n */\n dbSnapshotRef?: DbSnapshotRef\n /**\n * Populated once the server-side resolver has materialized a per-item\n * branch from `dbSnapshotRef`. The SDK exposes this to customer code\n * via `ReplayEnvironment`. Absent until the resolver lands.\n */\n dbBranchLease?: DbBranchLease\n }>\n}\n\nexport interface ExternalSpanResponse {\n id: string\n externalTraceId: string\n rawData: {\n span_data: {\n input: unknown\n output: unknown\n input_meta?: unknown\n output_meta?: unknown\n input_serialized?: { json: unknown; meta: unknown }\n output_serialized?: { json: unknown; meta: unknown }\n }\n }\n}\n\nexport interface CompleteReplayResponse {\n id: string\n status: string\n}\n\nexport interface SpanTreeNode {\n sourceSpanId: string\n traceFunctionKey: string\n spanName: string\n type: string\n output: unknown\n outputMeta?: unknown\n children: SpanTreeNode[]\n}\n\nexport interface SpanTreeResponse {\n root: SpanTreeNode\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 lease resolved by the Bitfab service from the source\n * trace's `dbSnapshotRef`. Carried on the replay context so that\n * `bitfab.db.url()` can return the branch URL transparently, and so the\n * process-isolated replay runner can materialize it into a `.env` overlay\n * file before customer code initializes its DB client.\n */\nexport interface DbBranchLease {\n leaseId: string\n /** Env var name the customer's app reads, e.g. \"DATABASE_URL\". */\n envKey: string\n databaseUrl: string\n expiresAt: string\n providerConsoleUrl?: string\n readOnly?: boolean\n /**\n * How the resolver pinned the snapshot.\n * - \"timestamp\": Neon snapshot at the SDK's wall-clock; bounded by\n * replication lag at that moment.\n * - \"lsn\": customer-LSN mapped to a replica snapshot (future; Ardent\n * mapping required).\n */\n precision: \"timestamp\" | \"lsn\"\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 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\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\"\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\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 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"],"mappings":";AASO,IAAM,cAAc;;;ACFpB,IAAM,sBAAsB;;;ACD5B,IAAM,cAAN,cAA0B,MAAM;AAAA,EACrC,YACE,SACgB,KAChB;AACA,UAAM,OAAO;AAFG;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;;;ACMA,IAAM,uBAAuB,oBAAI,IAAsB;AAUhD,SAAS,YAAe,SAAiC;AAC9D,uBAAqB,IAAI,OAAO;AAGhC,OAAK,QACF,QAAQ,MAAM;AACb,yBAAqB,OAAO,OAAO;AAAA,EACrC,CAAC,EACA,MAAM,MAAM;AAAA,EAEb,CAAC;AACH,SAAO;AACT;AAQA,eAAsB,YAAY,YAAoB,KAAqB;AACzE,MAAI,qBAAqB,SAAS,GAAG;AACnC;AAAA,EACF;AACA,QAAM,QAAQ,KAAK;AAAA,IACjB,QAAQ,WAAW,MAAM,KAAK,oBAAoB,CAAC;AAAA,IACnD,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,SAAS,CAAC;AAAA,EAC/D,CAAC;AACH;AAIA,IACE,OAAO,YAAY,eACnB,QAAQ,YAAY,QACpB,QAAQ,SAAS,QAAQ,MACzB;AACA,MAAI,aAAa;AACjB,UAAQ,GAAG,cAAc,MAAM;AAC7B,QAAI,qBAAqB,OAAO,KAAK,CAAC,YAAY;AAChD,mBAAa;AAGb,cAAQ;AAAA,QACN,MAAM,KAAK,oBAAoB,EAAE;AAAA,UAAI,CAAC,MACpC,EAAE,MAAM,MAAM;AAAA,UAEd,CAAC;AAAA,QACH;AAAA,MACF,EACG,KAAK,MAAM;AACV,qBAAa;AAAA,MACf,CAAC,EACA,MAAM,MAAM;AACX,qBAAa;AAAA,MACf,CAAC;AAAA,IACL;AAAA,EACF,CAAC;AACH;AAcO,IAAM,aAAN,MAAiB;AAAA,EAKtB,YAAY,QAA0B;AACpC,SAAK,SAAS,OAAO;AACrB,SAAK,aAAa,OAAO;AACzB,SAAK,UAAU,OAAO,WAAW;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,QACJ,UACA,SACA,SACY;AACZ,UAAM,MAAM,GAAG,KAAK,UAAU,GAAG,QAAQ;AACzC,UAAM,UAAU,SAAS,WAAW,KAAK;AACzC,UAAM,SAAS,SAAS,UAAU;AAElC,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,OAAO;AAG9D,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,aAAO,KAAK,UAAU,OAAO;AAAA,IAC/B,SAAS,OAAO;AACd,2BACE,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAEvD,aAAO,KAAK,UAAU;AAAA,QACpB,GAAG,OAAO;AAAA,UACR,OAAO,QAAQ,OAAO,EAAE;AAAA,YACtB,CAAC,CAAC,EAAE,CAAC,MAAM,OAAO,MAAM,YAAY,OAAO,MAAM;AAAA,UACnD;AAAA,QACF;AAAA,QACA,SAAS,CAAC;AAAA,QACV,QAAQ,CAAC,EAAE,MAAM,kBAAkB,OAAO,mBAAmB,CAAC;AAAA,MAChE,CAAC;AAAA,IACH;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC;AAAA,QACA,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,eAAe,UAAU,KAAK,MAAM;AAAA,QACtC;AAAA,QACA;AAAA,QACA,QAAQ,WAAW;AAAA,MACrB,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAY,MAAM,SAAS,KAAK;AACtC,cAAM,IAAI;AAAA,UACR,QAAQ,SAAS,MAAM,KAAK,UAAU,MAAM,GAAG,GAAG,CAAC;AAAA,QACrD;AAAA,MACF;AAEA,YAAM,SAAS,MAAM,SAAS,KAAK;AAGnC,UAAI,OAAO,OAAO;AAChB,YAAI,OAAO,KAAK;AACd,gBAAM,IAAI;AAAA,YACR,GAAG,OAAO,KAAK,qBAAqB,KAAK,UAAU,GAAG,OAAO,GAAG;AAAA,YAChE,OAAO;AAAA,UACT;AAAA,QACF;AACA,cAAM,IAAI,YAAY,OAAO,KAAK;AAAA,MACpC;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UAAI,iBAAiB,aAAa;AAChC,cAAM;AAAA,MACR;AACA,UAAI,iBAAiB,OAAO;AAC1B,YAAI,MAAM,SAAS,cAAc;AAC/B,gBAAM,IAAI,YAAY,2BAA2B,OAAO,IAAI;AAAA,QAC9D;AACA,cAAM,IAAI,YAAY,MAAM,OAAO;AAAA,MACrC;AACA,YAAM,IAAI,YAAY,wBAAwB;AAAA,IAChD,UAAE;AACA,mBAAa,SAAS;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eAAkB,MAA0B;AAChD,WAAO,KAAK,QAAW,6BAA6B,EAAE,KAAK,CAAC;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBACE,YACA,SACM;AACN,SAAK;AAAA,MACH,KAAK,QAAQ,sBAAsB,UAAU,WAAW;AAAA,QACtD,GAAG;AAAA,QACH,YAAY;AAAA,MACd,CAAC;AAAA,IACH,EAAE,MAAM,CAAC,UAAU;AACjB,UAAI;AACF,gBAAQ,MAAM,mCAAmC,KAAK;AAAA,MACxD,QAAQ;AAAA,MAAC;AAAA,IACX,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB,SAAoD;AACnE,WAAO;AAAA,MACL,KAAK,QAAQ,0BAA0B;AAAA,QACrC,GAAG;AAAA,QACH,YAAY;AAAA,MACd,CAAC;AAAA,IACH,EAAE,MAAM,CAAC,UAAU;AACjB,UAAI;AACF,gBAAQ,MAAM,2CAA2C,KAAK;AAAA,MAChE,QAAQ;AAAA,MAAC;AAAA,IACX,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAkB,SAAwC;AACxD,SAAK;AAAA,MACH,KAAK,QAAQ,2BAA2B;AAAA,QACtC,GAAG;AAAA,QACH,YAAY;AAAA,MACd,CAAC;AAAA,IACH,EAAE,MAAM,CAAC,UAAU;AACjB,UAAI;AACF,gBAAQ,MAAM,4CAA4C,KAAK;AAAA,MACjE,QAAQ;AAAA,MAAC;AAAA,IACX,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WACE,eACA,SAKkB;AAClB,UAAM,WAAW,2BAA2B,mBAAmB,aAAa,CAAC;AAC7E,WAAO;AAAA,MACL,KAAK,QAAQ,UAAU,SAAS,EAAE,QAAQ,QAAQ,CAAC;AAAA,IACrD,EAAE,MAAM,CAAC,UAAU;AACjB,UAAI;AACF,gBAAQ,MAAM,kCAAkC,KAAK;AAAA,MACvD,QAAQ;AAAA,MAAC;AAAA,IACX,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YACJ,kBACA,OACA,UACA,uBACA,iBACA,sBAC8B;AAC9B,UAAM,UAAmC,EAAE,kBAAkB,MAAM;AACnE,QAAI,UAAU;AACZ,cAAQ,WAAW;AAAA,IACrB;AACA,QAAI,0BAA0B,QAAW;AACvC,cAAQ,wBAAwB;AAAA,IAClC;AACA,QAAI,oBAAoB,QAAW;AACjC,cAAQ,kBAAkB;AAAA,IAC5B;AACA,QAAI,sBAAsB;AACxB,cAAQ,uBAAuB;AAAA,IACjC;AAKA,UAAM,UAAU,uBAAuB,OAAU;AACjD,WAAO,KAAK,QAA6B,yBAAyB,SAAS;AAAA,MACzE;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,gBAAgB,QAA+C;AACnE,UAAM,MAAM,GAAG,KAAK,UAAU,0BAA0B,MAAM;AAC9D,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,GAAM;AAE7D,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS,EAAE,eAAe,UAAU,KAAK,MAAM,GAAG;AAAA,QAClD,QAAQ,WAAW;AAAA,MACrB,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAY,MAAM,SAAS,KAAK;AACtC,cAAM,IAAI;AAAA,UACR,QAAQ,SAAS,MAAM,KAAK,UAAU,MAAM,GAAG,GAAG,CAAC;AAAA,QACrD;AAAA,MACF;AAEA,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B,SAAS,OAAO;AACd,UAAI,iBAAiB,aAAa;AAChC,cAAM;AAAA,MACR;AACA,UAAI,iBAAiB,OAAO;AAC1B,YAAI,MAAM,SAAS,cAAc;AAC/B,gBAAM,IAAI,YAAY,iCAAiC;AAAA,QACzD;AACA,cAAM,IAAI,YAAY,MAAM,OAAO;AAAA,MACrC;AACA,YAAM,IAAI,YAAY,wBAAwB;AAAA,IAChD,UAAE;AACA,mBAAa,SAAS;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAY,gBAAmD;AACnE,UAAM,MAAM,GAAG,KAAK,UAAU,4BAA4B,cAAc;AACxE,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,GAAM;AAE7D,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS,EAAE,eAAe,UAAU,KAAK,MAAM,GAAG;AAAA,QAClD,QAAQ,WAAW;AAAA,MACrB,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAY,MAAM,SAAS,KAAK;AACtC,cAAM,IAAI;AAAA,UACR,QAAQ,SAAS,MAAM,KAAK,UAAU,MAAM,GAAG,GAAG,CAAC;AAAA,QACrD;AAAA,MACF;AAEA,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B,SAAS,OAAO;AACd,UAAI,iBAAiB,aAAa;AAChC,cAAM;AAAA,MACR;AACA,UAAI,iBAAiB,OAAO;AAC1B,YAAI,MAAM,SAAS,cAAc;AAC/B,gBAAM,IAAI,YAAY,iCAAiC;AAAA,QACzD;AACA,cAAM,IAAI,YAAY,MAAM,OAAO;AAAA,MACrC;AACA,YAAM,IAAI,YAAY,wBAAwB;AAAA,IAChD,UAAE;AACA,mBAAa,SAAS;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eAAe,WAAoD;AACvE,WAAO,KAAK;AAAA,MACV;AAAA,MACA,EAAE,UAAU;AAAA,MACZ,EAAE,SAAS,IAAO;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,qBACJ,WACA,SACA,eACmC;AACnC,WAAO,KAAK;AAAA,MACV;AAAA,MACA,EAAE,WAAW,SAAS,cAAc;AAAA,MACpC,EAAE,SAAS,IAAO;AAAA,IACpB;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,qBAAqB,SAAgC;AACzD,UAAM,KAAK;AAAA,MACT;AAAA,MACA,EAAE,QAAQ;AAAA,MACV,EAAE,SAAS,IAAO;AAAA,IACpB;AAAA,EACF;AACF;;;ACvZA,IAAI,yBACF;AACF,IAAI,WAAW;AAUR,SAAS,+BACd,KACM;AACN,MAAI,CAAC,wBAAwB;AAC3B,6BAAyB;AAAA,EAC3B;AACA,aAAW;AACb;AAYO,SAAS,+BAAqC;AACnD,MAAI,CAAC,wBAAwB;AAC3B,YAAQ;AAAA,MACN;AAAA,IACF;AAAA,EACF;AACF;AAEO,IAAM,qBACX,OAAO,YAAY,eAAe,QAAQ,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,EAKhD;AAAA;AAAA,IAEE,CAAC,QAAQ,aAAa,EAAE,KAAK,GAAG;AAAA,IAE/B;AAAA,IACC,CAAC,QAEK;AACJ,qCAA+B,IAAI,iBAAiB;AAAA,IACtD;AAAA,EACF,EACC,MAAM,MAAM;AAAA,EAAC,CAAC;AAAA,IACjB,QAAQ,QAAQ,GACpB,KAAK,MAAM;AACX,aAAW;AACb,CAAC;AAEM,SAAS,yBAAkC;AAChD,SAAO;AACT;AAEO,SAAS,0BAA8D;AAC5E,SAAO,yBACF,IAAI,uBAAuB,IAC5B;AACN;;;AC1BA,IAAI,uBACF;AAEK,IAAM,qBAAoC,kBAAkB,KAAK,MAAM;AAC5E,yBAAuB,wBAA8C;AACvE,CAAC;AAGM,SAAS,mBAAyC;AACvD,SAAO,sBAAsB,SAAS,KAAK;AAC7C;AAGO,SAAS,qBAAwB,KAAoB,IAAgB;AAC1E,MAAI,sBAAsB;AACxB,WAAO,qBAAqB,IAAI,KAAK,EAAE;AAAA,EACzC;AACA,SAAO,GAAG;AACZ;;;ACzFA,OAAO,eAAe;AAkBtB,IAAM,uBAAuB;AAE7B,SAAS,cAAc,OAAwB;AAC7C,MAAI;AACF,UAAM,WAAY,OAA+C,aAC7D;AACJ,QAAI,YAAY,aAAa,UAAU;AACrC,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO,OAAO;AAChB;AAEA,SAAS,mBAAmB,OAAgB,QAAiC;AAC3E,MAAI;AACJ,MAAI;AACF,cAAU,oBAAoB,cAAc,KAAK,CAAC,KAAK,MAAM;AAAA,EAC/D,QAAQ;AACN,cAAU,oBAAoB,MAAM;AAAA,EACtC;AACA,SAAO,EAAE,MAAM,QAAQ;AACzB;AA+BO,SAAS,eAAe,OAAiC;AAC9D,MAAI;AACF,UAAM,EAAE,MAAM,KAAK,IAAI,UAAU,UAAU,KAAK;AAEhD,QAAI;AACJ,QAAI;AACF,aAAO,KAAK,UAAU,IAAI,EAAE;AAAA,IAC9B,QAAQ;AACN,aAAO,mBAAmB,OAAO,kCAAkC;AAAA,IACrE;AACA,QAAI,OAAO,sBAAsB;AAC/B,aAAO,mBAAmB,OAAO,aAAa,IAAI,QAAQ;AAAA,IAC5D;AAEA,WAAO,OAAO,EAAE,MAAM,KAAK,IAAI,EAAE,KAAK;AAAA,EACxC,QAAQ;AACN,QAAI;AACF,aAAO,EAAE,MAAM,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC,EAAE;AAAA,IACnD,QAAQ;AACN,aAAO,mBAAmB,OAAO,uBAAuB;AAAA,IAC1D;AAAA,EACF;AACF;AAeO,SAAS,iBAAiB,YAAsC;AACrE,MAAI,WAAW,SAAS,QAAW;AAEjC,WAAO,WAAW;AAAA,EACpB;AAKA,SAAO,UAAU,YAAY;AAAA,IAC3B,MAAM,WAAW;AAAA,IACjB,MAAM,WAAW;AAAA,EACnB,CAAC;AACH;","names":[]}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
getReplayContext,
|
|
9
9
|
isAsyncStorageInitDone,
|
|
10
10
|
serializeValue
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-SKJWF5VX.js";
|
|
12
12
|
|
|
13
13
|
// src/claudeAgentSdk.ts
|
|
14
14
|
function nowIso() {
|
|
@@ -772,6 +772,31 @@ async function runFunctionWithBaml(bamlSource, inputs, providers, envVars) {
|
|
|
772
772
|
};
|
|
773
773
|
}
|
|
774
774
|
|
|
775
|
+
// src/dbSnapshot.ts
|
|
776
|
+
var SUPPORTED_PROVIDERS = [
|
|
777
|
+
"neon",
|
|
778
|
+
"ardent",
|
|
779
|
+
"dolt",
|
|
780
|
+
"gfs",
|
|
781
|
+
"mongodb-atlas",
|
|
782
|
+
"dynamodb",
|
|
783
|
+
"snowflake",
|
|
784
|
+
"bigquery"
|
|
785
|
+
];
|
|
786
|
+
function validateDbSnapshotConfig(config) {
|
|
787
|
+
if (!SUPPORTED_PROVIDERS.includes(config.provider)) {
|
|
788
|
+
throw new BitfabError(
|
|
789
|
+
`dbSnapshot.provider "${config.provider}" is not supported. Supported providers: ${SUPPORTED_PROVIDERS.join(", ")}.`
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
function buildSnapshotRef(config, sdkWallClockBeforeFn) {
|
|
794
|
+
return {
|
|
795
|
+
provider: config.provider,
|
|
796
|
+
sdkWallClockBeforeFn
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
775
800
|
// src/langgraph.ts
|
|
776
801
|
var LANGSMITH_HIDDEN_TAG = "langsmith:hidden";
|
|
777
802
|
var LANGGRAPH_METADATA_KEYS = [
|
|
@@ -1246,6 +1271,81 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
1246
1271
|
}
|
|
1247
1272
|
};
|
|
1248
1273
|
|
|
1274
|
+
// src/replayEnvironment.ts
|
|
1275
|
+
var ReplayEnvironment = class {
|
|
1276
|
+
/**
|
|
1277
|
+
* The per-trace branch URL for the item currently being replayed.
|
|
1278
|
+
* Throws if read outside a replay item.
|
|
1279
|
+
*/
|
|
1280
|
+
get databaseUrl() {
|
|
1281
|
+
return this.require().databaseUrl;
|
|
1282
|
+
}
|
|
1283
|
+
/** When the per-trace branch URL stops being valid. ISO-8601. */
|
|
1284
|
+
get expiresAt() {
|
|
1285
|
+
return this.require().expiresAt;
|
|
1286
|
+
}
|
|
1287
|
+
/** Deep link to the branch in the provider console, if available. */
|
|
1288
|
+
get providerConsoleUrl() {
|
|
1289
|
+
return this.require().providerConsoleUrl;
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* True if the branch is read-only. Customer code can use this to skip
|
|
1293
|
+
* write operations during replay when the provider returned a read-only
|
|
1294
|
+
* lease.
|
|
1295
|
+
*/
|
|
1296
|
+
get readOnly() {
|
|
1297
|
+
return this.require().readOnly;
|
|
1298
|
+
}
|
|
1299
|
+
/** The historical trace ID that produced the input for this replay item. */
|
|
1300
|
+
get traceId() {
|
|
1301
|
+
return this.require().traceId;
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* How the resolver pinned this branch.
|
|
1305
|
+
* - "timestamp": snapshot at SDK wall clock; bounded by replication lag.
|
|
1306
|
+
* - "lsn": customer LSN mapped to a replica snapshot (future).
|
|
1307
|
+
*/
|
|
1308
|
+
get precision() {
|
|
1309
|
+
return this.require().precision;
|
|
1310
|
+
}
|
|
1311
|
+
/** True when read inside a replay item that has a resolved branch. */
|
|
1312
|
+
get active() {
|
|
1313
|
+
return this.read() !== null;
|
|
1314
|
+
}
|
|
1315
|
+
/** Non-throwing variant for callers that handle the inactive case. */
|
|
1316
|
+
snapshot() {
|
|
1317
|
+
return this.read();
|
|
1318
|
+
}
|
|
1319
|
+
read() {
|
|
1320
|
+
const ctx = getReplayContext();
|
|
1321
|
+
if (!ctx?.dbBranchLease) {
|
|
1322
|
+
return null;
|
|
1323
|
+
}
|
|
1324
|
+
const traceId = ctx.sourceBitfabTraceId ?? ctx.inputSourceTraceId;
|
|
1325
|
+
if (!traceId) {
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
const lease = ctx.dbBranchLease;
|
|
1329
|
+
return {
|
|
1330
|
+
databaseUrl: lease.databaseUrl,
|
|
1331
|
+
expiresAt: lease.expiresAt,
|
|
1332
|
+
providerConsoleUrl: lease.providerConsoleUrl,
|
|
1333
|
+
readOnly: lease.readOnly,
|
|
1334
|
+
traceId,
|
|
1335
|
+
precision: lease.precision
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
require() {
|
|
1339
|
+
const snapshot = this.read();
|
|
1340
|
+
if (!snapshot) {
|
|
1341
|
+
throw new Error(
|
|
1342
|
+
"ReplayEnvironment accessed outside of a replay item. Pass it to bitfab.replay({ environment }) and only read it inside the replayed function."
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
return snapshot;
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
|
|
1249
1349
|
// src/tracing.ts
|
|
1250
1350
|
var BitfabOpenAITracingProcessor = class {
|
|
1251
1351
|
/**
|
|
@@ -1737,6 +1837,10 @@ var Bitfab = class {
|
|
|
1737
1837
|
this.enabled = enabled;
|
|
1738
1838
|
}
|
|
1739
1839
|
this.bamlClient = config.bamlClient ?? null;
|
|
1840
|
+
if (config.dbSnapshot) {
|
|
1841
|
+
validateDbSnapshotConfig(config.dbSnapshot);
|
|
1842
|
+
}
|
|
1843
|
+
this.dbSnapshot = config.dbSnapshot;
|
|
1740
1844
|
this.httpClient = new HttpClient({
|
|
1741
1845
|
apiKey: this.apiKey,
|
|
1742
1846
|
serviceUrl: this.serviceUrl,
|
|
@@ -2043,6 +2147,7 @@ var Bitfab = class {
|
|
|
2043
2147
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2044
2148
|
if (isRootSpan && !activeTraceStates.has(traceId)) {
|
|
2045
2149
|
const replayCtxAtRoot = getReplayContext();
|
|
2150
|
+
const dbSnapshotRef = self.dbSnapshot ? buildSnapshotRef(self.dbSnapshot, startedAt) : void 0;
|
|
2046
2151
|
activeTraceStates.set(traceId, {
|
|
2047
2152
|
traceId,
|
|
2048
2153
|
startedAt,
|
|
@@ -2052,7 +2157,8 @@ var Bitfab = class {
|
|
|
2052
2157
|
},
|
|
2053
2158
|
...replayCtxAtRoot?.inputSourceTraceId && {
|
|
2054
2159
|
inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
|
|
2055
|
-
}
|
|
2160
|
+
},
|
|
2161
|
+
...dbSnapshotRef && { dbSnapshotRef }
|
|
2056
2162
|
});
|
|
2057
2163
|
pendingSpanPromises.set(traceId, []);
|
|
2058
2164
|
}
|
|
@@ -2072,6 +2178,7 @@ var Bitfab = class {
|
|
|
2072
2178
|
try {
|
|
2073
2179
|
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2074
2180
|
const replayCtx = getReplayContext();
|
|
2181
|
+
const dbSnapshotRefForSpan = isRootSpan ? activeTraceStates.get(traceId)?.dbSnapshotRef : void 0;
|
|
2075
2182
|
const spanPromise = self.sendWrapperSpan({
|
|
2076
2183
|
...baseSpanParams,
|
|
2077
2184
|
...params,
|
|
@@ -2081,6 +2188,9 @@ var Bitfab = class {
|
|
|
2081
2188
|
...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
|
|
2082
2189
|
...replayCtx?.inputSourceSpanId && {
|
|
2083
2190
|
inputSourceSpanId: replayCtx.inputSourceSpanId
|
|
2191
|
+
},
|
|
2192
|
+
...dbSnapshotRefForSpan && {
|
|
2193
|
+
dbSnapshotRef: dbSnapshotRefForSpan
|
|
2084
2194
|
}
|
|
2085
2195
|
});
|
|
2086
2196
|
if (isRootSpan) {
|
|
@@ -2101,7 +2211,8 @@ var Bitfab = class {
|
|
|
2101
2211
|
metadata: traceState?.metadata,
|
|
2102
2212
|
contexts: traceState?.contexts ?? [],
|
|
2103
2213
|
testRunId: traceState?.testRunId,
|
|
2104
|
-
inputSourceTraceId: traceState?.inputSourceTraceId
|
|
2214
|
+
inputSourceTraceId: traceState?.inputSourceTraceId,
|
|
2215
|
+
dbSnapshotRef: traceState?.dbSnapshotRef
|
|
2105
2216
|
});
|
|
2106
2217
|
activeTraceStates.delete(traceId);
|
|
2107
2218
|
} else {
|
|
@@ -2262,6 +2373,9 @@ var Bitfab = class {
|
|
|
2262
2373
|
if (params.inputSourceTraceId) {
|
|
2263
2374
|
rawTrace.input_source_trace_id = params.inputSourceTraceId;
|
|
2264
2375
|
}
|
|
2376
|
+
if (params.dbSnapshotRef) {
|
|
2377
|
+
rawTrace.db_snapshot_ref = params.dbSnapshotRef;
|
|
2378
|
+
}
|
|
2265
2379
|
this.httpClient.sendExternalTrace({
|
|
2266
2380
|
type: "sdk-function",
|
|
2267
2381
|
source: "typescript-sdk-function",
|
|
@@ -2304,7 +2418,10 @@ var Bitfab = class {
|
|
|
2304
2418
|
...params.contexts && params.contexts.length > 0 && {
|
|
2305
2419
|
contexts: params.contexts
|
|
2306
2420
|
},
|
|
2307
|
-
...params.prompt !== void 0 && { prompt: params.prompt }
|
|
2421
|
+
...params.prompt !== void 0 && { prompt: params.prompt },
|
|
2422
|
+
...params.dbSnapshotRef && {
|
|
2423
|
+
db_snapshot_ref: params.dbSnapshotRef
|
|
2424
|
+
}
|
|
2308
2425
|
}
|
|
2309
2426
|
};
|
|
2310
2427
|
if (params.parentSpanId) {
|
|
@@ -2337,7 +2454,7 @@ var Bitfab = class {
|
|
|
2337
2454
|
* @returns ReplayResult with items, testRunId, and testRunUrl
|
|
2338
2455
|
*/
|
|
2339
2456
|
async replay(traceFunctionKey, fn, options) {
|
|
2340
|
-
const { replay: doReplay } = await import("./replay-
|
|
2457
|
+
const { replay: doReplay } = await import("./replay-HGU5YAOK.js");
|
|
2341
2458
|
return doReplay(
|
|
2342
2459
|
this.httpClient,
|
|
2343
2460
|
this.serviceUrl,
|
|
@@ -2347,6 +2464,12 @@ var Bitfab = class {
|
|
|
2347
2464
|
);
|
|
2348
2465
|
}
|
|
2349
2466
|
};
|
|
2467
|
+
/**
|
|
2468
|
+
* Per-trace environment for `replay({ environment })`. Construct one,
|
|
2469
|
+
* pass it to replay, and read `env.databaseUrl` inside the replayed
|
|
2470
|
+
* function to pick up the per-trace branch URL.
|
|
2471
|
+
*/
|
|
2472
|
+
Bitfab.ReplayEnvironment = ReplayEnvironment;
|
|
2350
2473
|
var BitfabFunction = class {
|
|
2351
2474
|
constructor(client, traceFunctionKey) {
|
|
2352
2475
|
this.client = client;
|
|
@@ -2398,11 +2521,13 @@ var BitfabFunction = class {
|
|
|
2398
2521
|
|
|
2399
2522
|
export {
|
|
2400
2523
|
BitfabClaudeAgentHandler,
|
|
2524
|
+
SUPPORTED_PROVIDERS,
|
|
2401
2525
|
BitfabLangGraphCallbackHandler,
|
|
2526
|
+
ReplayEnvironment,
|
|
2402
2527
|
BitfabOpenAITracingProcessor,
|
|
2403
2528
|
getCurrentSpan,
|
|
2404
2529
|
getCurrentTrace,
|
|
2405
2530
|
Bitfab,
|
|
2406
2531
|
BitfabFunction
|
|
2407
2532
|
};
|
|
2408
|
-
//# sourceMappingURL=chunk-
|
|
2533
|
+
//# sourceMappingURL=chunk-XUW46356.js.map
|