TanStack
Adapters

OpenAI

The OpenAI adapter provides access to OpenAI's models, including GPT-4o, GPT-5, image generation (DALL-E), text-to-speech (TTS), and audio transcription (Whisper).

Using a third-party provider that speaks the OpenAI API (DeepSeek, Moonshot/Kimi, Together, Fireworks, a local LM Studio/vLLM server, …)? See the OpenAI-Compatible Adapter for a generic openaiCompatible({ baseURL, apiKey, models }) factory.

Installation

shell
npm i @tanstack/ai-openai

Basic Usage

ts
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";

const stream = chat({
  adapter: openaiText("gpt-5.2"),
  messages: [{ role: "user", content: "Hello!" }],
});

Chat Completions API

@tanstack/ai-openai ships two text adapters that hit different OpenAI endpoints. openaiText (default) calls the Responses API (/v1/responses). openaiChatCompletions calls the older Chat Completions API (/v1/chat/completions).

Pick whichever fits your wire format and feature needs:

openaiText (Responses)openaiChatCompletions (Chat Completions)
Endpoint/v1/responses/v1/chat/completions
Reasoning summariesYes — set modelOptions.reasoning.summary: 'auto' to surface reasoning text via REASONING_* eventsNo — reasoning tokens are still consumed but cannot be exposed
Wire-format compatibilityOpenAI-onlyMatches the older de-facto industry shape (Grok, Groq, OpenRouter, many local model servers)
Structured output streamingtext.format: { type: 'json_schema', strict: true } + stream: trueresponse_format: { type: 'json_schema', strict: true } + stream: true

Use openaiText when you want reasoning-summary streaming or OpenAI-specific Responses features. Use openaiChatCompletions when you're migrating off a Chat-Completions-style provider, share request-building code with other Chat-Completions adapters in your stack, or want the more battle-tested wire format.

ts
import { chat } from "@tanstack/ai";
import { openaiChatCompletions } from "@tanstack/ai-openai";

const stream = chat({
  adapter: openaiChatCompletions("gpt-5.2"),
  messages: [{ role: "user", content: "Hello!" }],
});

With an explicit API key:

ts
import { chat } from "@tanstack/ai";
import { createOpenaiChatCompletions } from "@tanstack/ai-openai";

const adapter = createOpenaiChatCompletions("gpt-5.2", process.env.OPENAI_API_KEY!, {
  // organization, baseURL, headers — all optional
});

const stream = chat({
  adapter,
  messages: [{ role: "user", content: "Hello!" }],
});

Both adapters work identically with Structured Outputs — including stream: true — and accept the same modelOptions (temperature, top_p, max_tokens, stop, …). The reasoning section below applies to openaiText; openaiChatCompletions accepts modelOptions.reasoning.effort but cannot stream summary text.

Basic Usage - Custom API Key

ts
import { chat } from "@tanstack/ai";
import { createOpenaiChat } from "@tanstack/ai-openai";

const adapter = createOpenaiChat("gpt-5.2", process.env.OPENAI_API_KEY!, {
  // ... your config options
});

const stream = chat({
  adapter,
  messages: [{ role: "user", content: "Hello!" }],
});

Configuration

ts
import { createOpenaiChat, type OpenAITextConfig } from "@tanstack/ai-openai";

const config: Omit<OpenAITextConfig, "apiKey"> = {
  organization: "org-...", // Optional
  baseURL: "https://api.openai.com/v1", // Optional, for custom endpoints
};

const adapter = createOpenaiChat("gpt-5.2", process.env.OPENAI_API_KEY!, config);

Example: Chat Completion

ts
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";

export async function POST(request: Request) {
  const { messages } = await request.json();

  const stream = chat({
    adapter: openaiText("gpt-5.2"),
    messages,
  });

  return toServerSentEventsResponse(stream);
}

Example: With Tools

ts
import { chat, toServerSentEventsResponse, toolDefinition } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { z } from "zod";

const getWeatherDef = toolDefinition({
  name: "get_weather",
  description: "Get the current weather",
  inputSchema: z.object({
    location: z.string(),
  }),
});

const getWeather = getWeatherDef.server(async ({ location }) => {
  // Fetch weather data
  return { temperature: 72, conditions: "sunny" };
});

export async function POST(request: Request) {
  const { messages } = await request.json();

  const stream = chat({
    adapter: openaiText("gpt-5.2"),
    messages,
    tools: [getWeather],
  });

  return toServerSentEventsResponse(stream);
}

Model Options

OpenAI supports various provider-specific options. Sampling parameters live here too — temperature, top_p, and max_output_tokens (the Responses API token-limit key) — rather than as root-level props on chat():

ts
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";

const stream = chat({
  adapter: openaiText("gpt-5.2"),
  messages: [{ role: "user", content: "Hello!" }],
  modelOptions: {
    temperature: 0.7,
    max_output_tokens: 1000,
    top_p: 0.9,
  },
});

The openaiChatCompletions adapter targets /v1/chat/completions, where the token-limit key is max_tokens (not max_output_tokens). If you previously passed temperature / topP / maxTokens at the root of chat(), see Moving Sampling Options into modelOptions.

Reasoning

Enable reasoning for models that support it (e.g., GPT-5, O3). This allows the model to show its reasoning process, which is streamed as thinking chunks:

ts
modelOptions: {
  reasoning: {
    effort: "medium", // "none" | "minimal" | "low" | "medium" | "high"
    summary: "detailed", // "auto" | "detailed" (optional)
  },
}

When reasoning is enabled, the model's reasoning process is streamed separately from the response text and appears as a collapsible thinking section in the UI.

Summarization

Summarize long text content:

ts
import { summarize } from "@tanstack/ai";
import { openaiSummarize } from "@tanstack/ai-openai";

const result = await summarize({
  adapter: openaiSummarize("gpt-5-mini"),
  text: "Your long text to summarize...",
  maxLength: 100,
  style: "concise", // "concise" | "bullet-points" | "paragraph"
});

console.log(result.summary);

Embeddings

Generate embedding vectors with the text-embedding-3 models:

ts
import { embed } from "@tanstack/ai";
import { openaiEmbedding } from "@tanstack/ai-openai";

const result = await embed({
  adapter: openaiEmbedding("text-embedding-3-small"),
  input: ["a red guitar", "a blue drum kit"],
});

console.log(result.embeddings[0]?.vector);
console.log(result.usage?.promptTokens);

Both models support Matryoshka dimension reduction via the top-level dimensions option:

ts
import { embed } from "@tanstack/ai";
import { openaiEmbedding } from "@tanstack/ai-openai";

const result = await embed({
  adapter: openaiEmbedding("text-embedding-3-large"),
  input: "a red guitar",
  dimensions: 1024,
});

See the Embeddings guide for the full API.

Image Generation

Generate images:

ts
import { generateImage } from "@tanstack/ai";
import { openaiImage } from "@tanstack/ai-openai";

const result = await generateImage({
  adapter: openaiImage("gpt-image-2"),
  prompt: "A futuristic cityscape at sunset",
  numberOfImages: 1,
  size: "1024x1024",
});

console.log(result.images);

Image Model Options

ts
import { generateImage } from "@tanstack/ai";
import { openaiImage } from "@tanstack/ai-openai";

const result = await generateImage({
  adapter: openaiImage("gpt-image-2"),
  prompt: "...",
  modelOptions: {
    quality: "high", // "high" | "medium" | "low" | "auto"
  },
});

Text-to-Speech

Generate speech from text:

ts
import { generateSpeech } from "@tanstack/ai";
import { openaiSpeech } from "@tanstack/ai-openai";

const result = await generateSpeech({
  adapter: openaiSpeech("tts-1"),
  text: "Hello, welcome to TanStack AI!",
  voice: "alloy",
  format: "mp3",
});

// result.audio contains base64-encoded audio
console.log(result.format); // "mp3"

TTS Voices

Available voices: alloy, echo, fable, onyx, nova, shimmer, ash, ballad, coral, sage, verse

TTS Model Options

ts
import { generateSpeech } from "@tanstack/ai";
import { openaiSpeech } from "@tanstack/ai-openai";

const result = await generateSpeech({
  adapter: openaiSpeech("tts-1-hd"),
  text: "High quality speech",
  modelOptions: {
    instructions: "Speak slowly and clearly.", // voice instructions (not supported by tts-1/tts-1-hd)
  },
});

Transcription

Transcribe audio to text:

ts
import { generateTranscription } from "@tanstack/ai";
import { openaiTranscription } from "@tanstack/ai-openai";
import { audioFile } from "./audio";

const result = await generateTranscription({
  adapter: openaiTranscription("whisper-1"),
  audio: audioFile, // File object or base64 string
  language: "en",
});

console.log(result.text); // Transcribed text

Transcription Model Options

ts
import { generateTranscription } from "@tanstack/ai";
import { openaiTranscription } from "@tanstack/ai-openai";
import { audioFile } from "./audio";

const result = await generateTranscription({
  adapter: openaiTranscription("whisper-1"),
  audio: audioFile,
  responseFormat: "verbose_json",
  prompt: "Technical terms: API, SDK",
  modelOptions: {
    temperature: 0,
    timestamp_granularities: ["word", "segment"],
  },
});

// Access the transcribed text
console.log(result.text);

Speaker Diarization

Use gpt-4o-transcribe-diarize for speaker-labeled transcripts:

ts
import { generateTranscription } from "@tanstack/ai";
import { openaiTranscription } from "@tanstack/ai-openai";
import { meetingAudioFile } from "./audio";

const result = await generateTranscription({
  adapter: openaiTranscription("gpt-4o-transcribe-diarize"),
  audio: meetingAudioFile,
  modelOptions: {
    known_speaker_names: ["agent", "customer"],
    known_speaker_references: [
      "data:audio/wav;base64,...",
      "data:audio/wav;base64,...",
    ],
  },
});

for (const segment of result.segments ?? []) {
  console.log(segment.speaker, segment.start, segment.end, segment.text);
}

When no response format is specified, gpt-4o-transcribe-diarize requests default to response_format: "diarized_json" and chunking_strategy: "auto"; passing a top-level responseFormat of "json" or "text" opts out of speaker segments. known_speaker_names and known_speaker_references must be provided together (up to 4, matching lengths). OpenAI does not support prompt, include, or timestamp_granularities with diarized transcription.

Environment Variables

Set your API key in environment variables:

shell
OPENAI_API_KEY=sk-...

API Reference

Every factory pair follows the same shape: the short factory (openaiText, openaiImage, …) reads OPENAI_API_KEY from the environment, while the create* variant takes an explicit API key. Both take model as the first argument.

openaiText(model, config?)

Creates an OpenAI text adapter against the Responses API (/v1/responses) using OPENAI_API_KEY from the environment.

Parameters:

  • model - OpenAI chat model id (e.g. "gpt-5.2", "gpt-4o-mini")
  • config?.organization - Organization ID (optional)
  • config?.baseURL - Custom base URL (optional)

createOpenaiChat(model, apiKey, config?)

Creates an OpenAI text adapter (Responses API) with an explicit API key.

openaiChatCompletions(model, config?)

Creates an OpenAI text adapter that targets /v1/chat/completions instead of the Responses API. See Chat Completions API for when to use this over openaiText.

createOpenaiChatCompletions(model, apiKey, config?)

Creates an OpenAI chat-completions adapter with an explicit API key.

openaiSummarize(model, config?) / createOpenaiSummarize(model, apiKey, config?)

Creates an OpenAI summarization adapter.

openaiImage(model, config?) / createOpenaiImage(model, apiKey, config?)

Creates an OpenAI image generation adapter (DALL-E, gpt-image).

openaiSpeech(model, config?) / createOpenaiSpeech(model, apiKey, config?)

Creates an OpenAI text-to-speech adapter.

openaiTranscription(model, config?) / createOpenaiTranscription(model, apiKey, config?)

Creates an OpenAI transcription adapter for Whisper, GPT-4o transcription, and GPT-4o diarized transcription models.

openaiVideo(model, config?) / createOpenaiVideo(model, apiKey, config?)

Creates an OpenAI video generation adapter (Sora). Experimental.

openaiRealtime(...) / openaiRealtimeToken(...)

Realtime voice adapters. See Realtime Voice Chat for usage.

Next Steps

Provider Tools

OpenAI exposes several native tools beyond user-defined function calls. Import them from @tanstack/ai-openai/tools and pass them into chat({ tools: [...] }).

For the full concept, a comparison matrix, and type-gating details, see Provider Tools.

webSearchTool

Enables the model to run a web search and return grounded results with citations. Pass a WebSearchToolConfig object (typed from the OpenAI SDK) to configure the tool.

ts
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { webSearchTool } from "@tanstack/ai-openai/tools";

const stream = chat({
  adapter: openaiText("gpt-5.2"),
  messages: [{ role: "user", content: "What's new in AI this week?" }],
  tools: [webSearchTool({ type: "web_search" })],
});

Supported models: GPT-4o, GPT-5, and Responses API-capable models. See Provider Tools.

webSearchPreviewTool

The preview variant of web search with additional options for controlling search context size and user location. Use this when you want fine-grained control over the search context sent to the model.

ts
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { webSearchPreviewTool } from "@tanstack/ai-openai/tools";

const stream = chat({
  adapter: openaiText("gpt-5.2"),
  messages: [{ role: "user", content: "Latest news about TypeScript" }],
  tools: [
    webSearchPreviewTool({
      type: "web_search_preview_2025_03_11",
      search_context_size: "high",
    }),
  ],
});

Supported models: GPT-4o and above. See Provider Tools.

fileSearchTool

Searches OpenAI vector stores that you have pre-populated, letting the model retrieve relevant document chunks. Provide the vector_store_ids to search and optionally limit results with max_num_results (1–50).

ts
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { fileSearchTool } from "@tanstack/ai-openai/tools";

const stream = chat({
  adapter: openaiText("gpt-5.2"),
  messages: [{ role: "user", content: "What does the handbook say about PTO?" }],
  tools: [
    fileSearchTool({
      type: "file_search",
      vector_store_ids: ["vs_abc123"],
      max_num_results: 5,
    }),
  ],
});

Supported models: GPT-4o and above. See Provider Tools.

imageGenerationTool

Allows the model to generate images inline during a conversation using DALL-E/GPT-Image. Pass quality, size, and style options via the config object.

ts
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { imageGenerationTool } from "@tanstack/ai-openai/tools";

const stream = chat({
  adapter: openaiText("gpt-5.2"),
  messages: [{ role: "user", content: "Draw a logo for my app" }],
  tools: [
    imageGenerationTool({
      quality: "high",
      size: "1024x1024",
    }),
  ],
});

Supported models: GPT-5 and GPT-Image-capable models. See Provider Tools.

codeInterpreterTool

Gives the model a sandboxed Python execution environment. The container field configures the execution environment; pass the full CodeInterpreterToolConfig object.

ts
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { codeInterpreterTool } from "@tanstack/ai-openai/tools";

const stream = chat({
  adapter: openaiText("gpt-5.2"),
  messages: [{ role: "user", content: "Analyse this CSV and plot a chart" }],
  tools: [
    codeInterpreterTool({ type: "code_interpreter", container: { type: "auto" } }),
  ],
});

Supported models: GPT-4o and above. See Provider Tools.

mcpTool

Connects the model to a remote MCP (Model Context Protocol) server, exposing all its capabilities as callable tools. Provide either server_url or connector_id — not both.

ts
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { mcpTool } from "@tanstack/ai-openai/tools";

const stream = chat({
  adapter: openaiText("gpt-5.2"),
  messages: [{ role: "user", content: "List my GitHub issues" }],
  tools: [
    mcpTool({
      server_url: "https://mcp.example.com",
      server_label: "github",
    }),
  ],
});

Supported models: GPT-4o and above. See Provider Tools.

computerUseTool

Lets the model observe a virtual desktop via screenshots and interact with it using keyboard and mouse events. Provide the display dimensions and the execution environment type.

ts
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { computerUseTool } from "@tanstack/ai-openai/tools";

const stream = chat({
  adapter: openaiText("computer-use-preview"),
  messages: [{ role: "user", content: "Open Chrome and navigate to example.com" }],
  tools: [
    computerUseTool({
      type: "computer_use_preview",
      display_width: 1024,
      display_height: 768,
      environment: "browser",
    }),
  ],
});

Supported models: computer-use-preview. See Provider Tools.

localShellTool

The model returns a local_shell tool call. Your app runs the command. Then your app sends the output back on the next request. localShellTool() takes no arguments. Add it to tools.

Match toolName on TOOL_CALL_START. Read input on the TOOL_CALL_END with the same toolCallId. input.command is the command. input.env is the environment variables.

ts
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { localShellTool } from "@tanstack/ai-openai/tools";

const stream = chat({
  adapter: openaiText("gpt-5.6"),
  messages: [{ role: "user", content: "Run the test suite and summarise failures" }],
  tools: [localShellTool()],
});

let localShellCallId: string | undefined;
for await (const chunk of stream) {
  if (chunk.type === "TOOL_CALL_START" && chunk.toolName === "local_shell") {
    localShellCallId = chunk.toolCallId;
  }
  if (chunk.type === "TOOL_CALL_END" && chunk.toolCallId === localShellCallId) {
    console.log(chunk.input);
  }
}

Send the command output as the tool result. Use { output: string }, or a string. The next request sends local_shell_call_output.

Supported models: GPT-5.x and other agent-capable models. See Provider Tools.

shellTool

shellTool() gives the model a shell. A container environment runs on OpenAI. A local environment runs in your app.

When your app must run the commands, pass environment: { type: "local" }. Match toolName on TOOL_CALL_START. Read input on the TOOL_CALL_END with the same toolCallId. input.commands is the command list. input.timeout_ms is the time limit. input.max_output_length is the output limit.

ts
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { shellTool } from "@tanstack/ai-openai/tools";

const stream = chat({
  adapter: openaiText("gpt-5.6"),
  messages: [{ role: "user", content: "Count lines in all JS files" }],
  tools: [shellTool({ environment: { type: "local" } })],
});

let shellCallId: string | undefined;
for await (const chunk of stream) {
  if (chunk.type === "TOOL_CALL_START" && chunk.toolName === "shell") {
    shellCallId = chunk.toolCallId;
  }
  if (chunk.type === "TOOL_CALL_END" && chunk.toolCallId === shellCallId) {
    console.log(chunk.input);
  }
}

Return one result per command:

ts
const shellResult = {
  output: [
    {
      stdout: "12\n",
      stderr: "",
      outcome: { type: "exit" as const, exit_code: 0 },
    },
  ],
};

outcome.type is "exit" or "timeout". The next request sends shell_call_output. If the same response has no shell_call_output, a shell call with no environment comes back to your app.

A container environment does not come back as a tool call. Pass environment to attach that container and hosted skills.

Supported models: GPT-5.x and other agent-capable models. Responses API only — Chat Completions does not support the shell tool. See Provider Tools.

Attaching hosted skills

Pass environment.skills to load provider-managed skill bundles into the shell's container (Responses API only).

ts
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { shellTool } from "@tanstack/ai-openai/tools";

export async function POST(request: Request) {
  const { messages } = await request.json();

  const stream = chat({
    adapter: openaiText("gpt-5.2"),
    messages,
    tools: [
      shellTool({
        environment: {
          type: "container_auto",
          skills: [
            { type: "skill_reference", skill_id: "skill_abc", version: "2" },
          ],
        },
      }),
    ],
  });

  return toServerSentEventsResponse(stream);
}

For the full reference — skill shape, version string format, and the Anthropic equivalent — see Provider Skills.

applyPatchTool

The model returns an apply_patch tool call. Your app applies the diff. Then your app sends the result back. applyPatchTool() takes no arguments. Add it to tools.

Match toolName on TOOL_CALL_START. Read input on the TOOL_CALL_END with the same toolCallId. input.operation is one file change. operation.type is create_file, update_file, or delete_file. operation.path is the file path. create_file and update_file also include operation.diff.

ts
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { applyPatchTool } from "@tanstack/ai-openai/tools";

const stream = chat({
  adapter: openaiText("gpt-5.6"),
  messages: [{ role: "user", content: "Fix the import paths in src/index.ts" }],
  tools: [applyPatchTool()],
});

let patchCallId: string | undefined;
for await (const chunk of stream) {
  if (chunk.type === "TOOL_CALL_START" && chunk.toolName === "apply_patch") {
    patchCallId = chunk.toolCallId;
  }
  if (chunk.type === "TOOL_CALL_END" && chunk.toolCallId === patchCallId) {
    console.log(chunk.input);
  }
}

Send this object as the tool result. The next request sends apply_patch_call_output.

ts
const patchResult = {
  status: "completed" as const,
  output: "Updated src/index.ts",
};

If the patch fails, set status to "failed". Put the error text in output. Keep applyPatchTool() in tools on the next request. Use the stream. chat({ stream: false }) returns only text. A patch-only turn then looks empty.

Supported models: GPT-5.x and other agent-capable models. See Provider Tools.

customTool

Defines a custom Responses API tool with an explicit name, description, and format. Use this when none of the structured tool types fits your use case. Unlike branded provider tools, customTool returns a plain Tool and is accepted by any chat model.

ts
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { customTool } from "@tanstack/ai-openai/tools";

const stream = chat({
  adapter: openaiText("gpt-5.2"),
  messages: [{ role: "user", content: "Look up order #1234" }],
  tools: [
    customTool({
      type: "custom",
      name: "lookup_order",
      description: "Look up the status of a customer order by order ID",
    }),
  ],
});

Supported models: all Responses API models. See Provider Tools.