@bitfab/sdk 0.64.30 → 0.65.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.
Files changed (38) hide show
  1. package/dist/{chunk-GLBBZLG2.js → chunk-3W6RLPGK.js} +4 -4
  2. package/dist/chunk-3W6RLPGK.js.map +1 -0
  3. package/dist/{chunk-PIMCRSR2.js → chunk-JIGT4NC5.js} +2 -2
  4. package/dist/{chunk-N3CG4AHU.js → chunk-JXIW4FOF.js} +2 -2
  5. package/dist/{chunk-BTCJ5XGH.js → chunk-SUXEIG4U.js} +42 -14
  6. package/dist/chunk-SUXEIG4U.js.map +1 -0
  7. package/dist/{chunk-DARYURT3.js → chunk-TNIJXUGC.js} +2 -2
  8. package/dist/chunk-TNIJXUGC.js.map +1 -0
  9. package/dist/{chunk-5H2UZ2LR.js → chunk-WW2RMMTA.js} +2 -2
  10. package/dist/chunk-WW2RMMTA.js.map +1 -0
  11. package/dist/{http-5HVQJKA7.js → http-7LTYEMJO.js} +2 -2
  12. package/dist/{http-JI7TADRE.js → http-NNVOMI63.js} +2 -2
  13. package/dist/index.cjs +38 -9
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +76 -43
  16. package/dist/index.d.ts +76 -43
  17. package/dist/index.js +5 -3
  18. package/dist/node.cjs +38 -9
  19. package/dist/node.cjs.map +1 -1
  20. package/dist/node.d.cts +1 -1
  21. package/dist/node.d.ts +1 -1
  22. package/dist/node.js +5 -3
  23. package/dist/node.js.map +1 -1
  24. package/dist/{replay-FRGREQBV.js → replay-D7AN6O25.js} +3 -3
  25. package/dist/{replay-WL7ZJP3A.js → replay-L56SESG7.js} +3 -3
  26. package/dist/replayCli.js +3 -3
  27. package/dist/seedCli.js +3 -3
  28. package/package.json +1 -1
  29. package/dist/chunk-5H2UZ2LR.js.map +0 -1
  30. package/dist/chunk-BTCJ5XGH.js.map +0 -1
  31. package/dist/chunk-DARYURT3.js.map +0 -1
  32. package/dist/chunk-GLBBZLG2.js.map +0 -1
  33. /package/dist/{chunk-PIMCRSR2.js.map → chunk-JIGT4NC5.js.map} +0 -0
  34. /package/dist/{chunk-N3CG4AHU.js.map → chunk-JXIW4FOF.js.map} +0 -0
  35. /package/dist/{http-5HVQJKA7.js.map → http-7LTYEMJO.js.map} +0 -0
  36. /package/dist/{http-JI7TADRE.js.map → http-NNVOMI63.js.map} +0 -0
  37. /package/dist/{replay-FRGREQBV.js.map → replay-D7AN6O25.js.map} +0 -0
  38. /package/dist/{replay-WL7ZJP3A.js.map → replay-L56SESG7.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/autoTrace.ts","../src/commitRef.ts","../src/openaiAgentsSpanContext.ts","../src/rivetContext.ts","../src/seedContext.ts","../src/client.ts","../src/replayRegistry.ts","../src/registryCommand.ts"],"sourcesContent":["import {\n type AsyncLocalStorageLike,\n createAsyncLocalStorage,\n} from \"./asyncStorage.js\"\nimport { isCaptureSuppressed } from \"./captureSuppression.js\"\nimport { warnOnce } from \"./warnOnce.js\"\n\nexport interface AutoTraceFunctionDefinition {\n id: string\n name: string\n file: string\n line: number\n column: number\n async?: boolean\n wrapper?: boolean\n}\n\nexport interface AutoTraceNodeConfiguration {\n functionName: string\n name?: string\n type?: \"llm\" | \"agent\" | \"function\" | \"guardrail\" | \"handoff\" | \"custom\"\n capture: boolean\n experimentId?: string\n /** @deprecated Use experimentId. */\n testRunId?: string\n mockOnReplay?: boolean\n replayReusable?: boolean\n // biome-ignore lint/suspicious/noExplicitAny: node finalizers receive the configured function's result, whose type is owned by the caller\n finalize?: (result: any) => unknown | Promise<unknown>\n}\n\nexport interface NestedTraceLink {\n traceFunctionKey: string\n traceId?: string\n rootSpanId?: string\n}\n\nexport interface AutoTraceEntry {\n context: AutoTraceContext\n depth: number\n spanStack: unknown\n}\n\nexport interface AutoTraceContext {\n readonly traceFunctionKey: string\n invoke<T>(\n definition: AutoTraceFunctionDefinition,\n inputs: unknown[],\n fn: () => T,\n entry: AutoTraceEntry,\n nodeConfiguration?: AutoTraceNodeConfiguration,\n ): T\n invokeNestedRoot<T>(\n name: string,\n functionName: string,\n inputs: unknown[],\n fn: () => T,\n entry: AutoTraceEntry,\n link: NestedTraceLink | undefined,\n ): T\n}\n\ninterface AutoTraceScope {\n entries: readonly AutoTraceEntry[]\n nodeConfiguration?: AutoTraceNodeConfiguration\n}\n\ninterface AutoTraceState {\n storage: AsyncLocalStorageLike<AutoTraceScope> | null\n browserScope: AutoTraceScope | undefined\n // Older SDK copies read this field when sharing the process-wide V4 state.\n capturePolicies: WeakMap<object, Map<string, ReadonlySet<string>>>\n activeRoots: number\n}\n\ninterface AutoTraceGlobal {\n __bitfabAutoTraceStateV4?: AutoTraceState\n __bitfab_auto_trace_transformed_v1__?: boolean\n}\n\ninterface AutoTraceAsyncGenerator {\n next(value?: unknown): Promise<IteratorResult<unknown, unknown>>\n return(value?: unknown): Promise<IteratorResult<unknown, unknown>>\n throw(error?: unknown): Promise<IteratorResult<unknown, unknown>>\n [Symbol.asyncIterator](): AutoTraceAsyncGenerator\n}\n\nconst autoTraceGlobal = globalThis as unknown as AutoTraceGlobal\nconst autoTraceState: AutoTraceState =\n autoTraceGlobal.__bitfabAutoTraceStateV4 ?? {\n storage: null,\n browserScope: undefined,\n capturePolicies: new WeakMap(),\n activeRoots: 0,\n }\nautoTraceGlobal.__bitfabAutoTraceStateV4 = autoTraceState\n\nfunction initializeAutoTraceStorage(): void {\n autoTraceState.storage ??= createAsyncLocalStorage<AutoTraceScope>()\n}\n\nfunction runWithAutoTraceScope<T>(scope: AutoTraceScope, fn: () => T): T {\n initializeAutoTraceStorage()\n if (autoTraceState.storage) {\n return autoTraceState.storage.run(scope, fn)\n }\n\n const previous = autoTraceState.browserScope\n autoTraceState.browserScope = scope\n try {\n return fn()\n } finally {\n autoTraceState.browserScope = previous\n }\n}\n\nexport function currentAutoTraceEntries(): readonly AutoTraceEntry[] {\n return currentAutoTraceScope()?.entries ?? []\n}\n\nfunction replaceEntry(\n entries: readonly AutoTraceEntry[],\n previous: AutoTraceEntry,\n updated: AutoTraceEntry,\n): readonly AutoTraceEntry[] {\n return entries.map((entry) => (entry === previous ? updated : entry))\n}\n\nexport function runWithAutoTraceEntry<T>(\n previous: AutoTraceEntry,\n updated: AutoTraceEntry,\n fn: () => T,\n): T {\n const result = runWithAutoTraceScope(\n { entries: replaceEntry(currentAutoTraceEntries(), previous, updated) },\n fn,\n )\n if (isAutoTraceAsyncGenerator(result)) {\n return wrapAutoTraceEntryAsyncGenerator(previous, updated, result) as T\n }\n return result\n}\n\nfunction wrapAutoTraceEntryAsyncGenerator(\n previous: AutoTraceEntry,\n updated: AutoTraceEntry,\n source: AutoTraceAsyncGenerator,\n): AutoTraceAsyncGenerator {\n const step = (\n method: \"next\" | \"return\" | \"throw\",\n value?: unknown,\n ): Promise<IteratorResult<unknown, unknown>> =>\n runWithAutoTraceScope(\n { entries: replaceEntry(currentAutoTraceEntries(), previous, updated) },\n () => source[method](value),\n )\n const wrapped: AutoTraceAsyncGenerator = {\n next: (value) => step(\"next\", value),\n return: (value) => step(\"return\", value),\n throw: (error) => step(\"throw\", error),\n [Symbol.asyncIterator]: () => wrapped,\n }\n return wrapped\n}\n\nexport function clearAutoTraceNodeConfiguration<T>(fn: () => T): T {\n return runWithAutoTraceScope({ entries: currentAutoTraceEntries() }, fn)\n}\n\nexport function runWithAutoTraceNodeConfiguration<T>(\n nodeConfiguration: AutoTraceNodeConfiguration,\n fn: () => T,\n): T {\n const scope = currentAutoTraceScope()\n if (!scope || scope.entries.length === 0) {\n return fn()\n }\n\n const result = runWithAutoTraceScope({ ...scope, nodeConfiguration }, fn)\n if (isAutoTraceAsyncGenerator(result)) {\n return wrapAutoTraceNodeAsyncGenerator(nodeConfiguration, result) as T\n }\n return result\n}\n\nconst SUBTREE_TRACING_DOCS_URL =\n \"https://docs.bitfab.ai/typescript-sdk#experimental-subtree-tracing\"\n\nfunction noteTransformedModule(): void {\n autoTraceGlobal.__bitfab_auto_trace_transformed_v1__ = true\n}\n\nfunction transformInstalled(): boolean {\n return autoTraceGlobal.__bitfab_auto_trace_transformed_v1__ === true\n}\n\nfunction runsOnNode(): boolean {\n return typeof process !== \"undefined\" && Boolean(process.versions?.node)\n}\n\nfunction closeAutoTraceRoot(): void {\n autoTraceState.activeRoots -= 1\n}\n\nfunction settleAutoTraceRoot(context: AutoTraceContext): void {\n closeAutoTraceRoot()\n if (transformInstalled() || !runsOnNode()) {\n return\n }\n warnOnce(\n \"auto-trace-transform-missing\",\n `trace root \"${context.traceFunctionKey}\" recorded only its own span. ` +\n \"No module in this build was compiled by @bitfab/sdk/transform, so calls \" +\n \"beneath the root were not discovered. Add the SDK's transform adapter \" +\n `to the server build: ${SUBTREE_TRACING_DOCS_URL}`,\n )\n}\n\nexport function runWithAutoTraceRootContext<T>(\n context: AutoTraceContext,\n spanStack: unknown,\n fn: () => T,\n): T {\n autoTraceState.activeRoots += 1\n const entries = [\n ...currentAutoTraceEntries(),\n { context, depth: 0, spanStack },\n ]\n let result: T\n try {\n result = runWithAutoTraceScope({ entries }, fn)\n } catch (error) {\n settleAutoTraceRoot(context)\n throw error\n }\n\n if (isAutoTraceAsyncGenerator(result)) {\n closeAutoTraceRoot()\n return wrapAutoTraceAsyncGenerator(context, spanStack, result) as T\n }\n\n if (result instanceof Promise) {\n return result.finally(() => {\n settleAutoTraceRoot(context)\n }) as T\n }\n\n settleAutoTraceRoot(context)\n return result\n}\n\nexport function invokeThroughAutoTraceEntries<T>(\n entries: readonly AutoTraceEntry[],\n step: (entry: AutoTraceEntry, next: () => T) => T,\n fn: () => T,\n): T {\n let run = fn\n for (let index = entries.length - 1; index >= 0; index -= 1) {\n const entry = entries[index] as AutoTraceEntry\n const next = run\n run = () => step(entry, next)\n }\n return run()\n}\n\nfunction isAutoTraceAsyncGenerator(\n value: unknown,\n): value is AutoTraceAsyncGenerator {\n if (value === null || typeof value !== \"object\") {\n return false\n }\n const candidate = value as Record<PropertyKey, unknown>\n return (\n typeof candidate.next === \"function\" &&\n typeof candidate.return === \"function\" &&\n typeof candidate.throw === \"function\" &&\n typeof candidate[Symbol.asyncIterator] === \"function\"\n )\n}\n\nfunction wrapAutoTraceAsyncGenerator(\n context: AutoTraceContext,\n spanStack: unknown,\n source: AutoTraceAsyncGenerator,\n): AutoTraceAsyncGenerator {\n const step = (\n method: \"next\" | \"return\" | \"throw\",\n value?: unknown,\n ): Promise<IteratorResult<unknown, unknown>> =>\n runWithAutoTraceRootContext(context, spanStack, () => source[method](value))\n const wrapped: AutoTraceAsyncGenerator = {\n next: (value) => step(\"next\", value),\n return: (value) => step(\"return\", value),\n throw: (error) => step(\"throw\", error),\n [Symbol.asyncIterator]: () => wrapped,\n }\n return wrapped\n}\n\nfunction wrapAutoTraceNodeAsyncGenerator(\n nodeConfiguration: AutoTraceNodeConfiguration,\n source: AutoTraceAsyncGenerator,\n): AutoTraceAsyncGenerator {\n const step = (\n method: \"next\" | \"return\" | \"throw\",\n value?: unknown,\n ): Promise<IteratorResult<unknown, unknown>> =>\n runWithAutoTraceNodeConfiguration(nodeConfiguration, () =>\n source[method](value),\n )\n const wrapped: AutoTraceAsyncGenerator = {\n next: (value) => step(\"next\", value),\n return: (value) => step(\"return\", value),\n throw: (error) => step(\"throw\", error),\n [Symbol.asyncIterator]: () => wrapped,\n }\n return wrapped\n}\n\nfunction shareFinalize(\n configuration: AutoTraceNodeConfiguration,\n): AutoTraceNodeConfiguration {\n const finalize = configuration.finalize\n if (finalize === undefined) {\n return configuration\n }\n let settled: Promise<unknown> | undefined\n return {\n ...configuration,\n finalize: (result: unknown) => {\n settled ??= Promise.resolve().then(() => finalize(result))\n return settled\n },\n }\n}\n\nexport function __bitfabAutoSpan<T>(\n definition: AutoTraceFunctionDefinition,\n inputs: unknown[],\n fn: () => T,\n): T {\n noteTransformedModule()\n const scope = currentAutoTraceScope()\n if (!scope || scope.entries.length === 0 || isCaptureSuppressed()) {\n return fn()\n }\n const nameParts = definition.name.split(\".\")\n const simpleName = nameParts[nameParts.length - 1]\n const nodeConfiguration =\n simpleName === scope.nodeConfiguration?.functionName\n ? shareFinalize(scope.nodeConfiguration)\n : undefined\n return invokeThroughAutoTraceEntries(\n scope.entries,\n (entry, next) =>\n entry.context.invoke(definition, inputs, next, entry, nodeConfiguration),\n fn,\n )\n}\n\n/**\n * Preserve a function's original call arguments for transform cases where\n * parameter bindings discard them, such as destructured arrow parameters.\n *\n * This helper is internal transform/runtime protocol. The proxy preserves the\n * target's callability, arity, async identity, and non-constructibility while\n * only allocating a trace closure beneath an active automatic trace root.\n *\n * @experimental The automatic tracing protocol may change.\n */\nexport function __bitfabAutoWrap<T extends (...args: never[]) => unknown>(\n definition: AutoTraceFunctionDefinition,\n fn: T,\n): T {\n noteTransformedModule()\n if (fn.name === \"\") {\n const nameParts = definition.name.split(\".\")\n const inferredName = nameParts[nameParts.length - 1]\n if (inferredName !== undefined) {\n Object.defineProperty(fn, \"name\", {\n configurable: true,\n value: inferredName,\n })\n }\n }\n\n const target = fn as unknown as (...args: unknown[]) => unknown\n return new Proxy(target, {\n apply(callTarget, thisArg, args) {\n if (!__bitfabAutoTraceActive()) {\n return Reflect.apply(callTarget, thisArg, args)\n }\n return __bitfabAutoSpan(definition, args, () =>\n Reflect.apply(callTarget, thisArg, args),\n )\n },\n }) as unknown as T\n}\n\n/**\n * Return whether the current call is inside an automatic trace root.\n *\n * Build transforms use this before allocating function metadata, captured\n * inputs, or an invocation closure. It is internal transform/runtime protocol,\n * not a supported application API.\n *\n * @experimental The automatic tracing protocol may change.\n */\nexport function __bitfabAutoTraceActive(): boolean {\n if (autoTraceState.activeRoots === 0 || isCaptureSuppressed()) {\n return false\n }\n return currentAutoTraceEntries().length > 0\n}\n\nfunction currentAutoTraceScope(): AutoTraceScope | undefined {\n initializeAutoTraceStorage()\n return autoTraceState.storage?.getStore() ?? autoTraceState.browserScope\n}\n\nexport function _resetAutoTraceTransformDetection(): void {\n autoTraceGlobal.__bitfab_auto_trace_transformed_v1__ = undefined\n}\n","import { gitRunner } from \"./gitCommand.js\"\nimport { readEnv } from \"./readEnv.js\"\n\nexport interface CommitRef {\n sha: string\n branch: string | null\n dirty: boolean | null\n remote: string | null\n rootSha: string | null\n}\n\ntype EnvLookup = (name: string) => string | undefined\ntype RemoteReader = (env: EnvLookup) => string | null\n\nconst EXPLICIT_SHA_ENV = \"BITFAB_COMMIT_SHA\"\nconst DISABLE_ENV = \"BITFAB_DISABLE_COMMIT_REF\"\nconst GIT_TIMEOUT_MS = 2_000\nconst GIT_MAX_BUFFER = 1024 * 1024\n\nconst VERCEL_PROVIDER_HOSTS: Record<string, string> = {\n github: \"github.com\",\n gitlab: \"gitlab.com\",\n bitbucket: \"bitbucket.org\",\n}\n\nfunction read(env: EnvLookup, name: string | null): string | null {\n if (name === null) {\n return null\n }\n const value = env(name)?.trim()\n return value ? value : null\n}\n\nfunction remoteFrom(...names: string[]): RemoteReader {\n return (env) => {\n const parts = names.map((name) => read(env, name))\n if (parts.some((part) => part === null)) {\n return null\n }\n return normalizeRemote(parts.join(\"/\"))\n }\n}\n\nfunction vercelRemote(env: EnvLookup): string | null {\n const provider = read(env, \"VERCEL_GIT_PROVIDER\")?.toLowerCase() ?? \"\"\n const host = VERCEL_PROVIDER_HOSTS[provider]\n const owner = read(env, \"VERCEL_GIT_REPO_OWNER\")\n const slug = read(env, \"VERCEL_GIT_REPO_SLUG\")\n if (!host || !owner || !slug) {\n return null\n }\n return `${host}/${owner}/${slug}`\n}\n\nfunction noRemote(): string | null {\n return null\n}\n\nconst PLATFORM_ENVS: ReadonlyArray<\n readonly [sha: string, branch: string | null, remote: RemoteReader]\n> = [\n [\"VERCEL_GIT_COMMIT_SHA\", \"VERCEL_GIT_COMMIT_REF\", vercelRemote],\n [\n \"GITHUB_SHA\",\n \"GITHUB_REF_NAME\",\n remoteFrom(\"GITHUB_SERVER_URL\", \"GITHUB_REPOSITORY\"),\n ],\n [\"RAILWAY_GIT_COMMIT_SHA\", \"RAILWAY_GIT_BRANCH\", noRemote],\n [\"RENDER_GIT_COMMIT\", \"RENDER_GIT_BRANCH\", noRemote],\n [\"SOURCE_VERSION\", null, noRemote],\n [\"CF_PAGES_COMMIT_SHA\", \"CF_PAGES_BRANCH\", noRemote],\n [\"CI_COMMIT_SHA\", \"CI_COMMIT_REF_NAME\", remoteFrom(\"CI_REPOSITORY_URL\")],\n [\n \"BUILD_SOURCEVERSION\",\n \"BUILD_SOURCEBRANCHNAME\",\n remoteFrom(\"BUILD_REPOSITORY_URI\"),\n ],\n [\"CIRCLE_SHA1\", \"CIRCLE_BRANCH\", remoteFrom(\"CIRCLE_REPOSITORY_URL\")],\n]\n\nexport const COMMIT_REF_ENV_NAMES: readonly string[] = [\n EXPLICIT_SHA_ENV,\n DISABLE_ENV,\n ...PLATFORM_ENVS.flatMap(([sha, branch]) =>\n branch === null ? [sha] : [sha, branch],\n ),\n \"GITHUB_SERVER_URL\",\n \"GITHUB_REPOSITORY\",\n \"CI_REPOSITORY_URL\",\n \"BUILD_REPOSITORY_URI\",\n \"CIRCLE_REPOSITORY_URL\",\n \"VERCEL_GIT_PROVIDER\",\n \"VERCEL_GIT_REPO_OWNER\",\n \"VERCEL_GIT_REPO_SLUG\",\n]\n\nexport function normalizeRemote(raw: string | null | undefined): string | null {\n const value = raw?.trim()\n if (!value) {\n return null\n }\n let host = \"\"\n let path = value\n if (value.includes(\"://\")) {\n try {\n const parsed = new URL(value)\n host = parsed.hostname\n path = parsed.pathname\n } catch {\n return null\n }\n } else {\n const separator = value.indexOf(\":\")\n if (separator !== -1) {\n const head = value.slice(0, separator)\n host = head.slice(head.lastIndexOf(\"@\") + 1)\n path = value.slice(separator + 1)\n }\n }\n path = path.replace(/^\\/+|\\/+$/g, \"\")\n if (path.endsWith(\".git\")) {\n path = path.slice(0, -\".git\".length).replace(/\\/+$/, \"\")\n }\n if (!host) {\n return path || null\n }\n return path ? `${host}/${path}` : host\n}\n\nexport function resolveCommitRefFromEnv(env: EnvLookup): CommitRef | null {\n const explicit = read(env, EXPLICIT_SHA_ENV)\n const platform = PLATFORM_ENVS.find(([sha]) => read(env, sha) !== null)\n if (!platform) {\n if (explicit === null) {\n return null\n }\n return {\n sha: explicit,\n branch: null,\n dirty: null,\n remote: null,\n rootSha: null,\n }\n }\n const [shaEnv, branchEnv, readRemote] = platform\n return {\n sha: explicit ?? (read(env, shaEnv) as string),\n branch: read(env, branchEnv),\n dirty: null,\n remote: readRemote(env),\n rootSha: null,\n }\n}\n\nexport async function resolveCommitRefFromGit(\n cwd: string,\n): Promise<CommitRef | null> {\n const git = await gitRunner({\n timeoutMs: GIT_TIMEOUT_MS,\n maxBuffer: GIT_MAX_BUFFER,\n keepProcessAlive: false,\n })\n if (!git) {\n return null\n }\n const run = async (args: string[]) => (await git(cwd, args))?.trim() ?? null\n const sha = await run([\"rev-parse\", \"HEAD\"])\n if (!sha) {\n return null\n }\n const [branch, status, remote, roots] = await Promise.all([\n run([\"symbolic-ref\", \"--short\", \"-q\", \"HEAD\"]),\n run([\"status\", \"--porcelain\"]),\n run([\"remote\", \"get-url\", \"origin\"]),\n run([\"rev-list\", \"--max-parents=0\", \"HEAD\"]),\n ])\n return {\n sha,\n branch: branch || null,\n dirty: status === null ? null : status.length > 0,\n remote: normalizeRemote(remote),\n rootSha: roots ? (roots.split(/\\s+/).sort()[0] ?? null) : null,\n }\n}\n\nexport function commitRefWire(ref: CommitRef): Record<string, unknown> {\n return {\n sha: ref.sha,\n branch: ref.branch,\n dirty: ref.dirty,\n remote: ref.remote,\n root_sha: ref.rootSha,\n }\n}\n\nlet resolved = false\nlet ref: CommitRef | null = null\nlet gitStarted = false\nlet settled: Promise<void> = Promise.resolve()\n\nexport function startCommitRefResolution(): void {\n if (resolved || gitStarted) {\n return\n }\n if (read(readEnv, DISABLE_ENV) !== null) {\n resolved = true\n return\n }\n const fromEnv = resolveCommitRefFromEnv(readEnv)\n if (fromEnv) {\n ref = fromEnv\n resolved = true\n return\n }\n gitStarted = true\n if (typeof process === \"undefined\") {\n resolved = true\n return\n }\n let cwd: string\n try {\n cwd = process.cwd?.() ?? \".\"\n } catch {\n resolved = true\n return\n }\n settled = resolveCommitRefFromGit(cwd).then(\n (fromGit) => {\n if (!resolved) {\n ref = fromGit\n resolved = true\n }\n },\n () => {\n resolved = true\n },\n )\n}\n\nexport function currentCommitRef(): CommitRef | null {\n startCommitRefResolution()\n return ref\n}\n\nexport function _commitRefSettled(): Promise<void> {\n return settled\n}\n\nexport function _resetCommitRef(): void {\n resolved = false\n ref = null\n gitStarted = false\n settled = Promise.resolve()\n}\n","const OPENAI_AGENTS_CONTEXT_STORAGE = Symbol.for(\n \"openai.agents.core.asyncLocalStorage\",\n)\nconst MAX_OPENAI_AGENTS_SPAN_CHAIN = 64\n\ninterface OpenAiAgentsSpanLike {\n spanId?: unknown\n traceId?: unknown\n previousSpan?: OpenAiAgentsSpanLike | null\n}\n\ninterface OpenAiAgentsContextStore {\n active?: unknown\n span?: OpenAiAgentsSpanLike | null\n}\n\ninterface OpenAiAgentsContextStorage {\n getStore(): OpenAiAgentsContextStore | undefined\n}\n\ninterface OpenAiAgentsSpanState {\n bitfabTraceIds: Map<string, string>\n endedSpanIds: Map<string, Set<string>>\n}\n\ninterface OpenAiAgentsSpanGlobal {\n __bitfabOpenAiAgentsSpansV1?: OpenAiAgentsSpanState\n}\n\nconst spanGlobal = globalThis as unknown as OpenAiAgentsSpanGlobal &\n Record<symbol, unknown>\nconst state: OpenAiAgentsSpanState = spanGlobal.__bitfabOpenAiAgentsSpansV1 ?? {\n bitfabTraceIds: new Map(),\n endedSpanIds: new Map(),\n}\nspanGlobal.__bitfabOpenAiAgentsSpansV1 = state\n\nexport function mapOpenAiAgentsTrace(\n agentsTraceId: string,\n bitfabTraceId: string,\n): void {\n state.bitfabTraceIds.set(agentsTraceId, bitfabTraceId)\n}\n\nexport function unmapOpenAiAgentsTrace(agentsTraceId: string): void {\n state.bitfabTraceIds.delete(agentsTraceId)\n state.endedSpanIds.delete(agentsTraceId)\n}\n\nexport function endOpenAiAgentsSpan(\n agentsTraceId: string,\n spanId: string,\n): void {\n if (!state.bitfabTraceIds.has(agentsTraceId)) {\n return\n }\n const ended = state.endedSpanIds.get(agentsTraceId) ?? new Set<string>()\n ended.add(spanId)\n state.endedSpanIds.set(agentsTraceId, ended)\n}\n\nfunction isOpenAiAgentsContextStorage(\n value: unknown,\n): value is OpenAiAgentsContextStorage {\n return (\n value !== null &&\n typeof value === \"object\" &&\n typeof (value as { getStore?: unknown }).getStore === \"function\"\n )\n}\n\nfunction currentOpenAiAgentsSpan(): OpenAiAgentsSpanLike | undefined {\n const storage = spanGlobal[OPENAI_AGENTS_CONTEXT_STORAGE]\n if (!isOpenAiAgentsContextStorage(storage)) {\n return undefined\n }\n try {\n const store = storage.getStore()\n return store?.active === true ? (store.span ?? undefined) : undefined\n } catch {\n return undefined\n }\n}\n\nfunction currentOpenAiAgentsSpans(): OpenAiAgentsSpanLike[] {\n const spans: OpenAiAgentsSpanLike[] = []\n let span = currentOpenAiAgentsSpan()\n while (span !== undefined && spans.length < MAX_OPENAI_AGENTS_SPAN_CHAIN) {\n spans.push(span)\n span = span.previousSpan ?? undefined\n }\n return spans\n}\n\nexport function openAiAgentsSpanChain(): string[] {\n if (state.bitfabTraceIds.size === 0) {\n return []\n }\n return currentOpenAiAgentsSpans()\n .map((span) => span.spanId)\n .filter((spanId): spanId is string => typeof spanId === \"string\")\n}\n\nexport function openAiAgentsParentSpanId(\n bitfabTraceId: string,\n enclosingSpanIds: readonly string[] | undefined,\n): string | undefined {\n if (state.bitfabTraceIds.size === 0) {\n return undefined\n }\n for (const span of currentOpenAiAgentsSpans()) {\n if (typeof span.spanId !== \"string\" || typeof span.traceId !== \"string\") {\n return undefined\n }\n if (state.bitfabTraceIds.get(span.traceId) !== bitfabTraceId) {\n return undefined\n }\n if (enclosingSpanIds?.includes(span.spanId)) {\n return undefined\n }\n if (!state.endedSpanIds.get(span.traceId)?.has(span.spanId)) {\n return span.spanId\n }\n }\n return undefined\n}\n","import {\n type AsyncLocalStorageLike,\n asyncStorageReady,\n createAsyncLocalStorage,\n isAsyncStorageInitDone,\n} from \"./asyncStorage.js\"\n\nexport interface RivetContextBinding<TContext extends object> {\n context: TContext\n fixtureContext: Record<string, unknown>\n recordedRoots: Set<string>\n proxy?: TContext\n}\n\nlet storage: AsyncLocalStorageLike<RivetContextBinding<object> | null> | null =\n null\nlet browserBinding: RivetContextBinding<object> | null = null\nconst STORAGE_SYMBOL = Symbol.for(\"bitfab.rivetContextStorage\")\n\nfunction initializeStorage(): void {\n if (storage) {\n return\n }\n const shared = globalThis as typeof globalThis & Record<symbol, unknown>\n const existing = shared[STORAGE_SYMBOL] as\n | AsyncLocalStorageLike<RivetContextBinding<object> | null>\n | undefined\n if (existing) {\n storage = existing\n return\n }\n const created = createAsyncLocalStorage<RivetContextBinding<object> | null>()\n if (created) {\n shared[STORAGE_SYMBOL] = created\n storage = created\n }\n}\n\nexport const rivetContextReady: Promise<void> = asyncStorageReady.then(() => {\n initializeStorage()\n})\n\nexport function getRivetContextBinding<TContext extends object>():\n | RivetContextBinding<TContext>\n | undefined {\n initializeStorage()\n return (storage?.getStore() ?? browserBinding ?? undefined) as\n | RivetContextBinding<TContext>\n | undefined\n}\n\nfunction runWithBrowserBinding<T>(\n binding: RivetContextBinding<object>,\n fn: () => T,\n): T {\n const previous = browserBinding\n browserBinding = binding\n try {\n const result = fn()\n if (result instanceof Promise) {\n return result.finally(() => {\n browserBinding = previous\n }) as T\n }\n browserBinding = previous\n return result\n } catch (error) {\n browserBinding = previous\n throw error\n }\n}\n\nexport function runWithRivetContext<T>(\n binding: RivetContextBinding<object>,\n fn: () => T,\n): T {\n initializeStorage()\n if (!storage && !isAsyncStorageInitDone()) {\n return rivetContextReady.then(() => runWithRivetContext(binding, fn)) as T\n }\n if (storage) {\n return storage.run(binding, fn)\n }\n return runWithBrowserBinding(binding, fn)\n}\n","import {\n type AsyncLocalStorageLike,\n asyncStorageReady,\n createAsyncLocalStorage,\n} from \"./asyncStorage.js\"\n\nexport interface SeedContext {\n traceId: string\n}\n\nlet seedContextStorage: AsyncLocalStorageLike<SeedContext | null> | null = null\nconst SEED_CONTEXT_STORAGE_SYMBOL = Symbol.for(\"bitfab.seedContextStorage\")\n\nexport const seedContextReady: Promise<void> = asyncStorageReady.then(() => {\n const shared = globalThis as typeof globalThis & Record<symbol, unknown>\n const existing = shared[SEED_CONTEXT_STORAGE_SYMBOL] as\n | AsyncLocalStorageLike<SeedContext | null>\n | undefined\n if (existing) {\n seedContextStorage = existing\n return\n }\n const created = createAsyncLocalStorage<SeedContext | null>()\n if (created) {\n shared[SEED_CONTEXT_STORAGE_SYMBOL] = created\n seedContextStorage = created\n }\n})\n\nexport function getSeedContext(): SeedContext | null {\n return seedContextStorage?.getStore() ?? null\n}\n\nexport function inSeedScope(): boolean {\n return getSeedContext() !== null\n}\n\nexport function runWithSeedContext<T>(ctx: SeedContext, fn: () => T): T {\n if (seedContextStorage) {\n return seedContextStorage.run(ctx, fn)\n }\n return fn()\n}\n","/**\n * Bitfab client for provider-based API calls.\n */\n\nimport {\n type AsyncLocalStorageLike,\n asyncStorageReady,\n createAsyncLocalStorage,\n isAsyncStorageInitDone,\n} from \"./asyncStorage.js\"\nimport {\n __bitfabAutoTraceActive,\n type AutoTraceContext,\n type AutoTraceEntry,\n type AutoTraceFunctionDefinition,\n type AutoTraceNodeConfiguration,\n clearAutoTraceNodeConfiguration,\n currentAutoTraceEntries,\n invokeThroughAutoTraceEntries,\n type NestedTraceLink,\n runWithAutoTraceEntry,\n runWithAutoTraceNodeConfiguration,\n runWithAutoTraceRootContext,\n} from \"./autoTrace.js\"\nimport {\n type AllowedEnvVars,\n type ProviderDefinition,\n runFunctionWithBaml,\n} from \"./baml.js\"\nimport { validateSpanId, validateTraceId } from \"./bitfabIds.js\"\nimport { isCaptureSuppressed } from \"./captureSuppression.js\"\nimport type { CaptureSurface, SurfaceRequest } from \"./captureSurface.js\"\nimport {\n assertSurfacesCompatible,\n DEFAULT_SURFACE,\n mixedTracingError,\n resolveSurface,\n} from \"./captureSurface.js\"\nimport { CategoriesClient } from \"./categories.js\"\nimport { BitfabClaudeAgentHandler } from \"./claudeAgentSdk.js\"\nimport {\n commitRefWire,\n currentCommitRef,\n startCommitRefResolution,\n} from \"./commitRef.js\"\nimport { DEFAULT_SERVICE_URL } from \"./constants.js\"\nimport { DatasetsClient } from \"./datasets.js\"\nimport type { DbSnapshotConfig, DbSnapshotRef } from \"./dbSnapshot.js\"\nimport { buildSnapshotRef, validateDbSnapshotConfig } from \"./dbSnapshot.js\"\nimport { MixedTracingError } from \"./errors.js\"\nimport { ExperimentsClient } from \"./experiments.js\"\nimport { GradersClient } from \"./graders.js\"\nimport {\n BitfabError,\n type CapturedSpan,\n experimentIdWireFields,\n HttpClient,\n SPAN_TYPES,\n type SpanLookup,\n type SpanType,\n} from \"./http.js\"\nimport { LabelsClient } from \"./labels.js\"\nimport { BitfabLangGraphCallbackHandler } from \"./langgraph.js\"\nimport {\n BitfabLangGraphIntegration,\n type LangGraphIntegrationOptions,\n} from \"./langgraphIntegration.js\"\nimport type {\n MockOverride,\n MockOverrideResolver,\n MockSource,\n MockTarget,\n MockValue,\n NodeMatcher,\n SpanNodeMeta,\n} from \"./mockOverride.js\"\nimport { NO_MOCK_OVERRIDE, resolveMockValue } from \"./mockOverride.js\"\nimport { BitfabOpenAIAgentHandler } from \"./openaiAgentSdk.js\"\nimport {\n openAiAgentsParentSpanId,\n openAiAgentsSpanChain,\n} from \"./openaiAgentsSpanContext.js\"\nimport { importOptionalPeer } from \"./optionalPeer.js\"\nimport { OrganizationMembersClient } from \"./organizationMembers.js\"\nimport { randomUuid } from \"./randomUuid.js\"\nimport type {\n ReplayOptions,\n ReplayResult,\n TraceIngestionType,\n} from \"./replay.js\"\nimport { deserializeInputs } from \"./replay.js\"\nimport { ReplayBranch } from \"./replayBranch.js\"\nimport type { DbBranchTimings } from \"./replayContext.js\"\nimport { getReplayContext } from \"./replayContext.js\"\nimport {\n BitfabRivetIntegration,\n type RivetContextLike,\n type RivetEffectType,\n type RivetIntegrationOptions,\n} from \"./rivetIntegration.js\"\nimport {\n getSeedContext,\n inSeedScope,\n runWithSeedContext,\n seedContextReady,\n} from \"./seedContext.js\"\nimport { deserializeValue, serializeValue } from \"./serialize.js\"\nimport {\n CONTENT_OFF_KEY,\n DECLARED_NODE_FIELD,\n ROOT_TRACE_FUNCTION_KEY_FIELD,\n SimulationPlan,\n type SimulationPlanSubmit,\n} from \"./simulationPlan.js\"\nimport {\n isFrameworkInstrumentation,\n makeSpanOrigin,\n type SpanInstrumentation,\n} from \"./spanOrigin.js\"\nimport { SpansClient } from \"./spans.js\"\nimport { StreamFinalizationError } from \"./streamFinalizationError.js\"\nimport { nowIsoTimestamp } from \"./timestamp.js\"\nimport {\n callerTraceMetadata,\n recordCallerTraceMetadata,\n retireTraceMetadata,\n} from \"./traceMetadata.js\"\nimport { TracesClient } from \"./traces.js\"\nimport { BitfabOpenAITracingProcessor } from \"./tracing.js\"\nimport { BitfabVercelAiHandler } from \"./vercelAiSdk.js\"\nimport { warnOnce } from \"./warnOnce.js\"\n\nfunction markTracedFunction<T extends (...args: never[]) => unknown>(\n target: T,\n traceFunctionKey: string,\n original: unknown,\n): T {\n Object.defineProperty(target, \"_bitfabTraceFunctionKey\", {\n value: traceFunctionKey,\n })\n Object.defineProperty(target, \"_bitfabWrappedFn\", { value: original })\n Object.defineProperty(target, \"bind\", {\n value(this: T, thisArg: unknown, ...boundArgs: unknown[]) {\n return markTracedFunction(\n Function.prototype.bind.call(this, thisArg, ...boundArgs) as T,\n traceFunctionKey,\n original,\n )\n },\n })\n return target\n}\n\nfunction resolveSpanExperimentId(\n options: Pick<SpanOptions, \"experimentId\" | \"testRunId\">,\n): string | undefined {\n const { experimentId, testRunId } = options\n if (\n experimentId !== undefined &&\n testRunId !== undefined &&\n experimentId !== testRunId\n ) {\n throw new BitfabError(\n `experimentId (\"${experimentId}\") and its deprecated alias testRunId (\"${testRunId}\") were both passed with different values. Pass only experimentId.`,\n )\n }\n return experimentId ?? testRunId\n}\n\n// Context entry for addContext calls - each entry is an object with multiple key-value pairs\ntype ContextEntry = Record<string, unknown>\n\n// Trace state for tracking trace-level data\ninterface TraceState {\n traceId: string\n traceFunctionKey?: string\n sessionId?: string\n name?: string\n contexts: ContextEntry[]\n startedAt: string\n experimentId?: string\n inputSourceTraceId?: string\n replayAttempt?: number\n dbSnapshotRef?: DbSnapshotRef\n // Set by getCurrentTrace().drop(); ridden out on trace completion so the\n // server scrubs and marks the trace `dropped` instead of `completed`.\n dropped?: boolean\n ingestionType?: TraceIngestionType\n truncation?: TraceTruncation\n}\n\ntype TruncationLimit = \"max_depth\" | \"max_captured_subtree_spans\"\n\nconst TRUNCATION_LIMIT_ORDER: readonly TruncationLimit[] = [\n \"max_depth\",\n \"max_captured_subtree_spans\",\n]\n\ninterface TraceTruncation {\n limits: Set<TruncationLimit>\n droppedSpans: number\n}\n\nexport type { CaptureSurface } from \"./captureSurface.js\"\n\n// Span context for tracking nested spans\ninterface SpanContext {\n traceId: string\n spanId: string\n contexts: ContextEntry[]\n prompt?: string\n surface?: CaptureSurface\n openAiAgentsSpanIds?: string[]\n spanName: string\n rootTraceFunctionKey: string\n instrumentation: SpanInstrumentation\n declaredNode: boolean\n isRoot: boolean\n}\n\n// Global map to track active trace states\nconst activeTraceStates = new Map<string, TraceState>()\n\nfunction truncationMetadata(\n truncation: TraceTruncation | undefined,\n): Record<string, unknown> | undefined {\n if (truncation === undefined || truncation.limits.size === 0) {\n return undefined\n }\n return {\n \"bitfab.truncated_by\": TRUNCATION_LIMIT_ORDER.filter((limit) =>\n truncation.limits.has(limit),\n ).join(\",\"),\n \"bitfab.dropped_spans\": truncation.droppedSpans,\n }\n}\n\nfunction rootTraceFunctionKeyOfTrace(\n traceId: string,\n fallback: string,\n): string {\n return activeTraceStates.get(traceId)?.traceFunctionKey ?? fallback\n}\n\nfunction rootTraceFunctionKeyOf(\n payload: Record<string, unknown>,\n): string | undefined {\n const captured = payload[ROOT_TRACE_FUNCTION_KEY_FIELD]\n if (typeof captured === \"string\") {\n return captured\n }\n const traceId = payload.traceId\n const live =\n typeof traceId === \"string\"\n ? activeTraceStates.get(traceId)?.traceFunctionKey\n : undefined\n if (typeof live === \"string\") {\n return live\n }\n const own = payload.traceFunctionKey\n return typeof own === \"string\" ? own : undefined\n}\n\nlet asyncLocalStorage: AsyncLocalStorageLike<SpanContext[]> | null = null\nconst SPAN_CONTEXT_STORAGE_SYMBOL = Symbol.for(\"bitfab.spanContextStorage\")\n\nconst initializeAsyncContext = () => {\n if (asyncLocalStorage) {\n return\n }\n const shared = globalThis as typeof globalThis & Record<symbol, unknown>\n const existing = shared[SPAN_CONTEXT_STORAGE_SYMBOL] as\n | AsyncLocalStorageLike<SpanContext[]>\n | undefined\n if (existing) {\n asyncLocalStorage = existing\n return\n }\n const created = createAsyncLocalStorage<SpanContext[]>()\n if (created) {\n shared[SPAN_CONTEXT_STORAGE_SYMBOL] = created\n asyncLocalStorage = created\n }\n}\n\nconst asyncLocalStorageReady: Promise<void> = asyncStorageReady.then(() => {\n initializeAsyncContext()\n})\n\n// Browser fallback: a single module-level stack shared across all async\n// execution chains. Works correctly for synchronous nesting and sequential\n// async nesting (the common browser cases), but breaks when multiple\n// independent spans are in-flight concurrently (e.g. Promise.all with\n// separate withSpan calls). In that scenario, whichever span resumes last\n// overwrites the shared stack, so inner spans may see the wrong parent.\n//\n// Node.js avoids this via AsyncLocalStorage, which gives each async chain\n// its own isolated copy of the stack.\n//\n// Potential future fixes:\n// - TC39 AsyncContext proposal (stage 2) would provide AsyncLocalStorage\n// semantics natively in all JS environments, including browsers.\n// https://github.com/tc39/proposal-async-context\n// - Zone.js could work today but is ~50KB, monkey-patches Promise/setTimeout/\n// fetch/etc., and can conflict with frameworks like React. Too invasive for\n// an SDK dependency.\nlet browserSpanStack: SpanContext[] = []\n\nfunction getSpanStack(): SpanContext[] {\n if (asyncLocalStorage) {\n return asyncLocalStorage.getStore() ?? []\n }\n return browserSpanStack\n}\n\nfunction enclosingSurface(): CaptureSurface | undefined {\n const stack = getSpanStack()\n return stack[stack.length - 1]?.surface\n}\n\nfunction runWithSpanStack<T>(stack: SpanContext[], fn: () => T): T {\n if (asyncLocalStorage) {\n return asyncLocalStorage.run(stack, fn)\n }\n // Browser fallback: save/restore the stack around the function call.\n // This is correct for sequential async but not for concurrent async -\n // see the browserSpanStack comment above for details.\n const previousStack = browserSpanStack\n browserSpanStack = stack\n try {\n const result = fn()\n if (result instanceof Promise) {\n return result.finally(() => {\n browserSpanStack = previousStack\n }) as T\n }\n browserSpanStack = previousStack\n return result\n } catch (error) {\n browserSpanStack = previousStack\n throw error\n }\n}\n\nfunction isAsyncGenerator(\n value: unknown,\n): value is AsyncGenerator<unknown, unknown, unknown> {\n if (value === null || typeof value !== \"object\") {\n return false\n }\n const candidate = value as Record<PropertyKey, unknown>\n return (\n typeof candidate.next === \"function\" &&\n typeof candidate.return === \"function\" &&\n typeof candidate.throw === \"function\" &&\n typeof candidate[Symbol.asyncIterator] === \"function\"\n )\n}\n\n// Wrap an async generator so that:\n// 1. Each .next()/.return()/.throw() resumes the generator body inside\n// the parent span's context, so nested withSpan calls nest correctly.\n// 2. The span is sent only after iteration completes (or errors), with\n// the yielded values plus any final return value as the result.\n//\n// Without this, async-generator functions returned from withSpan close their\n// span synchronously when the generator object is created - before any of\n// the body has run - and every child span becomes its own root trace.\nfunction wrapAsyncGenerator<TYield, TReturn>(\n source: AsyncGenerator<TYield, TReturn, unknown>,\n spanStack: SpanContext[],\n sendSpan: (params: { result: unknown; error?: string }) => Promise<void>,\n): AsyncGenerator<TYield, TReturn, unknown> {\n const yielded: TYield[] = []\n let returnValue: TReturn | undefined\n let finalized = false\n\n const finalize = (errorMsg?: string) => {\n if (finalized) {\n return\n }\n finalized = true\n void sendSpan({\n result: { yielded, return: returnValue },\n ...(errorMsg && { error: errorMsg }),\n })\n }\n\n const step = (\n method: \"next\" | \"return\" | \"throw\",\n arg: unknown,\n ): Promise<IteratorResult<TYield, TReturn>> =>\n runWithSpanStack(spanStack, () => {\n const op = source[method] as (\n a?: unknown,\n ) => Promise<IteratorResult<TYield, TReturn>>\n return op.call(source, arg)\n })\n\n const handle = async (\n method: \"next\" | \"return\" | \"throw\",\n arg: unknown,\n ): Promise<IteratorResult<TYield, TReturn>> => {\n try {\n const result = await step(method, arg)\n if (result.done) {\n returnValue = result.value\n finalize()\n } else {\n yielded.push(result.value)\n }\n return result\n } catch (error) {\n finalize(error instanceof Error ? error.message : String(error))\n throw error\n }\n }\n\n const wrapped = {\n next(arg?: unknown) {\n return handle(\"next\", arg)\n },\n return(value: TReturn | PromiseLike<TReturn>) {\n return handle(\"return\", value)\n },\n throw(err: unknown) {\n return handle(\"throw\", err)\n },\n [Symbol.asyncIterator]() {\n return wrapped\n },\n [Symbol.asyncDispose]() {\n return handle(\"return\", undefined).then(() => undefined)\n },\n } as AsyncGenerator<TYield, TReturn, unknown>\n\n return wrapped\n}\n\n// --- BAML Collector support for wrapBAML ---\n\ntype CollectorConstructor = new (name: string) => unknown\n\nlet cachedCollectorClass: CollectorConstructor | null | undefined\n\n/** @internal Reset the cached Collector class - for testing only. */\nexport function _resetCollectorCache(): void {\n cachedCollectorClass = undefined\n}\n\n/** @internal Inject a mock Collector class - for testing only. */\nexport function _setCollectorCache(cls: CollectorConstructor | null): void {\n cachedCollectorClass = cls\n}\n\n/** @internal Count of in-flight (registered, not yet completed) trace states - for testing only. */\nexport function _activeTraceStateCount(): number {\n return activeTraceStates.size\n}\n\nexport function _resolvedApiKey(client: Bitfab): string | null {\n const keyed = client as unknown as {\n findApiKey(callsKeyFunction: boolean): string | undefined\n }\n return keyed.findApiKey(true) ?? null\n}\n\nexport function _serviceUrl(client: Bitfab): string {\n return (client as unknown as { serviceUrl: string }).serviceUrl\n}\n\nasync function loadCollectorClass(): Promise<CollectorConstructor | null> {\n if (cachedCollectorClass !== undefined) {\n return cachedCollectorClass\n }\n try {\n // Reconstructed specifier (see importOptionalPeer): a consumer's bundler\n // must not try to resolve `@boundaryml/baml` at build time when it is not\n // installed (optional peer, only needed for BAML execution / collectors).\n const baml = await importOptionalPeer<typeof import(\"@boundaryml/baml\")>([\n \"@boundaryml\",\n \"baml\",\n ])\n cachedCollectorClass = baml.Collector as CollectorConstructor\n return cachedCollectorClass\n } catch {\n cachedCollectorClass = null\n return null\n }\n}\n\n// Typed accessors for the BAML Collector's internal structure.\n// Uses defensive access since these are untyped objects from the BAML runtime.\n\ninterface CollectorCall {\n selected?: boolean\n clientName?: string\n provider?: string\n usage?: {\n inputTokens?: number\n outputTokens?: number\n cachedInputTokens?: number\n }\n httpRequest?: {\n url?: string\n body?: { json: () => Record<string, unknown> | null }\n }\n}\n\ninterface CollectorLog {\n calls?: CollectorCall[]\n timing?: { durationMs?: number }\n}\n\ninterface CollectorLike {\n last?: CollectorLog | null\n usage?: {\n inputTokens?: number\n outputTokens?: number\n cachedInputTokens?: number\n }\n}\n\nfunction extractPromptFromCollector(collector: unknown): string | null {\n try {\n const c = collector as CollectorLike\n const calls = c?.last?.calls ?? []\n const selectedCall = calls.find((call) => call.selected) ?? calls[0]\n if (!selectedCall?.httpRequest?.body) {\n return null\n }\n const body = selectedCall.httpRequest.body.json()\n if (!body || typeof body !== \"object\") {\n return null\n }\n const messages = body.messages\n if (!Array.isArray(messages) || messages.length === 0) {\n return null\n }\n const rendered = (messages as Record<string, unknown>[])\n .filter(\n (msg): msg is { role: string; content: unknown } =>\n typeof msg === \"object\" &&\n msg !== null &&\n \"role\" in msg &&\n typeof (msg as { role: unknown }).role === \"string\",\n )\n .map((msg) => ({\n role: msg.role,\n content:\n typeof msg.content === \"string\"\n ? msg.content\n : JSON.stringify(msg.content),\n }))\n if (rendered.length > 0) {\n return JSON.stringify(rendered)\n }\n return null\n } catch {\n return null\n }\n}\n\nfunction extractContextFromCollector(\n collector: unknown,\n): Record<string, unknown> | null {\n try {\n const c = collector as CollectorLike\n const calls = c?.last?.calls ?? []\n const selectedCall = calls.find((call) => call.selected) ?? calls[0]\n const usage = c?.usage\n\n const context: Record<string, unknown> = {}\n if (selectedCall?.provider) {\n context.provider = selectedCall.provider\n }\n\n // Extract model from HTTP request body (OpenAI/Anthropic) or URL (Vertex AI)\n const body = selectedCall?.httpRequest?.body?.json()\n if (body && typeof body === \"object\" && typeof body.model === \"string\") {\n context.model = body.model\n } else {\n const url = selectedCall?.httpRequest?.url\n if (url) {\n const match = url.match(/\\/models\\/([^/:]+)/)\n if (match?.[1]) {\n context.model = match[1]\n }\n }\n }\n\n const inputTokens =\n usage?.inputTokens ?? selectedCall?.usage?.inputTokens ?? null\n const outputTokens =\n usage?.outputTokens ?? selectedCall?.usage?.outputTokens ?? null\n if (inputTokens !== null) {\n context.inputTokens = inputTokens\n }\n if (outputTokens !== null) {\n context.outputTokens = outputTokens\n }\n\n const durationMs = c?.last?.timing?.durationMs ?? null\n if (durationMs !== null) {\n context.durationMs = durationMs\n }\n\n return Object.keys(context).length > 0 ? context : null\n } catch {\n return null\n }\n}\n\n/**\n * Options for wrapBAML.\n */\nexport interface WrapBAMLOptions {\n /** Called after each BAML invocation with the Collector instance. */\n onCollector?: (collector: unknown) => void\n}\n\n/**\n * A function returned by wrapBAML that exposes the BAML collector from the last call.\n */\nexport interface WrappedBamlFn<TArgs extends unknown[], TReturn> {\n (...args: TArgs): Promise<TReturn>\n /** The BAML Collector instance from the most recent call. `null` before the first call or if @boundaryml/baml is unavailable. */\n collector: unknown | null\n}\n\n/**\n * A handle to the current active span, allowing context to be added.\n */\nexport interface CurrentSpan {\n /** The Bitfab ID for the current span. */\n readonly id: string\n /** The trace ID for the current span. */\n readonly traceId: string\n /**\n * Add a context entry to this span. Each call appends to the contexts array.\n * Context entries are stored in span_data.contexts as [{key, value}, ...].\n */\n addContext(context: Record<string, unknown>): void\n /**\n * Set the prompt for this span. Stored in span_data.prompt.\n * Calling multiple times overwrites the previous value.\n */\n setPrompt(prompt: string): void\n}\n\n/**\n * A detached handle to a previously-created trace, looked up by its\n * canonical Bitfab trace ID.\n *\n * Unlike `getCurrentTrace()`, this handle is not tied to AsyncLocalStorage -\n * each method sends to the server immediately. Useful for adding context\n * to a trace from a different process, request, or thread (e.g. a forked\n * agent that wants to annotate the original conversation's trace). Its methods\n * write whether or not capture is on.\n */\nexport interface DetachedTrace {\n /** The canonical Bitfab trace ID this handle resolves. */\n readonly traceId: string\n /**\n * Append a context entry to this trace. Each call adds one entry to the\n * server-side contexts array; existing entries are preserved.\n *\n * Resolves once the server has applied the change and REJECTS if the server\n * refused it. A detached patch targets an already-closed trace, so it rides\n * no batch and no later signal would reveal a silent failure - the caller is\n * the only one who can react.\n */\n addContext(context: Record<string, unknown>): Promise<void>\n /**\n * Merge metadata into this trace. Server-side shallow-merges the new keys\n * into the existing metadata object; existing keys are preserved unless\n * overwritten by the new values. Rejects if the server refused the update.\n */\n setMetadata(metadata: Record<string, unknown>): Promise<void>\n /**\n * Set the sessionId for this trace. Replaces any existing sessionId.\n * Rejects if the server refused the update.\n */\n setSessionId(sessionId: string): Promise<void>\n setName(name: string): Promise<void>\n}\n\nfunction isSpanType(value: unknown): value is SpanType {\n return SPAN_TYPES.some((spanType) => spanType === value)\n}\n\n/**\n * A handle to the current active trace, allowing trace-level context to be set.\n */\nexport interface CurrentTrace {\n /**\n * Set the session ID for this trace. Stored in the database session_id column.\n */\n setSessionId(sessionId: string): void\n setName(name: string): void\n /**\n * Set metadata for this trace. Stored in rawData.metadata.\n * Subsequent calls merge with existing metadata, with later values taking precedence.\n */\n setMetadata(metadata: Record<string, unknown>): void\n /**\n * Add a context entry to this trace. Each call appends to the contexts array.\n * Context entries are stored in rawData.contexts as [{key, value}, ...].\n */\n addContext(context: Record<string, unknown>): void\n /**\n * Flag this trace to be dropped. Once flagged, spans that complete afterward\n * are not uploaded at all, and when the trace completes the server scrubs any\n * payloads that already raced out (trace, external trace, and sibling spans),\n * marking it `dropped` instead of `completed` and retaining only a skeleton\n * audit record. Use this to discard runs you never want stored (e.g. health\n * checks, or a run you know contains sensitive data). Takes effect\n * immediately for later spans; the server-side scrub takes effect at trace\n * completion, so a trace that is flagged but never completes is not scrubbed.\n */\n drop(): void\n}\n\n// No-op implementations for when called outside a span context\nconst noOpSpan: CurrentSpan = {\n id: \"\",\n traceId: \"\",\n addContext(): void {\n // No-op\n },\n setPrompt(): void {\n // No-op\n },\n}\n\nconst noOpTrace: CurrentTrace = {\n setSessionId(): void {\n // No-op\n },\n setName(): void {},\n setMetadata(): void {\n // No-op\n },\n addContext(): void {\n // No-op\n },\n drop(): void {\n // No-op\n },\n}\n\n/**\n * Get a handle to the current active span.\n *\n * Call this from inside a traced function (wrapped with `withSpan`) to get\n * a span handle that allows adding context at runtime.\n *\n * Returns a no-op object if called outside of a span context (methods do nothing).\n */\nexport function getCurrentSpan(): CurrentSpan {\n const stack = getSpanStack()\n const current = stack[stack.length - 1]\n if (!current) {\n return noOpSpan\n }\n return {\n id: current.spanId,\n traceId: current.traceId,\n addContext(context: Record<string, unknown>): void {\n try {\n if (typeof context !== \"object\" || context === null) {\n return\n }\n // Push the entire context object as one entry\n current.contexts.push(context)\n } catch {\n // Silently ignore - never crash the host app\n }\n },\n setPrompt(prompt: string): void {\n try {\n if (typeof prompt !== \"string\") {\n return\n }\n current.prompt = prompt\n } catch {\n // Silently ignore - never crash the host app\n }\n },\n }\n}\n\n/**\n * Get the database branch the current replay item is running against.\n *\n * Call this from inside a function being replayed with `replay({ dbBranch })`\n * and point your database client at `branch.databaseUrl` so the replay reads\n * the data as it was at trace time:\n *\n * ```ts\n * const branch = getCurrentReplayBranch()\n * const url = branch?.databaseUrl ?? process.env.DATABASE_URL\n * ```\n *\n * Returns null outside a replay item, and for an item whose source trace\n * carried no DB snapshot reference, so live request code takes the same path\n * it always did.\n */\nexport function getCurrentReplayBranch(): ReplayBranch | null {\n const ctx = getReplayContext()\n if (!ctx?.dbBranchLease) {\n return null\n }\n // Surface the Bitfab traceId (what the customer sees in the dashboard), not\n // the external_traces.id. Falling back to the external ID keeps replays from\n // external sources working until the source-system path is fully wired.\n const traceId = ctx.sourceBitfabTraceId ?? ctx.inputSourceTraceId\n if (!traceId) {\n return null\n }\n return new ReplayBranch(ctx.dbBranchLease, traceId, ctx)\n}\n\n/**\n * Get a handle to the current active trace.\n *\n * Call this from inside a traced function (wrapped with `withSpan`) to get\n * a trace handle that allows setting trace-level context at runtime.\n *\n * Returns a no-op object if called outside of a span context (methods do nothing).\n */\nexport function getCurrentTrace(): CurrentTrace {\n const stack = getSpanStack()\n const current = stack[stack.length - 1]\n if (!current) {\n return noOpTrace\n }\n\n const traceId = current.traceId\n\n const getOrCreateTraceState = (): TraceState => {\n let traceState = activeTraceStates.get(traceId)\n if (!traceState) {\n traceState = {\n traceId,\n startedAt: nowIsoTimestamp(),\n contexts: [],\n }\n activeTraceStates.set(traceId, traceState)\n }\n return traceState\n }\n\n return {\n setSessionId(sessionId: string): void {\n try {\n const traceState = getOrCreateTraceState()\n traceState.sessionId = sessionId\n } catch {\n // Silently ignore - never crash the host app\n }\n },\n setName(name: string): void {\n if (typeof name !== \"string\" || name.length === 0) {\n return\n }\n try {\n getOrCreateTraceState().name = name\n } catch {}\n },\n setMetadata(metadata: Record<string, unknown>): void {\n try {\n if (typeof metadata !== \"object\" || metadata === null) {\n return\n }\n recordCallerTraceMetadata(traceId, metadata)\n } catch {\n // Silently ignore - never crash the host app\n }\n },\n addContext(context: Record<string, unknown>): void {\n try {\n if (typeof context !== \"object\" || context === null) {\n return\n }\n const traceState = getOrCreateTraceState()\n // Push the entire context object as one entry\n traceState.contexts.push(context)\n } catch {\n // Silently ignore - never crash the host app\n }\n },\n drop(): void {\n try {\n getOrCreateTraceState().dropped = true\n } catch {\n // Silently ignore - never crash the host app\n }\n },\n }\n}\n\n/**\n * Read an environment variable without throwing in non-Node runtimes\n * (browsers, edge workers) where `process` is absent. The SDK ships to\n * browsers, so this must never assume `process` exists.\n */\nfunction readEnv(name: string): string | undefined {\n if (typeof process !== \"undefined\" && process.env) {\n return process.env[name]\n }\n return undefined\n}\n\nconst CAPTURE_ENABLED_ENV = \"BITFAB_CAPTURE_ENABLED\"\n\nconst REPLAY_API_KEY_ENV = \"BITFAB_REPLAY_BITFAB_API_KEY\"\n\nconst TRUE_ENV_VALUES = new Set([\"1\", \"true\", \"yes\"])\nconst FALSE_ENV_VALUES = new Set([\"0\", \"false\", \"no\"])\n\nfunction readBooleanEnv(name: string): boolean | undefined {\n const raw = readEnv(name)\n if (raw === undefined) {\n return undefined\n }\n const value = raw.trim().toLowerCase()\n if (value === \"\") {\n return undefined\n }\n if (TRUE_ENV_VALUES.has(value)) {\n return true\n }\n if (FALSE_ENV_VALUES.has(value)) {\n return false\n }\n warnOnce(\n `unrecognized-boolean-env:${name}`,\n `${name}=\"${raw}\" is not 0 or 1; ignoring it.`,\n )\n return undefined\n}\n\nexport interface SeedCaseOptions {\n input: unknown[]\n expected?: unknown\n // biome-ignore lint/suspicious/noExplicitAny: matches the replay callable\n fn?: (...args: any[]) => unknown\n metadata?: Record<string, unknown>\n sessionId?: string\n name?: string\n spanName?: string\n spanType?: SpanType\n}\n\nexport interface SeedRunOptions<TArgs extends unknown[]> {\n args?: TArgs\n metadata?: Record<string, unknown>\n sessionId?: string\n name?: string\n}\n\nexport interface ReseedOptions {\n traceId: string\n}\n\nexport interface ReseedResult {\n traceId: string\n previousRunTraceId: string\n}\n\nexport interface BitfabConfig {\n /**\n * The API key for Bitfab API authentication. Resolved lazily, the first\n * time a span actually needs it, not at construction. When it resolves\n * empty, tracing is disabled (a no-op, unless `strict` is set).\n *\n * Accepts either a string or a function returning the key. The function\n * form is resolved at first use, so it survives the ESM trap where a shim\n * built at module load runs before the script body's `dotenv.config()`:\n * `apiKey: () => process.env.BITFAB_API_KEY`. When omitted (or it resolves\n * empty), the SDK also falls back to reading `BITFAB_API_KEY` from the\n * environment itself, again at first use.\n */\n apiKey?: string | (() => string | null | undefined)\n /** The base URL for the Bitfab API (default: https://bitfab.ai) */\n serviceUrl?: string\n /** Request timeout in milliseconds (default: 120000) */\n timeout?: number\n /** Environment variables for LLM provider API keys (only OPENAI_API_KEY is supported) */\n envVars?: AllowedEnvVars\n captureEnabled?: boolean\n enabled?: boolean\n /**\n * Fail loud instead of degrading quietly. When true, the first traced call\n * with no resolvable API key throws a `BitfabError` rather than silently\n * disabling tracing. Off by default so a missing telemetry key never takes\n * down the host app; turn it on in standalone scripts where a run that\n * emits no traces is a failure you want surfaced immediately.\n */\n strict?: boolean\n /** The generated BAML client instance (e.g., `b` from your baml_client). Used by wrapBAML() when no explicit client is passed. */\n bamlClient?: unknown\n /**\n * Per-trace database snapshot config. When set, every root span captures\n * a wall-clock timestamp (and, if `captureRef` is provided, a provider-\n * specific point-in-time ref) so the trace can later be replayed against\n * a branch materialized from that point.\n */\n dbSnapshot?: DbSnapshotConfig\n simulationPlan?: boolean\n}\n\nexport type { SpanType }\n\n/**\n * Controls when a span is captured.\n * - always: Capture the span even when it becomes the root of a new trace.\n * - nested: Capture the span only when another Bitfab span is already active.\n */\nexport type CaptureWhen = \"always\" | \"nested\"\n\n/**\n * Options for configuring span behavior.\n */\nexport interface SpanOptions {\n /**\n * The name of the span. Defaults to the function name if available,\n * otherwise falls back to the trace function key.\n */\n name?: string\n /**\n * The type of span. Defaults to \"custom\" if not specified.\n */\n type?: SpanType\n /**\n * Controls whether this span may start a new trace. Defaults to \"always\".\n *\n * Use \"nested\" for reusable helpers that should appear inside an existing\n * trace but should run untraced when called on their own.\n * Unknown values warn once and default to \"always\".\n */\n captureWhen?: CaptureWhen\n /**\n * When true, replay will reuse this span's historical output instead of\n * executing the wrapped function. Read by the \"marked\" replay strategy;\n * ignored outside replay and under the \"all\"/\"none\" strategies.\n *\n * Use this for child spans that are expensive (paid LLM/API calls),\n * slow, or non-deterministic - the root function still runs real code,\n * only the marked descendants return their recorded output. If a selected\n * occurrence is unavailable, replay fails the item without executing the\n * real child.\n */\n mockOnReplay?: boolean\n /**\n * Experimental selective-replay eligibility, recorded with this span.\n * Attests that the whole subtree is output-only with JSON value semantics:\n * no hidden mutable reads, required effects, input mutations, or reliance\n * on object identity. Include implementation/dependency changes in replay's\n * mustRun. Both capture-time and current declarations must allow reuse.\n * Does not affect ordinary replay or replace mockOnReplay safety protection.\n */\n replayReusable?: boolean\n /** Optional experiment ID included on the span and on the trace it starts. */\n experimentId?: string\n /**\n * @deprecated Use {@link experimentId}. Same meaning. Passing both with\n * different values throws.\n */\n testRunId?: string\n /**\n * Record a serializable view of a non-serializable result (e.g. a live\n * stream object) as the span output.\n *\n * When set, the wrapped function's raw return value is handed back to the\n * caller unchanged (so streaming and first-byte latency are untouched),\n * but instead of serializing that raw value, the span records\n * `await finalize(result)`. Use this to trace functions that return a live\n * stream consumed by the caller (Vercel AI SDK `streamText`, a\n * `ReadableStream`, an SSE response) while still capturing a serializable,\n * replayable output such as `{ text, usage, toolCalls }`.\n *\n * Reading from a multi-consumer stream result (like the AI SDK's, which\n * tees internally) does not disturb the caller's own consumption. For the\n * Vercel AI SDK shape, pass the prebuilt `finalizers.aiSdk` helper.\n *\n * Ignored for async-generator results, which are captured automatically.\n */\n // biome-ignore lint/suspicious/noExplicitAny: the result type is the wrapped fn's return; SpanOptions is not generic, so callers narrow it inside finalize\n finalize?: (result: any) => unknown | Promise<unknown>\n}\n\n/**\n * The standard method context passed to a decorator.\n *\n * Defined structurally instead of referencing TypeScript's built-in\n * `ClassMethodDecoratorContext`, which was added in TypeScript 5.0. This keeps\n * the SDK's non-decorator APIs consumable by projects on older compilers.\n */\nexport interface SpanMethodDecoratorContext<TThis, TValue> {\n readonly kind: \"method\"\n readonly name: string | symbol\n readonly static: boolean\n readonly private: boolean\n readonly access: {\n has(object: TThis): boolean\n get(object: TThis): TValue\n }\n addInitializer(initializer: (this: TThis) => void): void\n readonly metadata?: Record<PropertyKey, unknown>\n}\n\n/** A standard ECMAScript method decorator produced by {@link Bitfab.span}. */\nexport type SpanMethodDecorator = <TThis, TArgs extends unknown[], TReturn>(\n originalMethod: (this: TThis, ...args: TArgs) => TReturn,\n context: SpanMethodDecoratorContext<\n TThis,\n (this: TThis, ...args: TArgs) => TReturn\n >,\n) => (this: TThis, ...args: TArgs) => TReturn\n\n/** Trace-owned configuration for a function discovered beneath `trace()`. */\nexport interface NodeOptions extends Omit<SpanOptions, \"captureWhen\"> {\n /** Whether the enclosing trace captures this call. Defaults to true. */\n capture?: boolean\n}\n\ntype NodeConfigurationOptions = Omit<AutoTraceNodeConfiguration, \"functionName\">\n\ntype StandardNodeMethodDecorator = SpanMethodDecorator\n\ntype LegacyNodeMethodDecorator = <TThis, TArgs extends unknown[], TReturn>(\n target: object,\n propertyKey: string | symbol,\n descriptor: TypedPropertyDescriptor<(this: TThis, ...args: TArgs) => TReturn>,\n) => void\n\n/** A method decorator produced by {@link Bitfab.node}. */\nexport type NodeMethodDecorator = StandardNodeMethodDecorator &\n LegacyNodeMethodDecorator\n\n/** Options for experimental automatic subtree tracing. */\nexport interface TraceOptions {\n /** Root span name. Defaults to the decorated or wrapped function name. */\n name?: string\n /** Root span type. Descendants are always `function` spans. */\n type?: SpanType\n /**\n * Default replay-mocking policy for automatically captured descendants.\n * When true, those descendants are mocked by the default \"marked\" strategy\n * unless a node explicitly sets `mockOnReplay: false`. Defaults to false.\n */\n mockOnReplayDefault?: boolean\n /** Maximum number of recorded descendant levels. Defaults to 30. */\n maxDepth?: number\n maxCapturedSubtreeSpans?: number\n /** Qualified or simple function names to leave out of the subtree. */\n exclude?: readonly string[] | ReadonlySet<string>\n /** Record rest-argument wrapper functions. Defaults to false. */\n includeWrappers?: boolean\n}\n\ninterface EnclosingTrace {\n traceId: string\n spanId: string\n traceFunctionKey: string\n link: NestedTraceLink\n}\n\ninterface InternalSpanOptions extends SpanOptions {\n nameFor?: (receiver: unknown) => string\n instrumentation?: SpanInstrumentation\n functionId?: string\n captureContent?: boolean\n autoTraceDefinition?: AutoTraceFunctionDefinition\n declaredNode?: boolean\n surface?: SurfaceRequest\n nestedTrace?: NestedTraceLink\n enclosingTrace?: EnclosingTrace\n replayOutput?: () => unknown\n}\n\ntype StandardTraceMethodDecorator = <This, TArgs extends unknown[], TReturn>(\n method: (this: This, ...args: TArgs) => TReturn,\n context: { kind: \"method\"; name: string | symbol },\n) => (this: This, ...args: TArgs) => TReturn\n\ntype LegacyTraceMethodDecorator = <This, TArgs extends unknown[], TReturn>(\n target: object,\n propertyKey: string | symbol,\n descriptor: TypedPropertyDescriptor<(this: This, ...args: TArgs) => TReturn>,\n) => void\n\ntype TraceMethodDecorator = StandardTraceMethodDecorator &\n LegacyTraceMethodDecorator\n\nconst DEFAULT_AUTO_TRACE_MAX_DEPTH = 30\nconst DEFAULT_AUTO_TRACE_MAX_CAPTURED_SUBTREE_SPANS = 500\n\nfunction spanOwnerName(receiver: unknown): string | undefined {\n if (typeof receiver === \"function\") {\n return receiver.name !== \"\" ? receiver.name : undefined\n }\n if (typeof receiver !== \"object\" || receiver === null) {\n return undefined\n }\n const owner = (receiver as { constructor?: unknown }).constructor\n if (typeof owner !== \"function\" || owner === Object || owner.name === \"\") {\n return undefined\n }\n return owner.name\n}\n\nfunction qualifiedSpanName(\n receiver: unknown,\n functionName: string | undefined,\n): string | undefined {\n if (functionName === undefined) {\n return undefined\n }\n const owner = spanOwnerName(receiver)\n return owner === undefined ? functionName : `${owner}.${functionName}`\n}\n\nfunction autoTraceLimit(value: number | undefined, fallback: number): number {\n return value !== undefined && Number.isFinite(value) && value >= 0\n ? Math.floor(value)\n : fallback\n}\n\ninterface FunctionVersionResponse {\n id: string\n name: string\n versionId: string\n versionNumber: number | null\n prompt: string\n providers: ProviderDefinition[]\n}\n\n// Re-export BitfabError for backwards compatibility\nexport { BitfabError, MixedTracingError }\n\n/**\n * Client for making provider-based API calls via BAML.\n */\nexport class Bitfab {\n private readonly apiKeyConfig:\n | string\n | (() => string | null | undefined)\n | undefined\n /** Cached only once a non-empty key is found, so an early resolve (before env loaded) can't poison a later one. */\n private resolvedApiKey: string | undefined\n /** Gate the empty-key warning to fire at most once. */\n private apiKeyWarned: boolean = false\n private readonly serviceUrl: string\n private readonly timeout: number\n private readonly envVars: AllowedEnvVars\n private readonly captureConfiguredInCode: boolean | undefined\n private captureConfiguredFromEnv: boolean | undefined\n private readonly strict: boolean\n private readonly httpClient: HttpClient\n /** Dataset operations for the authenticated organization. */\n readonly datasets: DatasetsClient\n /** Organization-scoped groupings for assertions and scenarios. */\n readonly categories: CategoriesClient\n /** @deprecated Use {@link categories}. Same client. */\n readonly assertionCategories: CategoriesClient\n readonly organizationMembers: OrganizationMembersClient\n readonly traces: TracesClient\n readonly spans: SpansClient\n readonly labels: LabelsClient\n readonly graders: GradersClient\n readonly experiments: ExperimentsClient\n private readonly bamlClient: unknown\n private readonly dbSnapshot: DbSnapshotConfig | undefined\n private readonly simulationPlan: SimulationPlan\n /**\n * Mock overrides registered via {@link Bitfab.registerMockOverride}, applied\n * to every `replay` on this client (after any per-call `mockOverride`). In\n * registration order; first matcher wins within this list.\n */\n private readonly mockOverrides: MockOverride[] = []\n\n /**\n * Initialize the Bitfab client.\n *\n * @param config - Configuration options for the client\n */\n constructor(config: BitfabConfig) {\n this.apiKeyConfig = config.apiKey\n this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL\n this.timeout = config.timeout ?? 120000\n this.envVars = config.envVars ?? {}\n if (config.enabled !== undefined) {\n warnOnce(\n \"deprecated-enabled-option\",\n \"Bitfab({ enabled }) is deprecated; pass captureEnabled instead.\",\n )\n }\n this.captureConfiguredInCode =\n config.captureEnabled === undefined && config.enabled === undefined\n ? undefined\n : (config.captureEnabled ?? true) && (config.enabled ?? true)\n this.strict = config.strict ?? false\n this.bamlClient = config.bamlClient ?? null\n if (config.dbSnapshot) {\n validateDbSnapshotConfig(config.dbSnapshot)\n }\n this.dbSnapshot = config.dbSnapshot\n startCommitRefResolution()\n // The key is NOT read here. HttpClient gets a thunk so the key is resolved\n // at send time, after any in-script dotenv.config() has run.\n this.httpClient = new HttpClient({\n apiKey: () => this.resolveApiKey(),\n serviceUrl: this.serviceUrl,\n timeout: this.timeout,\n })\n this.simulationPlan = new SimulationPlan(\n this.httpClient,\n config.simulationPlan ?? true,\n )\n this.httpClient.externalSpanTransform = (payload, submit, drop) =>\n this.applySimulationPlan(payload, submit, drop)\n this.httpClient.externalTraceTransform = (payload, submit) =>\n this.simulationPlan.sendTrace(payload, submit)\n this.httpClient.releaseHeldExternalSpans = (timeoutMs) =>\n this.simulationPlan.release(timeoutMs)\n this.httpClient.stopSimulationPlan = () => this.simulationPlan.stop()\n if (this.findApiKey(false) !== undefined) {\n this.simulationPlan.refresh()\n }\n this.datasets = new DatasetsClient(this.httpClient)\n this.categories = new CategoriesClient(this.httpClient)\n this.assertionCategories = this.categories\n this.organizationMembers = new OrganizationMembersClient(this.httpClient)\n this.traces = new TracesClient(this.httpClient)\n this.spans = new SpansClient(this.httpClient)\n this.labels = new LabelsClient(this.httpClient)\n this.graders = new GradersClient(this.httpClient)\n this.experiments = new ExperimentsClient(this.httpClient)\n }\n\n /**\n * Decorate a class method as an automatically expanded trace root.\n *\n * Build instrumentation turns repository functions called beneath this\n * method into nested spans that capture inputs, outputs, and errors by\n * default. Source exclusions and sim-plan capture-off decisions are explicit\n * opt-outs from that default.\n * Without a compatible build transform, this still records the decorated\n * method as a normal rich root span but cannot discover child calls.\n *\n * @param traceFunctionKey - Groups traces and their capture controls.\n * @param options - Root presentation, subtree bounds, and exclusions.\n * @experimental Automatic child-call instrumentation is experimental.\n */\n trace(\n traceFunctionKey: string,\n options: TraceOptions = {},\n ): TraceMethodDecorator {\n const decorator = (...args: unknown[]): unknown => {\n if (args.length === 3) {\n const propertyKey = args[1] as string | symbol\n const descriptor = args[2] as TypedPropertyDescriptor<\n (this: unknown, ...methodArgs: unknown[]) => unknown\n >\n if (!descriptor || typeof descriptor.value !== \"function\") {\n throw new BitfabError(\"@bitfab.trace can only decorate methods\")\n }\n descriptor.value = this.createAutoTraceRoot(\n traceFunctionKey,\n String(propertyKey),\n options,\n descriptor.value,\n )\n return\n }\n\n const method = args[0]\n const context = args[1] as\n | { kind?: string; name?: string | symbol }\n | undefined\n if (\n typeof method !== \"function\" ||\n context?.kind !== \"method\" ||\n context.name === undefined\n ) {\n throw new BitfabError(\"@bitfab.trace can only decorate methods\")\n }\n return this.createAutoTraceRoot(\n traceFunctionKey,\n String(context.name),\n options,\n method as (this: unknown, ...methodArgs: unknown[]) => unknown,\n )\n }\n\n return decorator as TraceMethodDecorator\n }\n\n /**\n * Wrap a function as an automatically expanded trace root.\n *\n * This is the function-oriented equivalent of {@link Bitfab.trace}. Build\n * instrumentation turns repository functions called beneath the wrapped\n * function into nested spans that capture inputs, outputs, and errors by\n * default. Source exclusions and sim-plan capture-off decisions are explicit\n * opt-outs from that default. Without a compatible transform, this still\n * records one normal rich root span and runs the function unchanged.\n *\n * @param traceFunctionKey - Groups traces and their capture controls.\n * @param optionsOrFn - Options or the workflow entrypoint to wrap.\n * @param maybeFn - Workflow entrypoint when options are provided.\n * @experimental Automatic child-call instrumentation is experimental.\n */\n withTrace<This, TArgs extends unknown[], TReturn>(\n traceFunctionKey: string,\n fn: (this: This, ...args: TArgs) => TReturn,\n ): (this: This, ...args: TArgs) => TReturn\n withTrace<This, TArgs extends unknown[], TReturn>(\n traceFunctionKey: string,\n options: TraceOptions,\n fn: (this: This, ...args: TArgs) => TReturn,\n ): (this: This, ...args: TArgs) => TReturn\n withTrace<This, TArgs extends unknown[], TReturn>(\n traceFunctionKey: string,\n optionsOrFn: TraceOptions | ((this: This, ...args: TArgs) => TReturn),\n maybeFn?: (this: This, ...args: TArgs) => TReturn,\n ): (this: This, ...args: TArgs) => TReturn {\n const options = typeof optionsOrFn === \"function\" ? {} : optionsOrFn\n const fn = typeof optionsOrFn === \"function\" ? optionsOrFn : maybeFn\n if (!fn) {\n throw new BitfabError(\"bitfab.withTrace requires a function\")\n }\n const name = fn.name !== \"\" ? fn.name : traceFunctionKey\n return this.createAutoTraceRoot(traceFunctionKey, name, options, fn)\n }\n\n /**\n * Configure a transformed class method when it is discovered beneath a\n * {@link Bitfab.trace} root.\n *\n * The decorator creates no span or trace by itself. Beneath an active trace,\n * it can rename or retype the discovered call, capture its contents, mark it\n * for recorded-output replay, finalize its output, or omit it while leaving\n * captured descendants attached to the nearest captured parent.\n *\n * @param options - Trace-owned call configuration.\n * @experimental Automatic child-call instrumentation is experimental.\n */\n node(options: NodeOptions = {}): NodeMethodDecorator {\n const configuration = this.resolveNodeConfiguration(options)\n const decorator = (...args: unknown[]): unknown => {\n if (args.length === 3) {\n const descriptor = args[2] as TypedPropertyDescriptor<\n (this: unknown, ...methodArgs: unknown[]) => unknown\n >\n if (!descriptor || typeof descriptor.value !== \"function\") {\n throw new BitfabError(\"@bitfab.node can only decorate methods\")\n }\n descriptor.value = this.createAutoTraceNode(\n configuration,\n descriptor.value,\n String(args[1]),\n )\n return\n }\n\n const method = args[0]\n const context = args[1] as\n | { kind?: string; name?: string | symbol }\n | undefined\n if (typeof method !== \"function\" || context?.kind !== \"method\") {\n throw new BitfabError(\"@bitfab.node can only decorate methods\")\n }\n return this.createAutoTraceNode(\n configuration,\n method as (this: unknown, ...methodArgs: unknown[]) => unknown,\n String(context.name),\n )\n }\n\n return decorator as NodeMethodDecorator\n }\n\n /**\n * Configure a transformed standalone function when it is discovered beneath\n * a {@link Bitfab.trace} or {@link Bitfab.withTrace} root.\n *\n * This is the function-oriented equivalent of {@link Bitfab.node}. Without\n * an active automatic trace, the returned function runs normally and never\n * creates a span or trace. The function must be named so configuration can\n * be bound to its transformed definition without leaking to a descendant.\n *\n * @param optionsOrFn - Node options or the function to configure.\n * @param maybeFn - Function to configure when options are provided.\n * @experimental Automatic child-call instrumentation is experimental.\n */\n withNode<This, TArgs extends unknown[], TReturn>(\n fn: (this: This, ...args: TArgs) => TReturn,\n ): (this: This, ...args: TArgs) => TReturn\n withNode<This, TArgs extends unknown[], TReturn>(\n options: NodeOptions,\n fn: (this: This, ...args: TArgs) => TReturn,\n ): (this: This, ...args: TArgs) => TReturn\n withNode<This, TArgs extends unknown[], TReturn>(\n optionsOrFn: NodeOptions | ((this: This, ...args: TArgs) => TReturn),\n maybeFn?: (this: This, ...args: TArgs) => TReturn,\n internalFunctionName?: string,\n ): (this: This, ...args: TArgs) => TReturn {\n const options = typeof optionsOrFn === \"function\" ? {} : optionsOrFn\n const fn = typeof optionsOrFn === \"function\" ? optionsOrFn : maybeFn\n if (!fn) {\n throw new BitfabError(\"bitfab.withNode requires a function\")\n }\n const configuration = this.resolveNodeConfiguration(options)\n const functionName = internalFunctionName ?? fn.name\n if (functionName === \"\") {\n throw new BitfabError(\n \"bitfab.withNode requires a named function so the subtree transform can bind its configuration to the correct call.\",\n )\n }\n return this.createAutoTraceNode(configuration, fn, functionName)\n }\n\n private resolveNodeConfiguration(\n options: NodeOptions,\n ): NodeConfigurationOptions {\n const capture = options.capture ?? true\n if (!capture && options.mockOnReplay === true) {\n throw new BitfabError(\n \"bitfab.node({ capture: false }) cannot use mockOnReplay: true because an uncaptured node has no recorded output.\",\n )\n }\n const experimentId = resolveSpanExperimentId(options)\n return {\n capture,\n type: options.type ?? \"custom\",\n ...(options.name !== undefined && { name: options.name }),\n ...(experimentId !== undefined && { experimentId }),\n ...(options.mockOnReplay !== undefined && {\n mockOnReplay: options.mockOnReplay,\n }),\n ...(options.replayReusable !== undefined && {\n replayReusable: options.replayReusable,\n }),\n ...(options.finalize !== undefined && { finalize: options.finalize }),\n }\n }\n\n private createAutoTraceNode<This, TArgs extends unknown[], TReturn>(\n configuration: NodeConfigurationOptions,\n fn: (this: This, ...args: TArgs) => TReturn,\n functionName: string,\n ): (this: This, ...args: TArgs) => TReturn {\n const nodeConfiguration = { ...configuration, functionName }\n return function (this: This, ...args: TArgs): TReturn {\n if (!__bitfabAutoTraceActive()) {\n if (enclosingSurface() === \"opt-in\") {\n throw mixedTracingError(\"node()\", \"opt-out\", \"opt-in\")\n }\n return fn.apply(this, args)\n }\n return runWithAutoTraceNodeConfiguration(nodeConfiguration, () =>\n fn.apply(this, args),\n )\n }\n }\n\n private createAutoTraceRoot<This, TArgs extends unknown[], TReturn>(\n traceFunctionKey: string,\n name: string,\n options: TraceOptions,\n fn: (this: This, ...args: TArgs) => TReturn,\n ): (this: This, ...args: TArgs) => TReturn {\n const self = this\n self.simulationPlan.refresh()\n const maxDepth = autoTraceLimit(\n options.maxDepth,\n DEFAULT_AUTO_TRACE_MAX_DEPTH,\n )\n const maxCapturedSubtreeSpans = autoTraceLimit(\n options.maxCapturedSubtreeSpans,\n DEFAULT_AUTO_TRACE_MAX_CAPTURED_SUBTREE_SPANS,\n )\n const rootIsAsync = fn.constructor.name === \"AsyncFunction\"\n const excluded = new Set(options.exclude ?? [])\n const includeWrappers = options.includeWrappers ?? false\n const rootNameFor = (receiver: unknown): string =>\n options.name ??\n (fn.name !== \"\" ? (qualifiedSpanName(receiver, name) ?? name) : name)\n const rootOptions: InternalSpanOptions = {\n name: options.name,\n nameFor: rootNameFor,\n type: options.type ?? \"custom\",\n surface: \"opt-out\",\n instrumentation: \"trace\",\n }\n const buildTracedRoot = (\n spanOptions: InternalSpanOptions,\n ): ((this: This, ...args: TArgs) => TReturn) =>\n this.withSpan(\n traceFunctionKey,\n spanOptions,\n function (this: This, ...args: TArgs): TReturn {\n self.simulationPlan.refresh()\n let capturedSubtreeCount = 0\n const rootSpanStack = getSpanStack()\n const rootSpan = rootSpanStack[rootSpanStack.length - 1]\n const rootTraceState =\n rootSpan === undefined\n ? undefined\n : activeTraceStates.get(rootSpan.traceId)\n const truncation: TraceTruncation = rootTraceState?.truncation ?? {\n limits: new Set(),\n droppedSpans: 0,\n }\n const truncate = (limit: TruncationLimit): void => {\n truncation.limits.add(limit)\n truncation.droppedSpans += 1\n warnOnce(\n `auto-trace-truncated:${traceFunctionKey}`,\n `\"${traceFunctionKey}\" hit an automatic subtree capture limit (maxDepth=${maxDepth}, maxCapturedSubtreeSpans=${maxCapturedSubtreeSpans}); its trace is incomplete. Raise the limits, declare the calls you need with node(), or narrow the subtree with exclude.`,\n )\n }\n if (rootTraceState !== undefined) {\n rootTraceState.truncation = truncation\n }\n const spanStackOf = (entry: AutoTraceEntry): SpanContext[] =>\n entry.spanStack as SpanContext[]\n const openChild = <T>(\n entry: AutoTraceEntry,\n childOptions: InternalSpanOptions,\n inputs: unknown[],\n isAsync: boolean,\n invokeFn: () => T,\n ): T => {\n const runInside = (): T =>\n runWithAutoTraceEntry(\n entry,\n {\n context: autoTraceContext,\n depth: entry.depth + 1,\n spanStack: getSpanStack(),\n },\n invokeFn,\n )\n return runWithSpanStack(spanStackOf(entry), () => {\n if (isAsync) {\n const tracedAsyncChild = self.withSpan(\n traceFunctionKey,\n childOptions,\n async (..._inputs: unknown[]) => await runInside(),\n )\n return tracedAsyncChild(...inputs) as T\n }\n const tracedChild = self.withSpan(\n traceFunctionKey,\n childOptions,\n (..._inputs: unknown[]): T => runInside(),\n )\n return tracedChild(...inputs)\n })\n }\n const rootTraceFunctionKeyOfEntry = (\n entry: AutoTraceEntry,\n ): string => {\n const stack = spanStackOf(entry)\n const innermost = stack[stack.length - 1]\n return innermost === undefined\n ? traceFunctionKey\n : rootTraceFunctionKeyOfTrace(innermost.traceId, traceFunctionKey)\n }\n const recordsSpan = (\n entry: AutoTraceEntry,\n spanName: string,\n declared: boolean,\n ): boolean => {\n const contentOff =\n !declared &&\n self.simulationPlan.contentOffNow(\n rootTraceFunctionKeyOfEntry(entry),\n spanName,\n )\n const countsTowardCapturedSubtree = !declared && !contentOff\n const refusedBy: TruncationLimit | undefined =\n entry.depth >= maxDepth\n ? \"max_depth\"\n : countsTowardCapturedSubtree &&\n capturedSubtreeCount >= maxCapturedSubtreeSpans\n ? \"max_captured_subtree_spans\"\n : undefined\n if (refusedBy !== undefined) {\n truncate(refusedBy)\n return false\n }\n if (countsTowardCapturedSubtree) {\n capturedSubtreeCount += 1\n }\n return true\n }\n const autoTraceContext: AutoTraceContext = {\n traceFunctionKey,\n invoke<T>(\n definition: AutoTraceFunctionDefinition,\n inputs: unknown[],\n invokeFn: () => T,\n entry: AutoTraceEntry,\n nodeConfiguration?: AutoTraceNodeConfiguration,\n ): T {\n const nameParts = definition.name.split(\".\")\n const simpleName = nameParts[nameParts.length - 1]\n const invokeWithoutNode = (): T =>\n nodeConfiguration === undefined\n ? invokeFn()\n : clearAutoTraceNodeConfiguration(invokeFn)\n if (\n excluded.has(definition.name) ||\n (simpleName !== undefined && excluded.has(simpleName)) ||\n (nodeConfiguration === undefined &&\n definition.wrapper === true &&\n !includeWrappers)\n ) {\n return invokeWithoutNode()\n }\n if (nodeConfiguration?.capture === false) {\n return clearAutoTraceNodeConfiguration(invokeFn)\n }\n const spanName = nodeConfiguration?.name ?? definition.name\n const declaredNode = nodeConfiguration !== undefined\n if (!recordsSpan(entry, spanName, declaredNode)) {\n return invokeWithoutNode()\n }\n const mockOnReplay =\n nodeConfiguration?.mockOnReplay ?? options.mockOnReplayDefault\n const nodeExperimentId =\n nodeConfiguration === undefined\n ? undefined\n : resolveSpanExperimentId(nodeConfiguration)\n const childOptions: InternalSpanOptions = {\n name: spanName,\n type: nodeConfiguration?.type ?? \"function\",\n captureWhen: \"nested\",\n surface: \"opt-out\",\n instrumentation: \"trace\",\n functionId: definition.id,\n captureContent: true,\n autoTraceDefinition: definition,\n ...(declaredNode && { declaredNode }),\n ...(nodeExperimentId !== undefined && {\n experimentId: nodeExperimentId,\n }),\n ...(mockOnReplay !== undefined && {\n mockOnReplay,\n }),\n ...(nodeConfiguration?.replayReusable !== undefined && {\n replayReusable: nodeConfiguration.replayReusable,\n }),\n ...(nodeConfiguration?.finalize !== undefined && {\n finalize: nodeConfiguration.finalize,\n }),\n }\n return openChild(\n entry,\n childOptions,\n inputs,\n definition.async === true,\n invokeFn,\n )\n },\n invokeNestedRoot<T>(\n nestedName: string,\n nestedFunctionName: string,\n inputs: unknown[],\n invokeFn: () => T,\n entry: AutoTraceEntry,\n link: NestedTraceLink | undefined,\n ): T {\n if (\n excluded.has(nestedName) ||\n excluded.has(nestedFunctionName) ||\n !recordsSpan(entry, nestedName, true)\n ) {\n return invokeFn()\n }\n const childOptions: InternalSpanOptions = {\n name: nestedName,\n type: \"function\",\n captureWhen: \"nested\",\n surface: \"opt-out\",\n instrumentation: \"trace\",\n captureContent: true,\n declaredNode: true,\n ...(link !== undefined && { nestedTrace: link }),\n }\n return openChild(entry, childOptions, inputs, false, invokeFn)\n },\n }\n\n return runWithAutoTraceRootContext(\n autoTraceContext,\n getSpanStack(),\n () => fn.apply(this, args),\n )\n },\n )\n const tracedRoot = buildTracedRoot(rootOptions)\n const autoTraceRoot = function (this: This, ...args: TArgs): TReturn {\n if (!self.shouldRecord() || isCaptureSuppressed()) {\n return fn.apply(this, args)\n }\n const enclosing = currentAutoTraceEntries()\n if (enclosing.length === 0) {\n const firstPlanRead = rootIsAsync\n ? self.simulationPlan.waitForFirstRead()\n : undefined\n if (firstPlanRead !== undefined) {\n return firstPlanRead.then(() =>\n tracedRoot.apply(this, args),\n ) as TReturn\n }\n return tracedRoot.apply(this, args)\n }\n const link: NestedTraceLink | undefined =\n getReplayContext() || inSeedScope() ? undefined : { traceFunctionKey }\n const rootName = rootNameFor(this)\n return invokeThroughAutoTraceEntries(\n enclosing,\n (entry, next) =>\n entry.context.invokeNestedRoot(\n rootName,\n name,\n args,\n next,\n entry,\n link,\n ),\n () => {\n if (link === undefined) {\n return fn.apply(this, args)\n }\n const innermost = enclosing[enclosing.length - 1] as AutoTraceEntry\n const innermostNow =\n currentAutoTraceEntries().find(\n (entry) => entry.context === innermost.context,\n ) ?? innermost\n const innermostStack = innermostNow.spanStack as SpanContext[]\n const copy = innermostStack[innermostStack.length - 1]\n const detachedRoot = buildTracedRoot({\n ...rootOptions,\n ...(copy !== undefined && {\n enclosingTrace: {\n traceId: copy.traceId,\n spanId: copy.spanId,\n traceFunctionKey: innermost.context.traceFunctionKey,\n link,\n },\n }),\n })\n return runWithSpanStack([], () => detachedRoot.apply(this, args))\n },\n )\n }\n return markTracedFunction(autoTraceRoot, traceFunctionKey, fn)\n }\n\n private applySimulationPlan(\n payload: Record<string, unknown>,\n submit: SimulationPlanSubmit,\n drop: () => void,\n ): void {\n this.simulationPlan.send(\n payload,\n rootTraceFunctionKeyOf(payload),\n submit,\n drop,\n )\n }\n\n /**\n * Flush and permanently close this client's tracing resources: its pending\n * requests and the single span-transport worker shared by its decorators and\n * framework handlers.\n *\n * Resolves `false` when delivery failed or the deadline expired. Long-lived\n * processes never need this (the transport batches in the background and the\n * exit hook drains it); scripts and tests that want a hard guarantee should\n * await it.\n *\n * Deliberately not a `Symbol.asyncDispose` method: the SDK targets runtimes\n * where that symbol may be absent, and a computed key on a missing symbol\n * throws at class-definition time, taking the whole SDK down on load.\n */\n close(timeoutMs?: number): Promise<boolean> {\n return this.httpClient.close(timeoutMs)\n }\n\n /**\n * Resolve the API key lazily, the first time a span actually needs it.\n *\n * The key is intentionally NOT read at construction. In ESM, a shim that\n * does `new Bitfab({ apiKey: process.env.BITFAB_API_KEY })` is hoisted and\n * evaluated before the importing script's body runs `dotenv.config()`, so\n * the key would be empty at construction even though it is set moments\n * later. Resolving here (at first `withSpan` call / first request) reads\n * the key after env loading has run.\n *\n * Resolution order: the configured value (string, or function called each\n * time it is still unresolved), then a fallback read of `BITFAB_API_KEY`\n * from the environment. Once a non-empty key is found it is cached, so an\n * early resolve that found nothing never poisons a later one.\n */\n private findApiKey(callsKeyFunction: boolean): string | undefined {\n const replayKey = readEnv(REPLAY_API_KEY_ENV)\n if (replayKey && replayKey.trim() !== \"\") {\n return replayKey\n }\n if (this.resolvedApiKey !== undefined) {\n return this.resolvedApiKey\n }\n if (typeof this.apiKeyConfig === \"function\" && !callsKeyFunction) {\n return undefined\n }\n const fromConfig =\n typeof this.apiKeyConfig === \"function\"\n ? this.apiKeyConfig()\n : this.apiKeyConfig\n const candidate =\n fromConfig && fromConfig.trim() !== \"\"\n ? fromConfig\n : readEnv(\"BITFAB_API_KEY\")\n const key = candidate && candidate.trim() !== \"\" ? candidate : undefined\n if (key) {\n this.resolvedApiKey = key\n }\n return key\n }\n\n private resolveApiKey(): string | undefined {\n const key = this.findApiKey(true)\n if (key) {\n return key\n }\n if (this.strict) {\n throw new BitfabError(\n \"Bitfab: no API key resolved. Set BITFAB_API_KEY or pass apiKey to \" +\n \"new Bitfab(). If a script loads env with dotenv, load it before the \" +\n \"module that constructs the client is imported (e.g. \" +\n \"`node --env-file=.env script.ts`), or pass \" +\n \"`apiKey: () => process.env.BITFAB_API_KEY`.\",\n )\n }\n if (this.isCaptureConfigured() && !this.apiKeyWarned) {\n this.apiKeyWarned = true\n console.warn(\n \"Bitfab: apiKey is empty - tracing is disabled. Provide a valid API key to enable tracing.\",\n )\n }\n return undefined\n }\n\n private isCaptureConfigured(): boolean {\n if (this.captureConfiguredInCode !== undefined) {\n return this.captureConfiguredInCode\n }\n if (this.captureConfiguredFromEnv === undefined) {\n this.captureConfiguredFromEnv =\n readBooleanEnv(CAPTURE_ENABLED_ENV) ?? true\n }\n return this.captureConfiguredFromEnv\n }\n\n private isCaptureEnabled(): boolean {\n if (!this.isCaptureConfigured()) {\n return false\n }\n return this.resolveApiKey() !== undefined\n }\n\n private shouldRecord(): boolean {\n if (!this.isCaptureConfigured() && !getReplayContext() && !inSeedScope()) {\n return false\n }\n return this.resolveApiKey() !== undefined\n }\n\n private prepareFrameworkIntegration(): void {\n if (this.isCaptureConfigured() && this.strict) {\n this.resolveApiKey()\n }\n }\n\n get captureEnabled(): boolean {\n return this.isCaptureEnabled()\n }\n\n /**\n * Fetch the function with its current version and BAML prompt from the server.\n *\n * @param methodName - The name of the method to fetch\n * @returns The function with current version, BAML prompt, and provider definitions\n * @throws {BitfabError} If the function is not found or an error occurs\n */\n private async fetchFunctionVersion(\n methodName: string,\n ): Promise<FunctionVersionResponse> {\n const result =\n await this.httpClient.lookupFunction<FunctionVersionResponse>(methodName)\n\n // Check if function was not found\n if (result.id === null) {\n throw new BitfabError(\n `Function \"${methodName}\" not found. Create it at: ${this.serviceUrl}/functions`,\n \"/functions\",\n )\n }\n\n // Check if function has no prompt\n if (!result.prompt) {\n throw new BitfabError(\n `Function \"${methodName}\" has no prompt configured. Add one at: ${this.serviceUrl}/functions/${result.id}`,\n `/functions/${result.id}`,\n )\n }\n\n return result\n }\n\n /**\n * Call a method with the given named arguments via BAML execution.\n *\n * @param methodName - The name of the method to call\n * @param inputs - Named arguments to pass to the method\n * @returns The result of the BAML function execution\n * @throws {BitfabError} If service_url is not set, or if an error occurs\n */\n async call<T = unknown>(\n methodName: string,\n inputs: Record<string, unknown> = {},\n ): Promise<T> {\n try {\n const functionVersion = await this.fetchFunctionVersion(methodName)\n const executionResult = await runFunctionWithBaml(\n functionVersion.prompt,\n inputs,\n functionVersion.providers,\n this.envVars,\n )\n\n // Create trace for the local execution. A non-serializable result must\n // not throw into the user's `call()`: fall back to String() if\n // JSON.stringify throws or yields undefined (e.g. a function result).\n let resultStr: string\n if (typeof executionResult.result === \"string\") {\n resultStr = executionResult.result\n } else {\n try {\n resultStr =\n JSON.stringify(executionResult.result) ??\n String(executionResult.result)\n } catch {\n warnOnce(\n \"call-result-serialize\",\n \"a local execution result could not be JSON-serialized; storing its String() form instead. The call still returns its real value.\",\n )\n resultStr = String(executionResult.result)\n }\n }\n\n if (this.shouldRecord()) {\n this.httpClient.sendInternalTrace(functionVersion.id, {\n result: resultStr,\n source: \"typescript-sdk\",\n ...(Object.keys(inputs).length > 0 && { inputs }),\n ...(executionResult.rawCollector != null && {\n rawCollector: executionResult.rawCollector,\n }),\n })\n }\n\n return executionResult.result as T\n } catch (error) {\n if (error instanceof BitfabError) {\n throw error\n }\n if (error instanceof Error) {\n throw new BitfabError(error.message)\n }\n throw new BitfabError(\"Unknown error occurred during local execution\")\n }\n }\n\n /**\n * Get a tracing processor for OpenAI Agents SDK integration.\n *\n * This processor automatically captures traces and spans from the OpenAI Agents SDK\n * and sends them to Bitfab for monitoring and analysis.\n *\n * Example usage:\n * ```typescript\n * import { addTraceProcessor } from '@openai/agents';\n *\n * const client = new Bitfab({ apiKey: 'your-api-key' });\n * const processor = client.getOpenAiTracingProcessor();\n * addTraceProcessor(processor);\n * ```\n *\n * @returns A BitfabOpenAITracingProcessor instance configured for this client\n */\n getOpenAiTracingProcessor() {\n this.prepareFrameworkIntegration()\n return new BitfabOpenAITracingProcessor({\n serviceUrl: this.serviceUrl,\n shouldRecord: () => this.shouldRecord(),\n getActiveSpanContext: () => {\n const stack = getSpanStack()\n return stack[stack.length - 1] ?? null\n },\n _httpClient: this.httpClient,\n })\n }\n\n /**\n * Get an OpenAI Agents SDK handler that records a replayable root span.\n *\n * The processor from {@link getOpenAiTracingProcessor} captures everything\n * inside a run (LLM calls, tools, handoffs) but never sees the caller's\n * input, so a processor-only run records an empty-input root and is not\n * replayable. This handler's `wrapRun` is a drop-in for `run()` that opens a\n * `withSpan` root carrying the input and final output; the processor's spans\n * nest beneath it. Register the processor once at startup, then call\n * `handler.wrapRun(agent, input)` in place of `run(agent, input)`.\n *\n * ```typescript\n * import { addTraceProcessor, Agent, run } from \"@openai/agents\";\n *\n * addTraceProcessor(client.getOpenAiTracingProcessor());\n * const handler = client.getOpenAiAgentHandler(\"research-topic\");\n * const result = await handler.wrapRun(agent, \"Find X\");\n * ```\n *\n * @param traceFunctionKey - Groups traces under this key in Bitfab\n * @returns A BitfabOpenAIAgentHandler configured for this client\n */\n getOpenAiAgentHandler(traceFunctionKey: string) {\n return new BitfabOpenAIAgentHandler({\n traceFunctionKey,\n withSpan: this.withSpan.bind(this),\n getActiveSpanContext: () => {\n const stack = getSpanStack()\n return stack[stack.length - 1] ?? null\n },\n })\n }\n\n /**\n * Get a LangGraph/LangChain callback handler for tracing.\n *\n * The handler captures graph node execution, LLM calls, and tool\n * invocations as Bitfab spans with proper parent-child hierarchy.\n *\n * ```typescript\n * const handler = client.getLangGraphCallbackHandler(\"my-agent\");\n * const result = await agent.invoke(\n * { messages: [...] },\n * { callbacks: [handler] },\n * );\n * ```\n *\n * @param traceFunctionKey - Groups traces under this key in Bitfab\n * @returns A BitfabLangGraphCallbackHandler configured for this client\n */\n getLangGraphCallbackHandler(traceFunctionKey: string) {\n this.prepareFrameworkIntegration()\n return new BitfabLangGraphCallbackHandler({\n traceFunctionKey,\n serviceUrl: this.serviceUrl,\n shouldRecord: () => this.shouldRecord(),\n getActiveSpanContext: () => {\n const stack = getSpanStack()\n return stack[stack.length - 1] ?? null\n },\n _httpClient: this.httpClient,\n })\n }\n\n /**\n * Get a LangChain callback handler for tracing.\n *\n * Alias of {@link getLangGraphCallbackHandler}: LangChain chains and\n * LangGraph graphs share the same callback system, so one handler serves\n * both.\n *\n * ```typescript\n * const handler = client.getLangChainCallbackHandler(\"my-chain\");\n * const result = await chain.invoke(input, { callbacks: [handler] });\n * ```\n *\n * @param traceFunctionKey - Groups traces under this key in Bitfab\n * @returns A BitfabLangGraphCallbackHandler configured for this client\n */\n getLangChainCallbackHandler(traceFunctionKey: string) {\n return this.getLangGraphCallbackHandler(traceFunctionKey)\n }\n\n /**\n * Get the first-class LangGraph integration for tracing and replaying tools\n * executed by `ToolNode`.\n *\n * The integration combines `wrapTools()` for per-tool replay interception\n * with `createInvoker()` for a callback-configured replayable graph entry\n * point. Lower-level callback and root wrappers remain available.\n *\n * @param traceFunctionKey - Groups traces under this key in Bitfab\n * @param options - Controls which tools are marked for replay mocking\n * @experimental This API may change before it is stable.\n */\n getLangGraphIntegration(\n traceFunctionKey: string,\n options?: LangGraphIntegrationOptions,\n ): BitfabLangGraphIntegration {\n this.prepareFrameworkIntegration()\n const callbackHandler = new BitfabLangGraphCallbackHandler({\n traceFunctionKey,\n serviceUrl: this.serviceUrl,\n shouldRecord: () => this.shouldRecord(),\n getActiveSpanContext: () => {\n const stack = getSpanStack()\n return stack[stack.length - 1] ?? null\n },\n captureTools: false,\n _httpClient: this.httpClient,\n })\n return new BitfabLangGraphIntegration({\n client: this,\n traceFunctionKey,\n callbackHandler,\n options,\n })\n }\n\n /**\n * Get the first-class Rivet actor/workflow-context integration for an\n * existing trace root. The integration binds context and records effect\n * spans; it never creates another root.\n *\n * @param traceFunctionKey - Key used by the enclosing `withTrace()` root\n * @param options - Explicit per-context-path replay mocking policy\n * @experimental This API may change before it is stable.\n */\n getRivetIntegration<TContext extends object = RivetContextLike>(\n traceFunctionKey: string,\n options?: RivetIntegrationOptions,\n ): BitfabRivetIntegration<TContext> {\n return new BitfabRivetIntegration<TContext>(\n traceFunctionKey,\n {\n wrapEffect: <TArgs extends unknown[], TReturn>(\n effect: RivetEffectType,\n mockOnReplay: boolean,\n fn: (...args: TArgs) => TReturn,\n replayOutput?: () => unknown,\n ) => {\n const spanOptions: InternalSpanOptions = {\n name: effect,\n type: \"function\",\n captureWhen: \"nested\",\n mockOnReplay,\n surface: \"inherit\",\n instrumentation: \"rivet\",\n ...(replayOutput !== undefined && { replayOutput }),\n }\n return this.withSpan(traceFunctionKey, spanOptions, fn)\n },\n recordRootFixture: (context, recordedRoots) => {\n const root = getSpanStack()[0]\n if (!root) {\n return\n }\n const rootId = `${root.traceId}:${root.spanId}`\n if (recordedRoots.has(rootId)) {\n return\n }\n root.contexts.push(context)\n recordedRoots.add(rootId)\n },\n },\n options,\n )\n }\n\n /**\n * Get a Claude Agent SDK handler for tracing.\n *\n * The handler captures LLM turns, tool invocations, and subagent\n * execution as Bitfab spans with proper parent-child hierarchy.\n *\n * ```typescript\n * import { query } from \"@anthropic-ai/claude-agent-sdk\";\n *\n * const handler = client.getClaudeAgentHandler(\"my-agent\");\n * const options = handler.instrumentOptions({\n * model: \"claude-sonnet-4-5-...\",\n * });\n * for await (const msg of handler.wrapQuery(\n * query({ prompt: \"Do something\", options })\n * )) {\n * // process messages\n * }\n * ```\n *\n * @param traceFunctionKey - Groups traces under this key in Bitfab\n * @returns A BitfabClaudeAgentHandler configured for this client\n */\n getClaudeAgentHandler(traceFunctionKey: string) {\n this.prepareFrameworkIntegration()\n return new BitfabClaudeAgentHandler({\n traceFunctionKey,\n serviceUrl: this.serviceUrl,\n shouldRecord: () => this.shouldRecord(),\n getActiveSpanContext: () => {\n const stack = getSpanStack()\n return stack[stack.length - 1] ?? null\n },\n _httpClient: this.httpClient,\n })\n }\n\n /**\n * Get a Vercel AI SDK language-model middleware for tracing.\n *\n * Pass it to the AI SDK's `wrapLanguageModel` and use the wrapped model with\n * `generateText` / `streamText` / `generateObject` / `streamObject`. Every\n * call through that model is captured as a keyed `llm` span carrying the call\n * parameters (the prompt) as input and a serializable summary\n * (`{ text, toolCalls, usage, finishReason }`) as output. Streaming is\n * captured without disturbing the caller's live stream.\n *\n * ```typescript\n * import { wrapLanguageModel, streamText } from \"ai\";\n * import { openai } from \"@ai-sdk/openai\";\n *\n * const model = wrapLanguageModel({\n * model: openai(\"gpt-4o\"),\n * middleware: client.getVercelAiMiddleware(\"chat-turn\"),\n * });\n * const result = streamText({ model, messages });\n * ```\n *\n * @param traceFunctionKey - Groups traces under this key in Bitfab\n * @returns A Vercel AI SDK middleware configured for this client\n */\n getVercelAiMiddleware(traceFunctionKey: string) {\n return new BitfabVercelAiHandler({\n traceFunctionKey,\n withSpan: this.withSpan.bind(this),\n }).middleware\n }\n\n /**\n * Wrap a BAML client method to automatically capture prompt and LLM metadata.\n *\n * Creates a BAML Collector, calls the method through a tracked client,\n * then extracts rendered messages and token usage - calling setPrompt()\n * and addContext() on the current span automatically.\n *\n * The BAML client can be provided in the constructor or passed explicitly:\n *\n * ```typescript\n * // Option 1: bamlClient in constructor (use wrapBAML with just the method)\n * const client = new Bitfab({ apiKey: 'your-api-key', bamlClient: b });\n * const traced = client.withSpan('classify', { type: 'llm' },\n * client.wrapBAML(b.ClassifyText)\n * );\n *\n * // Option 2: pass bamlClient at call site\n * const client = new Bitfab({ apiKey: 'your-api-key' });\n * const traced = client.withSpan('classify', { type: 'llm' },\n * client.wrapBAML(b, b.ClassifyText)\n * );\n * ```\n *\n * @param methodOrClient - Either a BAML method (uses constructor bamlClient) or the BAML client instance\n * @param maybeMethodOrOptions - The BAML method when the first argument is a client, or WrapBAMLOptions when the first argument is the method\n * @param maybeOptions - WrapBAMLOptions when using the two-argument (client, method) form\n * @returns An async function with the same signature that instruments the BAML call\n */\n wrapBAML<TArgs extends unknown[], TReturn>(\n methodOrClient: unknown,\n maybeMethodOrOptions?:\n | ((...args: TArgs) => Promise<TReturn>)\n | WrapBAMLOptions,\n maybeOptions?: WrapBAMLOptions,\n ): WrappedBamlFn<TArgs, TReturn> {\n let bamlClient: unknown\n let method: (...args: TArgs) => Promise<TReturn>\n let options: WrapBAMLOptions | undefined\n\n if (typeof maybeMethodOrOptions === \"function\") {\n bamlClient = methodOrClient\n method = maybeMethodOrOptions\n options = maybeOptions\n } else {\n bamlClient = this.bamlClient\n method = methodOrClient as (...args: TArgs) => Promise<TReturn>\n options = maybeMethodOrOptions as WrapBAMLOptions | undefined\n if (!bamlClient) {\n throw new BitfabError(\n \"bamlClient is required for wrapBAML. Pass it in the constructor or as the first argument.\",\n )\n }\n }\n\n const methodName = method.name\n if (!methodName) {\n throw new BitfabError(\n \"wrapBAML requires a named function (e.g., b.ClassifyText).\",\n )\n }\n\n // Warm the Collector class cache so it's ready by the time the wrapper is called\n loadCollectorClass()\n\n const wrappedFn = async (...args: TArgs): Promise<TReturn> => {\n const CollectorClass = await loadCollectorClass()\n if (!CollectorClass) {\n // @boundaryml/baml not available - call method directly as fallback\n wrappedFn.collector = null\n return await (\n bamlClient as Record<string, (...a: TArgs) => Promise<TReturn>>\n )[methodName](...args)\n }\n\n const collector = new CollectorClass(\"bitfab-baml-tracing\")\n\n // Setting up the tracked client is a side-channel: a BAML version\n // mismatch, or a non-BAML object passed as `bamlClient`, must not stop\n // the user's call. If `withOptions` or the method lookup fails, fall back\n // to the untracked method so the call still runs (untraced).\n let trackedClient: Record<string, unknown>\n let trackedMethod: (...a: TArgs) => Promise<TReturn>\n try {\n trackedClient = (\n bamlClient as { withOptions: (opts: unknown) => unknown }\n ).withOptions({ collector }) as Record<string, unknown>\n const method = (\n trackedClient as Record<string, (...a: TArgs) => Promise<TReturn>>\n )[methodName]\n if (typeof method !== \"function\") {\n throw new BitfabError(\n \"bamlClient.withOptions did not return the wrapped method\",\n )\n }\n trackedMethod = method\n } catch {\n warnOnce(\n `wrapBAML-setup:${methodName}`,\n `BAML tracing setup failed for \"${methodName}\" (incompatible bamlClient or BAML version); calling it untraced. The call still runs; no span is recorded.`,\n )\n wrappedFn.collector = null\n return await (\n bamlClient as Record<string, (...a: TArgs) => Promise<TReturn>>\n )[methodName](...args)\n }\n\n const result = await trackedMethod.bind(trackedClient)(...args)\n\n wrappedFn.collector = collector\n\n try {\n const prompt = extractPromptFromCollector(collector)\n if (prompt) {\n getCurrentSpan().setPrompt(prompt)\n }\n const metadata = extractContextFromCollector(collector)\n if (metadata) {\n getCurrentSpan().addContext(metadata)\n }\n } catch {\n // Never crash the host app\n }\n\n try {\n options?.onCollector?.(collector)\n } catch {\n // Never crash the host app\n }\n\n return result\n }\n\n wrappedFn.collector = null as unknown | null\n\n return wrappedFn\n }\n\n /**\n * Wrap a function to automatically create a span for its inputs and outputs.\n *\n * The wrapped function behaves identically to the original, but sends\n * span data to Bitfab in the background after each call.\n *\n * Example usage:\n * ```typescript\n * const client = new Bitfab({ apiKey: 'your-api-key' });\n *\n * async function processOrder(orderId: string, items: string[]): Promise<{ total: number }> {\n * // ... process order\n * return { total: 100 };\n * }\n *\n * // Basic usage (defaults to \"custom\" span type)\n * const tracedProcessOrder = client.withSpan('order-processing', processOrder);\n *\n * // With explicit span type\n * const tracedProcessOrder = client.withSpan('order-processing', { type: 'function' }, processOrder);\n *\n * // Call the wrapped function normally\n * const result = await tracedProcessOrder('order-123', ['item-1', 'item-2']);\n * // Span is automatically sent to Bitfab\n * ```\n *\n * @param traceFunctionKey - A string identifier for grouping spans (e.g., 'order-processing', 'user-auth')\n * @param optionsOrFn - Either SpanOptions or the function to wrap\n * @param maybeFn - The function to wrap if options were provided\n * @returns A wrapped function with the same signature that creates spans for inputs and outputs\n */\n withSpan<TArgs extends unknown[], TReturn>(\n traceFunctionKey: string,\n optionsOrFn: SpanOptions | ((...args: TArgs) => TReturn),\n maybeFn?: (...args: TArgs) => TReturn,\n ): (...args: TArgs) => TReturn {\n this.simulationPlan.refresh()\n const options: InternalSpanOptions =\n typeof optionsOrFn === \"function\" ? {} : optionsOrFn\n const fn: (...args: TArgs) => TReturn =\n typeof optionsOrFn === \"function\" ? optionsOrFn : maybeFn!\n const self = this\n const optionExperimentId = resolveSpanExperimentId(options)\n\n // Detect Promise-returning fn at wrap time so the mock-fire path can\n // match the original return shape. `AsyncFunction` covers `async fn`\n // declarations; for plain functions that return a Promise we fall back\n // to a `fn.toString()` heuristic (looks for `Promise` or `await`).\n // Brittle for minified code, but mock-fire is the only consumer and a\n // sync fallback (returning a raw value) is the safe degradation.\n const fnIsAsyncFunction = fn.constructor.name === \"AsyncFunction\"\n const fnReturnsPromise =\n fnIsAsyncFunction ||\n (() => {\n try {\n const src = fn.toString()\n return /\\b(?:Promise|await)\\b/.test(src)\n } catch {\n return false\n }\n })()\n\n const wrappedFn = function (this: unknown, ...args: TArgs): TReturn {\n // Decide whether to trace at CALL time, not wrap time. The shim builds\n // this wrapper at module load, often before env (dotenv) has loaded, so\n // freezing the decision here would permanently disable tracing for a key\n // that is set moments later. Re-checking per call lets a late-resolved\n // key take effect; once a key is found the resolution is cached.\n if (!self.shouldRecord() || isCaptureSuppressed()) {\n return fn.apply(this, args)\n }\n\n // The Node-specific entry registers async_hooks synchronously, but ESM\n // chunk evaluation can load this module before that registration runs.\n // Re-check at call time so the first traced invocation gets native\n // context propagation instead of briefly using the browser fallback.\n initializeAsyncContext()\n\n // Defer until AsyncLocalStorage init completes. In Node.js, the\n // dynamic import resolves in one microtask; in browsers, the init\n // resolves immediately to a no-op. The `asyncLocalStorageInitDone`\n // flag prevents an infinite loop when AsyncLocalStorage is\n // unavailable (browsers).\n if (!asyncLocalStorage && !isAsyncStorageInitDone()) {\n return asyncLocalStorageReady.then(() =>\n wrappedFn.apply(this, args),\n ) as unknown as TReturn\n }\n\n const captureWhen: unknown =\n options.captureWhen === undefined ? \"always\" : options.captureWhen\n const resolvedCaptureWhen: CaptureWhen =\n captureWhen === \"always\" || captureWhen === \"nested\"\n ? captureWhen\n : \"always\"\n if (resolvedCaptureWhen !== captureWhen) {\n let invalidValue: string\n try {\n invalidValue = String(captureWhen)\n } catch {\n invalidValue = \"<unprintable>\"\n }\n warnOnce(\n `invalid-capture-when:${traceFunctionKey}`,\n `unknown captureWhen value \"${invalidValue}\"; defaulting to \"always\". Valid values: \"always\", \"nested\".`,\n )\n }\n\n if (resolvedCaptureWhen === \"nested\") {\n let hasParent = false\n try {\n hasParent = getSpanStack().length > 0\n } catch (setupError) {\n if (getReplayContext()) {\n throw setupError\n }\n warnOnce(\n `withSpan-setup:${traceFunctionKey}`,\n `tracing setup failed for \"${traceFunctionKey}\"; running it untraced. The function still runs and returns normally; no span is recorded.`,\n )\n return fn.apply(this, args) as TReturn\n }\n if (!hasParent) {\n return fn.apply(this, args) as TReturn\n }\n }\n\n // Tracing is a side-channel: building the span context must never stop\n // the user's function from running. If any of this setup throws (e.g. a\n // runtime without a usable `crypto`, or a context/snapshot edge), fall\n // back to running `fn` directly, untraced. The `!` definite-assignments\n // are sound because the catch always returns: reaching past the\n // try/catch means the try completed and both values were assigned.\n let newStack!: SpanContext[]\n let executeWithContext!: () => TReturn\n // Set only if THIS call registers root trace state below, so a setup\n // failure after registration can clean up the orphaned entry (see catch).\n let registeredTraceId: string | undefined\n let startedSpan: { traceId: string; spanId: string } | undefined\n try {\n // Get current span stack to determine trace context\n const currentStack = getSpanStack()\n const parentContext = currentStack[currentStack.length - 1]\n\n // Generate trace ID (replay override > parent > new)\n const replayCtxForTraceId = parentContext ? null : getReplayContext()\n const seedCtxForTraceId = parentContext ? null : getSeedContext()\n const traceId =\n parentContext?.traceId ??\n replayCtxForTraceId?.traceId ??\n seedCtxForTraceId?.traceId ??\n randomUuid()\n const spanId = randomUuid()\n const openAiAgentsParent =\n parentContext !== undefined && options.instrumentation === \"trace\"\n ? openAiAgentsParentSpanId(\n parentContext.traceId,\n parentContext.openAiAgentsSpanIds,\n )\n : undefined\n const parentSpanId = openAiAgentsParent ?? parentContext?.spanId ?? null\n const isRootSpan = parentSpanId === null\n if (options.enclosingTrace !== undefined) {\n options.enclosingTrace.link.traceId = traceId\n options.enclosingTrace.link.rootSpanId = spanId\n }\n\n const requestedSurface: SurfaceRequest =\n options.surface ?? DEFAULT_SURFACE\n const surface = resolveSurface(requestedSurface, parentContext?.surface)\n assertSurfacesCompatible(\n requestedSurface,\n surface,\n parentContext?.surface,\n traceFunctionKey,\n )\n\n const openAiAgentsSpanIds = openAiAgentsSpanChain()\n const functionNameAtOpen = fn.name !== \"\" ? fn.name : undefined\n const newContext: SpanContext = {\n traceId,\n spanId,\n contexts: [],\n spanName:\n options.name ??\n options.nameFor?.(this) ??\n qualifiedSpanName(this, functionNameAtOpen) ??\n traceFunctionKey,\n rootTraceFunctionKey: rootTraceFunctionKeyOfTrace(\n traceId,\n traceFunctionKey,\n ),\n instrumentation: options.instrumentation ?? \"span\",\n declaredNode: options.declaredNode === true,\n isRoot: isRootSpan,\n ...(surface !== undefined && { surface }),\n ...(openAiAgentsSpanIds.length > 0 && { openAiAgentsSpanIds }),\n }\n const nearestCapturedAncestorSpanId =\n self.nearestCapturedAncestorSpanIdOf(currentStack)\n newStack = [...currentStack, newContext]\n\n // Capture inputs and start time\n const inputs = args\n const startedAt = nowIsoTimestamp()\n const replayCtxAtStart = getReplayContext()\n const experimentId =\n replayCtxAtStart?.experimentId ?? optionExperimentId\n\n // Register trace state for root spans\n if (isRootSpan && !activeTraceStates.has(traceId)) {\n // Synchronously snapshot the wall clock the SDK sees right now,\n // before invoking the wrapped function. This timestamp is the Neon\n // snapshot pin used by the server-side resolver. It is captured on\n // every trace (no IO, harmless to store) so any trace can later be\n // replayed against a historical branch; the provider is attached\n // only when dbSnapshot is configured, otherwise resolved at replay.\n const dbSnapshotRef = buildSnapshotRef(self.dbSnapshot, startedAt)\n self.simulationPlan.refresh()\n activeTraceStates.set(traceId, {\n traceId,\n traceFunctionKey,\n startedAt,\n contexts: [],\n ...(experimentId !== undefined && { experimentId }),\n ...(replayCtxAtStart?.inputSourceTraceId && {\n inputSourceTraceId: replayCtxAtStart.inputSourceTraceId,\n }),\n ...(replayCtxAtStart?.replayAttempt !== undefined && {\n replayAttempt: replayCtxAtStart.replayAttempt,\n }),\n dbSnapshotRef,\n })\n registeredTraceId = traceId\n }\n\n self.httpClient.traceCompletion.start(traceId, spanId)\n startedSpan = { traceId, spanId }\n const functionName = functionNameAtOpen\n const baseSpanParams = {\n traceFunctionKey,\n rootTraceFunctionKey: newContext.rootTraceFunctionKey,\n functionName,\n spanName: newContext.spanName,\n traceId,\n spanId,\n parentSpanId,\n nearestCapturedAncestorSpanId,\n inputs,\n startedAt,\n spanType: options.type ?? \"custom\",\n instrumentation: options.instrumentation ?? \"span\",\n functionId: options.functionId,\n captureContent: options.captureContent ?? true,\n replayReusable: options.replayReusable,\n autoTraceDefinition: options.autoTraceDefinition,\n declaredNode: options.declaredNode === true,\n nestedTrace: options.nestedTrace,\n enclosingTrace: options.enclosingTrace,\n }\n\n // Helper to send the span and, for root spans, the trace completion\n // that follows it. Both are handed to the client's span transport,\n // which owns queueing, batching and delivery: nothing here waits on\n // the network, and replay confirms persistence with a server-side\n // barrier rather than by chaining upload promises.\n // Wrapped in try/catch so span errors never crash the host app.\n const sendSpan = async (params: {\n result: unknown\n error?: string\n mocked?: boolean\n mockTarget?: MockTarget\n mockSource?: MockSource\n }) => {\n const replayCtx = getReplayContext()\n try {\n const endedAt = nowIsoTimestamp()\n\n // If drop() was called on this trace, suppress the span PAYLOAD\n // upload for every span that completes after the flag was set.\n // The trace completion signal below still rides out with\n // `dropped: true`, so the server scrubs any sibling spans that\n // already raced out (fire-and-forget) before the flag was set.\n // Skipping the upload here is belt-and-suspenders on top of that\n // scrub: it avoids shipping payloads the server will only discard.\n const traceDropped =\n activeTraceStates.get(traceId)?.dropped === true\n\n // dbSnapshotRef is attached to the trace, not the span (see\n // sendTraceCompletion). A trace-level pin is what replay reads;\n // duplicating it on the root span would just leak the same\n // value into two places.\n if (!traceDropped) {\n self.sendWrapperSpan({\n ...baseSpanParams,\n ...params,\n contexts: newContext.contexts,\n prompt: newContext.prompt,\n endedAt,\n ...(experimentId !== undefined && { experimentId }),\n ...(replayCtx?.inputSourceSpanId && {\n inputSourceSpanId: replayCtx.inputSourceSpanId,\n }),\n })\n }\n\n if (isRootSpan) {\n const traceState = activeTraceStates.get(traceId)\n const truncated = truncationMetadata(traceState?.truncation)\n if (truncated !== undefined) {\n recordCallerTraceMetadata(traceId, truncated)\n }\n self.sendTraceCompletion({\n traceFunctionKey,\n traceId,\n startedAt: traceState?.startedAt ?? startedAt,\n endedAt,\n sessionId: traceState?.sessionId,\n name: traceState?.name,\n metadata: callerTraceMetadata(traceId),\n contexts: traceState?.contexts ?? [],\n experimentId: traceState?.experimentId,\n inputSourceTraceId: traceState?.inputSourceTraceId,\n replayAttempt: traceState?.replayAttempt,\n dbSnapshotRef: traceState?.dbSnapshotRef,\n dropped: traceState?.dropped,\n ingestionType: traceState?.ingestionType,\n // Built AFTER the wrapped fn finished, so `accessed` reflects\n // whether customer code obtained the branch URL during this\n // item. Omitted entirely when no lease was attached, so the\n // server can distinguish \"no branch\" from \"branch ignored\".\n ...(replayCtx?.dbBranchLease && {\n dbSnapshotUsage: {\n neonBranchId: replayCtx.dbBranchLease.neonBranchId,\n snapshotTimestamp:\n replayCtx.dbBranchLease.snapshotTimestamp,\n region: replayCtx.dbBranchLease.region,\n originalTraceId: replayCtx.sourceBitfabTraceId,\n accessed: replayCtx.dbSnapshotAccessed === true,\n timings: replayCtx.dbBranchTimings,\n },\n }),\n })\n activeTraceStates.delete(traceId)\n retireTraceMetadata(traceId)\n }\n } catch {\n // Silently ignore - user's result/exception takes priority\n } finally {\n self.httpClient.traceCompletion.end(traceId, spanId)\n }\n }\n\n // Record the span output. With `finalize`, the raw result is handed\n // back to the caller untouched (streaming stays live) while a drained,\n // serializable view is recorded as the span output instead. finalize\n // runs in the background and never affects the caller's value; a\n // throwing finalize records an error rather than crashing the host.\n const recordSpan = (result: unknown): void => {\n if (options.finalize) {\n // Tracked on the OWNING client: the span reaches the transport\n // only once finalize settles, so a flush that merely drained the\n // transport would race a root span not yet queued - and in replay\n // that root's trace would miss the barrier. Scoped per client so\n // one client's slow finalize cannot fail another's close().\n void self.httpClient.trackDeferred(\n Promise.resolve()\n .then(() => options.finalize!(result))\n .then((output) => sendSpan({ result: output }))\n .catch((error: unknown) =>\n sendSpan({\n result:\n error instanceof StreamFinalizationError\n ? error.output\n : undefined,\n error:\n error instanceof StreamFinalizationError\n ? error.message\n : error instanceof Error\n ? `finalize failed: ${error.message}`\n : `finalize failed: ${String(error)}`,\n }),\n ),\n )\n } else {\n void sendSpan({ result })\n }\n }\n\n const resolveMockOrAbort = async <T>(\n resolve: () => T | Promise<T>,\n ): Promise<T> => {\n try {\n return await resolve()\n } catch (error) {\n self.httpClient.traceCompletion.abort(traceId, spanId)\n throw error\n }\n }\n\n // Assign before mock interception because an asynchronous resolver can\n // decline after this setup block has already returned.\n executeWithContext = (): TReturn => {\n let result: TReturn\n try {\n result = fn.apply(this, args)\n } catch (error) {\n void sendSpan({\n result: undefined,\n error: error instanceof Error ? error.message : String(error),\n })\n throw error\n }\n\n if (result instanceof Promise) {\n return result\n .then((resolvedResult) => {\n recordSpan(resolvedResult)\n return resolvedResult\n })\n .catch((error: unknown) => {\n void sendSpan({\n result: undefined,\n error: error instanceof Error ? error.message : String(error),\n })\n throw error\n }) as TReturn\n }\n\n if (isAsyncGenerator(result)) {\n return wrapAsyncGenerator(result, newStack, sendSpan) as TReturn\n }\n\n recordSpan(result)\n return result\n }\n\n // Mock interception: for a non-root child span under an active mock\n // context, decide whether to substitute its output instead of running\n // real code. Precedence: a matching override wins (per-call before\n // registered, first matcher within each); otherwise the base strategy\n // (\"all\"/\"marked\") replays recorded output. The lookup key matches\n // buildMockTree: `${traceFunctionKey}:${spanName}:${idx}` with callIndex\n // scoped per (key, name) - see comment on buildMockTree.\n const replayCtxForMock = getReplayContext()\n if (replayCtxForMock?.selectiveReplay) {\n const decision = replayCtxForMock.selectiveReplay.enter({\n traceFunctionKey,\n spanName: baseSpanParams.spanName ?? traceFunctionKey,\n spanId,\n parentSpanId,\n inputs: args,\n safetyMock: !isRootSpan && options.mockOnReplay === true,\n reuseAllowed: options.replayReusable === true,\n canReuseOutput:\n options.finalize === undefined &&\n options.replayOutput === undefined &&\n !fn.constructor.name.includes(\"Generator\"),\n })\n if (decision.mock) {\n void sendSpan({\n result: decision.output,\n mocked: true,\n mockTarget: \"output\",\n mockSource: \"recorded\",\n })\n return (\n fnReturnsPromise\n ? Promise.resolve(decision.output)\n : decision.output\n ) as TReturn\n }\n }\n if (replayCtxForMock?.mockTree && !isRootSpan) {\n const counters = replayCtxForMock.callCounters!\n const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`\n const callIndex = counters.get(counterKey) ?? 0\n counters.set(counterKey, callIndex + 1)\n\n const mockKey = `${counterKey}:${callIndex}`\n const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey)\n\n // Emit a mocked span (flagged so the trace view marks it), then return\n // the value in the wrapped fn's call shape. fnReturnsPromise keeps a\n // sync-but-Promise-returning fn's `.then()` consumers working.\n const emitMock = (\n output: unknown,\n mockSource: MockSource,\n ): TReturn => {\n void sendSpan({\n result: output,\n mocked: true,\n mockTarget: \"output\",\n mockSource,\n })\n if (fnReturnsPromise) {\n return Promise.resolve(output) as TReturn\n }\n return output as TReturn\n }\n // Same, when the value resolves asynchronously (lazy recorded-output\n // fetch, or an async value function). A synchronous wrapped fn cannot\n // return a Promise its caller can use, so surface it under replay\n // rather than silently mis-typing the result.\n const emitMockAsync = (\n pending: Promise<unknown>,\n mockSource: MockSource,\n ): TReturn => {\n if (!fnReturnsPromise) {\n throw new BitfabError(\n `Cannot mock synchronous span \"${traceFunctionKey}\" with an ` +\n \"asynchronously-resolved value (lazy recorded-output fetch or \" +\n \"an async value function). Make the wrapped function async, or \" +\n 'use mock: \"all\" so recorded outputs are fetched eagerly.',\n )\n }\n return (async () => {\n const output = await resolveMockOrAbort(() => pending)\n void sendSpan({\n result: output,\n mocked: true,\n mockTarget: \"output\",\n mockSource,\n })\n return output\n })() as TReturn\n }\n // Resolve this span's recorded output. Prefer an inline output when\n // the tree carried one (eager \"all\", or an older server that ignores\n // includeOutputs and returns outputs without an externalSpanId); only\n // lazily fetch when the payload-free tree omitted it. Returns a value\n // or a Promise.\n const resolveRecordedOutput = (): unknown | Promise<unknown> => {\n const hasInlineOutput =\n mockSpan?.output !== undefined ||\n mockSpan?.outputMeta !== undefined\n if (\n !hasInlineOutput &&\n replayCtxForMock.fetchSpanOutput &&\n mockSpan?.externalSpanId\n ) {\n return replayCtxForMock.fetchSpanOutput(mockSpan.externalSpanId)\n }\n if (!mockSpan) {\n // No recorded counterpart at all (e.g. getOriginalOutput on a span\n // the changed code newly introduced).\n return Promise.reject(\n new BitfabError(\n `No recorded span to source output for \"${traceFunctionKey}\".`,\n ),\n )\n }\n let output = mockSpan.output\n if (\n mockSpan.outputMeta !== undefined &&\n mockSpan.outputMeta !== null\n ) {\n output = deserializeValue({\n json: mockSpan.output,\n meta: mockSpan.outputMeta,\n })\n }\n return output\n }\n const resolveBaseOutput = (): unknown | Promise<unknown> =>\n options.replayOutput !== undefined\n ? options.replayOutput()\n : resolveRecordedOutput()\n\n const shouldMockWithBaseStrategy =\n replayCtxForMock.mockStrategy === \"all\" ||\n (replayCtxForMock.mockStrategy === \"marked\" &&\n options.mockOnReplay === true)\n\n // 1) Overrides. A resolver can decline with NO_MOCK_OVERRIDE, which\n // continues to the next override and then the base strategy.\n if (replayCtxForMock.mockOverrides?.length) {\n const nodeMeta: SpanNodeMeta = {\n traceFunctionKey,\n spanName: baseSpanParams.spanName,\n type: options.type ?? \"custom\",\n originalSpanId: mockSpan?.sourceSpanId,\n }\n const overrideCtx = {\n node: nodeMeta,\n inputs: args,\n getOriginalOutput: () => Promise.resolve(resolveRecordedOutput()),\n }\n type OverrideResolution =\n | { matched: true; output: unknown }\n | { matched: false }\n const resolveOverrideFrom = (\n startIndex: number,\n ): OverrideResolution | Promise<OverrideResolution> => {\n for (\n let index = startIndex;\n index < replayCtxForMock.mockOverrides!.length;\n index += 1\n ) {\n const override = replayCtxForMock.mockOverrides![index]\n if (!override?.match(nodeMeta)) {\n continue\n }\n const injected = resolveMockValue(override.value, overrideCtx)\n if (injected instanceof Promise) {\n return injected.then((output) =>\n output === NO_MOCK_OVERRIDE\n ? resolveOverrideFrom(index + 1)\n : { matched: true, output },\n )\n }\n if (injected !== NO_MOCK_OVERRIDE) {\n return { matched: true, output: injected }\n }\n }\n return { matched: false }\n }\n\n const resolution = resolveOverrideFrom(0)\n if (resolution instanceof Promise) {\n if (!fnReturnsPromise) {\n throw new BitfabError(\n `Cannot resolve an asynchronous mock override for synchronous span \"${traceFunctionKey}\". ` +\n \"Make the wrapped function async or return NO_MOCK_OVERRIDE synchronously.\",\n )\n }\n return runWithSpanStack(newStack, async () => {\n const resolved = await resolveMockOrAbort(() => resolution)\n if (resolved.matched) {\n void sendSpan({\n result: resolved.output,\n mocked: true,\n mockTarget: \"output\",\n mockSource: \"override\",\n })\n return resolved.output\n }\n if (shouldMockWithBaseStrategy && !mockSpan) {\n throw new BitfabError(\n `Replay selected span \"${traceFunctionKey}:${baseSpanParams.spanName}\" for mocking, but recorded occurrence ${callIndex + 1} is unavailable. The real span was not executed.`,\n )\n }\n if (shouldMockWithBaseStrategy) {\n const output = await resolveMockOrAbort(() =>\n resolveBaseOutput(),\n )\n void sendSpan({\n result: output,\n mocked: true,\n mockTarget: \"output\",\n mockSource: \"recorded\",\n })\n return output\n }\n return executeWithContext()\n }) as TReturn\n }\n if (resolution.matched) {\n return emitMock(resolution.output, \"override\")\n }\n }\n\n // 2) Base strategy: replay recorded output for mocked spans.\n if (shouldMockWithBaseStrategy && !mockSpan) {\n throw new BitfabError(\n `Replay selected span \"${traceFunctionKey}:${baseSpanParams.spanName}\" for mocking, but recorded occurrence ${callIndex + 1} is unavailable. The real span was not executed.`,\n )\n }\n if (shouldMockWithBaseStrategy) {\n const recorded = resolveBaseOutput()\n if (recorded instanceof Promise) {\n return emitMockAsync(recorded, \"recorded\")\n }\n return emitMock(recorded, \"recorded\")\n }\n }\n } catch (setupError) {\n if (startedSpan) {\n self.httpClient.traceCompletion.abort(\n startedSpan.traceId,\n startedSpan.spanId,\n )\n }\n // If this call registered root trace state before failing, remove the\n // now-orphaned entry: no span or completion will follow, so leaving it\n // would leak the maps and block that trace id's completion forever.\n if (registeredTraceId) {\n activeTraceStates.delete(registeredTraceId)\n retireTraceMetadata(registeredTraceId)\n }\n if (setupError instanceof MixedTracingError) {\n throw setupError\n }\n // During replay (a controlled eval), a setup failure must surface, not\n // silently fall through. The setup region includes the mock\n // interception: if a matched mock can't build its output (e.g.\n // deserializeValue on bad outputMeta), swallowing it would run the real\n // function - real side effects, and a skewed mock call counter - which\n // defeats replay. Re-throw so the replay runner records it as that\n // item's error. The never-crash fallback below is for production hosts.\n if (getReplayContext() || inSeedScope()) {\n throw setupError\n }\n // Tracing setup failed; run the user's function untraced so a tracing\n // failure never crashes the host app while preserving its receiver.\n warnOnce(\n `withSpan-setup:${traceFunctionKey}`,\n `tracing setup failed for \"${traceFunctionKey}\"; running it untraced. The function still runs and returns normally; no span is recorded.`,\n )\n return fn.apply(this, args) as TReturn\n }\n\n // Run OUTSIDE the setup try: a throw from the user's own function must\n // propagate unchanged and never be mistaken for a tracing failure (which\n // would double-invoke `fn`).\n return runWithSpanStack(newStack, executeWithContext)\n }\n return markTracedFunction(wrappedFn, traceFunctionKey, fn)\n }\n\n /**\n * Create a standard ECMAScript method decorator that records each invocation\n * as a span.\n *\n * It supports instance, static, and private methods; use\n * {@link Bitfab.withSpan} for standalone functions, class fields, and\n * accessors.\n *\n * @example\n * ```typescript\n * const bitfab = new Bitfab({ apiKey: process.env.BITFAB_API_KEY });\n *\n * class OrderService {\n * @bitfab.span(\"order-processing\", { type: \"agent\" })\n * async process(orderId: string) {\n * return { orderId };\n * }\n * }\n * ```\n *\n * @param traceFunctionKey - A string identifier for grouping spans\n * @param options - Span configuration applied to the decorated method\n * @returns A standard ECMAScript method decorator\n */\n span(\n traceFunctionKey: string,\n options: SpanOptions = {},\n ): SpanMethodDecorator {\n return (originalMethod, context) => {\n if (context.kind !== \"method\") {\n throw new BitfabError(\n `Bitfab span decorators can only decorate methods; ${String(context.name)} is a ${context.kind}`,\n )\n }\n\n return this.withSpan(traceFunctionKey, options, originalMethod)\n }\n }\n\n /**\n * Get a detached handle to a previously-created trace, looked up by the\n * canonical Bitfab trace ID.\n *\n * The returned handle is not tied to AsyncLocalStorage - each method sends\n * to the server immediately. Useful for adding context to a trace from a\n * different process or thread than the one that created it.\n *\n * Throws synchronously if `traceId` is not a valid Bitfab trace ID. The\n * server returns 404 if no trace exists with that ID in the org; the failure surfaces as a\n * logged warning (fire-and-forget) or via the awaited promise.\n *\n * Example:\n * ```typescript\n * const trace = client.getTrace(traceId);\n * await trace.addContext({ refund_status: \"approved\" });\n * await trace.setMetadata({ region: \"us-west\" });\n * ```\n */\n getTrace(traceId: string): DetachedTrace {\n validateTraceId(traceId)\n\n return {\n traceId,\n addContext: (context: Record<string, unknown>): Promise<void> => {\n if (typeof context !== \"object\" || context === null) {\n return Promise.resolve()\n }\n return this.httpClient.patchTrace(traceId, {\n appendContexts: [context],\n })\n },\n setMetadata: (metadata: Record<string, unknown>): Promise<void> => {\n if (typeof metadata !== \"object\" || metadata === null) {\n return Promise.resolve()\n }\n return this.httpClient.patchTrace(traceId, { mergeMetadata: metadata })\n },\n setSessionId: (sessionId: string): Promise<void> => {\n if (typeof sessionId !== \"string\" || sessionId.length === 0) {\n return Promise.resolve()\n }\n return this.httpClient.patchTrace(traceId, { setSessionId: sessionId })\n },\n setName: (name: string): Promise<void> => {\n if (typeof name !== \"string\" || name.length === 0) {\n return Promise.resolve()\n }\n return this.httpClient.patchTrace(traceId, { setName: name })\n },\n }\n }\n\n /**\n * Fetch one persisted span from a trace without loading the full trace.\n * Look up by `id` alone, or by `name` and/or `type` (both must match when\n * both are given). Name and type lookups return the last matching span by\n * default. Pass `occurrence` as `\"first\"` or a zero-based index to select a\n * different match. Returns `null` when no span matches.\n */\n async getTraceSpan(\n traceId: string,\n lookup: SpanLookup,\n ): Promise<CapturedSpan | null> {\n validateTraceId(traceId)\n const hasId = lookup.id !== undefined\n const hasNameOrType = lookup.name !== undefined || lookup.type !== undefined\n if (hasId === hasNameOrType) {\n throw new BitfabError(\"Provide id, or name and/or type\")\n }\n if (hasId) {\n validateSpanId(lookup.id)\n } else {\n if (lookup.name !== undefined && lookup.name.length === 0) {\n throw new BitfabError(\"name must be a non-empty string\")\n }\n if (lookup.type !== undefined && !isSpanType(lookup.type)) {\n throw new BitfabError(`type must be one of ${SPAN_TYPES.join(\", \")}`)\n }\n const occurrence = lookup.occurrence ?? \"last\"\n if (\n occurrence !== \"first\" &&\n occurrence !== \"last\" &&\n (!Number.isInteger(occurrence) || occurrence < 0)\n ) {\n throw new BitfabError(\n 'occurrence must be \"first\", \"last\", or a non-negative integer',\n )\n }\n }\n return this.httpClient.getTraceSpan(traceId, lookup)\n }\n\n async getSpan(spanId: string): Promise<CapturedSpan | null> {\n if (typeof spanId !== \"string\" || spanId.trim().length === 0) {\n throw new BitfabError(\"spanId must be a non-empty string\")\n }\n return this.httpClient.getSpan(spanId)\n }\n\n /**\n * Get a function wrapper for a specific trace function key.\n *\n * This provides a fluent API alternative to calling withSpan directly,\n * allowing you to bind the traceFunctionKey once and wrap multiple functions.\n *\n * Example usage:\n * ```typescript\n * const client = new Bitfab({ apiKey: 'your-api-key' });\n *\n * const orderFunc = client.getFunction('order-processing');\n * const tracedProcessOrder = orderFunc.withSpan(processOrder);\n * const tracedValidateOrder = orderFunc.withSpan(validateOrder);\n * ```\n *\n * @param traceFunctionKey - A string identifier for grouping spans\n * @returns A BitfabFunction instance for wrapping functions\n */\n getFunction(traceFunctionKey: string): BitfabFunction {\n this.simulationPlan.refresh()\n return new BitfabFunction(this, traceFunctionKey)\n }\n\n /**\n * Send trace completion when a root span ends.\n * Internal method to record trace completion with end time.\n * Queued on the client's span transport; delivery is the transport's job.\n */\n private sendTraceCompletion(params: {\n traceFunctionKey: string\n traceId: string\n startedAt: string\n endedAt: string\n sessionId?: string\n name?: string\n metadata?: Record<string, unknown>\n contexts?: ContextEntry[]\n experimentId?: string\n inputSourceTraceId?: string\n replayAttempt?: number\n dbSnapshotRef?: DbSnapshotRef\n dropped?: boolean\n ingestionType?: TraceIngestionType\n /**\n * Replay DB branch usage record, present only when a lease was\n * attached to the replay item. Serialized as `db_snapshot_usage` so\n * the server can stamp the trace's metadata at ingest.\n */\n dbSnapshotUsage?: {\n neonBranchId: string\n snapshotTimestamp?: string\n /**\n * The branch's region. Recorded so a duration gap between items can be\n * attributed to a cross-region round trip rather than to the code.\n */\n region?: string\n /** Bitfab trace id of the original trace this replay item pinned to. */\n originalTraceId?: string\n accessed: boolean\n /** Server-measured provisioning timings, echoed back verbatim. */\n timings?: DbBranchTimings\n }\n }): void {\n // Build the raw trace object for the externalTraces endpoint\n const rawTrace: Record<string, unknown> = {\n id: params.traceId,\n started_at: params.startedAt,\n ended_at: params.endedAt,\n }\n\n // Add optional fields to rawData\n if (params.name) {\n rawTrace.name = params.name\n }\n if (params.metadata && Object.keys(params.metadata).length > 0) {\n rawTrace.metadata = params.metadata\n }\n if (params.contexts && params.contexts.length > 0) {\n rawTrace.contexts = params.contexts\n }\n if (params.inputSourceTraceId) {\n rawTrace.input_source_trace_id = params.inputSourceTraceId\n }\n if (params.replayAttempt !== undefined) {\n rawTrace.replay_attempt = params.replayAttempt\n }\n if (params.dbSnapshotRef) {\n rawTrace.db_snapshot_ref = params.dbSnapshotRef\n }\n const commitRef = currentCommitRef()\n if (commitRef) {\n rawTrace.commit_ref = commitRefWire(commitRef)\n }\n if (params.ingestionType) {\n rawTrace.ingestion_type = params.ingestionType\n }\n if (params.dbSnapshotUsage) {\n rawTrace.db_snapshot_usage = {\n neon_branch_id: params.dbSnapshotUsage.neonBranchId,\n ...(params.dbSnapshotUsage.snapshotTimestamp && {\n snapshot_timestamp: params.dbSnapshotUsage.snapshotTimestamp,\n }),\n ...(params.dbSnapshotUsage.region && {\n region: params.dbSnapshotUsage.region,\n }),\n ...(params.dbSnapshotUsage.originalTraceId && {\n original_trace_id: params.dbSnapshotUsage.originalTraceId,\n // Deprecated wire alias, kept so this SDK still reports usage\n // against servers that predate the rename.\n source_trace_id: params.dbSnapshotUsage.originalTraceId,\n }),\n accessed: params.dbSnapshotUsage.accessed,\n // Echoed verbatim (camelCase inside) rather than re-cased into this\n // record's snake_case: it is the server's own object coming back, and\n // a translation layer here is one more thing to drift.\n ...(params.dbSnapshotUsage.timings && {\n timings: params.dbSnapshotUsage.timings,\n }),\n }\n }\n\n this.httpClient.sendExternalTrace({\n id: params.traceId,\n type: \"sdk-function\",\n source: \"typescript-sdk-function\",\n traceFunctionKey: params.traceFunctionKey,\n externalTrace: rawTrace,\n completed: true,\n ...(params.dropped && { dropped: true }),\n ...(params.sessionId && { sessionId: params.sessionId }),\n ...(params.experimentId !== undefined &&\n experimentIdWireFields(params.experimentId)),\n })\n }\n\n private sendsSpanOnCompletion(context: SpanContext): boolean {\n return (\n context.isRoot ||\n context.declaredNode ||\n isFrameworkInstrumentation(context.instrumentation) ||\n !this.simulationPlan.contentOffNow(\n context.rootTraceFunctionKey,\n context.spanName,\n )\n )\n }\n\n private nearestCapturedAncestorSpanIdOf(stack: SpanContext[]): string | null {\n for (let index = stack.length - 1; index >= 0; index -= 1) {\n const candidate = stack[index]\n if (candidate !== undefined && this.sendsSpanOnCompletion(candidate)) {\n return candidate.spanId\n }\n }\n return null\n }\n\n private contentOffBySimulationPlan(params: {\n rootTraceFunctionKey?: string\n spanName: string\n instrumentation: SpanInstrumentation\n parentSpanId: string | null\n }): boolean {\n if (\n params.rootTraceFunctionKey === undefined ||\n isFrameworkInstrumentation(params.instrumentation)\n ) {\n return false\n }\n return (\n this.simulationPlan.contentOffNow(\n params.rootTraceFunctionKey,\n params.spanName,\n ) ||\n (params.parentSpanId !== null && this.simulationPlan.unavailableNow())\n )\n }\n\n /**\n * Send a wrapper span from function execution.\n * Internal method to record spans when using withSpan.\n * Queued on the client's span transport; delivery is the transport's job.\n */\n private sendWrapperSpan(params: {\n traceFunctionKey: string\n rootTraceFunctionKey?: string\n functionName?: string\n spanName: string\n traceId: string\n spanId: string\n parentSpanId: string | null\n inputs?: unknown[]\n result: unknown\n error?: string\n startedAt: string\n endedAt: string\n spanType: SpanType\n instrumentation: SpanInstrumentation\n contexts?: ContextEntry[]\n prompt?: string\n experimentId?: string\n inputSourceSpanId?: string\n mocked?: boolean\n mockTarget?: MockTarget\n mockSource?: MockSource\n functionId?: string\n captureContent: boolean\n nearestCapturedAncestorSpanId?: string | null\n replayReusable?: boolean\n autoTraceDefinition?: AutoTraceFunctionDefinition\n declaredNode?: boolean\n nestedTrace?: NestedTraceLink\n enclosingTrace?: EnclosingTrace\n }): void {\n const contentOff =\n params.declaredNode !== true && this.contentOffBySimulationPlan(params)\n const buildsContent = params.captureContent && !contentOff\n const serializedInputs = buildsContent\n ? serializeValue(params.inputs)\n : undefined\n const serializedResult = buildsContent\n ? serializeValue(params.result)\n : undefined\n\n // Format as an external span with the wrapper format\n const externalSpan: Record<string, unknown> = {\n id: params.spanId,\n trace_id: params.traceId,\n started_at: params.startedAt,\n ended_at: params.endedAt,\n span_origin: makeSpanOrigin(params.instrumentation),\n span_data: {\n name: params.spanName,\n ...(params.replayReusable !== undefined && {\n replay_reusable: params.replayReusable,\n }),\n type: params.spanType,\n ...(params.functionId !== undefined && {\n function_id: params.functionId,\n content_captured: params.captureContent,\n }),\n ...(params.autoTraceDefinition !== undefined && {\n function_file: params.autoTraceDefinition.file,\n function_line: params.autoTraceDefinition.line,\n function_column: params.autoTraceDefinition.column,\n }),\n ...(params.nestedTrace?.traceId !== undefined && {\n nested_trace_id: params.nestedTrace.traceId,\n nested_trace_function_key: params.nestedTrace.traceFunctionKey,\n nested_root_span_id: params.nestedTrace.rootSpanId,\n }),\n ...(params.enclosingTrace !== undefined && {\n enclosing_trace_id: params.enclosingTrace.traceId,\n enclosing_trace_function_key: params.enclosingTrace.traceFunctionKey,\n enclosing_span_id: params.enclosingTrace.spanId,\n }),\n ...(serializedInputs !== undefined && {\n input: serializedInputs.json,\n ...(serializedInputs.meta !== undefined && {\n input_meta: serializedInputs.meta,\n }),\n }),\n ...(serializedResult !== undefined && {\n output: serializedResult.json,\n ...(serializedResult.meta !== undefined && {\n output_meta: serializedResult.meta,\n }),\n }),\n ...(params.functionName !== undefined && {\n function_name: params.functionName,\n }),\n ...(params.captureContent &&\n params.error !== undefined && {\n error: params.error,\n error_source: \"code\",\n }),\n ...(params.captureContent &&\n params.contexts &&\n params.contexts.length > 0 && {\n contexts: params.contexts,\n }),\n ...(buildsContent &&\n params.prompt !== undefined && { prompt: params.prompt }),\n ...(contentOff && { [CONTENT_OFF_KEY]: true }),\n },\n }\n\n // Add parent_id for nested spans\n if (params.parentSpanId) {\n externalSpan.parent_id = params.parentSpanId\n }\n if (\n params.nearestCapturedAncestorSpanId !== undefined &&\n params.nearestCapturedAncestorSpanId !== null &&\n params.nearestCapturedAncestorSpanId !== params.parentSpanId\n ) {\n externalSpan.nearest_captured_ancestor_id =\n params.nearestCapturedAncestorSpanId\n }\n if (params.inputSourceSpanId) {\n externalSpan.input_source_span_id = params.inputSourceSpanId\n }\n\n this.httpClient.sendExternalSpan({\n id: params.spanId,\n traceId: params.traceId,\n type: \"sdk-function\",\n source: \"typescript-sdk-function\",\n sourceTraceId: params.traceId,\n traceFunctionKey: params.traceFunctionKey,\n ...(params.rootTraceFunctionKey !== undefined && {\n [ROOT_TRACE_FUNCTION_KEY_FIELD]: params.rootTraceFunctionKey,\n }),\n ...(params.declaredNode === true && { [DECLARED_NODE_FIELD]: true }),\n rawSpan: externalSpan,\n ...(params.experimentId !== undefined &&\n experimentIdWireFields(params.experimentId)),\n ...(params.mocked && { mocked: true }),\n ...(params.mockTarget && { mockTarget: params.mockTarget }),\n ...(params.mockSource && { mockSource: params.mockSource }),\n })\n }\n\n /**\n * Replay historical traces through a function and create an experiment.\n *\n * Fetches the last N traces for the given trace function key, re-runs each\n * through the provided function, and returns comparison data.\n *\n * Accepts either a `withSpan`-wrapped function (under the same key) or any\n * plain callable: plain callables are wrapped internally so each replayed\n * invocation records a trace tied to the experiment. The plain-callable form\n * is how handler-instrumented workflows (LangGraph/LangChain, Claude Agent\n * SDK) replay - those record traces under a key with no `withSpan`-wrapped\n * root in the app.\n *\n * @param traceFunctionKey - The trace function key to replay\n * @param fn - The function to run recorded inputs through\n * @param options - Optional replay options. When `traceIds` is passed,\n * `limit` is ignored (with a warning): an explicit ID list already\n * determines how many traces replay.\n * @returns ReplayResult with items, experimentId, and experimentUrl\n */\n /**\n * Register a mock override applied to every subsequent `replay` on this\n * client, so downstream real code runs against a value you supply for the\n * matched span. Instance-scoped (no global state); call {@link clearMockOverrides}\n * to reset. Per-call `replay({ mockOverride })` overrides take precedence, and\n * both take precedence over the base `mock` strategy.\n *\n * ```ts\n * // Object form (value is a flat value here)\n * bitfab.registerMockOverride({\n * match: (node) => node.traceFunctionKey === \"classify-intent\",\n * value: { label: \"refund\" },\n * })\n * // Ordered form (equivalent); value may also be a function of the context\n * bitfab.registerMockOverride(\n * (node) => node.traceFunctionKey === \"classify-intent\",\n * ({ inputs }) => ({ label: \"refund\" }),\n * )\n * // Keyed form: the resolver only sees spans for this trace function key.\n * bitfab.registerMockOverride(\"classify-intent\", ({ inputs }) => ({\n * label: String(inputs[0]),\n * }))\n * // Or one resolver for every child span:\n * bitfab.registerMockOverride(({ node }) =>\n * node.traceFunctionKey === \"classify-intent\"\n * ? { label: \"refund\" }\n * : NO_MOCK_OVERRIDE,\n * )\n * ```\n */\n registerMockOverride(override: MockOverride): void\n registerMockOverride(resolver: MockOverrideResolver): void\n registerMockOverride(match: NodeMatcher, value: MockValue): void\n registerMockOverride(\n traceFunctionKey: string,\n override: MockOverride | MockOverrideResolver,\n ): void\n registerMockOverride(\n overrideOrResolverOrMatch:\n | string\n | MockOverride\n | MockOverrideResolver\n | NodeMatcher,\n ...values: [] | [MockValue | MockOverride | MockOverrideResolver]\n ): void {\n let override: MockOverride\n if (typeof overrideOrResolverOrMatch === \"string\") {\n const keyedOverride = values[0]\n if (values.length !== 1 || keyedOverride === undefined) {\n throw new BitfabError(\n \"registerMockOverride(traceFunctionKey, override) requires a resolver function or { match, value } override.\",\n )\n }\n if (typeof keyedOverride === \"function\") {\n override = {\n match: (node) => node.traceFunctionKey === overrideOrResolverOrMatch,\n value: keyedOverride,\n }\n } else if (\n typeof keyedOverride === \"object\" &&\n keyedOverride !== null &&\n \"match\" in keyedOverride &&\n \"value\" in keyedOverride\n ) {\n override = {\n match: (node) =>\n node.traceFunctionKey === overrideOrResolverOrMatch &&\n keyedOverride.match(node),\n value: keyedOverride.value,\n }\n } else {\n throw new BitfabError(\n \"registerMockOverride(traceFunctionKey, override) requires a resolver function or { match, value } override.\",\n )\n }\n } else if (typeof overrideOrResolverOrMatch !== \"function\") {\n override = overrideOrResolverOrMatch\n } else if (values.length === 0) {\n override = { match: () => true, value: overrideOrResolverOrMatch }\n } else {\n override = {\n match: overrideOrResolverOrMatch as NodeMatcher,\n value: values[0],\n }\n }\n this.mockOverrides.push(override)\n }\n\n /** Remove all overrides registered via {@link registerMockOverride}. */\n clearMockOverrides(): void {\n this.mockOverrides.length = 0\n }\n\n seedTrace(traceFunctionKey: string, options: SeedCaseOptions): string\n seedTrace<TArgs extends unknown[], TReturn>(\n traceFunctionKey: string,\n fn: (...args: TArgs) => TReturn,\n options?: SeedRunOptions<TArgs>,\n ): Promise<string>\n seedTrace(\n traceFunctionKey: string,\n optionsOrFn: SeedCaseOptions | ((...args: never[]) => unknown),\n runOptions?: SeedRunOptions<never[]>,\n ): string | Promise<string> {\n if (typeof optionsOrFn === \"function\") {\n return this.seedTraceByRunning(traceFunctionKey, optionsOrFn, runOptions)\n }\n return this.seedTraceFromCase(traceFunctionKey, optionsOrFn)\n }\n\n private seedTraceFromCase(\n traceFunctionKey: string,\n options: SeedCaseOptions,\n ): string {\n const { input } = options\n const fn =\n (options.fn as { _bitfabWrappedFn?: (...args: unknown[]) => unknown })\n ?._bitfabWrappedFn ?? options.fn\n if (fn && input.length < fn.length) {\n throw new BitfabError(\n `Seeded case supplies ${input.length} argument(s) but ${\n fn.name === \"\" ? \"the function\" : fn.name\n } requires ${fn.length}. Fix the case, or omit fn to seed it anyway.`,\n )\n }\n\n const traceId = randomUuid()\n const startedAt = nowIsoTimestamp()\n\n if (options.metadata !== undefined) {\n recordCallerTraceMetadata(traceId, options.metadata)\n }\n activeTraceStates.set(traceId, {\n traceId,\n traceFunctionKey,\n startedAt,\n contexts: [],\n ingestionType: \"seeded\",\n ...(options.sessionId !== undefined && { sessionId: options.sessionId }),\n ...(options.name !== undefined && { name: options.name }),\n })\n\n try {\n this.sendWrapperSpan({\n traceFunctionKey,\n rootTraceFunctionKey: traceFunctionKey,\n spanName: options.spanName ?? traceFunctionKey,\n traceId,\n spanId: randomUuid(),\n parentSpanId: null,\n inputs: input,\n result: options.expected,\n startedAt,\n endedAt: startedAt,\n spanType: options.spanType ?? \"agent\",\n instrumentation: \"span\",\n captureContent: true,\n })\n this.sendTraceCompletion({\n traceFunctionKey,\n traceId,\n startedAt,\n endedAt: startedAt,\n sessionId: options.sessionId,\n name: options.name,\n metadata: callerTraceMetadata(traceId),\n contexts: [],\n ingestionType: \"seeded\",\n })\n } finally {\n activeTraceStates.delete(traceId)\n retireTraceMetadata(traceId)\n }\n\n return traceId\n }\n\n private async seedTraceByRunning(\n traceFunctionKey: string,\n fn: (...args: never[]) => unknown,\n options?: SeedRunOptions<never[]>,\n ): Promise<string> {\n const wrappedKey = (fn as { _bitfabTraceFunctionKey?: string })\n ._bitfabTraceFunctionKey\n let target = fn\n if (wrappedKey === undefined) {\n const seedRootOptions: InternalSpanOptions = {\n name: traceFunctionKey,\n type: \"agent\",\n surface: \"neutral\",\n }\n target = this.withSpan(\n traceFunctionKey,\n seedRootOptions,\n fn as (...args: unknown[]) => unknown,\n ) as (...args: never[]) => unknown\n } else if (wrappedKey !== traceFunctionKey) {\n throw new BitfabError(\n `Function is wrapped with trace function key '${wrappedKey}' but ` +\n `seedTrace was called with '${traceFunctionKey}'. Pass matching ` +\n \"keys, or pass the unwrapped function to seed it under the \" +\n \"explicit key.\",\n )\n }\n\n await seedContextReady\n\n const traceId = randomUuid()\n if (options?.metadata !== undefined) {\n recordCallerTraceMetadata(traceId, options.metadata)\n }\n activeTraceStates.set(traceId, {\n traceId,\n traceFunctionKey,\n startedAt: nowIsoTimestamp(),\n contexts: [],\n ingestionType: \"seeded\",\n ...(options?.sessionId !== undefined && { sessionId: options.sessionId }),\n ...(options?.name !== undefined && { name: options.name }),\n })\n\n const args = (options?.args ?? []) as never[]\n let unrecorded = true\n try {\n await runWithSeedContext({ traceId }, async () => target(...args))\n } finally {\n try {\n const { flushTraces } = await import(\"./http.js\")\n await flushTraces(30_000)\n } finally {\n unrecorded = activeTraceStates.delete(traceId)\n retireTraceMetadata(traceId)\n }\n }\n if (unrecorded) {\n throw new BitfabError(\n `seedTrace recorded nothing for '${traceFunctionKey}': the call ` +\n \"finished without a root span. Check that an API key resolves \" +\n \"(BITFAB_API_KEY or apiKey), and that fn is a regular or async \" +\n \"function rather than a generator.\",\n )\n }\n return traceId\n }\n\n async reseedTrace(\n traceFunctionKey: string,\n // biome-ignore lint/suspicious/noExplicitAny: the recorded inputs are deserialized from the trace, typed args would be incorrect\n fn: (...args: any[]) => unknown,\n options: ReseedOptions,\n ): Promise<ReseedResult> {\n const source = await this.httpClient.getReseedSource(options.traceId)\n if (source.traceFunctionKey !== traceFunctionKey) {\n throw new BitfabError(\n `Trace ${options.traceId} belongs to '${source.traceFunctionKey}', not '${traceFunctionKey}'.`,\n )\n }\n const args = deserializeInputs({\n input: source.input,\n input_meta: source.inputMeta,\n })\n const runTraceId = await this.seedTraceByRunning(\n traceFunctionKey,\n fn as (...args: never[]) => unknown,\n {\n args: args as never[],\n metadata: source.metadata,\n ...(source.sessionId === null ? {} : { sessionId: source.sessionId }),\n ...(source.name === null ? {} : { name: source.name }),\n },\n )\n const adopted = await this.httpClient.reseedTrace(\n options.traceId,\n runTraceId,\n )\n return {\n traceId: adopted.traceId,\n previousRunTraceId: adopted.previousRunTraceId,\n }\n }\n\n async replay<TReturn>(\n traceFunctionKey: string,\n // biome-ignore lint/suspicious/noExplicitAny: replay deserializes inputs from historical data, typed args would be incorrect\n fn: (...args: any[]) => TReturn | Promise<TReturn>,\n options?: ReplayOptions,\n ): Promise<ReplayResult<TReturn>> {\n const wrappedKey = (fn as { _bitfabTraceFunctionKey?: string })\n ._bitfabTraceFunctionKey\n let replayFn = fn\n if (wrappedKey === undefined) {\n // Name the root span after the key (not the callable's name) so it\n // matches the production root span: handler-instrumented roots (Claude\n // Agent SDK, OpenAI Agents) are named after the trace function key, so\n // naming the auto-wrap after fn.name would make the replayed root read\n // differently from the trace it replays.\n const replayRootOptions: InternalSpanOptions = {\n name: traceFunctionKey,\n type: \"agent\",\n surface: \"neutral\",\n }\n replayFn = this.withSpan(traceFunctionKey, replayRootOptions, fn)\n } else if (wrappedKey !== traceFunctionKey) {\n throw new BitfabError(\n `Function is wrapped with trace function key '${wrappedKey}' but ` +\n `replay was called with '${traceFunctionKey}'. Pass matching keys, ` +\n \"or pass the unwrapped function to replay it under the explicit key.\",\n )\n }\n const { replay: doReplay } = await import(\"./replay.js\")\n return doReplay(\n this.httpClient,\n this.serviceUrl,\n traceFunctionKey,\n replayFn,\n options,\n this.mockOverrides,\n )\n }\n}\n\n/**\n * Represents a Bitfab function that can wrap user functions for tracing.\n *\n * This provides a fluent API for binding a traceFunctionKey once and\n * then wrapping multiple functions with that key.\n *\n * Example usage:\n * ```typescript\n * const client = new Bitfab({ apiKey: 'your-api-key' });\n *\n * const orderFunc = client.getFunction('order-processing');\n * const tracedProcessOrder = orderFunc.withSpan(processOrder);\n * const tracedValidateOrder = orderFunc.withSpan(validateOrder);\n * ```\n */\nexport class BitfabFunction {\n constructor(\n private readonly client: Bitfab,\n private readonly traceFunctionKey: string,\n ) {}\n\n /**\n * Wrap a function to automatically create a span for its inputs and outputs.\n *\n * The wrapped function behaves identically to the original, but sends\n * span data to Bitfab in the background after each call.\n *\n * Example usage:\n * ```typescript\n * const orderFunc = client.getFunction('order-processing');\n *\n * // Basic usage (defaults to \"custom\" span type)\n * const tracedProcessOrder = orderFunc.withSpan(processOrder);\n *\n * // With explicit span type\n * const tracedProcessOrder = orderFunc.withSpan({ type: 'function' }, processOrder);\n * ```\n *\n * @param optionsOrFn - Either SpanOptions or the function to wrap\n * @param maybeFn - The function to wrap if options were provided\n * @returns A wrapped function with the same signature that creates spans\n */\n withSpan<TArgs extends unknown[], TReturn>(\n optionsOrFn: SpanOptions | ((...args: TArgs) => TReturn),\n maybeFn?: (...args: TArgs) => TReturn,\n ): (...args: TArgs) => TReturn {\n // Handle overloaded signature\n const options: SpanOptions =\n typeof optionsOrFn === \"function\" ? {} : optionsOrFn\n const fn: (...args: TArgs) => TReturn =\n typeof optionsOrFn === \"function\" ? optionsOrFn : maybeFn!\n\n return this.client.withSpan(this.traceFunctionKey, options, fn)\n }\n\n /**\n * Create a standard ECMAScript method decorator bound to this function key.\n *\n * @example\n * ```typescript\n * const orders = client.getFunction(\"order-processing\");\n *\n * class OrderService {\n * @orders.span({ type: \"agent\" })\n * async process(orderId: string) {\n * return { orderId };\n * }\n * }\n * ```\n *\n * @param options - Span configuration applied to the decorated method\n * @returns A standard ECMAScript method decorator\n */\n span(options: SpanOptions = {}): SpanMethodDecorator {\n return this.client.span(this.traceFunctionKey, options)\n }\n\n /**\n * Wrap a function as an automatically expanded trace root bound to this\n * function's key.\n *\n * Equivalent to `client.withTrace(key, options, fn)` but reuses the key\n * bound on this handle.\n *\n * @param optionsOrFn - Trace options or the workflow entrypoint to wrap\n * @param maybeFn - Workflow entrypoint when options are provided\n * @returns A wrapped function with the same signature\n * @experimental Automatic child-call instrumentation is experimental.\n */\n withTrace<This, TArgs extends unknown[], TReturn>(\n fn: (this: This, ...args: TArgs) => TReturn,\n ): (this: This, ...args: TArgs) => TReturn\n withTrace<This, TArgs extends unknown[], TReturn>(\n options: TraceOptions,\n fn: (this: This, ...args: TArgs) => TReturn,\n ): (this: This, ...args: TArgs) => TReturn\n withTrace<This, TArgs extends unknown[], TReturn>(\n optionsOrFn: TraceOptions | ((this: This, ...args: TArgs) => TReturn),\n maybeFn?: (this: This, ...args: TArgs) => TReturn,\n ): (this: This, ...args: TArgs) => TReturn {\n if (typeof optionsOrFn === \"function\") {\n return this.client.withTrace(this.traceFunctionKey, optionsOrFn)\n }\n if (!maybeFn) {\n throw new BitfabError(\"BitfabFunction.withTrace requires a function\")\n }\n return this.client.withTrace(this.traceFunctionKey, optionsOrFn, maybeFn)\n }\n\n /**\n * Create a trace method decorator bound to this function's key.\n *\n * Equivalent to `client.trace(key, options)` but reuses the key bound on\n * this handle.\n *\n * @param options - Trace configuration applied to the decorated method\n * @returns A method decorator configured for this key\n * @experimental Automatic child-call instrumentation is experimental.\n */\n trace(options: TraceOptions = {}): TraceMethodDecorator {\n return this.client.trace(this.traceFunctionKey, options)\n }\n\n /**\n * Get a Vercel AI SDK language-model middleware bound to this function's key.\n *\n * Equivalent to `client.getVercelAiMiddleware(key)` but reuses the key bound\n * on this handle, so an outer `withSpan` root and the middleware-traced model\n * calls share one key without repeating the string. With a matching key, the\n * outer span is the replayable root and the model-call spans nest beneath it.\n *\n * Nesting is captured when the model is called, so keep the\n * `generateText` / `streamText` call inside this handle's `withSpan`; the\n * middleware object itself can be created anywhere.\n *\n * ```typescript\n * const chatTurn = client.getFunction(\"chat-turn\");\n * const runChatTurn = chatTurn.withSpan(\n * { type: \"agent\", finalize: finalizers.aiSdk },\n * (messages) => streamText({ model, messages }),\n * );\n * const model = wrapLanguageModel({\n * model: openai(\"gpt-4o\"),\n * middleware: chatTurn.getVercelAiMiddleware(),\n * });\n * ```\n *\n * @returns A Vercel AI SDK middleware configured for this client and key\n */\n getVercelAiMiddleware() {\n return this.client.getVercelAiMiddleware(this.traceFunctionKey)\n }\n\n /**\n * Get a Claude Agent SDK handler bound to this function's key.\n *\n * Equivalent to `client.getClaudeAgentHandler(key)` but reuses the key bound\n * on this handle, so an outer `withSpan` root and the handler share one key\n * without repeating the string. With a matching key, the outer span is the\n * replayable root and every handler span nests beneath it.\n *\n * Use the handler inside this handle's `withSpan` body so its spans capture\n * the enclosing root; framework calls made with no active span record their\n * own root instead.\n *\n * ```typescript\n * const pipeline = client.getFunction(\"my-agent\");\n * const tracedRun = pipeline.withSpan({ type: \"agent\" }, async (prompt) => {\n * const handler = pipeline.getClaudeAgentHandler();\n * const options = handler.instrumentOptions({ model: \"claude-sonnet-4-6\" });\n * for await (const msg of handler.wrapQuery(query({ prompt, options }))) { ... }\n * });\n * ```\n *\n * @returns A Claude Agent SDK handler configured for this client and key\n */\n getClaudeAgentHandler() {\n return this.client.getClaudeAgentHandler(this.traceFunctionKey)\n }\n\n /**\n * Get a LangGraph/LangChain callback handler bound to this function's key.\n *\n * Equivalent to `client.getLangGraphCallbackHandler(key)` but reuses the key\n * bound on this handle, so an outer `withSpan` root and the handler share one\n * key without repeating the string. With a matching key, the outer span is\n * the replayable root and the LangGraph spans nest beneath it.\n *\n * Use the handler inside this handle's `withSpan` body so its spans capture\n * the enclosing root; framework calls made with no active span record their\n * own root instead.\n *\n * ```typescript\n * const pipeline = client.getFunction(\"my-pipeline\");\n * const tracedRun = pipeline.withSpan({ type: \"agent\" }, async (query) => {\n * const handler = pipeline.getLangGraphCallbackHandler();\n * return agent.invoke({ messages: [...] }, { callbacks: [handler] });\n * });\n * ```\n *\n * @returns A LangGraph/LangChain callback handler for this client and key\n */\n getLangGraphCallbackHandler() {\n return this.client.getLangGraphCallbackHandler(this.traceFunctionKey)\n }\n\n /**\n * Alias of {@link getLangGraphCallbackHandler} - LangChain and LangGraph\n * share one callback system, so the same bound handler serves both.\n *\n * @returns A LangChain callback handler for this client and key\n */\n getLangChainCallbackHandler() {\n return this.client.getLangChainCallbackHandler(this.traceFunctionKey)\n }\n\n /**\n * Get the first-class LangGraph tool replay integration bound to this key.\n *\n * @experimental This API may change before it is stable.\n */\n getLangGraphIntegration(options?: LangGraphIntegrationOptions) {\n return this.client.getLangGraphIntegration(this.traceFunctionKey, options)\n }\n\n /**\n * Get the first-class Rivet actor-context integration bound to this key.\n * Use its `bind()` or `withContext()` outside this handle's `withTrace()`\n * root, then call `getContext()` inside the root.\n *\n * @param options - Explicit per-context-path replay mocking policy\n * @experimental This API may change before it is stable.\n */\n getRivetIntegration<TContext extends object = RivetContextLike>(\n options?: RivetIntegrationOptions,\n ): BitfabRivetIntegration<TContext> {\n return this.client.getRivetIntegration<TContext>(\n this.traceFunctionKey,\n options,\n )\n }\n\n /**\n * Wrap a BAML client method to automatically capture prompt and LLM metadata.\n * Delegates to the parent client's wrapBAML method.\n *\n * Unlike the other methods on this handle, `wrapBAML` does NOT use the bound\n * key: it opens no span of its own. It enriches the *current* span (via\n * `getCurrentSpan().setPrompt()` / `addContext()`), so call it inside a\n * function wrapped by this handle's `withSpan` - the bound key keys that\n * wrapper, and the BAML prompt/metadata attach to it.\n *\n * @param methodOrClient - Either a BAML method (uses constructor bamlClient) or the BAML client instance\n * @param maybeMethodOrOptions - The BAML method when the first argument is a client, or WrapBAMLOptions when the first argument is the method\n * @param maybeOptions - WrapBAMLOptions when using the two-argument (client, method) form\n * @returns An async function with the same signature that instruments the BAML call\n */\n wrapBAML<TArgs extends unknown[], TReturn>(\n methodOrClient: unknown,\n maybeMethodOrOptions?:\n | ((...args: TArgs) => Promise<TReturn>)\n | WrapBAMLOptions,\n maybeOptions?: WrapBAMLOptions,\n ): WrappedBamlFn<TArgs, TReturn> {\n return this.client.wrapBAML(\n methodOrClient,\n maybeMethodOrOptions,\n maybeOptions,\n )\n }\n}\n","import type { ApprovalState } from \"./approval.js\"\nimport { _resolvedApiKey, _serviceUrl, type Bitfab } from \"./client.js\"\nimport { BitfabError } from \"./errors.js\"\nimport {\n type CodeChangeFile,\n ReplayError,\n type ReplayItem,\n type ReplayOptions,\n type ReplayResult,\n reportReplayProgress,\n serializeReplayResult,\n warnJudgeAssertionsDeprecated,\n} from \"./replay.js\"\nimport {\n describeReplayInterrupt,\n type ReplayInterruptSignal,\n} from \"./replayInterrupt.js\"\nimport { canonicalCliFlag, withCliFlags } from \"./replayInvocation.js\"\nimport { warnOnce } from \"./warnOnce.js\"\n\ntype ReplayFunction = (\n // biome-ignore lint/suspicious/noExplicitAny: replay functions receive historical arguments\n ...args: any[]\n) => unknown | Promise<unknown>\n\nexport type ReplayRegistryOptions = Omit<\n ReplayOptions,\n \"onItemStart\" | \"onItemFinish\" | \"onProgress\" | \"onExperimentStart\"\n>\n\nexport interface ReplayRegistryContext {\n /** Values supplied through `--params` and repeated `--param name=value`. */\n params: Readonly<Record<string, unknown>>\n}\n\nexport type ReplayOptionsFactory = (\n context: ReplayRegistryContext,\n) => ReplayRegistryOptions | Promise<ReplayRegistryOptions>\n\nexport interface ReplayRegistration {\n /** Client instance used by the production traced function. */\n client: Bitfab\n /** The exact traced function production calls. */\n fn: ReplayFunction\n /**\n * Required for handler-instrumented or otherwise plain callables. Omit for a\n * `withSpan`-wrapped function and the registry reads the wrapper's key.\n */\n traceFunctionKey?: string\n /**\n * Per-function replay behavior and defaults. Put executable configuration\n * such as `mockOverride` and `adaptInputs` here; command-line values override\n * overlapping scalar defaults such as `mock` and `maxConcurrency`.\n */\n options?: ReplayRegistryOptions\n /** Build executable replay behavior from caller-supplied CLI parameters. */\n optionsFactory?: ReplayOptionsFactory\n}\n\nexport type ReplayRegistry = Record<string, ReplayRegistration>\n\n/**\n * Define the project-owned list of replayable functions.\n *\n * The registry is deliberately data-only: the SDK owns the replay CLI, so an\n * SDK upgrade can add replay features without regenerating the project file.\n */\nexport function defineReplayRegistry<TRegistry extends ReplayRegistry>(\n registry: TRegistry,\n): TRegistry {\n validateReplayRegistry(registry)\n return registry\n}\n\nconst REGISTRATION_FIELDS = new Set([\n \"client\",\n \"fn\",\n \"traceFunctionKey\",\n \"options\",\n \"optionsFactory\",\n])\n\nexport function validateReplayRegistry(registry: ReplayRegistry): void {\n for (const [pipeline, registration] of Object.entries(registry)) {\n if (typeof registration !== \"object\" || registration === null) {\n throw new BitfabError(\n `Replay registry entry '${pipeline}' must be an object with client and fn.`,\n )\n }\n const unknownFields = Object.keys(registration).filter(\n (field) => !REGISTRATION_FIELDS.has(field),\n )\n if (unknownFields.length > 0) {\n warnOnce(\n `replay-registry-unknown-fields:${pipeline}:${unknownFields.join(\",\")}`,\n `Replay registry entry '${pipeline}' has unknown field(s): ${unknownFields.join(\", \")}. ` +\n \"They are ignored. An entry takes client, fn, traceFunctionKey, options, and optionsFactory. \" +\n \"Put replay options such as adaptInputs, mock, and mockOverride inside options. \" +\n \"A later SDK release will reject unknown fields.\",\n )\n }\n }\n}\n\nexport interface ReplayCliIo {\n stdout?: (line: string) => void\n stderr?: (line: string) => void\n readFile?: (path: string) => Promise<string>\n}\n\ninterface ReplayCliArgs {\n pipeline: string\n limit?: number\n attempts?: number\n traceIds?: string[]\n name?: string\n notes?: string\n metadata?: Record<string, string>\n maxConcurrency?: number\n codeChangePath?: string\n experimentGroupId?: string\n experimentGroupName?: string\n datasetIds?: string[]\n graderIds?: string[]\n onlyWithAssertions?: boolean\n skipAssertionJudging?: boolean\n judgeAssertionsFlag?: boolean\n mock?: \"none\" | \"all\" | \"marked\"\n dbBranch?: boolean\n noCodeChange?: boolean\n dryRun?: boolean\n failOnError?: boolean\n paramsPath?: string\n params: string[]\n resume?: string\n force?: boolean\n flags: Record<string, unknown>\n}\n\ninterface CodeChange {\n description: string\n files: CodeChangeFile[]\n}\n\nconst VALUE_FLAGS = new Set([\n \"--limit\",\n \"--attempts\",\n \"--trace-ids\",\n \"--name\",\n \"--notes\",\n \"--metadata\",\n \"--concurrency\",\n \"--max-concurrency\",\n \"--code-change\",\n \"--experiment-group-id\",\n \"--experiment-group-name\",\n \"--dataset-id\",\n \"--dataset-ids\",\n \"--grader-ids\",\n \"--mock\",\n \"--param\",\n \"--params\",\n \"--resume\",\n])\n\nconst BOOLEAN_FLAGS = new Set([\n \"--only-with-assertions\",\n \"--skip-assertion-judging\",\n \"--judge-assertions\",\n \"--db-branch\",\n \"--no-db-branch\",\n \"--no-code-change\",\n \"--dry-run\",\n \"--fail-on-error\",\n \"--force\",\n])\n\nconst HELP_FLAGS = new Set([\"--help\", \"-h\"])\n\nconst REPEATABLE_FLAGS = new Set([\"--metadata\", \"--param\"])\n\nfunction recordFlag(\n flags: Record<string, unknown>,\n flag: string,\n raw?: string,\n): void {\n const name = canonicalCliFlag(flag.slice(2))\n if (raw === undefined) {\n flags[name] = true\n return\n }\n const value = flag === \"--param\" ? raw.split(\"=\")[0] : raw\n if (REPEATABLE_FLAGS.has(flag)) {\n const previous = flags[name]\n flags[name] = [...(Array.isArray(previous) ? previous : []), value]\n return\n }\n flags[name] = value\n}\n\nconst MAX_ATTEMPTS = 100\n\nconst MAX_PINNED_TRACE_IDS = 100\n\nconst INTERRUPT_EXIT_CODES: Record<ReplayInterruptSignal, number> = {\n SIGINT: 130,\n SIGTERM: 143,\n}\n\nconst RESUME_CONFLICTING_FLAGS: ReadonlyArray<[keyof ReplayCliArgs, string]> = [\n [\"traceIds\", \"--trace-ids\"],\n [\"datasetIds\", \"--dataset-ids\"],\n [\"limit\", \"--limit\"],\n [\"attempts\", \"--attempts\"],\n [\"graderIds\", \"--grader-ids\"],\n [\"onlyWithAssertions\", \"--only-with-assertions\"],\n [\"dryRun\", \"--dry-run\"],\n]\n\nconst RESUME_CLEARED_OPTIONS: Partial<ReplayOptions> = {\n traceIds: undefined,\n limit: undefined,\n datasetId: undefined,\n datasetIds: undefined,\n attempts: undefined,\n graderIds: undefined,\n onlyWithAssertions: undefined,\n dryRun: undefined,\n}\n\nfunction hasApprovedAssertion(\n assertions: { approvalState: ApprovalState }[],\n): boolean {\n return assertions.some((assertion) => assertion.approvalState === \"approved\")\n}\n\nasync function boundTraceIds(\n client: Bitfab,\n traceIds: string[],\n limit: number,\n onlyWithAssertions: boolean,\n): Promise<string[]> {\n if (!Number.isInteger(limit) || limit < 1) {\n throw new BitfabError(\"Replay limit must be a positive integer.\")\n }\n if (!onlyWithAssertions || limit >= traceIds.length) {\n return traceIds.slice(0, limit)\n }\n const selected: string[] = []\n for (const traceId of traceIds) {\n const { assertions, inheritedFrom } =\n await client.traces.getAssertions(traceId)\n if (inheritedFrom === null && hasApprovedAssertion(assertions)) {\n selected.push(traceId)\n if (selected.length === limit) {\n break\n }\n }\n }\n if (selected.length === 0) {\n throw new BitfabError(\n \"No traces with approved assertions matched this replay selection.\",\n )\n }\n return selected\n}\n\nasync function pinnedDatasetMembers(\n client: Bitfab,\n datasetIds: string[],\n limit: number,\n onlyWithAssertions = false,\n): Promise<string[] | undefined> {\n const members = new Set<string>()\n for (const datasetId of datasetIds) {\n const { traceIds } = await client.datasets.listTraces(datasetId)\n for (const traceId of traceIds) {\n members.add(traceId)\n }\n }\n const ordered = [...members].sort()\n if (limit >= ordered.length) {\n return undefined\n }\n if (limit > MAX_PINNED_TRACE_IDS) {\n throw new BitfabError(\n `Bounding a dataset replay to ${limit} names one trace ID per replayed item, and a replay accepts at most ${MAX_PINNED_TRACE_IDS}. Drop the limit to replay every selected dataset in full.`,\n )\n }\n return boundTraceIds(client, ordered, limit, onlyWithAssertions)\n}\n\nfunction usage(registry: ReplayRegistry | undefined): string {\n const pipelines =\n registry === undefined ? \"pipeline\" : Object.keys(registry).join(\"|\")\n return `Usage: bitfab-replay --registry <path> <${pipelines}> [options]\\n\\nOptions:\\n --limit N (bounds a trace ID or dataset selection instead of replacing it)\\n --attempts N (replay each trace N times in this run, max ${MAX_ATTEMPTS})\\n --trace-ids id1,id2\\n --name NAME (what this run is testing, such as \"baseline\"; Bitfab already records the commit, branch, and datasets)\\n --notes TEXT (run conditions Bitfab cannot see, such as a forced feature flag)\\n --metadata key=value (repeatable; tags kept on the experiment, such as which schedule launched it, and filterable through client.experiments.list)\\n --concurrency N, --max-concurrency N\\n --code-change PATH, --no-code-change\\n --experiment-group-id UUID (join this experiment group; a new UUID creates the group)\\n --experiment-group-name NAME (name the group when this run creates it or it has no name yet; requires --experiment-group-id)\\n --dataset-ids uuid1,uuid2 (--dataset-id is the same flag)\\n --grader-ids id1,id2\\n --only-with-assertions (replay only traces carrying an approved assertion)\\n --skip-assertion-judging (don't judge approved assertions on each replay; judging is on by default and costs model calls)\\n --judge-assertions (deprecated: judging is on by default)\\n --mock none|all|marked\\n --db-branch, --no-db-branch\\n --dry-run (resolve inputs, run nothing)\\n --fail-on-error (exit 1 after printing the result when any replayed item errored; under --dry-run, items whose inputs failed to resolve count)\\n --resume EXPERIMENT_ID (finish an experiment that stopped partway, running only the items it did not finish, with the traces, attempts, and graders it started with)\\n --force (with --resume, resume even when the experiment may still be running or the code changed since it started)\\n --params PATH\\n --param name=value (repeatable)\\n -h, --help`\n}\n\nexport class ReplayCliHelp extends Error {}\n\n/** One case to seed, in the shape `--seed` reads from JSON or JSONL. */\nexport interface SeedCase {\n /** Arguments spread into the registered function at replay. */\n input: unknown[]\n /** The output this case should produce. */\n expected?: unknown\n /** Recorded on the trace, for tracing a seeded case back to its source row. */\n metadata?: Record<string, unknown>\n sessionId?: string\n}\n\nexport interface SeedResult {\n pipeline: string\n traceFunctionKey: string\n traceIds: string[]\n}\n\nexport interface ReseedRegistryResult {\n pipeline: string\n traceFunctionKey: string\n reseeded: { traceId: string; previousRunTraceId: string }[]\n}\n\nexport async function reseedFromRegistry(\n registry: ReplayRegistry,\n pipeline: string,\n traceIds: readonly string[],\n): Promise<ReseedRegistryResult> {\n const registration = registry[pipeline]\n if (registration === undefined) {\n throw new BitfabError(\n `Unknown pipeline '${pipeline}'. Registered: ${Object.keys(registry).join(\", \")}`,\n )\n }\n const traceFunctionKey = resolveTraceFunctionKey(registration)\n const reseeded: ReseedRegistryResult[\"reseeded\"] = []\n for (const traceId of traceIds) {\n reseeded.push(\n await registration.client.reseedTrace(traceFunctionKey, registration.fn, {\n traceId,\n }),\n )\n }\n return { pipeline, traceFunctionKey, reseeded }\n}\n\nfunction parseSeedCases(raw: string, path: string, run: boolean): SeedCase[] {\n const trimmed = raw.trim()\n if (trimmed.length === 0) {\n throw new BitfabError(`Seed file '${path}' is empty.`)\n }\n const values: unknown[] = trimmed.startsWith(\"[\")\n ? (JSON.parse(trimmed) as unknown[])\n : trimmed\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line.length > 0)\n .map((line) => JSON.parse(line) as unknown)\n\n return values.map((value, index) => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new BitfabError(\n `Seed case ${index} in '${path}' must be an object with an \"input\" array.`,\n )\n }\n const { input } = value as { input?: unknown }\n if (!Array.isArray(input)) {\n throw new BitfabError(\n `Seed case ${index} in '${path}' is missing an \"input\" array. Wrap a single argument as [arg].`,\n )\n }\n if (run && \"expected\" in value) {\n throw new BitfabError(\n `Seed case ${index} in '${path}' carries \"expected\", which --run does not record: the case is run once and its output is what the run produced. Remove the field, or drop --run to record the case without running it.`,\n )\n }\n return value as SeedCase\n })\n}\n\n/**\n * Seed cases through an already-registered pipeline.\n *\n * The registration is the whole point: it already holds the client, the exact\n * function production calls, and the trace function key replay selects by, so\n * a seeded case is guaranteed to line up with the replay that will read it.\n * Passing the registered function to `seedTrace` also means a case that cannot\n * supply its required arguments is rejected here rather than at replay.\n */\nexport async function seedFromRegistry(\n registry: ReplayRegistry,\n pipeline: string,\n cases: readonly SeedCase[],\n options: { run?: boolean } = {},\n): Promise<SeedResult> {\n const registration = registry[pipeline]\n if (registration === undefined) {\n throw new BitfabError(\n `Unknown pipeline '${pipeline}'. Registered: ${Object.keys(registry).join(\", \")}`,\n )\n }\n const traceFunctionKey = resolveTraceFunctionKey(registration)\n if (options.run === true) {\n const traceIds: string[] = []\n for (const seedCase of cases) {\n traceIds.push(\n await registration.client.seedTrace(traceFunctionKey, registration.fn, {\n args: seedCase.input,\n metadata: seedCase.metadata,\n sessionId: seedCase.sessionId,\n }),\n )\n }\n return { pipeline, traceFunctionKey, traceIds }\n }\n const traceIds = cases.map((seedCase) =>\n registration.client.seedTrace(traceFunctionKey, {\n input: seedCase.input,\n expected: seedCase.expected,\n fn: registration.fn,\n metadata: seedCase.metadata,\n sessionId: seedCase.sessionId,\n }),\n )\n const { flushTraces } = await import(\"./http.js\")\n await flushTraces(30_000)\n return { pipeline, traceFunctionKey, traceIds }\n}\n\nconst SEED_USAGE =\n 'Usage: bitfab-seed --registry <path> <pipeline> --from-trace <id>[,<id>...]\\n bitfab-seed --registry <path> <pipeline> --cases <cases.jsonl> [--run]\\n\\n--from-trace re-seeds each trace: the registered function runs once on the trace\\'s recorded inputs and the result is recorded under the same trace id, with the previous run kept as history under a new id. Nothing is mocked and no experiment is created. Labels, assertions, and dataset membership stay on the trace.\\n\\n--cases seeds new cases. Each case is a JSON object with an \"input\" array, plus optional \"expected\", \"metadata\", and \"sessionId\"; a JSON array of those objects works too. Without --run, each case is written as a trace directly (\"input\" becomes the root span input and \"expected\" its output) and nothing executes. With --run, each case runs once through the registered function with capture off and the execution is recorded, so the output is what the run produced; cases carrying \"expected\" are rejected.\\n\\nbitfab-replay --registry <path> <pipeline> --seed <cases.jsonl> [--run] is the older spelling of --cases.'\n\nexport async function runSeedCli(\n registry: ReplayRegistry,\n argv: readonly string[],\n io: ReplayCliIo = {},\n): Promise<SeedResult | ReseedRegistryResult> {\n const stdout = io.stdout ?? console.log\n const stderr = io.stderr ?? console.error\n if (argv.some((value) => HELP_FLAGS.has(value))) {\n throw new ReplayCliHelp(SEED_USAGE)\n }\n const pipeline = argv[0]\n if (pipeline === undefined || registry[pipeline] === undefined) {\n throw new BitfabError(SEED_USAGE)\n }\n const fromTraceIndex = argv.indexOf(\"--from-trace\")\n if (fromTraceIndex !== -1) {\n const raw = argv[fromTraceIndex + 1]\n if (raw === undefined || raw.startsWith(\"--\")) {\n throw new BitfabError(\"--from-trace requires one or more trace ids.\")\n }\n const traceIds = commaSeparated(\"--from-trace\", raw)\n stderr(\n `[seed] Re-seeding ${traceIds.length} trace(s) through \"${pipeline}\"...`,\n )\n const result = await reseedFromRegistry(registry, pipeline, traceIds)\n for (const entry of result.reseeded) {\n stderr(\n `[seed] ${entry.traceId} now holds the fresh run; the previous run is kept as ${entry.previousRunTraceId}`,\n )\n }\n stdout(JSON.stringify(result, null, 2))\n return result\n }\n const casesFlagIndex = argv.indexOf(\"--cases\")\n const seedIndex =\n casesFlagIndex === -1 ? argv.indexOf(\"--seed\") : casesFlagIndex\n if (seedIndex === -1) {\n throw new BitfabError(SEED_USAGE)\n }\n const casesPath = argv[seedIndex + 1]\n if (casesPath === undefined || casesPath.startsWith(\"--\")) {\n throw new BitfabError(`${argv[seedIndex]} requires a path to a cases file.`)\n }\n const run = argv.includes(\"--run\")\n const readFile = io.readFile ?? defaultReadFile\n const cases = parseSeedCases(await readFile(casesPath), casesPath, run)\n\n stderr(\n run\n ? `[seed] Running ${cases.length} case(s) through \"${pipeline}\"...`\n : `[seed] Seeding ${cases.length} case(s) into \"${pipeline}\"...`,\n )\n const result = await seedFromRegistry(registry, pipeline, cases, { run })\n stderr(\n `[seed] ${run ? \"Recorded\" : \"Wrote\"} ${result.traceIds.length} trace(s) for \"${result.traceFunctionKey}\". Replay them with --trace-ids ${result.traceIds.slice(0, 3).join(\",\")}${result.traceIds.length > 3 ? \",...\" : \"\"}`,\n )\n stdout(JSON.stringify(result, null, 2))\n return result\n}\n\nfunction requirePositiveInteger(\n flag: string,\n raw: string,\n max?: number,\n): number {\n const value = Number(raw)\n if (!Number.isInteger(value) || value < 1) {\n throw new BitfabError(\n `${flag} must be a positive integer (received '${raw}').`,\n )\n }\n if (max !== undefined && value > max) {\n throw new BitfabError(`${flag} must be at most ${max} (received '${raw}').`)\n }\n return value\n}\n\nfunction commaSeparated(flag: string, raw: string): string[] {\n const values = raw\n .split(\",\")\n .map((value) => value.trim())\n .filter((value) => value.length > 0)\n if (values.length === 0) {\n throw new BitfabError(`${flag} must contain at least one value.`)\n }\n return values\n}\n\nexport function checkReplayCliArgs(argv: readonly string[]): void {\n parseReplayCliArgs(undefined, argv)\n}\n\nfunction parseReplayCliArgs(\n registry: ReplayRegistry | undefined,\n argv: readonly string[],\n): ReplayCliArgs {\n if (argv.some((value) => HELP_FLAGS.has(value))) {\n throw new ReplayCliHelp(usage(registry))\n }\n const pipeline = argv[0]\n if (\n pipeline === undefined ||\n (registry !== undefined && registry[pipeline] === undefined)\n ) {\n throw new BitfabError(usage(registry))\n }\n\n const parsed: ReplayCliArgs = { pipeline, params: [], flags: {} }\n for (let index = 1; index < argv.length; index += 1) {\n const flag = argv[index]\n if (BOOLEAN_FLAGS.has(flag)) {\n recordFlag(parsed.flags, flag)\n switch (flag) {\n case \"--db-branch\":\n if (parsed.dbBranch === false) {\n throw new BitfabError(\n \"--db-branch and --no-db-branch cannot be used together.\",\n )\n }\n parsed.dbBranch = true\n break\n case \"--no-db-branch\":\n if (parsed.dbBranch === true) {\n throw new BitfabError(\n \"--db-branch and --no-db-branch cannot be used together.\",\n )\n }\n parsed.dbBranch = false\n break\n case \"--only-with-assertions\":\n // Left undefined when absent, never set to false: absence means the\n // command line said nothing, so a registry that set the option keeps\n // it. Only a present flag speaks.\n parsed.onlyWithAssertions = true\n break\n case \"--skip-assertion-judging\":\n parsed.skipAssertionJudging = true\n break\n case \"--judge-assertions\":\n parsed.judgeAssertionsFlag = true\n break\n case \"--no-code-change\":\n parsed.noCodeChange = true\n break\n case \"--dry-run\":\n parsed.dryRun = true\n break\n case \"--fail-on-error\":\n parsed.failOnError = true\n break\n case \"--force\":\n parsed.force = true\n break\n }\n continue\n }\n if (!VALUE_FLAGS.has(flag)) {\n throw new BitfabError(\n `Unknown replay option '${flag}'.\\n${usage(registry)}`,\n )\n }\n const raw = argv[index + 1]\n if (raw === undefined || raw.startsWith(\"--\")) {\n throw new BitfabError(`${flag} requires a value.`)\n }\n index += 1\n recordFlag(parsed.flags, flag, raw)\n\n switch (flag) {\n case \"--limit\":\n parsed.limit = requirePositiveInteger(flag, raw)\n break\n case \"--attempts\":\n parsed.attempts = requirePositiveInteger(flag, raw, MAX_ATTEMPTS)\n break\n case \"--trace-ids\":\n parsed.traceIds = commaSeparated(flag, raw)\n break\n case \"--name\":\n parsed.name = raw\n break\n case \"--notes\":\n parsed.notes = raw\n break\n case \"--metadata\": {\n const [key, value] = parseMetadataTag(raw)\n parsed.metadata = { ...parsed.metadata, [key]: value }\n break\n }\n case \"--concurrency\":\n case \"--max-concurrency\":\n parsed.maxConcurrency = requirePositiveInteger(flag, raw)\n break\n case \"--code-change\":\n parsed.codeChangePath = raw\n break\n case \"--experiment-group-id\":\n parsed.experimentGroupId = raw\n break\n case \"--experiment-group-name\":\n parsed.experimentGroupName = raw\n break\n case \"--dataset-id\":\n case \"--dataset-ids\":\n parsed.datasetIds = commaSeparated(flag, raw)\n break\n case \"--grader-ids\":\n parsed.graderIds = commaSeparated(flag, raw)\n break\n case \"--mock\":\n if (raw !== \"none\" && raw !== \"all\" && raw !== \"marked\") {\n throw new BitfabError(\n `--mock must be one of: none, all, marked (received '${raw}').`,\n )\n }\n parsed.mock = raw\n break\n case \"--params\":\n parsed.paramsPath = raw\n break\n case \"--param\":\n parsed.params.push(raw)\n break\n case \"--resume\":\n parsed.resume = raw\n break\n }\n }\n if (parsed.force === true && parsed.resume === undefined) {\n throw new BitfabError(\"--force only applies with --resume <experimentId>.\")\n }\n if (parsed.resume !== undefined) {\n const conflicting = RESUME_CONFLICTING_FLAGS.filter(\n ([key]) => parsed[key] !== undefined,\n ).map(([, flag]) => flag)\n if (conflicting.length > 0) {\n throw new BitfabError(\n `--resume reuses the traces, attempts, and graders experiment ${parsed.resume} started with, so it cannot be combined with ${conflicting.join(\", \")}.`,\n )\n }\n }\n if (parsed.traceIds !== undefined && parsed.datasetIds !== undefined) {\n throw new BitfabError(\n \"--trace-ids and --dataset-ids select different replay sources and cannot be used together.\",\n )\n }\n if (parsed.codeChangePath !== undefined && parsed.noCodeChange === true) {\n throw new BitfabError(\n \"--code-change and --no-code-change cannot be used together.\",\n )\n }\n return parsed\n}\n\nfunction resolveRegistrationDatasetIds(\n options: ReplayRegistryOptions,\n): string[] | undefined {\n if (options.datasetId !== undefined && options.datasetIds !== undefined) {\n throw new BitfabError(\n \"Replay registry options datasetId and datasetIds are the same selector: set one of them.\",\n )\n }\n if (options.datasetId !== undefined) {\n return [options.datasetId]\n }\n return options.datasetIds\n}\n\nfunction describeSelection(options: ReplayOptions): string {\n if (options.traceIds !== undefined) {\n return `${options.traceIds.length} traces`\n }\n if (options.limit !== undefined) {\n return `${options.limit} traces`\n }\n const datasetCount = options.datasetIds?.length ?? 1\n return datasetCount > 1\n ? `${datasetCount} datasets' traces`\n : \"dataset traces\"\n}\n\nfunction resolveTraceFunctionKey(registration: ReplayRegistration): string {\n const wrappedKey = (\n registration.fn as ReplayFunction & {\n _bitfabTraceFunctionKey?: string\n }\n )._bitfabTraceFunctionKey\n const key = registration.traceFunctionKey ?? wrappedKey\n if (key === undefined) {\n throw new BitfabError(\n \"Replay registry entry uses a plain function. Set traceFunctionKey to the key its production handler records.\",\n )\n }\n return key\n}\n\nasync function defaultReadFile(path: string): Promise<string> {\n const fs = await import(\"node:fs/promises\").catch(() => null)\n if (fs === null) {\n throw new BitfabError(\n \"--code-change requires a runtime that can read local files.\",\n )\n }\n return fs.readFile(path, \"utf8\")\n}\n\nasync function loadCodeChange(\n path: string | undefined,\n readFile: (path: string) => Promise<string>,\n): Promise<CodeChange | undefined> {\n if (path === undefined) {\n return undefined\n }\n const value = JSON.parse(await readFile(path)) as Partial<CodeChange>\n if (typeof value.description !== \"string\" || !Array.isArray(value.files)) {\n throw new BitfabError(\n `Invalid --code-change file '${path}': expected { description, files }.`,\n )\n }\n return { description: value.description, files: value.files }\n}\n\nfunction parseMetadataTag(raw: string): [string, string] {\n const separator = raw.indexOf(\"=\")\n const key = raw.slice(0, separator).trim()\n if (separator < 1 || key.length === 0) {\n throw new BitfabError(\n `Invalid --metadata '${raw}': expected a non-empty key=value pair.`,\n )\n }\n return [key, raw.slice(separator + 1)]\n}\n\nfunction parseParameter(raw: string): [string, unknown] {\n const separator = raw.indexOf(\"=\")\n const key = raw.slice(0, separator).trim()\n if (separator < 1 || key.length === 0) {\n throw new BitfabError(\n `Invalid --param '${raw}': expected a non-empty name=value pair.`,\n )\n }\n const value = raw.slice(separator + 1)\n try {\n return [key, JSON.parse(value) as unknown]\n } catch {\n return [key, value]\n }\n}\n\nasync function loadParameters(\n path: string | undefined,\n rawParameters: readonly string[],\n readFile: (path: string) => Promise<string>,\n): Promise<Record<string, unknown>> {\n let params: Record<string, unknown> = {}\n if (path !== undefined) {\n const value = JSON.parse(await readFile(path)) as unknown\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new BitfabError(\n `Invalid --params file '${path}': expected a JSON object.`,\n )\n }\n params = { ...(value as Record<string, unknown>) }\n }\n for (const raw of rawParameters) {\n const [key, value] = parseParameter(raw)\n params[key] = value\n }\n return params\n}\n\nfunction valuesEqual(left: unknown, right: unknown): boolean {\n try {\n return JSON.stringify(left) === JSON.stringify(right)\n } catch {\n return Object.is(left, right)\n }\n}\n\nfunction renderSummary(\n pipeline: string,\n result: ReplayResult<unknown>,\n stderr: (line: string) => void,\n): void {\n let same = 0\n let changed = 0\n let matched = 0\n let missed = 0\n let errors = 0\n let carriedOver = 0\n\n for (const item of result.items) {\n if (item.carriedOver === true) {\n carriedOver += 1\n continue\n }\n if (item.error !== null) {\n errors += 1\n continue\n }\n const equal = valuesEqual(item.result, item.originalOutput)\n if (item.ingestionType === \"seeded\") {\n if (equal) {\n matched += 1\n } else {\n missed += 1\n }\n } else if (equal) {\n same += 1\n } else {\n changed += 1\n }\n }\n\n stderr(\"\\n─── Summary ───\")\n stderr(` Pipeline: ${pipeline}`)\n stderr(` Replayed: ${result.items.length - carriedOver}`)\n if (carriedOver > 0) {\n stderr(` Carried over from the earlier run: ${carriedOver}`)\n }\n if (result.attempts > 1) {\n stderr(` Attempts: ${result.attempts}`)\n }\n if (same > 0 || changed > 0 || matched + missed === 0) {\n stderr(` Same: ${same}`)\n stderr(` Changed: ${changed}`)\n }\n if (matched > 0 || missed > 0) {\n stderr(` Matched expected: ${matched}`)\n stderr(` Missed expected: ${missed}`)\n }\n if (errors > 0) {\n stderr(` Errors: ${errors}`)\n }\n stderr(`\\n ${result.experimentUrl}`)\n}\n\nfunction countErroredItems(result: ReplayResult<unknown>): {\n errored: number\n replayed: number\n} {\n const replayed = result.items.filter((item) => item.carriedOver !== true)\n return {\n errored: replayed.filter((item) => item.error !== null).length,\n replayed: replayed.length,\n }\n}\n\nfunction renderDryRun(\n pipeline: string,\n result: ReplayResult<unknown>,\n stderr: (line: string) => void,\n): void {\n stderr(\"\\n─── Dry run ───\")\n stderr(` Pipeline: ${pipeline}`)\n stderr(` Resolved: ${result.items.length} (nothing was executed)`)\n for (const item of result.items) {\n stderr(`\\n ${item.originalTraceId}`)\n stderr(` args: ${safeJson(item.input)}`)\n }\n // Current servers record no experiment for a dry run; an older one still does.\n if (result.experimentUrl !== null) {\n stderr(`\\n ${result.experimentUrl}`)\n }\n}\n\nfunction safeJson(value: unknown): string {\n try {\n return JSON.stringify(value) ?? String(value)\n } catch {\n return String(value)\n }\n}\n\nfunction shellQuote(value: string): string {\n return /^[\\w@%+=:,./-]+$/.test(value)\n ? value\n : `'${value.replace(/'/g, `'\"'\"'`)}'`\n}\n\nfunction resumeCommand(\n registryPath: string | undefined,\n args: ReplayCliArgs,\n experimentId: string,\n): string {\n const carried = [\n ...(args.mock === undefined ? [] : [\"--mock\", args.mock]),\n ...(args.paramsPath === undefined ? [] : [\"--params\", args.paramsPath]),\n ...args.params.flatMap((param) => [\"--param\", param]),\n ]\n return [\n \"Resume with: bitfab-replay --registry\",\n registryPath === undefined ? \"<path>\" : shellQuote(registryPath),\n ...[args.pipeline, ...carried, \"--resume\", experimentId].map(shellQuote),\n ].join(\" \")\n}\n\nfunction reportReplayError(\n error: ReplayError,\n stderr: (line: string) => void,\n): void {\n for (const item of error.items as ReplayItem<unknown>[]) {\n stderr(\n `${item.originalTraceId}: ${String(item.traceError ?? item.replayError ?? item.error)}`,\n )\n }\n}\n\nconst API_KEY_LINE_PREFIX = \"@@bitfab:api-key \"\n\nconst PRINT_API_KEY_FLAG = \"--print-api-key\"\n\n/**\n * Run the SDK-owned replay command against a project-owned registry.\n *\n * The installed `bitfab-replay` executable calls this after loading the\n * registry passed through `--registry`. The SDK owns every common flag,\n * lifecycle callback, and output contract.\n */\nexport async function runReplayCli(\n registry: ReplayRegistry,\n argv: readonly string[] = typeof process === \"undefined\"\n ? []\n : process.argv.slice(2),\n io: ReplayCliIo = {},\n registryPath?: string,\n): Promise<ReplayResult<unknown> | null> {\n const stdout = io.stdout ?? console.log\n const stderr = io.stderr ?? console.error\n const printApiKey = argv.includes(PRINT_API_KEY_FLAG)\n const args = parseReplayCliArgs(\n registry,\n argv.filter((arg) => arg !== PRINT_API_KEY_FLAG),\n )\n if (printApiKey) {\n const client = registry[args.pipeline].client\n const apiKey = _resolvedApiKey(client)\n const serviceUrl = _serviceUrl(client)\n stdout(`${API_KEY_LINE_PREFIX}${JSON.stringify({ apiKey, serviceUrl })}`)\n return null\n }\n if (args.judgeAssertionsFlag) {\n warnJudgeAssertionsDeprecated(stderr)\n }\n const registration = registry[args.pipeline]\n const traceFunctionKey = resolveTraceFunctionKey(registration)\n const readFile = io.readFile ?? defaultReadFile\n const params = await loadParameters(args.paramsPath, args.params, readFile)\n const dynamicOptions = await registration.optionsFactory?.({ params })\n const registrationOptions: ReplayRegistryOptions = {\n ...registration.options,\n ...dynamicOptions,\n }\n const registrationDatasetIds =\n resolveRegistrationDatasetIds(registrationOptions)\n if (\n registrationOptions.traceIds !== undefined &&\n registrationDatasetIds !== undefined\n ) {\n throw new BitfabError(\n \"Replay registry options traceIds and datasetIds select different sources and cannot be used together.\",\n )\n }\n const selectedDatasetIds = args.datasetIds ?? registrationDatasetIds\n const codeChange = await loadCodeChange(args.codeChangePath, readFile)\n let startedExperimentId: string | undefined\n\n const options: ReplayOptions = {\n ...registrationOptions,\n ...(args.name === undefined ? {} : { name: args.name }),\n ...(args.notes === undefined ? {} : { notes: args.notes }),\n ...(args.metadata === undefined ? {} : { metadata: args.metadata }),\n ...(args.attempts === undefined ? {} : { attempts: args.attempts }),\n ...(args.maxConcurrency === undefined\n ? {}\n : { maxConcurrency: args.maxConcurrency }),\n ...(args.experimentGroupId === undefined\n ? {}\n : { experimentGroupId: args.experimentGroupId }),\n ...(args.experimentGroupName === undefined\n ? {}\n : { experimentGroupName: args.experimentGroupName }),\n datasetId: undefined,\n datasetIds: selectedDatasetIds,\n ...(args.graderIds === undefined ? {} : { graderIds: args.graderIds }),\n ...(args.onlyWithAssertions === undefined\n ? {}\n : { onlyWithAssertions: args.onlyWithAssertions }),\n ...(args.skipAssertionJudging === undefined\n ? {}\n : { skipAssertionJudging: args.skipAssertionJudging }),\n ...(args.mock === undefined ? {} : { mock: args.mock }),\n ...(args.dbBranch === undefined\n ? {}\n : {\n dbBranch:\n args.dbBranch &&\n registrationOptions.dbBranch !== undefined &&\n registrationOptions.dbBranch !== false\n ? registrationOptions.dbBranch\n : args.dbBranch,\n }),\n ...(args.noCodeChange === true\n ? { codeChangeDescription: null, codeChangeFiles: null }\n : {}),\n ...(args.dryRun === true ? { dryRun: true } : {}),\n ...(codeChange === undefined\n ? {}\n : {\n codeChangeDescription: codeChange.description,\n codeChangeFiles: codeChange.files,\n }),\n onItemStart: reportReplayProgress,\n onItemFinish: reportReplayProgress,\n onExperimentStart: ({ experimentId, experimentUrl }) => {\n startedExperimentId = experimentId\n stderr(`[replay] Experiment ${experimentId}: ${experimentUrl}`)\n },\n onInterrupt: ({ experimentId, signal, markedInterrupted }) => {\n stderr(\n `${describeReplayInterrupt(experimentId, markedInterrupted)} ${resumeCommand(registryPath, args, experimentId)}`,\n )\n process.exit(INTERRUPT_EXIT_CODES[signal])\n },\n }\n withCliFlags(options, args.flags)\n\n if (args.resume !== undefined) {\n Object.assign(options, RESUME_CLEARED_OPTIONS, {\n resume: args.resume,\n ...(args.force === true ? { force: true } : {}),\n })\n stderr(\n `[replay] Resuming experiment ${args.resume} of \"${traceFunctionKey}\"...`,\n )\n } else {\n if (args.traceIds !== undefined) {\n options.traceIds = args.traceIds\n options.datasetIds = undefined\n } else if (args.datasetIds !== undefined) {\n options.traceIds = undefined\n }\n\n const bound = args.limit ?? registrationOptions.limit\n options.limit = undefined\n\n if (options.traceIds !== undefined) {\n if (bound !== undefined) {\n options.traceIds = await boundTraceIds(\n registration.client,\n options.traceIds,\n bound,\n options.onlyWithAssertions === true,\n )\n }\n } else if (options.datasetIds !== undefined) {\n if (bound !== undefined) {\n const pinned = await pinnedDatasetMembers(\n registration.client,\n options.datasetIds,\n bound,\n options.onlyWithAssertions === true,\n )\n if (pinned !== undefined) {\n options.traceIds = pinned\n }\n }\n } else {\n options.limit = bound ?? 10\n }\n\n const attemptsSuffix =\n options.attempts !== undefined && options.attempts > 1\n ? ` (${options.attempts} attempts each)`\n : \"\"\n stderr(\n `[replay] ${args.dryRun === true ? \"Resolving inputs for\" : \"Replaying\"} ${describeSelection(options)} from \"${traceFunctionKey}\"${attemptsSuffix}...`,\n )\n }\n\n let result: ReplayResult<unknown>\n try {\n result = await registration.client.replay(\n traceFunctionKey,\n registration.fn,\n options,\n )\n } catch (error) {\n if (error instanceof ReplayError) {\n reportReplayError(error, stderr)\n }\n const experimentId =\n error instanceof ReplayError ? error.experimentId : startedExperimentId\n if (experimentId !== undefined) {\n stderr(resumeCommand(registryPath, args, experimentId))\n }\n throw error\n }\n if (args.dryRun === true) {\n renderDryRun(args.pipeline, result, stderr)\n } else {\n renderSummary(args.pipeline, result, stderr)\n }\n stdout(serializeReplayResult(result))\n if (result.items.length === 0) {\n throw new BitfabError(\n `No traces matched \"${traceFunctionKey}\", so nothing was replayed. Seed cases with --seed, or capture a trace first.`,\n )\n }\n if (args.failOnError === true) {\n const { errored, replayed } = countErroredItems(result)\n if (errored > 0) {\n throw new BitfabError(\n `[replay] ${errored} of ${replayed} ${args.dryRun === true ? \"resolved\" : \"replayed\"} items errored; exiting 1 because of --fail-on-error`,\n )\n }\n }\n return result\n}\n","import { register } from \"tsx/esm/api\"\nimport { BitfabError } from \"./errors.js\"\nimport { ReplayError } from \"./replay.js\"\nimport {\n ReplayCliHelp,\n type ReplayCliIo,\n type ReplayRegistry,\n validateReplayRegistry,\n} from \"./replayRegistry.js\"\n\nexport type RegistryImporter = (path: string) => Promise<unknown>\n\nlet isTypeScriptLoaderRegistered = false\n\nexport async function parseRegistryCommandArgs(\n argv: readonly string[],\n usage: string,\n): Promise<{ registryPath: string; commandArgs: string[] }> {\n if (\n argv.some((value) => value === \"--help\" || value === \"-h\") &&\n !argv.includes(\"--registry\")\n ) {\n throw new ReplayCliHelp(usage)\n }\n const registryFlagIndex = argv.indexOf(\"--registry\")\n const registryPath = argv[registryFlagIndex + 1]\n if (\n registryFlagIndex === -1 ||\n registryPath === undefined ||\n registryPath.startsWith(\"--\")\n ) {\n throw new BitfabError(usage)\n }\n\n const commandArgs = argv.filter(\n (_value, index) =>\n index !== registryFlagIndex && index !== registryFlagIndex + 1,\n )\n if (commandArgs.length === 0) {\n throw new BitfabError(usage)\n }\n const { resolve } = await import(\"node:path\")\n return { registryPath: resolve(registryPath), commandArgs }\n}\n\nexport async function importRegistry(path: string): Promise<unknown> {\n const { pathToFileURL } = await import(\"node:url\")\n if (!isTypeScriptLoaderRegistered) {\n register()\n isTypeScriptLoaderRegistered = true\n }\n try {\n return await import(pathToFileURL(path).href)\n } catch (error) {\n if (isModuleNotFoundError(error)) {\n throw new BitfabError(`${error.message}\\n\\n${registryImportHint(path)}`)\n }\n throw error\n }\n}\n\nfunction isModuleNotFoundError(error: unknown): error is Error {\n const code = (error as { code?: unknown } | null)?.code\n return (\n error instanceof Error &&\n (code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\")\n )\n}\n\nfunction registryImportHint(path: string): string {\n return [\n `The replay registry '${path}' could not load one of its imports.`,\n \"A registry belongs in the package that builds the traced function's call and already depends on everything it imports (its tools, clients, and config), and bitfab-replay runs from that package's directory.\",\n \"When the traced function lives in a lower-level library that an app package calls, put the registry in the app package and add @bitfab/sdk to it. Copying the app's dependencies into the library is the wrong fix.\",\n `See \"When the traced function needs the app's dependencies\" at https://docs.bitfab.ai/typescript-sdk.`,\n ].join(\"\\n\")\n}\n\nexport function resolveRegistry(\n moduleValue: unknown,\n path: string,\n): ReplayRegistry {\n if (typeof moduleValue !== \"object\" || moduleValue === null) {\n throw new BitfabError(\n `Replay registry '${path}' must export a default registry.`,\n )\n }\n\n const registry = (moduleValue as { default?: unknown }).default\n if (typeof registry !== \"object\" || registry === null) {\n throw new BitfabError(\n `Replay registry '${path}' must export a default registry.`,\n )\n }\n validateReplayRegistry(registry as ReplayRegistry)\n return registry as ReplayRegistry\n}\n\nexport async function runRegistryMain(\n run: () => Promise<unknown>,\n io: ReplayCliIo = {},\n): Promise<number> {\n const stdout = io.stdout ?? console.log\n const stderr = io.stderr ?? console.error\n try {\n await run()\n return 0\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n if (error instanceof ReplayCliHelp) {\n stdout(message)\n return 0\n }\n if (error instanceof ReplayError) {\n stderr(`Experiment ${error.experimentId}: ${error.experimentUrl}`)\n }\n stderr(message)\n return 1\n }\n}\n\nasync function isEntrypoint(moduleUrl: string): Promise<boolean> {\n const executablePath = process.argv[1]\n if (executablePath === undefined) {\n return false\n }\n const [{ realpath }, { fileURLToPath }] = await Promise.all([\n import(\"node:fs/promises\"),\n import(\"node:url\"),\n ])\n return (\n (await realpath(executablePath)) ===\n (await realpath(fileURLToPath(moduleUrl)))\n )\n}\n\nexport function runIfEntrypoint(\n moduleUrl: string,\n main: () => Promise<number>,\n): void {\n void isEntrypoint(moduleUrl).then((entrypoint) => {\n if (entrypoint) {\n void main().then((exitCode) => {\n process.exitCode = exitCode\n })\n }\n })\n}\n"],"mappings":"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","names":["traceIds","result","usage"]}