Skip to main content
Package: @bitfab/sdk. Dual ESM/CJS. Node.js ≥ 18 and modern browsers.

Module Exports

Constants

class Bitfab

new Bitfab(config: BitfabConfig)

captureEnabled

Effective capture state. Reading it resolves the API key lazily, including the BITFAB_API_KEY fallback. It is false when capture was explicitly disabled or no key resolves. Under strict: true, reading it without a key throws BitfabError instead.

withSpan

Wraps a function so that each invocation produces a span. Returns: a function with the same signature as the input. Semantics:
  • Returns a wrapper whether or not capture is on. With captureEnabled: false, the wrapper runs the function untraced. It sends nothing, except inside a replay item or the one call seedTrace runs
  • Span name defaults to the function’s qualified name, resolved per call: Order.process when the function runs as a method of an Order instance (or Order.build for a static method), process for a plain function, then traceFunctionKey when the function is anonymous. The raw fn.name still travels separately as function_name
  • Span type defaults to "custom"
  • testRunId links the span to a test run. If the span starts a trace, that trace is linked too
  • captureWhen: "nested" records the span only when another Bitfab span is active. Without a parent, the wrapped function runs normally. It does not create a root trace. The default is "always". An unknown value warns once and falls back to that default
  • Input arguments are serialized via superjson. This preserves type information
  • The return value is serialized as output, whether it is a plain value or a resolved Promise
  • A value that fails to serialize is stubbed instead of dropping the span. A warning notes it may not be replayable. A span whose encoded payload still exceeds the per-request byte ceiling ships anyway, with its largest fields stubbed first and its identifying fields stubbed last. This way an oversized span degrades instead of vanishing
  • SpanOptions.finalize?: (result) => unknown | Promise<unknown> records a serializable view of a non-serializable result, such as a live stream. The raw result is returned to the caller unchanged. await finalize(result) is recorded as the span output instead. It runs in the background. A throwing finalize records an error instead of crashing. It is ignored for async-generator results. Pair it with the exported finalizers.aiSdk for the Vercel AI SDK, or finalizers.readableStream
  • An async-generator span remains open until iteration completes, returns early, or throws. A nested span created inside the generator body inherits that span. To include spans created by the consumer in the same trace, wrap the controller that owns the iteration in an outer root span
  • A thrown error is recorded on the span, in the error and error_source: "code" fields. It is then re-thrown
  • Spans nest automatically, via AsyncLocalStorage on Node or a module-level stack as a browser fallback
  • Spans exist only where you create them. Nesting is automatic, but only between spans that exist. One wrapper around the outermost function records a single-node trace. See Instrumentation
  • withSpan() is the opt-in tracing surface. trace()/withTrace() and node()/withNode() are the opt-out surface. A withSpan() entered beneath an active subtree trace throws MixedTracingError. Configure a discovered call with node()/withNode() instead
  • The browser fallback does not isolate concurrent async chains. Promise.all with independent withSpan calls may see the wrong parent

trace / withTrace

Experimental automatic subtree roots. With a compatible @bitfab/transform adapter, every discovered first-party call beneath the root records its full inputs, output, and thrown error by default. Without a transform, only the normal rich root span is recorded. TraceOptions controls the root name and type. The root name defaults to the function’s qualified name, resolved per call (Pipeline.run for a decorated method), then the trace function key when the function is anonymous. It also controls maxDepth (default 30), maxSpans (default 500), qualified or simple exclude names, and includeWrappers (default false). An excluded call is omitted. Its captured descendants attach to the nearest captured parent instead. A subtree root entered beneath another subtree root starts its own independent trace while every enclosing root keeps recording. The nested root’s function and its whole subtree appear in each enclosing trace with separate span IDs and the shape that root would record alone, and each root applies its own maxDepth, maxSpans, exclude, and capture policy to its copy, while node() and withNode() configuration applies in every copy, including testRunId and the finalized output, with finalize running once per call. Framework integration spans attach inside the innermost trace only. The enclosing trace’s span for the nested root carries nested_trace_id, nested_trace_function_key, and nested_root_span_id. The nested root span carries enclosing_trace_id, enclosing_span_id, and enclosing_trace_function_key. Inside replay() or seedTrace() a nested root starts no trace of its own and the item’s trace records it as an ordinary descendant. Set mockOnReplayDefault: true on the trace options to make replay mocking the default for every automatically captured node. This applies under the default mock: "marked" strategy. A configured node with mockOnReplay: false overrides that default and runs live. The option defaults to false. It does not change mock: "all" behavior. A confirmed Studio capture policy can narrow rich content to selected function IDs, once the policy loads. No policy, or a failed initial policy request, leaves full capture enabled. node({ capture: false }) and exclude remain explicit source-level opt-outs. trace() and node() are the opt-out tracing surface. withSpan() is the opt-in surface. A subtree root entered beneath an active withSpan() throws MixedTracingError. So does a withSpan() entered beneath a subtree root. Framework integration spans never trip the check. The LangGraph, OpenAI Agents, and Vercel AI SDK wrappers open their spans on the surrounding surface, and the callback-based handlers emit spans directly. The root span that replay() and seedTrace() wrap around an undecorated callable belongs to neither surface, so a subtree root called from that callable nests beneath it.

span

Returns an optional standard ECMAScript method decorator that delegates to withSpan. Requirements and semantics:
  • Requires TypeScript 5.0 or newer, and a compiler or transpiler that supports the standard ECMAScript decorator transform
  • experimentalDecorators must be disabled or omitted. The legacy decorator transform is not supported
  • emitDecoratorMetadata must be disabled or omitted. It is not compatible with standard decorators
  • Supports instance, static, and private methods
  • Preserves the method receiver, arguments, return type, errors, nesting, and all SpanOptions
  • withSpan remains the recommended default on every TypeScript version. Use it for TypeScript 4.x, standalone functions, class fields, accessors, and legacy-decorator projects

node

Configures a transformed class method only when it is discovered beneath an enclosing trace() or withTrace() call. It never creates a span or trace by itself. It inherits the enclosing trace function key. Semantics:
  • Outside an active subtree trace, the method runs normally with no capture or replay behavior. Beneath an active withSpan() with no enclosing trace, it throws MixedTracingError instead. node() belongs to the opt-out surface, so it has no trace to configure there
  • capture: true (the default) keeps the call captured, with its inputs, output, and errors, even when a confirmed automatic capture policy has not selected it
  • name, type, testRunId, mockOnReplay, and finalize have the same behavior as their SpanOptions counterparts
  • Under a trace with mockOnReplayDefault: true, an omitted node policy inherits the trace default. Setting mockOnReplay: false on the node keeps it live instead, under the default "marked" replay strategy
  • capture: false omits the call. Its captured descendants attach to the nearest captured parent instead
  • capture: false with mockOnReplay: true throws BitfabError, because an uncaptured call has no recorded output
  • Requires a compatible @bitfab/transform integration to discover the method. Standard and legacy method decorator output are accepted when the transform runs before decorators are lowered

withNode

Function-oriented equivalent of node(). The wrapped function remains eligible for the automatic subtree transform. Without an active trace() or withTrace() call, it runs normally and never creates a span or trace. Beneath an active withSpan(), it throws MixedTracingError instead. The function must have a stable name (a referenced function binding or named function expression). Passing an anonymous inline function throws BitfabError, because the transform could not safely bind its configuration to one discovered call.

getFunction

Returns a BitfabFunction bound to traceFunctionKey.

wrapBAML

Framework integration → see BAML framework guide for examples.
Returns: a WrappedBamlFn, an async function with a .collector property set after each call. Throws:
  • BitfabError if form 1 is used without bamlClient in constructor
  • BitfabError if the method has no .name
Semantics:
  • If @boundaryml/baml is not installed, the method is called directly. .collector is null in that case
  • Otherwise, it creates a BAML Collector, calls the method through a tracked client, then:
    • Calls getCurrentSpan().setPrompt(...) with the rendered messages as JSON
    • Calls getCurrentSpan().addContext({ model, provider, inputTokens, outputTokens, durationMs })
  • The onCollector callback fires after each invocation. Errors in the callback are swallowed

getTrace

Returns a DetachedTrace handle for annotating a trace after its root span has closed, from any process or thread. Throws: BitfabError if traceId is not a canonical Bitfab trace ID. Semantics:
  • All methods on the returned handle block (return Promise<void>), resolving once the server has applied the change
  • When capture is off outside a replay item or a seedTrace call, methods return Promise.resolve() immediately
  • The server returns 404 if no trace exists with that ID. The returned promise rejects with a BitfabError in that case, rather than logging it

getTraceSpan

Fetches one persisted span without loading its trace. traceId is the canonical Bitfab trace ID. lookup is either { id }, using the span’s Bitfab ID, or { name, occurrence? }. occurrence is "first" | "last" | number. It defaults to "last". Numeric occurrences are zero-based in start-time order. Returns null when no trace or span matches.

datasets

Dataset operations for the authenticated organization. These are the same operations the Bitfab MCP tools expose to a coding agent.
  • save is an upsert keyed on (traceFunctionKey, name). created is true for a new dataset and false when an existing one was updated. An omitted description leaves the existing one untouched.
  • list takes an optional traceFunctionKey. Without it, every dataset in the organization is returned.
  • Dataset carries id, traceFunctionKey, name, description, traceCount, graders ({ id, name }[]), createdAt, and updatedAt.
  • listTraces returns { datasetId, traceIds }, the same membership a replay with datasetId selects.
  • addTraces and addGraders accept 1 to 100 ids. They report partial acceptance instead of rejecting the whole call. An id outside the organization, or under another trace function, comes back in skippedTraceIds or skippedGraderIds. An id already present comes back in alreadyPresentTraceIds or alreadyAssignedGraderIds.
  • removeTraces never deletes a trace, only its membership in the dataset. Ids that were not members come back in notPresentTraceIds. removeGraders reports notAssignedGraderIds the same way.
  • rerunGraders re-scores every trace in the dataset. graderIds defaults to every assigned grader. An unassigned id rejects the call. It waits up to timeoutMs (default 90,000), polling every pollIntervalMs (default 1,000). It returns the last run seen. Pass wait: false to return as soon as the run is queued instead. A request matching an in-flight run joins it, reported as joinedExisting: true.
  • GraderRerun has status (pending | running | completed | errored), graderIds, progress ({ completedTraces, totalTraces, graderCount } while running), result ({ tracesGraded, gradersRun } when completed), and error.
  • A dataset id from another organization rejects with a 404 BitfabError.

traces

An assertion says what should happen when a trace is replayed.
  • getAssertions returns { assertions, inheritedFrom }. Attach assertions to the ORIGINAL trace. Reading a replay that has none of its own returns the nearest ancestor’s assertions instead, resolved through the replay lineage. That ancestor trace is named in inheritedFrom. inheritedFrom is null when the assertions are the trace’s own. Writing assertions onto a replay trace pins them to one run instead of to the case, so write them on the original.
  • The common case is to omit targetOnEvaluatedTrace. Omitting it checks the whole trace. Set it to narrow the check instead, to either { kind: "output" } or { kind: "span", name, occurrence? }.
  • Targets are span names, never span ids, because an id captured on the original resolves to nothing on the replay. occurrence accepts "first", "last" (the default), or a 0-based index for a span the trace calls more than once.
  • assertion, passCriteria, and failCriteria are the same three fields save_grader takes, so an assertion that proves out across many traces is promoted into a grader by copying them.
  • Passing an entry’s id edits that assertion. Omitting it adds a new one instead. This way two callers adding different assertions to one trace never overwrite each other. archiveAssertions hides rows from every read. It keeps them for audit.
  • saveAssertionssource defaults to "agent" when omitted, recording that a coding agent, not a person, authored the assertion.
  • saveAssertions writes one trace. saveAssertionsAll takes one update per trace and writes them all in a single request, so a publisher covering hundreds of traces makes one call. The singular delegates to it, so both go through the same route.
  • The server writes the whole batch in one transaction, so a rejected batch writes nothing and there is no half-saved state to reconcile. Results come back as one flat array covering every trace, each row carrying its own traceId. A batch takes up to 500 traces, up to 50 assertions per trace, and at most 1000 assertions in total. Passing an empty updates array writes nothing and sends no request.

labels

Writes the same pass/fail verdicts the save_agent_labels MCP tool writes, from inside a replay process rather than a coding-agent session.
  • Key a direct verdict by traceId. Key a replay verdict by originalTraceId plus the top-level testRunId it ran under, adding attempt when the experiment ran each trace more than once.
  • Omit assertionId and the verdict scores the whole trace. Pass one and the verdict scores that single assertion. A per-assertion verdict and a whole-trace verdict can both sit on the same trace.
  • { skip: true } withholds a verdict. { archive: true } clears a previous one. Skip is the right answer for an attempt that crashed, was punctured, or fell back to a schema default. It is also right for an assertion whose target could not be resolved. A FAIL in those cases would read as a behavior regression, rather than a check that never ran.
  • LabelOutcome.key echoes the id you addressed the verdict by, so a caller can verify persistence without ever holding a server-generated replay trace id.
saveHuman and saveHumanAll write the verdicts save_human_labels writes over MCP. They are validated on write with no approval step, so they satisfy search_traces validated: true immediately. Use them only when a human decided the verdict, such as capturing a known production bug as a regression case. An agent’s own first-pass guesses go through save so they keep the approve-or-edit loop. Approving an existing agent verdict is not on this surface at all: that happens in Studio, by a person. Like saveAll, the batch is all-or-nothing. A trace outside the organization, a repeated target, or an assertionId that is not active on its trace rejects the whole call and writes nothing, so a thrown error never leaves part of the batch committed.
get and getAll read verdicts back. Each trace carries its effective verdict plus one row per scored assertion, keyed by the same assertionId the write used, so a per-assertion verdict is verifiable per assertion rather than as a passed/failed tally. get returns null when the trace is not in this organization. One getAll call accepts up to 100 ids.

graders

Reads the individual verdicts each automated grader recorded, one row per grader per trace, the same breakdown the get_grader_labels MCP tool returns. Pass traceIds to see every grader’s verdict on those traces, graderId to see one grader’s most recent verdicts across traces, or both to narrow. Passing neither raises. This is the per-grader detail that labels.get does not carry, since that returns one grader-agnostic verdict per trace.

Replay registry and command

defineReplayRegistry preserves the registry’s inferred TypeScript type. Each entry contains a client, the exact production fn, an optional traceFunctionKey for a plain handler root, static options, and an optional optionsFactory({ params }). A withSpan-wrapped function supplies its key automatically. The factory receives JSON values from --params and --param. It can construct executable options such as mockOverride. Direct parameters override file values. Registry options exclude lifecycle callbacks, because the installed command owns progress reporting. The package installs bitfab-replay. Run bitfab-replay --registry <path> <pipeline> [options]. The command loads .ts registries directly. Command values override overlapping scalar defaults without removing unrelated executable options. The command wires both lifecycle callbacks to reportReplayProgress. It writes the human summary to stderr. It writes the full serialized ReplayResult to stdout. A run whose selection matched no traces exits non-zero, rather than reporting a clean run of zero items. The stderr summary counts items by their source’s ingestionType. A captured item’s recorded output is a previous run, so it counts as Same or Changed. A seeded item’s is the value the case expected, so it counts as Matched expected or Missed expected. One run can replay both. Both pairs print when both are present. An item with an error counts under Errors instead. --seed <cases.jsonl> seeds cases through the same registration instead of replaying. Each line is a JSON object with an input array plus optional expected, metadata, and sessionId. A JSON array of those objects works too. The registration already holds the client, the callable, and the trace function key. Because of that, a seeded case is written against the exact function the later replay selects. A case that cannot supply the function’s required arguments is rejected at seed time instead. Add --run to run each case once through the registered function and record the execution, rather than writing the case directly. The output is then what the run produced, so a case carrying expected is rejected. The registration’s adaptInputs is a replay hook. It is not applied at seed time, so a seeded trace is never adapted twice.

seedTrace

Two forms, chosen by the second argument. Pass a case to write a trace without running anything. Pass a function to run it once and record that execution. Writes a replayable trace from a case without running anything. Returns the trace ID for use with replay({ traceIds: [...] }). The recorded root span carries input as its input and expected as its output, so replay reports each item against the value you expected rather than against a previous run. name is the trace’s title and a searchable field. Put the case’s own label there, such as a ticket id or a dataset row name, so the seeded trace can be found by it. spanName labels the root span only. Use it to turn a corpus you already hold, such as a dataset export, a spreadsheet, or hand-written cases, into traces. Passing fn checks the case against the function’s required argument count, so a case that could never run fails here instead of at replay. Omit it when the seeding script cannot import the callable. A trace seeded from a case has no child spans and no database pin. Because of that, replay mocking has nothing recorded to substitute. dbBranch refuses it for the same reason. Supply mockOverride at replay time for calls that must not run.

Seeding by running once

Runs fn once. Records the execution as an original trace. Returns the trace ID for use with replay({ traceIds: [...] }). Capture stays off. This records exactly one call, with the same semantics capture-on would give it: a root span, a first-party subtree bounded by the enclosing withTrace root, and no mocking. The recorded input is the real call. The output is what the run produced. There is no expected to pass as a result. The trace lands under traceFunctionKey, with ingestion_type: seeded. replay selects it. Each replay links back to it as originalTraceId. fn resolves exactly as it does for replay. A withSpan-wrapped function records under its own key, and that key must match traceFunctionKey. A plain callable is wrapped under the key given here instead. An exception is recorded on the root span. The trace still persists. The exception is re-thrown. A call that records nothing, because no API key resolved, rejects rather than returning an ID replay could never find. metadata is stored on the trace. It is handed to a later replay’s adaptInputs hook as ctx.metadata. This way a case’s provenance rides with the trace, instead of through the recorded inputs. If the traced function also emits its own trace through an integration, the caller’s metadata is merged onto that export and wins on any shared key, so the provenance recorded here is what replay reads back. Unlike a trace seeded from a case, this one has a full recorded subtree, so replay mocking works as it does for a captured trace. It still carries no database pin, so dbBranch refuses it.

reseedTrace

Re-seeds one trace. Reads the trace’s recorded root inputs, name, session, and metadata from Bitfab, runs fn once exactly as seedTrace would, then asks Bitfab to adopt that run under traceId. The trace keeps its id, labels, assertions, dataset membership, name, and metadata. The previous run is kept as its own trace, previousRunTraceId, with reseedOfTraceId pointing back at traceId. Nothing is mocked and no test run is created. fn resolves as it does for replay and seedTrace, and traceFunctionKey must match the trace’s own key. A run that throws is recorded but not adopted, and the error is re-thrown. Bitfab rejects a run from another function, one that already belongs to a dataset, or a trace that is itself a previous run. Graders on the datasets holding the trace are re-queued, and default replay selection skips previous runs.

reseedFromRegistry

Re-seeds each trace through an already-registered pipeline, using the registration’s client, callable, and trace function key. The installed bitfab-seed --registry <path> <pipeline> --from-trace <id>[,<id>...] command calls it; bitfab-seed --cases <cases.jsonl> [--run] is the home of what bitfab-replay --seed does, and that spelling still works.

seedFromRegistry

Seeds cases through an already-registered pipeline, using its client, callable, and trace function key. Returns { pipeline, traceFunctionKey, traceIds }. With { run: true }, each case is run once through the registered function. The execution is recorded instead of the case, so a case carrying expected is rejected.

registerMockOverride

Registers an instance-scoped override for every subsequent replay. The keyed form invokes a resolver only for child spans with that trace function key. When its second argument is { match, value }, both the key and matcher must match. A global resolver can route on ctx.node.traceFunctionKey. Return NO_MOCK_OVERRIDE to continue to the next override and then the replay’s base mock strategy. A synchronous span requires a synchronous resolver result, including a synchronous sentinel decline. A mixed-tree global resolver can return the sentinel synchronously for sync keys, and a Promise for async keys. undefined and null remain valid mocked outputs. Per-call overrides take precedence over registered overrides.

clearMockOverrides

Removes every override registered on this client.

replay

Returns: { items, testRunId, testRunUrl, attempts }. See ReplayResult. Notes:
  • A trace replays only when its root span has serializable inputs. It can also replay when it was instrumented through a framework handler. That handler’s recorded root input is serializable. If the original inputs were stubbed as non-serializable at capture time, the trace cannot be replayed
  • fn may already be a withSpan-wrapped function. That function carries its trace function key and is used as-is. fn may instead be a plain callable. replay() wraps that callable under the key automatically. Either way, new spans link to the test run via async context. Don’t wrap an already-wrapped function in a fresh closure. The closure has no trace function key, so replay() wraps the closure as the root, and the inner wrapped function records a second span, nesting a duplicate
  • Inputs are deserialized from historical spans and passed positionally
  • When codeChangeFiles is omitted, replay() auto-captures the code change. By default it captures a rename-aware working-tree diff against the nearest trunk ref, tried in this order: origin/HEAD, origin/main, origin/master, main, master, or HEAD when none exist. The diff is bounded to 60 files, 500,000 bytes per file, and 2,000,000 bytes total. Set BITFAB_CODE_CHANGE_PATH to point at a JSON file with description and/or files instead, and that file wins over the git diff. BITFAB_CODE_CHANGE_BASE forces which trunk ref to diff against. BITFAB_DISABLE_CODE_CHANGE_CAPTURE opts the whole process out of auto-capture. Passing codeChangeFiles: null opts out one call instead
  • When the Bitfab plugin sets BITFAB_REPLAY_RESULT_PATH, replay() writes the full ReplayResult, using serializeReplayResult, to that file after the run completes. A caller does not have to hand-parse stdout as a result. A write failure is logged. It never throws

call

Executes a server-configured BAML function locally using envVars. Throws: BitfabError on lookup failure or execution error.

Framework Integrations

Handlers returned by these methods are framework-native adapters. They plug into each framework’s own callback, processor, or hook surface, and emit Bitfab spans automatically. They reuse the owning Bitfab client’s lazy OTel worker, so client.close() releases the withSpan and framework transport together. Directly constructed handlers own their transport and expose close(timeoutMs?) instead. For usage examples and semantics, see the per-framework guides. Signatures here are canonical.

getLangGraphCallbackHandler

Returns a duck-typed LangChain/LangGraph callback handler. Pass it in config.callbacks when invoking a graph or chain. Root framework invocations are registered immediately as pending external traces. They are completed when the root callback ends. The handler-created root is replayable from the framework input, so a separate withSpan root is only needed for meaningful surrounding application work. See LangGraph framework guide. Aliased as getLangChainCallbackHandler(traceFunctionKey) for plain LangChain projects. The returned handler and behavior are identical. The handler class is also exported as BitfabLangChainCallbackHandler.

getLangGraphIntegration

Experimental (alpha). Returns the LangGraph integration. wrapTools(tools) wraps each tool’s public invoke() boundary. createInvoker(graph) returns (input, config?) => output. It adds callbackHandler through LangGraph’s public withConfig() API. It preserves invocation-time config and existing callbacks. It records only input as the replayable root input. Use the lower-level callbackHandler and wrapInvoke(fn) when meaningful application work around the graph invocation belongs inside the trace. Integration-managed tools are marked for replay mocking by default. Recorded ToolMessage and Command results are reconstructed with the current tool-call ID. An expected tool output that is missing during replay fails closed, instead of running the live tool. Calls are matched by tool name and occurrence order, so repeated concurrent calls to the same tool are not yet recommended. Install @langchain/core and @langchain/langgraph. Both are optional SDK peers. See the LangGraph framework guide and current limitations.

getOpenAiTracingProcessor

Returns a processor to register with @openai/agentsaddTraceProcessor. This keeps the SDK’s default OpenAI exporter. setTraceProcessors replaces it instead. The processor captures agent internals. Pair it with getOpenAiAgentHandler for a replayable root. See OpenAI Agents framework guide.

getOpenAiAgentHandler

Returns a handler whose wrapRun(agent, input, options?) is a drop-in for @openai/agentsrun(). It records a keyed, replayable root span carrying the run input. The tracing processor’s spans nest underneath it. Called from inside an already-active span, it runs run() directly instead of opening a second root. Pass { stream: true } to options for a streamed run instead. The result is handed back immediately. The final output is recorded on the span once the stream drains. First-byte latency is untouched as a result. See OpenAI Agents framework guide.

getClaudeAgentHandler

Returns a handler exposing instrumentOptions(options), wrapResponse(stream, opts?), and wrapQuery(stream, opts?) for the Claude Agent SDK. Wrap query()’s async iterator with wrapQuery. wrapResponse exists for naming symmetry with the Python SDK’s wrapper around ClaudeSDKClient.receiveResponse(). TypeScript has no equivalent of that method to wrap directly. Pass { input: prompt } to the wrap call to record a replayable root span. See Claude Agent SDK framework guide.

getVercelAiMiddleware

Returns a Vercel AI SDK language model middleware. Pass it to the AI SDK’s wrapLanguageModel, then use the wrapped model with generateText / streamText / generateObject / streamObject. Every call is captured as a keyed llm span carrying the call parameters as input. Streaming is captured without disturbing the live stream. See Vercel AI SDK framework guide. The middleware’s structural types are exported for callers that wrap or inspect it. All three are open ([key: string]: unknown), so provider-specific fields pass through untouched:
BitfabVercelAiHandler is the class the middleware is built from, exported for direct construction with { traceFunctionKey, withSpan }.

wrapBAML

See the BAML framework guide for examples. Full signature under wrapBAML above.

class BitfabFunction

Fluent wrapper binding a traceFunctionKey. Obtained via client.getFunction(key).

withSpan

Delegates to client.withSpan(boundKey, optionsOrFn, maybeFn).

span

Returns an optional TypeScript 5.0+ standard method decorator bound to this handle’s trace function key. It is equivalent to client.span(boundKey, options). The same standard-transform requirements as Bitfab#span apply. withSpan remains the recommended default. It is required for TypeScript 4.x projects and non-method callables.

getVercelAiMiddleware

Delegates to client.getVercelAiMiddleware(boundKey), reusing the bound key so a withSpan root and the middleware share it. See Nesting with core tracing.

getClaudeAgentHandler

Delegates to client.getClaudeAgentHandler(boundKey), reusing the bound key so a withSpan root and the handler share it. See Nesting with core tracing.

getLangGraphCallbackHandler

Delegates to client.getLangGraphCallbackHandler(boundKey), reusing the bound key so a withSpan root and the handler share it. See Nesting with core tracing.

getLangChainCallbackHandler

Alias of getLangGraphCallbackHandler. LangChain and LangGraph share one callback system.

getLangGraphIntegration

Experimental (alpha). Delegates to client.getLangGraphIntegration(boundKey, options). Pass its wrapped tools to the graph, then call createInvoker(graph) to get the callback-configured, replayable entry point.

wrapBAML

Identical signature and semantics to Bitfab#wrapBAML. Unlike the getXHandler() methods above, it does not use the bound key. It opens no span of its own. It enriches the current span instead, so call it inside a function wrapped by this handle’s withSpan.

class BitfabError

Extends Error.
Thrown for SDK-originated failures, such as a missing function, missing prompt, or misconfiguration. Also thrown for a request that reaches the server and comes back with a non-2xx response. status is the HTTP status code, present only on that non-2xx response failure. It is absent for network failures, timeouts, and an error returned inside an otherwise-successful response body. retryAfterMs carries the server’s Retry-After header in milliseconds, when it sent one. It is present only alongside status. Never thrown for transport errors on withSpan paths. Those are swallowed.

class MixedTracingError

Extends Error.
Thrown when opt-in tracing (withSpan, span) and opt-out tracing (withTrace, trace, node, withNode) meet in one call stack. The message names which surface was entered inside which and how to resolve it. Unlike other tracing setup failures, it is not swallowed. The wrapped function does not run untraced, because a wiring mistake the check exists to report would otherwise be invisible.

class BitfabLangGraphCallbackHandler

Duck-types LangChain’s callback handler interface without importing @langchain/core. Obtained via client.getLangGraphCallbackHandler(key). No direct instantiation needed for normal use. It records chain, LLM, tool, and retriever roots as pending traces on start, then completes them on root end. Full callback surface documented in LangGraph framework guide.

class BitfabOpenAITracingProcessor

Implements the OpenAI Agents SDK TracingProcessor interface. Obtained via client.getOpenAiTracingProcessor(). No direct instantiation needed for normal use. See OpenAI Agents framework guide.

class BitfabOpenAIAgentHandler

Run wrapper for the OpenAI Agents SDK. Obtained via client.getOpenAiAgentHandler(key). Exposes wrapRun(agent, input, options?), a drop-in for run() that records a keyed, replayable root span. See OpenAI Agents framework guide.

class BitfabClaudeAgentHandler

Handler for the Claude Agent SDK. Obtained via client.getClaudeAgentHandler(key). Exposes instrumentOptions, wrapResponse, and wrapQuery. See Claude Agent SDK framework guide for method signatures and usage.

Functions

finalizers

Prebuilt SpanOptions.finalize helpers. aiSdk awaits the Vercel AI SDK’s text, usage, finish reason, tool calls, and tool results, without consuming its live stream. It records { text, usage, finishReason, toolCalls, toolResults }. The recorded usage prefers the result’s totalUsage over its usage field, when both resolve. Rejected or absent fields become undefined. readableStream tees a stream. It passes the caller’s live branch to onLive. It records the other branch as chunks. Because a ReadableStream is single-consumer, the caller must use the branch received by onLive.

getCurrentSpan()

Returns the innermost active span. Outside a span context, returns a no-op whose traceId is "".

getCurrentTrace()

Returns a handle to the active trace. Outside a span context, returns a no-op.

flushTraces(timeoutMs?: number)

Forces the private OpenTelemetry batch processor to export pending spans. Also waits for any remaining mutation requests. Both share one total deadline. Default timeoutMs: 5000. Use before process.exit() in short-lived scripts. Returns true when queued exports completed successfully within the deadline, or false when delivery failed or the flush timed out.

Bitfab.close(timeoutMs?: number)

Flushes pending requests. Permanently shuts down this client’s private OTel transport. Both happen within one total deadline (default 30000). Idempotent. Returns false if delivery or shutdown misses the deadline. Use it when a long-running process creates transient clients. Shared clients may remain open until the process-wide exit hook runs instead.

Transport environment variables

Commit ref environment variables

CommitRef

The commit the traced code was running at, sent as commit_ref on every root trace completion. sha is the commit. branch is the checked-out branch, or null when detached or unknown. dirty is true when the working tree had uncommitted or untracked changes, false when it was clean, and null when the SDK could not tell, which is always the case when the ref came from environment variables rather than git. remote is the origin URL reduced to host/owner/repo with any credentials removed, so a CI checkout token never reaches the trace. root_sha is the repository’s first commit, so two checkouts of the same repository match even without a remote. Resolution order is BITFAB_COMMIT_SHA, then the deploy platform’s build variables, then git in the process’s working directory (see Commit ref environment variables). Environment resolution is synchronous and free. The git path runs once per process in a child process off the event loop, with a two second timeout per command, so it never sits on the code path that ran the traced function. A trace that completes before it lands ships without a commit_ref, and a process with neither variables nor a repository never sends one. The result, including a negative one, is memoized for the life of the process. Set BITFAB_DISABLE_COMMIT_REF to opt the process out entirely. On the wire the field is root_sha. In a browser bundle neither environment variables nor git exist, so no commit_ref is sent.

Interfaces

BitfabConfig

See constructor table.

SpanOptions

NodeOptions is Omit<SpanOptions, "captureWhen"> & { capture?: boolean }. capture defaults to true. mockOnReplay may only select a node whose capture remains enabled.

CaptureWhen

SpanType

CurrentSpan

CurrentTrace

DetachedTrace

Returned by client.getTrace(traceId), where traceId is the canonical Bitfab trace ID. Methods have the same semantics as CurrentTrace, but send to the server immediately. They block until the server responds. They reject if the update is refused. When capture is off outside a replay item or a seedTrace call, all methods return Promise.resolve() instead.

CapturedSpan

Returned by getTraceSpan. SpanLookup is { id: string } | { name: string; occurrence?: "first" | "last" | number }.

WrapBAMLOptions

WrappedBamlFn<TArgs, TReturn>

collector is null before the first call or when @boundaryml/baml is unavailable. After each successful call, it holds the BAML Collector instance from that invocation.

ReplayOptions

onItemStart fires when a worker begins processing an item, before replay loads its inputs, prepares mocks or a database branch, or invokes customer code. Pair it with onItemFinish. onItemFinish fires exactly once per item as it finishes, in completion order rather than input order. Together they distinguish queued items from in-flight items whose callback has not returned. Both callbacks always carry the item. onItemFinish never represents whole-run completion. Replay doesn’t know pass/fail at finish time, because verdicts are assigned later. The totals only split ran-ok from errored as a result. A throwing lifecycle callback never crashes the run. The deprecated onProgress callback receives the same per-item finish events, plus its legacy item-less terminal complete event. It is ignored when onItemFinish is also provided. The SDK exports a ready-made reporter, reportReplayProgress, that accepts either lifecycle event. Pass it as both onItemStart and onItemFinish to write lifecycle events to stderr. The Bitfab plugin uses start events to identify in-flight traces in its liveness heartbeat. It uses finish events to write per-item result files.

getCurrentReplayBranch()

Call it inside the replayed function to get the branch resolved for the item currently running. Each bounded replay worker provisions its own branch, so maxConcurrency also bounds live branches. With attempts set, each attempt resolves its own branch too. It returns null outside a replay item, and for an item whose source trace carried no DB snapshot reference. That is the fallback path. Use it like this: const url = branch?.databaseUrl ?? process.env.DATABASE_URL. The value object is immutable and per item, built from the replay context, so parallel items each see their own branch. Reading databaseUrl marks the trace as having used the branch, reported as accessed. The other fields inspect the branch without exposing the connection string. They deliberately do not mark the branch as accessed. That also means the URL does not appear in JSON.stringify(branch). Every field the service puts on the lease is copied onto the branch, so one added server-side is readable before you upgrade the SDK. It just won’t be in the type yet. databaseUrl is the sole exception. It is the credential. It is also the only member that may mark the branch as accessed. dbBranch tunes the branch itself: { minCu: 2, maxCu: 2, warmupSql: "SELECT 1;" }. Setting minCu equal to maxCu pins the compute, so items stay comparable. warmupSql runs inside the branch’s readiness check, so warm-up time is never charged to the replayed call. Invalid warm-up SQL fails the branch rather than quietly handing back a cold one. All fields are optional. dbBranch: true enables branching. It leaves the mirror’s own defaults in place.

ReplayResult<T>

traceOutline and originalTraceOutline are the replayed and the original trace’s span trees with no inputs or outputs: each span’s name, type, nesting, order, duration, tokens, model, errors, and whether it was mocked. The server builds both when the run completes, so they are null on progress items, on items whose replay produced no trace (traceOutline only), and against older servers. They exist for grading. Compare the two trees to tell whether a replay reached its output by the same path. That means the same tool calls in the same order, with no new child-span errors and no mocked span that used to run real code.
Unprefixed fields describe this replay. Anything describing the trace being replayed carries the original prefix. A trace error means the replayed function started and threw. A replay error means Bitfab could not invoke it, for example because database warmup, input loading, or mock preparation failed. The original thrown value is preserved in the corresponding field. error remains its string message, for compatibility and JSON output. If database branch resolution fails, replayError is a DbBranchReplayError with code, message, originalTraceId, and an optional cause. Resolver codes such as branch_create_failed, snapshot_from_replaced_origin, invalid_snapshot_ref, seeded_trace_has_no_snapshot (the source was seeded, so it pinned no database instant), and internal_error therefore remain available in memory, progress events, result files, and ReplayError.items. HTTP, timeout, and network failures while requesting a lease use lease_request_failed, with the original client exception as cause. If trace delivery or test-run finalization fails after items have settled, replay() throws ReplayError. It exposes items, testRunId, testRunUrl, and cause, so callers do not lose the individual failures:

serializeReplayResult

Returns indented JSON while preserving structured fields from traceError and replayError, including DbBranchReplayError.code, originalTraceId, and nested cause. Use it for direct-run stdout instead of raw JSON.stringify. Raw JSON.stringify reduces JavaScript Error objects to {}.

AllowedEnvVars

Only OPENAI_API_KEY is currently allowed.

ActiveSpanContext

Passed to the OpenAI tracing processor’s span-linking hook.

Mock override types

SpanNodeMeta is a span’s structural identity during replay. It carries no output payload, so matching runs on metadata only. spanName resolves as options.name ?? fn.name ?? traceFunctionKey. originalSpanId is this span’s id in the replayed trace, absent when the live span has no recorded counterpart, such as one the changed code newly introduced. MockOverrideCtx.inputs is the live arguments the changed code passed on this run. getOriginalOutput() returns the span’s recorded output, memoized per replay item. It rejects when the span has no recorded counterpart. A synchronous span requires a synchronous resolver result, including a synchronous NO_MOCK_OVERRIDE. null and undefined are legitimate mocked outputs, so returning either substitutes that value rather than declining. Decline with NO_MOCK_OVERRIDE.

Replay supporting types

AdaptContext.metadata is the original trace’s stored metadata, what seedTrace or getCurrentTrace().setMetadata put on it. It is requested from the server only when an adapter is registered. When the traced function also emits its own trace through an integration that exports trace metadata, the caller’s metadata is merged with that export and wins on any shared key, so an integration no longer replaces the provenance the caller stored. Keys the integration set are kept alongside it, so this can carry keys the caller never wrote. Requires v0.52.3 or later.

Replay registry types

Lifecycle callbacks are excluded from ReplayRegistryOptions because the installed command owns progress reporting. ReplayRegistryContext.params holds the values supplied through --params and repeated --param name=value.

Seed option types

The two seedTrace overloads. SeedCaseOptions writes a trace without running anything. SeedRunOptions runs the function once and records it. spanName and spanType label and type the root span the case form writes. They have no run-form equivalent, because the executed function names its own root span.

Database snapshot types

DbSnapshotConfig optionally pins the provider at capture time. It is not required. The provider is otherwise resolved at replay time. See Database branching.

CaptureSurface

Two surfaces can own an active span. withSpan / span produce "opt-in", while trace / withTrace / node / withNode produce "opt-out". Entering one beneath the other throws MixedTracingError before the inner function runs. Framework-managed spans and replay/seed root wrappers are neutral and may contain either surface.

Error Behavior Summary

Module Resolution

  • Node.js ESM: dist/index.js
  • Node.js CJS: dist/index.cjs
  • Browser: works, but AsyncLocalStorage-dependent features degrade (see withSpan semantics)