Adding AI features to a web application often starts with a simple requirement: send a prompt to a model and display its response. The work becomes more complicated when you need streaming responses, different AI providers, structured output, and reliable error handling.

Each provider can have its own API, authentication requirements, request format, and response structure. If the application communicates directly with provider-specific APIs throughout the codebase, switching models can require changes in several places.

TanStack AI offers a different approach. It provides a TypeScript SDK with a common API for connecting to supported AI providers. You can handle chat requests, stream responses to the browser, and change the provider adapter without rebuilding the entire application around a different API.

This article explains how to integrate TanStack AI into a React application, stream model responses, switch between providers, and prepare the integration for production.

What Is TanStack AI?

TanStack AI is a TypeScript SDK for building AI-powered applications. It supports streaming chat, tool calling, structured outputs, and other AI activities through provider adapters.

Its architecture separates three main responsibilities:

  • The core SDK manages chat execution, messages, streaming, and tools.
  • Provider adapters connect the application to specific AI services.
  • Client packages handle the interaction between the server and the user interface.

For example, an application can use an OpenAI adapter for text generation today and evaluate an Anthropic or Gemini adapter later. The provider-specific connection changes, while much of the surrounding application logic can remain the same.

TanStack AI supports providers including OpenAI, Anthropic, Google Gemini, OpenRouter, and Ollama. The available features depend on the selected adapter and model.

It is important to understand that a common API does not make every model identical. Different models can support different context sizes, tool-calling capabilities, input formats, and output limits.

Why Use TanStack AI Instead of Calling Provider APIs Directly?

Calling a provider API directly can be a reasonable choice for a small application with one model and a limited feature set.

However, as the application grows, provider-specific code can spread across API handlers, error handling, message processing, and streaming logic.

TanStack AI helps centralize these responsibilities.

AreaDirect provider integrationTanStack AI
Provider communicationImplement each provider's API separatelyUse supported provider adapters
StreamingHandle provider-specific stream formatsUse the SDK's streaming interface
TypeScript supportDefine types around each APIUse shared typed APIs and adapter-specific types
Provider switchingUpdate provider-specific integration codeReplace the adapter and validate compatibility
Tool callingImplement provider-specific tool handlingUse the SDK's tool system where supported
Framework integrationBuild your own client-side integrationUse framework packages such as the React client
Control over requestsDirect access to provider APIsSome details are managed by the SDK and adapter

TanStack AI does not eliminate the need to understand the underlying provider. It reduces the amount of provider-specific plumbing that application developers need to maintain.

Step 1: Install TanStack AI

For a React application that uses an OpenAI model, install the core package, React integration, and OpenAI adapter.

Bash
npm install @tanstack/ai @tanstack/ai-react @tanstack/ai-openai

These packages have separate responsibilities:

  • @tanstack/ai provides the core chat and streaming functionality.
  • @tanstack/ai-react provides React hooks for managing chat state.
  • @tanstack/ai-openai provides the OpenAI adapter.

You also need a compatible server environment and a valid provider API key.

Keep the installed package versions compatible. Before upgrading, review the documentation for the specific version used by your application because APIs and provider capabilities can change.

Step 2: Configure the Provider on the Server

The server should communicate with the AI provider so that provider credentials do not need to be exposed to browser code.

Store the API key in an environment variable:

Code
OPENAI_API_KEY=your_server_side_api_key

Do not commit a real key to source control. For local development, keep secret files out of version control and use your hosting platform's secret management for production.

Next, create a server endpoint that accepts a chat request and returns a stream.

The following example uses a standard Web Request and Response API, making the handler suitable for a server route that supports these interfaces.

TypeScript
import {
    chat,
    chatParamsFromRequest,
    toServerSentEventsResponse
} from "@tanstack/ai";

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

export async function POST(request: Request) {
    const { messages, threadId, runId } =
        await chatParamsFromRequest(request);

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

    return toServerSentEventsResponse(stream);
}

The model identifier is an example. Replace it with a model supported by your account and the installed adapter.

Here is what happens in the handler:

  1. chatParamsFromRequest() reads the chat parameters sent by the client.
  2. openaiText() configures the provider adapter and model.
  3. chat() starts the model interaction and returns a stream.
  4. toServerSentEventsResponse() converts the stream into an HTTP response that the client can consume.

The framework-specific route file depends on your application. In TanStack Start, place the handler in an appropriate server route and follow the routing conventions of your installed version.

The example shows the core integration. A production endpoint must also enforce authentication where needed, validate requests, apply rate limits, and handle provider errors appropriately.

Step 3: Build a React Chat Interface

Once the server endpoint is available, the React client can send messages and display responses as they arrive.

TanStack AI provides the useChat hook and a transport function for consuming server-sent events.

TypeScript
import { useState } from "react";

import {
    useChat,
    fetchServerSentEvents
} from "@tanstack/ai-react";

export default function ChatPage() {
    const [input, setInput] = useState("");

    const {
        messages,
        sendMessage,
        isLoading,
        stop
    } = useChat({
        connection: fetchServerSentEvents("/api/chat")
    });

    function handleSubmit(
        event: React.FormEvent<HTMLFormElement>
    ) {
        event.preventDefault();

        const message = input.trim();

        if (!message || isLoading) {
            return;
        }

        sendMessage({ text: message });
        setInput("");
    }

    return (
        <main>
            <h1>AI Assistant</h1>

            <section aria-live="polite">
                {messages.map((message, index) => (
                    <div key={message.id ?? index}>
                        <strong>{message.role}</strong>

                        {message.parts?.map((part, partIndex) => {
                            if (part.type !== "text") {
                                return null;
                            }

                            return (
                                <p key={partIndex}>
                                    {part.content}
                                </p>
                            );
                        })}
                    </div>
                ))}
            </section>

            <form onSubmit={handleSubmit}>
                <input
                    value={input}
                    onChange={(event) =>
                        setInput(event.target.value)
                    }
                    placeholder="Ask a question..."
                    disabled={isLoading}
                />

                <button
                    type="submit"
                    disabled={isLoading || !input.trim()}
                >
                    Send
                </button>

                {isLoading && (
                    <button
                        type="button"
                        onClick={stop}
                    >
                        Stop
                    </button>
                )}
            </form>
        </main>
    );
}

This example illustrates the main interaction pattern, but message-part types and exact hook signatures should be checked against the installed TanStack AI version. Keep the rendering logic aligned with the SDK's actual message types, particularly when displaying tool calls or non-text content.

The browser sends a message to the server endpoint. The server calls the model, and the response arrives incrementally. The hook updates the message state as stream events are received.

Streaming improves the experience because users can begin reading the response before the model finishes generating it.

Step 4: Connect a Different AI Provider

One of TanStack AI's main benefits is its adapter-based design.

Suppose the application currently uses OpenAI:

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

const adapter = openaiText("gpt-5.2");

You can evaluate another supported provider by installing its adapter and configuring the corresponding model.

For example, the Anthropic adapter uses a different package:

Bash
npm install @tanstack/ai-anthropic

The server-side configuration can then use an Anthropic model adapter:

TypeScript
import { anthropicText } from "@tanstack/ai-anthropic";

const adapter = anthropicText("claude-model-id");

Replace the illustrative model identifier with one supported by the provider and adapter version you use.

The server's chat execution can continue to use the common chat() API:

TypeScript
const stream = chat({
    adapter,
    messages,
    threadId,
    runId
});

This separation allows the application to keep most of its chat flow unchanged while the adapter handles provider-specific communication.

However, switching providers still requires testing. A model may interpret prompts differently, support different tools, or have different limits. Authentication, rate limits, pricing, and response quality can also vary.

Step 5: Make Provider Selection Configurable

Some applications need different models for different tasks. For example, a simple classification task may not need the same model as a complex reasoning task.

You can keep provider selection on the server and choose an adapter using a validated configuration value.

TypeScript
import { openaiText } from "@tanstack/ai-openai";
import { anthropicText } from "@tanstack/ai-anthropic";

type ProviderName = "openai" | "anthropic";

function getAdapter(provider: ProviderName) {
    switch (provider) {
        case "openai":
            return openaiText("gpt-5.2");

        case "anthropic":
            return anthropicText("claude-model-id");
    }
}

The identifiers are examples and should be replaced with supported models.

For a production application, read the provider choice from validated server configuration or an authorized application setting. Do not blindly accept a model name or provider value supplied by the browser.

A configurable design can make it easier to evaluate models, control costs, and respond to provider changes. It also creates an opportunity to apply different policies to different workloads.

Step 6: Handle Errors and Cancellation

AI requests can fail for several reasons:

  • The provider is temporarily unavailable.
  • The API key is invalid or expired.
  • The account has reached a usage limit.
  • The request exceeds a model's supported input size.
  • The client disconnects before the response finishes.

Treat these as expected operational conditions rather than exceptional events that never occur.

At the HTTP boundary, validate the incoming request and handle errors according to the conventions of your framework.

TypeScript
export async function POST(request: Request) {
    try {
        const { messages, threadId, runId } =
            await chatParamsFromRequest(request);

        // Apply authentication, authorization,
        // input validation, and rate limiting here.

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

        return toServerSentEventsResponse(stream);
    } catch (error) {
        console.error("AI request failed", error);

        return Response.json(
            { error: "Unable to process the AI request" },
            { status: 500 }
        );
    }
}

This simplified example demonstrates a generic error boundary. A production handler should distinguish invalid requests, authentication failures, rate limits, and provider errors instead of returning status 500 for every failure. Also verify how the framework represents thrown HTTP responses and streaming errors.

For cancellation, connect the client's stop operation to the server-side abort mechanism supported by the SDK. Cancelling the browser request alone does not always guarantee that provider generation stops immediately.

Avoid logging complete prompts or model responses when they may contain sensitive information.

Best Practices for Production Applications

Keep API keys on the server

Never embed private provider keys in browser bundles. Store them in server-side environment variables or a secret manager and restrict access to the services that need them.

Validate user input

Apply request-size limits and validate message structure before invoking a model. Authentication and authorization should be checked before users can access protected data or tools.

Control cost and usage

Set reasonable request limits, enforce per-user quotas, and monitor token usage where the provider exposes it. Streaming does not eliminate model costs.

Use structured output for machine-readable data

If the application expects a specific object, use the SDK's structured-output support and a schema instead of relying on the model to return perfectly formatted JSON.

Validate the result before using it in business logic.

Treat tool calls as security-sensitive operations

A model can request a tool, but the server should decide whether that action is permitted. Validate tool inputs, check user permissions, and require human approval for sensitive actions where appropriate.

Test each provider independently

Use the same representative prompts and evaluation cases when comparing providers. Test normal responses, invalid inputs, timeouts, cancellation, and rate-limit handling.

Do not assume that a provider switch will preserve the same response quality or behavior.

Common Mistakes to Avoid

MistakeWhy it causes problemsBetter approach
Exposing API keys in React codeUsers can inspect browser assets and requestsKeep provider credentials on the server
Assuming all models behave the sameModels differ in capabilities, limits, and output qualityVerify model features and test representative prompts
Returning raw provider errorsError details may expose internal informationLog appropriate diagnostics on the server and return safe client messages
Accepting unlimited chat historyLarge prompts can increase latency and costApply context limits and manage conversation history
Trusting model-generated tool argumentsInputs may be incorrect or maliciousValidate tool inputs and authorize each operation
Ignoring client cancellationUnneeded model generation may continueUse the SDK's supported cancellation mechanism
Switching providers without evaluationA technically successful integration may produce worse resultsRun quality, latency, and cost tests before rollout

Advantages and Disadvantages of TanStack AI

Advantages

  • Provides a shared TypeScript API for supported AI providers.
  • Supports streaming responses for interactive applications.
  • Separates provider integration from much of the application logic.
  • Offers React hooks and a headless client for different UI requirements.
  • Supports additional capabilities such as tool calling and structured outputs.

Disadvantages

  • Requires compatible SDK and adapter versions.
  • Does not remove differences between model capabilities or output quality.
  • Adds another dependency to the application's architecture.
  • Requires careful server-side security and operational controls.
  • Provider switching still requires testing and may require changes to prompts, tools, or output handling.

Summary

TanStack AI simplifies the work of connecting a TypeScript application to different AI models. Its shared API and provider adapters help developers build streaming chat interfaces without maintaining a separate integration for every provider.

Start with one provider, create a server endpoint, and use the React integration to display streamed responses. Once the basic flow works, add provider selection, structured outputs, cancellation, and monitoring as the application requires them.

For production use, keep credentials on the server, validate requests, limit usage, and test each provider before switching. The SDK makes integration easier, but security, reliability, cost management, and response quality remain application responsibilities.