bitfab 0.14.0 → 0.15.0
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-QT7HWOKU.js +131 -0
- package/dist/chunk-QT7HWOKU.js.map +1 -0
- package/dist/{chunk-RMQX546G.js → chunk-YPG3XIG4.js} +369 -12
- package/dist/chunk-YPG3XIG4.js.map +1 -0
- package/dist/index.cjs +405 -392
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -6
- package/dist/node.cjs +405 -392
- package/dist/node.cjs.map +1 -1
- package/dist/node.js +5 -5
- package/dist/{replay-LNP2K3DN.js → replay-3MQS22GS.js} +43 -15
- package/dist/replay-3MQS22GS.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-OW2EJK7T.js +0 -470
- package/dist/chunk-OW2EJK7T.js.map +0 -1
- package/dist/chunk-RMQX546G.js.map +0 -1
- package/dist/replay-LNP2K3DN.js.map +0 -1
package/dist/node.js
CHANGED
|
@@ -13,19 +13,19 @@ import {
|
|
|
13
13
|
BitfabFunction,
|
|
14
14
|
BitfabLangGraphCallbackHandler,
|
|
15
15
|
BitfabOpenAITracingProcessor,
|
|
16
|
+
DEFAULT_SERVICE_URL,
|
|
16
17
|
ReplayEnvironment,
|
|
17
18
|
SUPPORTED_PROVIDERS,
|
|
19
|
+
__version__,
|
|
20
|
+
flushTraces,
|
|
18
21
|
getCurrentSpan,
|
|
19
22
|
getCurrentTrace
|
|
20
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-YPG3XIG4.js";
|
|
21
24
|
import {
|
|
22
25
|
BitfabError,
|
|
23
|
-
DEFAULT_SERVICE_URL,
|
|
24
|
-
__version__,
|
|
25
26
|
assertAsyncStorageRegistered,
|
|
26
|
-
flushTraces,
|
|
27
27
|
registerAsyncLocalStorageClass
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-QT7HWOKU.js";
|
|
29
29
|
|
|
30
30
|
// src/asyncStorageNode.ts
|
|
31
31
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BitfabError,
|
|
3
3
|
deserializeValue,
|
|
4
|
-
flushTraces,
|
|
5
4
|
replayContextReady,
|
|
6
5
|
runWithReplayContext
|
|
7
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QT7HWOKU.js";
|
|
8
7
|
|
|
9
8
|
// src/replay.ts
|
|
10
9
|
function deserializeInputs(spanData) {
|
|
@@ -62,6 +61,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
|
|
|
62
61
|
let result;
|
|
63
62
|
let error = null;
|
|
64
63
|
const replayedTraceId = crypto.randomUUID();
|
|
64
|
+
const pendingPersistence = [];
|
|
65
65
|
try {
|
|
66
66
|
const span = await httpClient.getExternalSpan(serverItem.externalSpanId);
|
|
67
67
|
const spanData = span.rawData?.span_data ?? {};
|
|
@@ -84,7 +84,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
|
|
|
84
84
|
mockTree,
|
|
85
85
|
callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
|
|
86
86
|
mockStrategy,
|
|
87
|
-
dbBranchLease: lease
|
|
87
|
+
dbBranchLease: lease,
|
|
88
|
+
pendingPersistence
|
|
88
89
|
},
|
|
89
90
|
() => fn(...inputs)
|
|
90
91
|
);
|
|
@@ -92,6 +93,7 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy,
|
|
|
92
93
|
} catch (e) {
|
|
93
94
|
error = e instanceof Error ? e.message : String(e);
|
|
94
95
|
} finally {
|
|
96
|
+
await Promise.allSettled(pendingPersistence);
|
|
95
97
|
if (lease) {
|
|
96
98
|
try {
|
|
97
99
|
await httpClient.releaseDbBranchLease(lease.neonBranchId);
|
|
@@ -179,20 +181,46 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
|
179
181
|
)
|
|
180
182
|
);
|
|
181
183
|
const resultItems = await mapWithConcurrency(tasks, maxConcurrency);
|
|
182
|
-
await
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const completeResult = await httpClient.completeReplay(testRunId);
|
|
186
|
-
serverTraceIds = completeResult.traceIds ?? {};
|
|
187
|
-
} catch (e) {
|
|
184
|
+
const completeResult = await httpClient.completeReplay(testRunId);
|
|
185
|
+
const serverTraceIds = completeResult.traceIds;
|
|
186
|
+
if (serverTraceIds === void 0) {
|
|
188
187
|
try {
|
|
189
|
-
console.
|
|
188
|
+
console.warn(
|
|
189
|
+
"Bitfab: server did not return replay trace IDs; item.traceId will be null (server upgrade required for verdict persistence)"
|
|
190
|
+
);
|
|
190
191
|
} catch {
|
|
191
192
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
for (const item of resultItems) {
|
|
194
|
+
item.traceId = null;
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
const missing = [];
|
|
198
|
+
let completedCount = 0;
|
|
199
|
+
for (const item of resultItems) {
|
|
200
|
+
if (item.traceId) {
|
|
201
|
+
const mapped = serverTraceIds[item.traceId];
|
|
202
|
+
if (item.error === null) {
|
|
203
|
+
completedCount += 1;
|
|
204
|
+
if (mapped === void 0) {
|
|
205
|
+
missing.push(item.traceId);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
item.traceId = mapped ?? null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (missing.length > 0) {
|
|
212
|
+
const serverCount = completeResult.traceCount !== void 0 ? ` The server persisted ${completeResult.traceCount} trace(s) for this run.` : "";
|
|
213
|
+
if (missing.length === completedCount) {
|
|
214
|
+
throw new BitfabError(
|
|
215
|
+
`Replay completed but the server has no persisted trace for any of the ${completedCount} completed item(s) (testRunId ${testRunId}).${serverCount} Trace uploads were awaited, so either the uploads failed (check for "Bitfab: Failed to create" errors above) or the replayed function is not wrapped with withSpan.`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
console.error(
|
|
220
|
+
`Bitfab: server has no persisted trace for ${missing.length} of ${completedCount} completed replay item(s) (testRunId ${testRunId}).${serverCount} Their traceId is null and verdicts cannot be persisted for them. Missing: ${missing.join(", ")}`
|
|
221
|
+
);
|
|
222
|
+
} catch {
|
|
223
|
+
}
|
|
196
224
|
}
|
|
197
225
|
}
|
|
198
226
|
return {
|
|
@@ -204,4 +232,4 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
|
|
|
204
232
|
export {
|
|
205
233
|
replay
|
|
206
234
|
};
|
|
207
|
-
//# sourceMappingURL=replay-
|
|
235
|
+
//# sourceMappingURL=replay-3MQS22GS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/replay.ts"],"sourcesContent":["/**\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 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). Mutually\n * exclusive with `traceIds` — an explicit ID list already determines how\n * many traces replay, so passing both throws.\n */\n limit?: number\n /** Optional list of specific trace IDs to replay (max 100). */\n traceIds?: 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 * - \"none\": everything runs real code (default)\n * - \"all\": every child withSpan returns historical output\n * - \"marked\": only spans tagged with { mockOnReplay: true } in SpanOptions are mocked\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\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 /** Token usage from the original trace, or null if not captured. */\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\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 tokens: TokenUsage | 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): 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 = crypto.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 // Build mock tree when mocking is active\n let mockTree: MockTree | undefined\n if (mockStrategy === \"all\" || mockStrategy === \"marked\") {\n const treeResponse = await httpClient.getSpanTree(\n serverItem.externalSpanId,\n )\n mockTree = buildMockTree(treeResponse.root)\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 tokens: serverItem.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): 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 results[index] = await tasks[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 throw new BitfabError(\n \"Pass either limit or traceIds, not both: an explicit trace ID list already determines how many traces replay.\",\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?.codeChangeDescription,\n options?.codeChangeFiles,\n options?.environment !== undefined, // includeDbBranchLease\n options?.experimentGroupId,\n )\n\n const mockStrategy: MockStrategy = options?.mock ?? \"none\"\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 ),\n )\n const resultItems = await mapWithConcurrency(tasks, maxConcurrency)\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\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 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/package.json
CHANGED
package/dist/chunk-OW2EJK7T.js
DELETED
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
// src/version.generated.ts
|
|
2
|
-
var __version__ = "0.14.0";
|
|
3
|
-
|
|
4
|
-
// src/constants.ts
|
|
5
|
-
var DEFAULT_SERVICE_URL = "https://bitfab.ai";
|
|
6
|
-
|
|
7
|
-
// src/errors.ts
|
|
8
|
-
var BitfabError = class extends Error {
|
|
9
|
-
constructor(message, url) {
|
|
10
|
-
super(message);
|
|
11
|
-
this.url = url;
|
|
12
|
-
this.name = "BitfabError";
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
// src/http.ts
|
|
17
|
-
var pendingTracePromises = /* @__PURE__ */ new Set();
|
|
18
|
-
function awaitOnExit(promise) {
|
|
19
|
-
pendingTracePromises.add(promise);
|
|
20
|
-
void promise.finally(() => {
|
|
21
|
-
pendingTracePromises.delete(promise);
|
|
22
|
-
}).catch(() => {
|
|
23
|
-
});
|
|
24
|
-
return promise;
|
|
25
|
-
}
|
|
26
|
-
async function flushTraces(timeoutMs = 5e3) {
|
|
27
|
-
if (pendingTracePromises.size === 0) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
await Promise.race([
|
|
31
|
-
Promise.allSettled(Array.from(pendingTracePromises)),
|
|
32
|
-
new Promise((resolve) => setTimeout(resolve, timeoutMs))
|
|
33
|
-
]);
|
|
34
|
-
}
|
|
35
|
-
if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) {
|
|
36
|
-
let isFlushing = false;
|
|
37
|
-
process.on("beforeExit", () => {
|
|
38
|
-
if (pendingTracePromises.size > 0 && !isFlushing) {
|
|
39
|
-
isFlushing = true;
|
|
40
|
-
Promise.allSettled(
|
|
41
|
-
Array.from(pendingTracePromises).map(
|
|
42
|
-
(p) => p.catch(() => {
|
|
43
|
-
})
|
|
44
|
-
)
|
|
45
|
-
).then(() => {
|
|
46
|
-
isFlushing = false;
|
|
47
|
-
}).catch(() => {
|
|
48
|
-
isFlushing = false;
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
var HttpClient = class {
|
|
54
|
-
constructor(config) {
|
|
55
|
-
this.apiKey = config.apiKey;
|
|
56
|
-
this.serviceUrl = config.serviceUrl;
|
|
57
|
-
this.timeout = config.timeout ?? 12e4;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Make an HTTP request to the Bitfab API. Defaults to POST; pass
|
|
61
|
-
* `options.method` to use a different verb (e.g. "PATCH").
|
|
62
|
-
*
|
|
63
|
-
* @param endpoint - The API endpoint (without base URL)
|
|
64
|
-
* @param payload - The request body
|
|
65
|
-
* @param options - Optional request options
|
|
66
|
-
* @returns The parsed JSON response
|
|
67
|
-
* @throws {BitfabError} If the request fails
|
|
68
|
-
*/
|
|
69
|
-
async request(endpoint, payload, options) {
|
|
70
|
-
const url = `${this.serviceUrl}${endpoint}`;
|
|
71
|
-
const timeout = options?.timeout ?? this.timeout;
|
|
72
|
-
const method = options?.method ?? "POST";
|
|
73
|
-
const controller = new AbortController();
|
|
74
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
75
|
-
let body;
|
|
76
|
-
let serializationError;
|
|
77
|
-
try {
|
|
78
|
-
body = JSON.stringify(payload);
|
|
79
|
-
} catch (error) {
|
|
80
|
-
serializationError = error instanceof Error ? error.message : String(error);
|
|
81
|
-
body = JSON.stringify({
|
|
82
|
-
...Object.fromEntries(
|
|
83
|
-
Object.entries(payload).filter(
|
|
84
|
-
([, v]) => typeof v === "string" || typeof v === "number"
|
|
85
|
-
)
|
|
86
|
-
),
|
|
87
|
-
rawSpan: {},
|
|
88
|
-
errors: [
|
|
89
|
-
{ source: "sdk", step: "json_serialize", error: serializationError }
|
|
90
|
-
]
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
try {
|
|
94
|
-
const response = await fetch(url, {
|
|
95
|
-
method,
|
|
96
|
-
headers: {
|
|
97
|
-
"Content-Type": "application/json",
|
|
98
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
99
|
-
},
|
|
100
|
-
body,
|
|
101
|
-
signal: controller.signal
|
|
102
|
-
});
|
|
103
|
-
if (!response.ok) {
|
|
104
|
-
const errorText = await response.text();
|
|
105
|
-
throw new BitfabError(
|
|
106
|
-
`HTTP ${response.status}: ${errorText.slice(0, 500)}`
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
const result = await response.json();
|
|
110
|
-
if (result.error) {
|
|
111
|
-
if (result.url) {
|
|
112
|
-
throw new BitfabError(
|
|
113
|
-
`${result.error} Configure it at: ${this.serviceUrl}${result.url}`,
|
|
114
|
-
result.url
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
throw new BitfabError(result.error);
|
|
118
|
-
}
|
|
119
|
-
return result;
|
|
120
|
-
} catch (error) {
|
|
121
|
-
if (error instanceof BitfabError) {
|
|
122
|
-
throw error;
|
|
123
|
-
}
|
|
124
|
-
if (error instanceof Error) {
|
|
125
|
-
if (error.name === "AbortError") {
|
|
126
|
-
throw new BitfabError(`Request timed out after ${timeout}ms`);
|
|
127
|
-
}
|
|
128
|
-
throw new BitfabError(error.message);
|
|
129
|
-
}
|
|
130
|
-
throw new BitfabError("Unknown error occurred");
|
|
131
|
-
} finally {
|
|
132
|
-
clearTimeout(timeoutId);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Look up a function by name.
|
|
137
|
-
* Blocks until complete - needed for function execution.
|
|
138
|
-
*/
|
|
139
|
-
async lookupFunction(name) {
|
|
140
|
-
return this.request("/api/sdk/functions/lookup", { name });
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Send an internal trace (from BAML execution).
|
|
144
|
-
* Fire-and-forget with awaitOnExit - doesn't block the caller.
|
|
145
|
-
*/
|
|
146
|
-
sendInternalTrace(functionId, payload) {
|
|
147
|
-
void awaitOnExit(
|
|
148
|
-
this.request(`/api/sdk/functions/${functionId}/traces`, {
|
|
149
|
-
...payload,
|
|
150
|
-
sdkVersion: __version__
|
|
151
|
-
})
|
|
152
|
-
).catch((error) => {
|
|
153
|
-
try {
|
|
154
|
-
console.error("Bitfab: Failed to create trace:", error);
|
|
155
|
-
} catch {
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Send an external span (from withSpan wrapper or OpenAI tracing).
|
|
161
|
-
* Fire-and-forget with awaitOnExit - doesn't block the caller.
|
|
162
|
-
* Returns the tracked promise so callers can optionally await it.
|
|
163
|
-
*/
|
|
164
|
-
sendExternalSpan(payload) {
|
|
165
|
-
return awaitOnExit(
|
|
166
|
-
this.request("/api/sdk/externalSpans", {
|
|
167
|
-
...payload,
|
|
168
|
-
sdkVersion: __version__
|
|
169
|
-
})
|
|
170
|
-
).catch((error) => {
|
|
171
|
-
try {
|
|
172
|
-
console.error("Bitfab: Failed to create external span:", error);
|
|
173
|
-
} catch {
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Send an external trace (from OpenAI tracing).
|
|
179
|
-
* Fire-and-forget with awaitOnExit - doesn't block the caller.
|
|
180
|
-
*/
|
|
181
|
-
sendExternalTrace(payload) {
|
|
182
|
-
void awaitOnExit(
|
|
183
|
-
this.request("/api/sdk/externalTraces", {
|
|
184
|
-
...payload,
|
|
185
|
-
sdkVersion: __version__
|
|
186
|
-
})
|
|
187
|
-
).catch((error) => {
|
|
188
|
-
try {
|
|
189
|
-
console.error("Bitfab: Failed to create external trace:", error);
|
|
190
|
-
} catch {
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Partial update of an existing external trace identified by sourceTraceId.
|
|
196
|
-
* Used by the detached `client.getTrace(id)` handle. Fire-and-forget;
|
|
197
|
-
* returns a tracked promise that callers may optionally await.
|
|
198
|
-
*/
|
|
199
|
-
patchTrace(sourceTraceId, payload) {
|
|
200
|
-
const endpoint = `/api/sdk/externalTraces/${encodeURIComponent(sourceTraceId)}`;
|
|
201
|
-
return awaitOnExit(
|
|
202
|
-
this.request(endpoint, payload, { method: "PATCH" })
|
|
203
|
-
).catch((error) => {
|
|
204
|
-
try {
|
|
205
|
-
console.error("Bitfab: Failed to patch trace:", error);
|
|
206
|
-
} catch {
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Start a replay session by fetching historical traces.
|
|
212
|
-
* Blocking call — creates a test run and returns lightweight item references.
|
|
213
|
-
*/
|
|
214
|
-
async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId) {
|
|
215
|
-
const payload = { traceFunctionKey };
|
|
216
|
-
if (limit !== void 0) {
|
|
217
|
-
payload.limit = limit;
|
|
218
|
-
}
|
|
219
|
-
if (traceIds) {
|
|
220
|
-
payload.traceIds = traceIds;
|
|
221
|
-
}
|
|
222
|
-
if (codeChangeDescription !== void 0) {
|
|
223
|
-
payload.codeChangeDescription = codeChangeDescription;
|
|
224
|
-
}
|
|
225
|
-
if (codeChangeFiles !== void 0) {
|
|
226
|
-
payload.codeChangeFiles = codeChangeFiles;
|
|
227
|
-
}
|
|
228
|
-
if (includeDbBranchLease) {
|
|
229
|
-
payload.includeDbBranchLease = true;
|
|
230
|
-
}
|
|
231
|
-
if (experimentGroupId !== void 0) {
|
|
232
|
-
payload.experimentGroupId = experimentGroupId;
|
|
233
|
-
}
|
|
234
|
-
const timeout = includeDbBranchLease ? 18e4 : 3e4;
|
|
235
|
-
return this.request("/api/sdk/replay/start", payload, {
|
|
236
|
-
timeout
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Fetch an external span by ID.
|
|
241
|
-
* Blocking GET request.
|
|
242
|
-
*/
|
|
243
|
-
async getExternalSpan(spanId) {
|
|
244
|
-
const url = `${this.serviceUrl}/api/sdk/externalSpans/${spanId}`;
|
|
245
|
-
const controller = new AbortController();
|
|
246
|
-
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
247
|
-
try {
|
|
248
|
-
const response = await fetch(url, {
|
|
249
|
-
method: "GET",
|
|
250
|
-
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
251
|
-
signal: controller.signal
|
|
252
|
-
});
|
|
253
|
-
if (!response.ok) {
|
|
254
|
-
const errorText = await response.text();
|
|
255
|
-
throw new BitfabError(
|
|
256
|
-
`HTTP ${response.status}: ${errorText.slice(0, 500)}`
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
return await response.json();
|
|
260
|
-
} catch (error) {
|
|
261
|
-
if (error instanceof BitfabError) {
|
|
262
|
-
throw error;
|
|
263
|
-
}
|
|
264
|
-
if (error instanceof Error) {
|
|
265
|
-
if (error.name === "AbortError") {
|
|
266
|
-
throw new BitfabError("Request timed out after 30000ms");
|
|
267
|
-
}
|
|
268
|
-
throw new BitfabError(error.message);
|
|
269
|
-
}
|
|
270
|
-
throw new BitfabError("Unknown error occurred");
|
|
271
|
-
} finally {
|
|
272
|
-
clearTimeout(timeoutId);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Fetch the span tree for a root span.
|
|
277
|
-
* Blocking GET request.
|
|
278
|
-
*/
|
|
279
|
-
async getSpanTree(externalSpanId) {
|
|
280
|
-
const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}`;
|
|
281
|
-
const controller = new AbortController();
|
|
282
|
-
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
283
|
-
try {
|
|
284
|
-
const response = await fetch(url, {
|
|
285
|
-
method: "GET",
|
|
286
|
-
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
287
|
-
signal: controller.signal
|
|
288
|
-
});
|
|
289
|
-
if (!response.ok) {
|
|
290
|
-
const errorText = await response.text();
|
|
291
|
-
throw new BitfabError(
|
|
292
|
-
`HTTP ${response.status}: ${errorText.slice(0, 500)}`
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
return await response.json();
|
|
296
|
-
} catch (error) {
|
|
297
|
-
if (error instanceof BitfabError) {
|
|
298
|
-
throw error;
|
|
299
|
-
}
|
|
300
|
-
if (error instanceof Error) {
|
|
301
|
-
if (error.name === "AbortError") {
|
|
302
|
-
throw new BitfabError("Request timed out after 30000ms");
|
|
303
|
-
}
|
|
304
|
-
throw new BitfabError(error.message);
|
|
305
|
-
}
|
|
306
|
-
throw new BitfabError("Unknown error occurred");
|
|
307
|
-
} finally {
|
|
308
|
-
clearTimeout(timeoutId);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Mark a replay test run as completed.
|
|
313
|
-
* Blocking call.
|
|
314
|
-
*/
|
|
315
|
-
async completeReplay(testRunId) {
|
|
316
|
-
return this.request(
|
|
317
|
-
"/api/sdk/replay/complete",
|
|
318
|
-
{ testRunId },
|
|
319
|
-
{ timeout: 3e4 }
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Ask the server to materialize a per-trace DB branch lease from a
|
|
324
|
-
* captured `dbSnapshotRef`. Blocking — the resolver creates a Neon
|
|
325
|
-
* snapshot + preview branch and polls operations to readiness, which
|
|
326
|
-
* can take seconds.
|
|
327
|
-
*/
|
|
328
|
-
async resolveDbBranchLease(testRunId, traceId, dbSnapshotRef) {
|
|
329
|
-
return this.request(
|
|
330
|
-
"/api/sdk/replay/resolveDbBranchLease",
|
|
331
|
-
{ testRunId, traceId, dbSnapshotRef },
|
|
332
|
-
{ timeout: 9e4 }
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
/** Release a previously-resolved DB branch by deleting its Neon branch. Idempotent server-side. */
|
|
336
|
-
async releaseDbBranchLease(neonBranchId) {
|
|
337
|
-
await this.request(
|
|
338
|
-
"/api/sdk/replay/releaseDbBranchLease",
|
|
339
|
-
{ neonBranchId },
|
|
340
|
-
{ timeout: 3e4 }
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
};
|
|
344
|
-
|
|
345
|
-
// src/asyncStorage.ts
|
|
346
|
-
var AsyncLocalStorageClass = null;
|
|
347
|
-
var initDone = false;
|
|
348
|
-
function registerAsyncLocalStorageClass(cls) {
|
|
349
|
-
if (!AsyncLocalStorageClass) {
|
|
350
|
-
AsyncLocalStorageClass = cls;
|
|
351
|
-
}
|
|
352
|
-
initDone = true;
|
|
353
|
-
}
|
|
354
|
-
function assertAsyncStorageRegistered() {
|
|
355
|
-
if (!AsyncLocalStorageClass) {
|
|
356
|
-
console.warn(
|
|
357
|
-
"Bitfab: AsyncLocalStorage not available \u2014 nested span context will not propagate."
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
var asyncStorageReady = (typeof process !== "undefined" && process.versions?.node ? (
|
|
362
|
-
// The join trick hides "node:async_hooks" from static analysis so
|
|
363
|
-
// bundlers that ban Node.js built-ins don't fail at build time.
|
|
364
|
-
// webpackIgnore tells webpack/turbopack to emit a native import()
|
|
365
|
-
// so Node.js can resolve the module at runtime.
|
|
366
|
-
import(
|
|
367
|
-
/* webpackIgnore: true */
|
|
368
|
-
["node", "async_hooks"].join(":")
|
|
369
|
-
).then(
|
|
370
|
-
(mod) => {
|
|
371
|
-
registerAsyncLocalStorageClass(mod.AsyncLocalStorage);
|
|
372
|
-
}
|
|
373
|
-
).catch(() => {
|
|
374
|
-
})
|
|
375
|
-
) : Promise.resolve()).then(() => {
|
|
376
|
-
initDone = true;
|
|
377
|
-
});
|
|
378
|
-
function isAsyncStorageInitDone() {
|
|
379
|
-
return initDone;
|
|
380
|
-
}
|
|
381
|
-
function createAsyncLocalStorage() {
|
|
382
|
-
return AsyncLocalStorageClass ? new AsyncLocalStorageClass() : null;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// src/replayContext.ts
|
|
386
|
-
var replayContextStorage = null;
|
|
387
|
-
var replayContextReady = asyncStorageReady.then(() => {
|
|
388
|
-
replayContextStorage = createAsyncLocalStorage();
|
|
389
|
-
});
|
|
390
|
-
function getReplayContext() {
|
|
391
|
-
return replayContextStorage?.getStore() ?? null;
|
|
392
|
-
}
|
|
393
|
-
function runWithReplayContext(ctx, fn) {
|
|
394
|
-
if (replayContextStorage) {
|
|
395
|
-
return replayContextStorage.run(ctx, fn);
|
|
396
|
-
}
|
|
397
|
-
return fn();
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// src/serialize.ts
|
|
401
|
-
import superjson from "superjson";
|
|
402
|
-
var MAX_SERIALIZED_BYTES = 512e3;
|
|
403
|
-
function describeValue(value) {
|
|
404
|
-
try {
|
|
405
|
-
const ctorName = value?.constructor?.name;
|
|
406
|
-
if (ctorName && ctorName !== "Object") {
|
|
407
|
-
return ctorName;
|
|
408
|
-
}
|
|
409
|
-
} catch {
|
|
410
|
-
}
|
|
411
|
-
return typeof value;
|
|
412
|
-
}
|
|
413
|
-
function unserializableStub(value, reason) {
|
|
414
|
-
let summary;
|
|
415
|
-
try {
|
|
416
|
-
summary = `<unserializable: ${describeValue(value)} (${reason})>`;
|
|
417
|
-
} catch {
|
|
418
|
-
summary = `<unserializable (${reason})>`;
|
|
419
|
-
}
|
|
420
|
-
return { json: summary };
|
|
421
|
-
}
|
|
422
|
-
function serializeValue(value) {
|
|
423
|
-
try {
|
|
424
|
-
const { json, meta } = superjson.serialize(value);
|
|
425
|
-
let size;
|
|
426
|
-
try {
|
|
427
|
-
size = JSON.stringify(json).length;
|
|
428
|
-
} catch {
|
|
429
|
-
return unserializableStub(value, "stringify_failed_after_superjson");
|
|
430
|
-
}
|
|
431
|
-
if (size > MAX_SERIALIZED_BYTES) {
|
|
432
|
-
return unserializableStub(value, `too_large_${size}_bytes`);
|
|
433
|
-
}
|
|
434
|
-
return meta ? { json, meta } : { json };
|
|
435
|
-
} catch {
|
|
436
|
-
try {
|
|
437
|
-
return { json: JSON.parse(JSON.stringify(value)) };
|
|
438
|
-
} catch {
|
|
439
|
-
return unserializableStub(value, "json_stringify_failed");
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
function deserializeValue(serialized) {
|
|
444
|
-
if (serialized.meta === void 0) {
|
|
445
|
-
return serialized.json;
|
|
446
|
-
}
|
|
447
|
-
return superjson.deserialize({
|
|
448
|
-
json: serialized.json,
|
|
449
|
-
meta: serialized.meta
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
export {
|
|
454
|
-
__version__,
|
|
455
|
-
DEFAULT_SERVICE_URL,
|
|
456
|
-
BitfabError,
|
|
457
|
-
flushTraces,
|
|
458
|
-
HttpClient,
|
|
459
|
-
registerAsyncLocalStorageClass,
|
|
460
|
-
assertAsyncStorageRegistered,
|
|
461
|
-
asyncStorageReady,
|
|
462
|
-
isAsyncStorageInitDone,
|
|
463
|
-
createAsyncLocalStorage,
|
|
464
|
-
replayContextReady,
|
|
465
|
-
getReplayContext,
|
|
466
|
-
runWithReplayContext,
|
|
467
|
-
serializeValue,
|
|
468
|
-
deserializeValue
|
|
469
|
-
};
|
|
470
|
-
//# sourceMappingURL=chunk-OW2EJK7T.js.map
|