Skip to main content
@voightxyz/openai instruments the official OpenAI Node SDK. Wrap your client once and every chat.completions.create or responses.create call — non-streaming or streaming — lands in Voight with prompts, tokens, cache reads, tool calls, latency, and errors. This is the same SDK-instrumentation model Sentry / Vercel AI / LangChain use. Same backend, same dashboard as @voightxyz/anthropic and library mode — events from all three land side-by-side under the same agent. From the root of your app:
The wizard detects openai in your package.json, prompts for your Voight key + privacy level, validates the key, and writes a ready-to-import src/lib/voight.ts with the wrapped client. 30 seconds, zero copy-paste. Continue below if you’d rather wire it manually.

Install

Requirements:
  • Node.js 18+ (uses global fetch)
  • openai SDK 4.0.0+

Quick start

That’s it. Every call is captured automatically. Visit your dashboard to see them in the AI Apps section.

Tracing & per-user tags

For production apps, wrap each request boundary with withTrace to group every LLM call inside one request into one trace, and to attribute cost per end-user with one line of code:
Every wrapped LLM call inside the withTrace block gets stamped with metadata.tags = { userId, plan, ... } automatically. The dashboard’s AI Apps section then surfaces:
  • Users sub-tab — per-user spend, traces, tokens (driven by tags.userId)
  • Trace cards — every withTrace block as one drillable card with cost, latency, and event timeline
  • User filter pill — narrow Overview / Models / Tools to one user
Full surface in Tracing. The per-user pattern in depth — with examples for Auth0, Clerk, NextAuth, custom JWT, and anonymous flows — lives at per-user spend.

Options

A missing or empty API key is non-fatal: the wrapper prints a one-line warning and returns the original client. Production keeps running.

What’s captured

Supported endpoints

The wrapper intercepts two paths:
  • client.chat.completions.create — Chat Completions (non-streaming + streaming, tool calling)
  • client.responses.create — Responses API (non-streaming + streaming, function calling, reasoning models)
Everything else on the OpenAI client passes through untouched. Embeddings, images, audio, and the Azure OpenAI client are on the 0.2.0 roadmap.

Chat Completions

Streaming works without setup. The wrapper auto-injects stream_options.include_usage: true so the final chunk carries the token count, and a per-index aggregator reassembles tool-call argument fragments.
An explicit stream_options: { include_usage: false } from the caller is preserved — you opt out of token capture for streaming events but everything else is still captured.

Responses API

The Responses API is OpenAI’s surface for apps built after Mar 2025: typed output items, stateful conversations via previous_response_id, built-in tools, and explicit reasoning_tokens for o1 / o3 models.
Events from this surface carry metadata.api: 'responses' so dashboards can distinguish call sites from Chat Completions. Streaming is a typed event sequence (response.created, response.output_text.delta, response.output_item.added, response.function_call_arguments.delta, response.completed); the wrapper’s state machine reacts to the critical events and passes the rest through unchanged.
For reasoning models, metadata.tokens.reasoning separates the “thinking” overhead from the visible answer so cost analysis stays accurate.

Tool / function calling

Works the same for both endpoints:
On the captured event:
  • toolExecuted: 'get_weather' — first tool’s name. Renders in the audit-log DETAIL column with the same shape as Bash/Edit hook events.
  • metadata.toolCalls: [{ id, name, arguments }] — full array. arguments is the raw JSON string the model produced (we don’t parse it — invalid JSON is a real failure mode you need to debug).
Streaming function calls work the same — fragment deltas across chunks (Chat Completions) or function_call_arguments.delta events (Responses) are concatenated per tool index.

OpenTelemetry side-channel

By default the wrapper POSTs each captured call directly to api.voight.xyz. Set otel: true to additionally emit each call as an OpenTelemetry span — useful when the host process already runs an OTel pipeline (Langfuse, Phoenix, Datadog, Sentry, or @voightxyz/vercel-ai) and you want Voight events to appear there too.
Each span is named voight.openai.chat (or voight.openai.responses) and carries the standard gen_ai.* semantic-convention attributes (gen_ai.system, gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.usage.cache_read_input_tokens, gen_ai.response.finish_reasons) plus the parallel Vercel-style ai.* namespace. The direct ingest path is unchanged — otel: true is purely additive.

Dedup marker

Every emitted span carries voight.source: 'wrapper'. If you also use @voightxyz/vercel-ai ≥ 0.1.1 in the same process, that exporter skips wrapper-emitted spans automatically — no duplicate events in your dashboard.

Optional peer dependency

@opentelemetry/api is now an optional peer dependency. If you never set otel: true, nothing changes. If you set otel: true but the package isn’t installed, the wrapper logs a single warning and falls back to direct ingest only.

Privacy

Three levels apply to prompts, response text, and tool-call arguments. The function name in toolExecuted is treated as a tag (not user content) and survives all levels. Standard scrubs 12 patterns: PEM private keys, JWTs, Anthropic / OpenAI / Stripe live / GitHub / AWS / Slack / Voight API keys, emails, E.164 phones, and Luhn-validated credit cards. Token counts, model ids, and timing are NEVER scrubbed — they’re numeric or tags, no PII risk. See PII patterns for the full catalogue.

How it compares

The packages coexist — wrap your OpenAI client AND call voight.log() for your own domain events under the same agent. Adding withTrace on top groups them all per-request.

Source

Roadmap

  • Embeddings (embeddings.create)
  • Image generation (images.generate)
  • Audio (Whisper / TTS)
  • Azure OpenAI client
See the changelog for shipped releases.