> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bitfab.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK Reference

> Pure API reference for the @bitfab/sdk npm package.

Package: `@bitfab/sdk`. Dual ESM/CJS. Node.js ≥ 18 and modern browsers.

## Module Exports

```typescript theme={null}
// Values
export { Bitfab, BitfabError, BitfabFunction, getCurrentSpan, getCurrentTrace, DetachedTrace } from "@bitfab/sdk"
export { BitfabOpenAITracingProcessor } from "@bitfab/sdk"
export { flushTraces } from "@bitfab/sdk"
export { reportReplayProgress } from "@bitfab/sdk"
export { __version__, DEFAULT_SERVICE_URL, BITFAB_PROGRESS_PREFIX } from "@bitfab/sdk"

// Types
export type {
  AllowedEnvVars,
  BamlExecutionResult,
  BitfabConfig,
  CapturedSpan,
  CurrentSpan,
  CurrentTrace,
  ProviderDefinition,
  ReplayItem,
  ReplayOptions,
  ReplayResult,
  SpanOptions,
  SpanLookup,
  SpanOccurrence,
  SpanType,
  ActiveSpanContext,
  DetachedTrace,
  TraceResponse,
  TracingProcessor,
  WrapBAMLOptions,
  WrappedBamlFn,
} from "@bitfab/sdk"
```

## Constants

| Export                | Type     | Value                   |
| --------------------- | -------- | ----------------------- |
| `DEFAULT_SERVICE_URL` | `string` | `"https://bitfab.ai"`   |
| `__version__`         | `string` | Current package version |

## `class Bitfab`

### `new Bitfab(config: BitfabConfig)`

| Param               | Type                  | Default               | Description                                                                |
| ------------------- | --------------------- | --------------------- | -------------------------------------------------------------------------- |
| `config.apiKey`     | `string \| undefined` | --                    | API key. Empty/whitespace/undefined disables tracing with a `console.warn` |
| `config.serviceUrl` | `string`              | `"https://bitfab.ai"` | Base URL for Bitfab API                                                    |
| `config.timeout`    | `number`              | `120000`              | HTTP request timeout (ms)                                                  |
| `config.envVars`    | `AllowedEnvVars`      | `{}`                  | LLM provider keys for `call()` (only `OPENAI_API_KEY`)                     |
| `config.enabled`    | `boolean`             | `true`                | When `false`, `withSpan` returns the function unwrapped                    |
| `config.bamlClient` | `unknown`             | `null`                | Generated BAML client (for `wrapBAML()` without explicit client arg)       |

### `withSpan`

```typescript theme={null}
withSpan<TArgs extends unknown[], TReturn>(
  traceFunctionKey: string,
  optionsOrFn: SpanOptions | ((...args: TArgs) => TReturn),
  maybeFn?: (...args: TArgs) => TReturn,
): (...args: TArgs) => TReturn
```

Wraps a function so that each invocation produces a span.

**Returns:** a function with the same signature as the input.

**Semantics:**

* If `enabled === false`, returns the original function unchanged
* Span `name` defaults to `fn.name || traceFunctionKey`
* Span `type` defaults to `"custom"`
* Input arguments are serialized via superjson (type-preserving)
* Return value (sync or the resolved Promise value) is serialized as output
* `SpanOptions.finalize?: (result) => unknown | Promise<unknown>` records a serializable view of a non-serializable result (a live stream). The raw result is returned to the caller unchanged; `await finalize(result)` is recorded as the span output. Runs in the background; a throwing `finalize` records an error instead of crashing. Ignored for async-generator results. Pair with the exported `finalizers.aiSdk` (Vercel AI SDK) or `finalizers.readableStream`
* Async-generator spans remain open until iteration completes, returns early, or throws. Nested spans created inside the generator body inherit 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.
* Thrown errors are recorded (`error` and `error_source: "code"` fields) and re-thrown
* Spans nest automatically via `AsyncLocalStorage` (Node) or a module-level stack (browser fallback)
* Browser fallback does not isolate concurrent async chains (`Promise.all` with independent `withSpan` calls may see the wrong parent)

### `getFunction`

```typescript theme={null}
getFunction(traceFunctionKey: string): BitfabFunction
```

Returns a `BitfabFunction` bound to `traceFunctionKey`.

### `wrapBAML`

Framework integration → see [BAML framework guide](/frameworks/baml) for examples.

```typescript theme={null}
// Form 1 -- uses bamlClient from constructor
wrapBAML<TArgs, TReturn>(
  method: (...args: TArgs) => Promise<TReturn>,
  options?: WrapBAMLOptions,
): WrappedBamlFn<TArgs, TReturn>

// Form 2 -- explicit client
wrapBAML<TArgs, TReturn>(
  bamlClient: unknown,
  method: (...args: TArgs) => Promise<TReturn>,
  options?: WrapBAMLOptions,
): WrappedBamlFn<TArgs, TReturn>
```

**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`
* Otherwise: 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 })`
* `onCollector` callback fires after each invocation; errors in the callback are swallowed

### `getTrace`

```typescript theme={null}
getTrace(traceId: string): DetachedTrace
```

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 are fire-and-forget (return `Promise<unknown>`)
* When `enabled === false`, methods return `Promise.resolve()` immediately
* Pending requests are tracked so `flushTraces()` waits for them
* Server returns 404 if no trace exists with that ID; failure is logged as a warning

### `getTraceSpan`

```typescript theme={null}
getTraceSpan(traceId: string, lookup: SpanLookup): Promise<CapturedSpan | null>
```

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

### `replay`

```typescript theme={null}
async replay<TReturn>(
  traceFunctionKey: string,
  fn: (...args: any[]) => TReturn | Promise<TReturn>,
  options?: ReplayOptions,
): Promise<ReplayResult<TReturn>>
```

| Option              | Type                          | Default                                                                           |
| ------------------- | ----------------------------- | --------------------------------------------------------------------------------- |
| `limit`             | `number`                      | `5` (maximum 5,000). Ignored when `traceIds` or `datasetId` is passed             |
| `traceIds`          | `string[]`                    | -- (max 100; the ID count determines how many traces replay)                      |
| `name`              | `string`                      | -- (display name for the resulting experiment/test run)                           |
| `maxConcurrency`    | `number`                      | `10`                                                                              |
| `experimentGroupId` | `string`                      | --                                                                                |
| `datasetId`         | `string`                      | -- (durably attributes the experiment to a dataset)                               |
| `graderIds`         | `string[]`                    | -- (graders attached to this run, unioned with the dataset's at grading; max 100) |
| `onProgress`        | `(p: ReplayProgress) => void` | -- (fires per item as it settles with running totals)                             |

**Returns:** `{ items, testRunId, testRunUrl }`. See `ReplayResult`.

**Notes:**

* A trace replays only when its root span has serializable inputs, or it was instrumented through a [framework handler](/typescript-sdk#replaying-handler-instrumented-functions) (whose recorded root input is serializable). If the original inputs were stubbed as non-serializable at capture time, the trace cannot be replayed.
* `fn` may be an already-`withSpan`-wrapped function (carries its trace function key, used as-is) or a plain callable (`replay()` wraps it 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

### `call`

```typescript theme={null}
async call<T = unknown>(
  methodName: string,
  inputs?: Record<string, unknown>,
): Promise<T>
```

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/hook surface and emit Bitfab spans automatically. For usage examples and semantics, see the per-framework guides; signatures here are canonical.

#### `getLangGraphCallbackHandler`

```typescript theme={null}
getLangGraphCallbackHandler(traceFunctionKey: string): BitfabLangGraphCallbackHandler
```

Returns a duck-typed LangChain/LangGraph callback handler. Pass in `config.callbacks` when invoking a graph/chain. Root framework invocations are registered immediately as pending external traces and 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](/frameworks/langgraph).

Aliased as `getLangChainCallbackHandler(traceFunctionKey)` for plain LangChain projects; the returned handler and behavior are identical. The handler class is also exported as `BitfabLangChainCallbackHandler`.

#### `getOpenAiTracingProcessor`

```typescript theme={null}
getOpenAiTracingProcessor(): BitfabOpenAITracingProcessor
```

Returns a processor to register with `@openai/agents`' `addTraceProcessor` (which keeps the SDK's default OpenAI exporter; `setTraceProcessors` replaces it). Captures agent internals; pair it with `getOpenAiAgentHandler` for a replayable root. See [OpenAI Agents framework guide](/frameworks/openai-agents).

#### `getOpenAiAgentHandler`

```typescript theme={null}
getOpenAiAgentHandler(traceFunctionKey: string): BitfabOpenAIAgentHandler
```

Returns a handler whose `wrapRun(agent, input, options?)` is a drop-in for `@openai/agents`' `run()` that records a keyed, replayable root span carrying the run input (the tracing processor's spans nest underneath). See [OpenAI Agents framework guide](/frameworks/openai-agents).

#### `getClaudeAgentHandler`

```typescript theme={null}
getClaudeAgentHandler(traceFunctionKey: string): BitfabClaudeAgentHandler
```

Returns a handler exposing `instrumentOptions(options)`, `wrapResponse(stream, opts?)`, and `wrapQuery(stream, opts?)` for the Claude Agent SDK. Pass `{ input: prompt }` to the wrap call to record a replayable root span. See [Claude Agent SDK framework guide](/frameworks/claude-agent-sdk).

#### `getVercelAiMiddleware`

```typescript theme={null}
getVercelAiMiddleware(traceFunctionKey: string): BitfabLanguageModelMiddleware
```

Returns a Vercel AI SDK [language model middleware](https://ai-sdk.dev/docs/ai-sdk-core/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](/frameworks/vercel-ai-sdk).

#### `wrapBAML`

See the [BAML framework guide](/frameworks/baml) for examples; full signature under [`wrapBAML`](#wrapbaml) above.

## `class BitfabFunction`

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

### `withSpan`

```typescript theme={null}
withSpan<TArgs, TReturn>(
  optionsOrFn: SpanOptions | ((...args: TArgs) => TReturn),
  maybeFn?: (...args: TArgs) => TReturn,
): (...args: TArgs) => TReturn
```

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

### `getVercelAiMiddleware`

```typescript theme={null}
getVercelAiMiddleware(): BitfabLanguageModelMiddleware
```

Delegates to `client.getVercelAiMiddleware(boundKey)`, reusing the bound key so a `withSpan` root and the middleware share it. See [Nesting with core tracing](/frameworks/vercel-ai-sdk#nesting-with-core-tracing).

### `getClaudeAgentHandler`

```typescript theme={null}
getClaudeAgentHandler(): BitfabClaudeAgentHandler
```

Delegates to `client.getClaudeAgentHandler(boundKey)`, reusing the bound key so a `withSpan` root and the handler share it. See [Nesting with core tracing](/frameworks/claude-agent-sdk#nesting-with-core-tracing).

### `getLangGraphCallbackHandler`

```typescript theme={null}
getLangGraphCallbackHandler(): BitfabLangGraphCallbackHandler
```

Delegates to `client.getLangGraphCallbackHandler(boundKey)`, reusing the bound key so a `withSpan` root and the handler share it. See [Nesting with core tracing](/frameworks/langgraph#nesting-with-core-tracing).

### `getLangChainCallbackHandler`

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

### `wrapBAML`

Identical signature and semantics to `Bitfab#wrapBAML`. Unlike the `getXHandler()` methods above, it does not use the bound key; it opens no span and enriches the *current* span, so call it inside a function wrapped by this handle's `withSpan`.

## `class BitfabError`

Extends `Error`.

```typescript theme={null}
class BitfabError extends Error {
  constructor(message: string, url?: string)
  readonly url?: string
}
```

Thrown for SDK-originated failures (missing function, missing prompt, misconfiguration). Never thrown for transport errors on `withSpan` paths -- those are swallowed.

## `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](/frameworks/langgraph).

## `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](/frameworks/openai-agents).

## `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](/frameworks/openai-agents).

## `class BitfabClaudeAgentHandler`

Handler for the Claude Agent SDK. Obtained via `client.getClaudeAgentHandler(key)`. Exposes `instrumentOptions`, `wrapResponse`, and `wrapQuery`. See [Claude Agent SDK framework guide](/frameworks/claude-agent-sdk) for method signatures and usage.

## Functions

### `getCurrentSpan()`

```typescript theme={null}
function getCurrentSpan(): CurrentSpan
```

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

### `getCurrentTrace()`

```typescript theme={null}
function getCurrentTrace(): CurrentTrace
```

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

### `flushTraces(timeoutMs?: number)`

```typescript theme={null}
async function flushTraces(timeoutMs?: number): Promise<void>
```

Waits for pending trace dispatches to complete. Default `timeoutMs`: `5000`. Use before `process.exit()` in short-lived scripts. Resolves after `timeoutMs` or when all pending sends finish, whichever comes first.

## Interfaces

### `BitfabConfig`

See [constructor table](#new-bitfab-config-bitfab-config).

### `SpanOptions`

```typescript theme={null}
interface SpanOptions {
  name?: string    // defaults to fn.name || traceFunctionKey
  type?: SpanType  // defaults to "custom"
}
```

### `SpanType`

```typescript theme={null}
type SpanType = "llm" | "agent" | "function" | "guardrail" | "handoff" | "custom"
```

### `CurrentSpan`

```typescript theme={null}
interface CurrentSpan {
  readonly id: string
  readonly traceId: string
  addContext(context: Record<string, unknown>): void
  setPrompt(prompt: string): void
}
```

| Method       | Semantics                                                                                                |
| ------------ | -------------------------------------------------------------------------------------------------------- |
| `id`         | Canonical Bitfab span ID. Empty when outside a span                                                      |
| `traceId`    | UUID string. Empty when outside a span                                                                   |
| `addContext` | Pushes the object as a single entry onto `span_data.contexts`. Non-object input is ignored. Never throws |
| `setPrompt`  | Overwrites `span_data.prompt`. Non-string input is ignored. Never throws                                 |

### `CurrentTrace`

```typescript theme={null}
interface CurrentTrace {
  setSessionId(sessionId: string): void
  setMetadata(metadata: Record<string, unknown>): void
  addContext(context: Record<string, unknown>): void
  drop(): void
}
```

| Method         | Semantics                                                                                                                                                                                                                                                                                                                                                                                                                                |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `setSessionId` | Stored on the trace's `session_id` DB column                                                                                                                                                                                                                                                                                                                                                                                             |
| `setMetadata`  | Shallow-merges with existing metadata. Later keys overwrite                                                                                                                                                                                                                                                                                                                                                                              |
| `addContext`   | Appends an entry to `rawData.contexts`. Accumulates across calls                                                                                                                                                                                                                                                                                                                                                                         |
| `drop`         | Flags the trace to be dropped. Once flagged, spans that complete afterward are not uploaded at all, and the flag rides out on the completion payload, so at completion the server scrubs any payloads that already raced out (the trace, its external trace, and sibling spans), deletes the archived S3 objects, and marks it `dropped` instead of `completed`, keeping only a skeleton audit row. A no-op outside a span. Never throws |

### `DetachedTrace`

```typescript theme={null}
interface DetachedTrace {
  readonly traceId: string
  addContext(context: Record<string, unknown>): Promise<unknown>
  setMetadata(metadata: Record<string, unknown>): Promise<unknown>
  setSessionId(sessionId: string): Promise<unknown>
}
```

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 (fire-and-forget). When `enabled === false`, all methods return `Promise.resolve()`.

### `WrapBAMLOptions`

```typescript theme={null}
interface WrapBAMLOptions {
  onCollector?: (collector: unknown) => void
}
```

### `WrappedBamlFn<TArgs, TReturn>`

```typescript theme={null}
interface WrappedBamlFn<TArgs extends unknown[], TReturn> {
  (...args: TArgs): Promise<TReturn>
  collector: unknown | null
}
```

`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`

```typescript theme={null}
interface ReplayOptions {
  limit?: number      // ignored (with a warning) when traceIds is passed
  traceIds?: string[] // max 100; the ID count determines how many traces replay
  name?: string       // display name for the resulting experiment/test run
  maxConcurrency?: number  // default 10
  experimentGroupId?: string
  environment?: ReplayEnvironment // per-trace DB branch exposed to the replayed fn
  datasetId?: string       // durably attributes the experiment to a dataset
  graderIds?: string[]     // graders attached to this run, unioned with the dataset's at grading (max 100)
  onProgress?: (progress: ReplayProgress) => void // per-item running totals
}

interface ReplayProgress {
  testRunId?: string // replay test run id
  completed: number  // items finished so far (succeeded or errored)
  total: number      // items in the run
  succeeded: number  // of completed, how many the fn ran without throwing
  errored: number    // of completed, how many threw
  item?: {           // the single trace that just settled
    traceId?: string | null      // null during the run; the server replay id arrives at completion
    originalTraceId: string | null // the original (historical) trace replayed
    sourceTraceId: string | null // deprecated alias for originalTraceId
    input?: unknown[]
    result?: unknown
    originalOutput?: unknown
    error: string | null       // its replay error, or null when it ran ok
    durationMs: number | null  // how long this one trace took to replay
    tokens?: TokenUsage | null
    model?: string | null
    dbSnapshotRef?: DbSnapshotRef | null
  }
}
```

`onProgress` fires once per item as it settles (completion order, not input order), so you can render live progress and consume the settled item's input/output before the whole replay finishes. Replay doesn't know pass/fail yet (verdicts are assigned later), so the totals only split ran-ok vs errored. A throwing callback never crashes the run. The SDK exports a ready-made reporter, `reportReplayProgress`, that you can pass straight in (`onProgress: reportReplayProgress`): it writes the running totals plus settled item payload to stderr, which the Bitfab plugin polls to report live progress and write per-item result files while the replay runs in the background, so scripts never hand-format the protocol.

`ReplayEnvironment` is read inside the replayed function: `environment.databaseUrl`, `environment.expiresAt`, `environment.providerConsoleUrl`, `environment.readOnly`, `environment.traceId`, and `environment.active` (false when no per-trace branch was resolved). Reading `databaseUrl` outside a replay item throws.

### `ReplayResult<T>`

```typescript theme={null}
interface ReplayResult<T> {
  items: ReplayItem<T>[]
  testRunId: string
  testRunUrl: string
}

interface ReplayItem<T> {
  input: unknown[]
  result: T | undefined
  originalOutput: unknown
  error: string | null
  durationMs: number | null // the original trace's duration
  tokens: TokenUsage | null // the replayed run's token usage (compare vs the original)
  model: string | null // the original trace's model
  traceId: string | null // the new replay trace's server id, written in after the run completes (null on older servers)
  originalTraceId: string // the original (historical) trace being replayed
  originalSpanId: string // the original root span the inputs were read from
  sourceTraceId: string // deprecated alias for originalTraceId
  sourceSpanId: string // deprecated alias for originalSpanId
  dbSnapshotRef: DbSnapshotRef | null // the source trace's snapshot pin, if any
}
```

### `AllowedEnvVars`

```typescript theme={null}
interface AllowedEnvVars {
  OPENAI_API_KEY?: string
}
```

Only `OPENAI_API_KEY` is currently whitelisted.

### `ActiveSpanContext`

```typescript theme={null}
interface ActiveSpanContext {
  traceId: string
  spanId: string
}
```

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

## Error Behavior Summary

| Situation                                                                   | Behavior                                                                                |
| --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| Empty / missing `apiKey`                                                    | `console.warn`, `enabled` forced to `false`                                             |
| `withSpan` transport failure                                                | Swallowed. User's return value / exception passes through                               |
| User function throws                                                        | Span records `error` and `error_source: "code"`, error re-thrown                        |
| `addContext` / `setPrompt` with invalid input                               | Silently ignored. Never throws                                                          |
| `call()` -- function not found                                              | `BitfabError` with URL to `/functions`                                                  |
| `call()` -- no prompt configured                                            | `BitfabError` with URL to `/functions/{id}`                                             |
| `wrapBAML` -- missing client                                                | `BitfabError`                                                                           |
| `replay()` -- plain callable `fn` (not `withSpan`-wrapped)                  | Auto-wrapped under the trace function key, so spans link to the test run (not an error) |
| `replay()` -- `fn` wrapped under a key that differs from `traceFunctionKey` | `BitfabError` (key mismatch)                                                            |

## Module Resolution

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