# llms-full (private-aware)
> Built from GitHub files and website pages. Large files may be truncated.

--- docs/getting-started/devtools.md ---
---
title: Devtools
id: devtools
---

TanStack Devtools is a unified devtools panel for inspecting and debugging TanStack libraries, including TanStack AI. It provides real-time insights into AI interactions, tool calls, and state changes, making it easier to develop and troubleshoot AI-powered applications.

## Features
- **Real-time Monitoring** - View live chat messages, tool invocations, and AI responses.
- **Tool Call Inspection** - Inspect input and output of tool calls.
- **State Visualization** - Visualize chat state and message history.
- **Error Tracking** - Monitor errors and exceptions in AI interactions.

## Installation
To use TanStack Devtools with TanStack AI, install the `@tanstack/react-ai-devtools` package:

```bash
npm install -D @tanstack/react-ai-devtools @tanstack/react-devtools
```

Or the `@tanstack/solid-ai-devtools` package for SolidJS:
```bash
npm install -D @tanstack/solid-ai-devtools @tanstack/solid-devtools
```

## Usage

Import and include the Devtools component in your application:

```tsx
import { TanStackDevtools } from '@tanstack/react-devtools'
import { aiDevtoolsPlugin } from '@tanstack/react-ai-devtools'

const App = () => {
  return (
    <>
       <TanStackDevtools 
          plugins={[
            // ... other plugins
            aiDevtoolsPlugin(),
          ]}
          // this config is important to connect to the server event bus
          eventBusConfig={{
            connectToServerBus: true,
          }}
        />
    </>
  )
}
```

--- docs/getting-started/overview.md ---
---
title: Overview
id: overview
---

TanStack AI is a lightweight, type-safe SDK for building production-ready AI experiences. Its framework-agnostic core provides type-safe tool/function calling, streaming responses, and first-class React and Solid integrations, with adapters for multiple LLM providers — enabling predictable, composable, and testable AI features across any stack.

## Key Features

- ✅ **Type-Safe** - Full TypeScript support with Zod schema inference
- ✅ **Streaming** - Built-in streaming support for real-time responses
- ✅ **Isomorphic Tools** - Define once with `toolDefinition()`, implement with `.server()` or `.client()`
- ✅ **Framework Agnostic** - Core library works anywhere
- ✅ **Multiple Providers** - OpenAI, Anthropic, Gemini, Ollama, and more
- ✅ **Approval Flow** - Built-in support for tool approval workflows
- ✅ **Automatic Execution** - Both server and client tools execute automatically

## Framework Agnostic

The framework-agnostic core of TanStack AI provides the building blocks for creating AI experiences in any environment, including:

- **Next.js** - API routes and App Router
- **TanStack Start** - React Start or Solid Start (recommended!)
- **Express** - Node.js server
- **Remix Router v7** - Loaders and actions

TanStack AI lets you define a tool once and provide environment-specific implementations. Using `toolDefinition()` to declare the tool’s input/output types and the server behavior with `.server()` (or a client implementation with `.client()`). These isomorphic tools can be invoked from the AI runtime regardless of framework.

```typescript
import { toolDefinition } from '@tanstack/ai'

// Define a tool
const getProductsDef = toolDefinition({
  name: 'getProducts',
  inputSchema: z.object({ query: z.string() }),
  outputSchema: z.array(z.object({ id: z.string(), name: z.string() })),
})

// Create server implementation
const getProducts = getProductsDef.server(async ({ query }) => {
  return await db.products.search(query)
})

// Use in AI chat
chat({ tools: [getProducts] })
```

## Core Packages

The TanStack AI ecosystem consists of several packages:

### `@tanstack/ai`
The core AI library that provides:
- AI adapter interface for connecting to LLM providers
- Chat completion and streaming
- Isomorphic tool/function calling system
- Agent loop strategies
- Type-safe tool definitions with `toolDefinition()`
- Type-safe provider options based on adapter & model selection
- Type-safe content modalities (text, image, audio, video, document) based on model capabilities

### `@tanstack/ai-client`
A framework-agnostic headless client for managing chat state:
- Message management with full type safety
- Streaming support
- Connection adapters (SSE, HTTP stream, custom)
- Automatic tool execution (server and client)
- Tool approval flow handling

### `@tanstack/ai-react`
React hooks for TanStack AI:
- `useChat` hook for chat interfaces
- Automatic state management
- Tool approval flow support
- Type-safe message handling with `InferChatMessages`

### `@tanstack/ai-solid`
Solid hooks for TanStack AI:
- `useChat` hook for chat interfaces
- Automatic state management
- Tool approval flow support
- Type-safe message handling with `InferChatMessages`

## Adapters

With the help of adapters, TanStack AI can connect to various LLM providers. Available adapters include:

- **@tanstack/ai-openai** - OpenAI (GPT-4, GPT-3.5, etc.)
- **@tanstack/ai-anthropic** - Anthropic (Claude)
- **@tanstack/ai-gemini** - Google Gemini
- **@tanstack/ai-ollama** - Ollama (local models)

## Next Steps

- [Quick Start Guide](./quick-start) - Get up and running in minutes
- [Tools Guide](../guides/tools) - Learn about the isomorphic tool system
- [API Reference](../api/ai) - Explore the full API

## Links discovered
- [Quick Start Guide](https://github.com/tanstack/ai/blob/main/docs/getting-started/quick-start.md)
- [Tools Guide](https://github.com/tanstack/ai/blob/main/docs/guides/tools.md)
- [API Reference](https://github.com/tanstack/ai/blob/main/docs/api/ai.md)

--- docs/getting-started/quick-start.md ---
---
title: Quick Start
id: quick-start
---

Get started with TanStack AI in minutes. This guide will walk you through creating a simple chat application using the React integration and OpenAI adapter.

## Installation

```bash
npm install @tanstack/ai @tanstack/ai-react @tanstack/ai-openai
# or
pnpm add @tanstack/ai @tanstack/ai-react @tanstack/ai-openai
#or
yarn add @tanstack/ai @tanstack/ai-react @tanstack/ai-openai
```

## Server Setup

First, create an API route that handles chat requests. Here's a simplified example:

```typescript
// app/api/chat/route.ts (Next.js)
// or src/routes/api/chat.ts (TanStack Start)
import { chat, toStreamResponse } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";

export async function POST(request: Request) {
  // Check for API key
  if (!process.env.OPENAI_API_KEY) {
    return new Response(
      JSON.stringify({
        error: "OPENAI_API_KEY not configured",
      }),
      {
        status: 500,
        headers: { "Content-Type": "application/json" },
      }
    );
  }

  const { messages, conversationId } = await request.json();

  try {
    // Create a streaming chat response
    const stream = chat({
      adapter: openai(),
      messages,
      model: "gpt-4o",
      conversationId
    });

    // Convert stream to HTTP response
    return toStreamResponse(stream);
  } catch (error) {
    return new Response(
      JSON.stringify({
        error: error instanceof Error ? error.message : "An error occurred",
      }),
      {
        status: 500,
        headers: { "Content-Type": "application/json" },
      }
    );
  }
}
```

## Client Setup

To use the chat API from your React frontend, create a `Chat` component:

```typescript
// components/Chat.tsx
import { useState } from "react";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";

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

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

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (input.trim() && !isLoading) {
      sendMessage(input);
      setInput("");
    }
  };

  return (
    <div className="flex flex-col h-screen">
      {/* Messages */}
      <div className="flex-1 overflow-y-auto p-4">
        {messages.map((message) => (
          <div
            key={message.id}
            className={`mb-4 ${
              message.role === "assistant" ? "text-blue-600" : "text-gray-800"
            }`}
          >
            <div className="font-semibold mb-1">
              {message.role === "assistant" ? "Assistant" : "You"}
            </div>
            <div>
              {message.parts.map((part, idx) => {
                if (part.type === "thinking") {
                  return (
                    <div
                      key={idx}
                      className="text-sm text-gray-500 italic mb-2"
                    >
                      💭 Thinking: {part.content}
                    </div>
                  );
                }
                if (part.type === "text") {
                  return <div key={idx}>{part.content}</div>;
                }
                return null;
              })}
            </div>
          </div>
        ))}
      </div>

      {/* Input */}
      <form onSubmit={handleSubmit} className="p-4 border-t">
        <div className="flex gap-2">
          <input
            type="text"
            value={input}
            onChange={(e) => setInput(e.target.value)}
            placeholder="Type a message..."
            className="flex-1 px-4 py-2 border rounded-lg"
            disabled={isLoading}
          />
          <button
            type="submit"
            disabled={!input.trim() || isLoading}
            className="px-6 py-2 bg-blue-600 text-white rounded-lg disabled:opacity-50"
          >
            Send
          </button>
        </div>
      </form>
    </div>
  );
}
```

## Environment Variables

To connect to AI providers, set your API keys in your environment variables. Create a `.env.local` file (or `.env` depending on your setup):
```bash
# OpenAI
OPENAI_API_KEY=your-openai-api-key

# Anthropic
ANTHROPIC_API_KEY=your-anthropic-api-key

# Google Gemini
GEMINI_API_KEY=your-gemini-api-key
```

## That's It!

You now have a working chat application. The `useChat` hook handles:

- Message state management
- Streaming responses
- Loading states
- Error handling

## Using Tools

Since TanStack AI is framework-agnostic, you can define and use tools in any environment. Here’s a quick example of defining a tool and using it in a chat:

```typescript
import { toolDefinition } from '@tanstack/ai'

const getProductsDef = toolDefinition({
  name: 'getProducts',
  inputSchema: z.object({ query: z.string() }),
})

const getProducts = getProductsDef.server(async ({ query }) => {
  return await db.products.search(query)
})

chat({ tools: [getProducts] })
```

## Next Steps

- Learn about [Tools](../guides/tools) to add function calling
- Check out [Client Tools](../guides/client-tools) for frontend operations
- See the [API Reference](../api/ai) for more options


## Links discovered
- [Tools](https://github.com/tanstack/ai/blob/main/docs/guides/tools.md)
- [Client Tools](https://github.com/tanstack/ai/blob/main/docs/guides/client-tools.md)
- [API Reference](https://github.com/tanstack/ai/blob/main/docs/api/ai.md)

--- docs/reference/index.md ---
---
id: "@tanstack/ai"
title: "@tanstack/ai"
---

# @tanstack/ai

## Classes

- [BaseAdapter](classes/BaseAdapter.md)
- [BatchStrategy](classes/BatchStrategy.md)
- [CompositeStrategy](classes/CompositeStrategy.md)
- [ImmediateStrategy](classes/ImmediateStrategy.md)
- [PartialJSONParser](classes/PartialJSONParser.md)
- [PunctuationStrategy](classes/PunctuationStrategy.md)
- [StreamProcessor](classes/StreamProcessor.md)
- [ToolCallManager](classes/ToolCallManager.md)
- [WordBoundaryStrategy](classes/WordBoundaryStrategy.md)

## Interfaces

- [AgentLoopState](interfaces/AgentLoopState.md)
- [AIAdapter](interfaces/AIAdapter.md)
- [AIAdapterConfig](interfaces/AIAdapterConfig.md)
- [ApprovalRequestedStreamChunk](interfaces/ApprovalRequestedStreamChunk.md)
- [AudioPart](interfaces/AudioPart.md)
- [BaseStreamChunk](interfaces/BaseStreamChunk.md)
- [ChatCompletionChunk](interfaces/ChatCompletionChunk.md)
- [ChatOptions](interfaces/ChatOptions.md)
- [ChunkRecording](interfaces/ChunkRecording.md)
- [ChunkStrategy](interfaces/ChunkStrategy.md)
- [ClientTool](interfaces/ClientTool.md)
- [ContentPartSource](interfaces/ContentPartSource.md)
- [ContentStreamChunk](interfaces/ContentStreamChunk.md)
- [DefaultMessageMetadataByModality](interfaces/DefaultMessageMetadataByModality.md)
- [DocumentPart](interfaces/DocumentPart.md)
- [DoneStreamChunk](interfaces/DoneStreamChunk.md)
- [EmbeddingOptions](interfaces/EmbeddingOptions.md)
- [EmbeddingResult](interfaces/EmbeddingResult.md)
- [ErrorStreamChunk](interfaces/ErrorStreamChunk.md)
- [ImagePart](interfaces/ImagePart.md)
- [InternalToolCallState](interfaces/InternalToolCallState.md)
- [JSONParser](interfaces/JSONParser.md)
- [JSONSchema](interfaces/JSONSchema.md)
- [ModelMessage](interfaces/ModelMessage.md)
- [ProcessorResult](interfaces/ProcessorResult.md)
- [ProcessorState](interfaces/ProcessorState.md)
- [ResponseFormat](interfaces/ResponseFormat.md)
- [ServerTool](interfaces/ServerTool.md)
- [StreamProcessorEvents](interfaces/StreamProcessorEvents.md)
- [StreamProcessorHandlers](interfaces/StreamProcessorHandlers.md)
- [StreamProcessorOptions](interfaces/StreamProcessorOptions.md)
- [SummarizationOptions](interfaces/SummarizationOptions.md)
- [SummarizationResult](interfaces/SummarizationResult.md)
- [TextPart](interfaces/TextPart.md)
- [ThinkingPart](interfaces/ThinkingPart.md)
- [ThinkingStreamChunk](interfaces/ThinkingStreamChunk.md)
- [Tool](interfaces/Tool.md)
- [ToolCall](interfaces/ToolCall.md)
- [ToolCallPart](interfaces/ToolCallPart.md)
- [ToolCallStreamChunk](interfaces/ToolCallStreamChunk.md)
- [ToolConfig](interfaces/ToolConfig.md)
- [ToolDefinition](interfaces/ToolDefinition.md)
- [ToolDefinitionConfig](interfaces/ToolDefinitionConfig.md)
- [ToolDefinitionInstance](interfaces/ToolDefinitionInstance.md)
- [ToolInputAvailableStreamChunk](interfaces/ToolInputAvailableStreamChunk.md)
- [ToolResultPart](interfaces/ToolResultPart.md)
- [ToolResultStreamChunk](interfaces/ToolResultStreamChunk.md)
- [UIMessage](interfaces/UIMessage.md)
- [VideoPart](interfaces/VideoPart.md)

## Type Aliases

- [AgentLoopStrategy](type-aliases/AgentLoopStrategy.md)
- [AnyClientTool](type-aliases/AnyClientTool.md)
- [ChatStreamOptionsForModel](type-aliases/ChatStreamOptionsForModel.md)
- [ChatStreamOptionsUnion](type-aliases/ChatStreamOptionsUnion.md)
- [ConstrainedContent](type-aliases/ConstrainedContent.md)
- [ConstrainedModelMessage](type-aliases/ConstrainedModelMessage.md)
- [ContentPart](type-aliases/ContentPart.md)
- [ContentPartForModalities](type-aliases/ContentPartForModalities.md)
- [ExtractModalitiesForModel](type-aliases/ExtractModalitiesForModel.md)
- [ExtractModelsFromAdapter](type-aliases/ExtractModelsFromAdapter.md)
- [InferSchemaType](type-aliases/InferSchemaType.md)
- [InferToolInput](type-aliases/InferToolInput.md)
- [InferToolName](type-aliases/InferToolName.md)
- [InferToolOutput](type-aliases/InferToolOutput.md)
- [MessagePart](type-aliases/MessagePart.md)
- [ModalitiesArrayToUnion](type-aliases/ModalitiesArrayToUnion.md)
- [Modality](type-aliases/Modality.md)
- [SchemaInput](type-aliases/SchemaInput.md)
- [StreamChunk](type-aliases/StreamChunk.md)
- [StreamChunkType](type-aliases/StreamChunkType.md)
- [ToolCallState](type-aliases/ToolCallState.md)
- [ToolResultState](type-aliases/ToolResultState.md)

## Variables

- [aiEventClient](variables/aiEventClient.md)
- [defaultJSONParser](variables/defaultJSONParser.md)

## Functions

- [chat](functions/chat.md)
- [chatOptions](functions/chatOptions.md)
- [combineStrategies](functions/combineStrategies.md)
- [convertMessagesToModelMessages](functions/convertMessagesToModelMessages.md)
- [convertZodToJsonSchema](functions/convertZodToJsonSchema.md)
- [createReplayStream](functions/createReplayStream.md)
- [embedding](functions/embedding.md)
- [generateMessageId](functions/generateMessageId.md)
- [maxIterations](functions/maxIterations.md)
- [messages](functions/messages.md)
- [modelMessagesToUIMessages](functions/modelMessagesToUIMessages.md)
- [modelMessageToUIMessage](functions/modelMessageToUIMessage.md)
- [normalizeToUIMessage](functions/normalizeToUIMessage.md)
- [parsePartialJSON](functions/parsePartialJSON.md)
- [summarize](functions/summarize.md)
- [toolDefinition](functions/toolDefinition.md)
- [toServerSentEventsStream](functions/toServerSentEventsStream.md)
- [toStreamResponse](functions/toStreamResponse.md)
- [uiMessageToModelMessages](functions/uiMessageToModelMessages.md)
- [untilFinishReason](functions/untilFinishReason.md)


## Links discovered
- [BaseAdapter](https://github.com/tanstack/ai/blob/main/docs/reference/classes/BaseAdapter.md)
- [BatchStrategy](https://github.com/tanstack/ai/blob/main/docs/reference/classes/BatchStrategy.md)
- [CompositeStrategy](https://github.com/tanstack/ai/blob/main/docs/reference/classes/CompositeStrategy.md)
- [ImmediateStrategy](https://github.com/tanstack/ai/blob/main/docs/reference/classes/ImmediateStrategy.md)
- [PartialJSONParser](https://github.com/tanstack/ai/blob/main/docs/reference/classes/PartialJSONParser.md)
- [PunctuationStrategy](https://github.com/tanstack/ai/blob/main/docs/reference/classes/PunctuationStrategy.md)
- [StreamProcessor](https://github.com/tanstack/ai/blob/main/docs/reference/classes/StreamProcessor.md)
- [ToolCallManager](https://github.com/tanstack/ai/blob/main/docs/reference/classes/ToolCallManager.md)
- [WordBoundaryStrategy](https://github.com/tanstack/ai/blob/main/docs/reference/classes/WordBoundaryStrategy.md)
- [AgentLoopState](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/AgentLoopState.md)
- [AIAdapter](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/AIAdapter.md)
- [AIAdapterConfig](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/AIAdapterConfig.md)
- [ApprovalRequestedStreamChunk](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ApprovalRequestedStreamChunk.md)
- [AudioPart](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/AudioPart.md)
- [BaseStreamChunk](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/BaseStreamChunk.md)
- [ChatCompletionChunk](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ChatCompletionChunk.md)
- [ChatOptions](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ChatOptions.md)
- [ChunkRecording](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ChunkRecording.md)
- [ChunkStrategy](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ChunkStrategy.md)
- [ClientTool](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ClientTool.md)
- [ContentPartSource](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ContentPartSource.md)
- [ContentStreamChunk](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ContentStreamChunk.md)
- [DefaultMessageMetadataByModality](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/DefaultMessageMetadataByModality.md)
- [DocumentPart](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/DocumentPart.md)
- [DoneStreamChunk](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/DoneStreamChunk.md)
- [EmbeddingOptions](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/EmbeddingOptions.md)
- [EmbeddingResult](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/EmbeddingResult.md)
- [ErrorStreamChunk](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ErrorStreamChunk.md)
- [ImagePart](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ImagePart.md)
- [InternalToolCallState](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/InternalToolCallState.md)
- [JSONParser](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/JSONParser.md)
- [JSONSchema](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/JSONSchema.md)
- [ModelMessage](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ModelMessage.md)
- [ProcessorResult](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ProcessorResult.md)
- [ProcessorState](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ProcessorState.md)
- [ResponseFormat](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ResponseFormat.md)
- [ServerTool](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ServerTool.md)
- [StreamProcessorEvents](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/StreamProcessorEvents.md)
- [StreamProcessorHandlers](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/StreamProcessorHandlers.md)
- [StreamProcessorOptions](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/StreamProcessorOptions.md)
- [SummarizationOptions](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/SummarizationOptions.md)
- [SummarizationResult](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/SummarizationResult.md)
- [TextPart](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/TextPart.md)
- [ThinkingPart](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ThinkingPart.md)
- [ThinkingStreamChunk](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ThinkingStreamChunk.md)
- [Tool](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/Tool.md)
- [ToolCall](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ToolCall.md)
- [ToolCallPart](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ToolCallPart.md)
- [ToolCallStreamChunk](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ToolCallStreamChunk.md)
- [ToolConfig](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ToolConfig.md)
- [ToolDefinition](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ToolDefinition.md)
- [ToolDefinitionConfig](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ToolDefinitionConfig.md)
- [ToolDefinitionInstance](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ToolDefinitionInstance.md)
- [ToolInputAvailableStreamChunk](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ToolInputAvailableStreamChunk.md)
- [ToolResultPart](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ToolResultPart.md)
- [ToolResultStreamChunk](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/ToolResultStreamChunk.md)
- [UIMessage](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/UIMessage.md)
- [VideoPart](https://github.com/tanstack/ai/blob/main/docs/reference/interfaces/VideoPart.md)
- [AgentLoopStrategy](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/AgentLoopStrategy.md)
- [AnyClientTool](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/AnyClientTool.md)
- [ChatStreamOptionsForModel](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/ChatStreamOptionsForModel.md)
- [ChatStreamOptionsUnion](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/ChatStreamOptionsUnion.md)
- [ConstrainedContent](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/ConstrainedContent.md)
- [ConstrainedModelMessage](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/ConstrainedModelMessage.md)
- [ContentPart](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/ContentPart.md)
- [ContentPartForModalities](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/ContentPartForModalities.md)
- [ExtractModalitiesForModel](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/ExtractModalitiesForModel.md)
- [ExtractModelsFromAdapter](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/ExtractModelsFromAdapter.md)
- [InferSchemaType](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/InferSchemaType.md)
- [InferToolInput](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/InferToolInput.md)
- [InferToolName](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/InferToolName.md)
- [InferToolOutput](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/InferToolOutput.md)
- [MessagePart](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/MessagePart.md)
- [ModalitiesArrayToUnion](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/ModalitiesArrayToUnion.md)
- [Modality](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/Modality.md)
- [SchemaInput](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/SchemaInput.md)
- [StreamChunk](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/StreamChunk.md)
- [StreamChunkType](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/StreamChunkType.md)
- [ToolCallState](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/ToolCallState.md)
- [ToolResultState](https://github.com/tanstack/ai/blob/main/docs/reference/type-aliases/ToolResultState.md)
- [aiEventClient](https://github.com/tanstack/ai/blob/main/docs/reference/variables/aiEventClient.md)
- [defaultJSONParser](https://github.com/tanstack/ai/blob/main/docs/reference/variables/defaultJSONParser.md)
- [chat](https://github.com/tanstack/ai/blob/main/docs/reference/functions/chat.md)
- [chatOptions](https://github.com/tanstack/ai/blob/main/docs/reference/functions/chatOptions.md)
- [combineStrategies](https://github.com/tanstack/ai/blob/main/docs/reference/functions/combineStrategies.md)
- [convertMessagesToModelMessages](https://github.com/tanstack/ai/blob/main/docs/reference/functions/convertMessagesToModelMessages.md)
- [convertZodToJsonSchema](https://github.com/tanstack/ai/blob/main/docs/reference/functions/convertZodToJsonSchema.md)
- [createReplayStream](https://github.com/tanstack/ai/blob/main/docs/reference/functions/createReplayStream.md)
- [embedding](https://github.com/tanstack/ai/blob/main/docs/reference/functions/embedding.md)
- [generateMessageId](https://github.com/tanstack/ai/blob/main/docs/reference/functions/generateMessageId.md)
- [maxIterations](https://github.com/tanstack/ai/blob/main/docs/reference/functions/maxIterations.md)
- [messages](https://github.com/tanstack/ai/blob/main/docs/reference/functions/messages.md)
- [modelMessagesToUIMessages](https://github.com/tanstack/ai/blob/main/docs/reference/functions/modelMessagesToUIMessages.md)
- [modelMessageToUIMessage](https://github.com/tanstack/ai/blob/main/docs/reference/functions/modelMessageToUIMessage.md)
- [normalizeToUIMessage](https://github.com/tanstack/ai/blob/main/docs/reference/functions/normalizeToUIMessage.md)
- [parsePartialJSON](https://github.com/tanstack/ai/blob/main/docs/reference/functions/parsePartialJSON.md)
- [summarize](https://github.com/tanstack/ai/blob/main/docs/reference/functions/summarize.md)
- [toolDefinition](https://github.com/tanstack/ai/blob/main/docs/reference/functions/toolDefinition.md)
- [toServerSentEventsStream](https://github.com/tanstack/ai/blob/main/docs/reference/functions/toServerSentEventsStream.md)
- [toStreamResponse](https://github.com/tanstack/ai/blob/main/docs/reference/functions/toStreamResponse.md)

--- scripts/generate-docs.ts ---
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { generateReferenceDocs } from '@tanstack/typedoc-config'

const __dirname = fileURLToPath(new URL('.', import.meta.url))

/** @type {import('@tanstack/typedoc-config').Package[]} */
const packages = [
  {
    name: 'ai',
    entryPoints: [
      resolve(__dirname, '../packages/typescript/ai/src/index.ts').replaceAll(
        '\\',
        '/',
      ),
    ],
    tsconfig: resolve(
      __dirname,
      '../packages/typescript/ai/tsconfig.docs.json',
    ).replaceAll('\\', '/'),
    outputDir: resolve(__dirname, '../docs/reference').replaceAll('\\', '/'),
    exclude: [
      '**/*.spec.ts',
      '**/*.test.ts',
      '**/__tests__/**',
      '**/node_modules/**',
      '**/dist/**',
    ],
  },
]

await generateReferenceDocs({ packages })

console.log('\n✅ All markdown files have been processed!')

process.exit(0)


--- docs/guides/agentic-cycle.md ---
---
title: Agentic Cycle
id: agentic-cycle
---

The agentic cycle is the pattern where the LLM repeatedly calls tools, receives results, and continues reasoning until it can provide a final answer. This enables complex multi-step operations.

```mermaid
graph TD
    A[User sends message] --> B[LLM analyzes request]
    B --> C{Does task need tools?}
    C -->|No| D[Generate text response]
    C -->|Yes| E[Call appropriate tool]
    E --> F{Where does<br/>tool execute?}
    F -->|Server| G[Execute on server]
    F -->|Client| H[Execute on client]
    G --> I[Tool returns result]
    H --> I
    I --> J[Add result to conversation]
    J --> K[LLM analyzes result]
    K --> L{Task complete?}
    L -->|No| E
    L -->|Yes| D
    D --> M[Stream response to user]
    M --> N[Done]
    
    style E fill:#e1f5ff
    style G fill:#ffe1e1
    style H fill:#ffe1e1
    style L fill:#fff4e1
```

### Detailed Agentic Flow

```mermaid
sequenceDiagram
    participant User
    participant Client
    participant Server
    participant LLM
    participant Tools
    
    User->>Client: "What's the weather in SF and LA?"
    Client->>Server: Send message
    Server->>LLM: Message + tool definitions
    
    Note over LLM: Cycle 1: Call first tool
    
    LLM->>Server: tool_call: get_weather(SF)
    Server->>Tools: Execute get_weather
    Tools-->>Server: {temp: 65, conditions: "sunny"}
    Server->>LLM: tool_result
    
    Note over LLM: Cycle 2: Call second tool
    
    LLM->>Server: tool_call: get_weather(LA)
    Server->>Tools: Execute get_weather
    Tools-->>Server: {temp: 75, conditions: "clear"}
    Server->>LLM: tool_result
    
    Note over LLM: Cycle 3: Generate answer
    
    LLM-->>Server: content: "SF is 65°F..."
    Server-->>Client: Stream response
    Client->>User: Display answer
```

### Multi-Step Example

Here's a real-world example of the agentic cycle:

**User**: "Find me flights to Paris under $500 and book the cheapest one"

**Cycle 1**: LLM calls `searchFlights({destination: "Paris", maxPrice: 500})`
- Tool returns: `[{id: "F1", price: 450}, {id: "F2", price: 480}]`

**Cycle 2**: LLM analyzes results and calls `bookFlight({flightId: "F1"})`
- Tool requires approval (sensitive operation)
- User approves
- Tool returns: `{bookingId: "B123", confirmed: true}`

**Cycle 3**: LLM generates final response
- "I found 2 flights under $500. I've booked the cheapest one (Flight F1) for $450. Your booking ID is B123."

### Code Example: Agentic Weather Assistant

```typescript
// Tool definitions
const getWeatherDef = toolDefinition({
  name: "get_weather",
  description: "Get current weather for a city",
  inputSchema: z.object({
    city: z.string(),
  }),
});

const getClothingAdviceDef = toolDefinition({
  name: "get_clothing_advice",
  description: "Get clothing recommendations based on weather",
  inputSchema: z.object({
    temperature: z.number(),
    conditions: z.string(),
  }),
});

// Server implementations
const getWeather = getWeatherDef.server(async ({ city }) => {
  const response = await fetch(`https://api.weather.com/v1/${city}`);
  return await response.json();
});

const getClothingAdvice = getClothingAdviceDef.server(async ({ temperature, conditions }) => {
  // Business logic for clothing recommendations
  if (temperature < 50) {
    return { recommendation: "Wear a warm jacket" };
  }
  return { recommendation: "Light clothing is fine" };
});

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

  const stream = chat({
    adapter: openai(),
    messages,
    model: "gpt-4o",
    tools: [getWeather, getClothingAdvice],
  });

  return toStreamResponse(stream);
}
```

**User**: "What should I wear in San Francisco today?"

**Agentic Cycle**:
1. LLM calls `get_weather({city: "San Francisco"})` → Returns `{temp: 62, conditions: "cloudy"}`
2. LLM calls `get_clothing_advice({temperature: 62, conditions: "cloudy"})` → Returns `{recommendation: "Light jacket recommended"}`
3. LLM generates: "The weather in San Francisco is 62°F and cloudy. I recommend wearing a light jacket."

--- docs/api/ai.md ---
---
title: TanStack AI Core API
id: tanstack-ai-api
---

The core AI library for TanStack AI.

## Installation

```bash
npm install @tanstack/ai
```

## `chat(options)`

Creates a streaming chat response.

```typescript
import { chat } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";

const stream = chat({
  adapter: openai(),
  messages: [{ role: "user", content: "Hello!" }],
  model: "gpt-4o",
  tools: [myTool],
  systemPrompts: ["You are a helpful assistant"],
  agentLoopStrategy: maxIterations(20),
});
```

### Parameters

- `adapter` - An AI adapter instance (e.g., `openai()`, `anthropic()`)
- `messages` - Array of chat messages
- `model` - Model identifier (type-safe based on adapter)
- `tools?` - Array of tools for function calling
- `systemPrompts?` - System prompts to prepend to messages
- `agentLoopStrategy?` - Strategy for agent loops (default: `maxIterations(5)`)
- `abortController?` - AbortController for cancellation
- `providerOptions?` - Provider-specific options

### Returns

An async iterable of `StreamChunk`.

## `summarize(options)`

Creates a text summarization.

```typescript
import { summarize } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";

const result = await summarize({
  adapter: openai(),
  model: "gpt-4o",
  text: "Long text to summarize...",
  maxLength: 100,
  style: "concise",
});
```

### Parameters

- `adapter` - An AI adapter instance
- `model` - Model identifier (type-safe based on adapter)
- `text` - Text to summarize
- `maxLength?` - Maximum length of summary
- `style?` - Summary style ("concise" | "detailed")

### Returns

A `SummarizationResult` with the summary text.

## `embedding(options)`

Creates embeddings for text input.

```typescript
import { embedding } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";

const result = await embedding({
  adapter: openai(),
  model: "text-embedding-3-small",
  input: "Text to embed",
});
```

### Parameters

- `adapter` - An AI adapter instance
- `model` - Embedding model identifier (type-safe based on adapter)
- `input` - Text or array of texts to embed

### Returns

An `EmbeddingResult` with embeddings array.

## `toolDefinition(config)`

Creates an isomorphic tool definition that can be instantiated for server or client execution.

```typescript
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";

const myToolDef = toolDefinition({
  name: "my_tool",
  description: "Tool description",
  inputSchema: z.object({
    param: z.string(),
  }),
  outputSchema: z.object({
    result: z.string(),
  }),
  needsApproval: false, // Optional
});

// Or create client implementation
const myClientTool = myToolDef.client(async ({ param }) => {
  // Client-side implementation
  return { result: "..." };
});

// Use directly in chat() (server-side, no execute)
chat({
  tools: [myToolDef],
  // ...
});

// Or create server implementation
const myServerTool = myToolDef.server(async ({ param }) => {
  // Server-side implementation
  return { result: "..." };
});

// Use directly in chat() (server-side, no execute)
chat({
  tools: [myServerTool],
  // ...
});
```

### Parameters

- `name` - Tool name (must be unique)
- `description` - Tool description for the model
- `inputSchema` - Zod schema for input validation
- `outputSchema?` - Zod schema for output validation
- `needsApproval?` - Whether tool requires user approval
- `metadata?` - Additional metadata

### Returns

A `ToolDefinition` object with `.server()` and `.client()` methods for creating concrete implementations.

## `toServerSentEventsStream(stream, abortController?)`

Converts a stream to a ReadableStream in Server-Sent Events format.

```typescript
import { toServerSentEventsStream, chat } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";

const stream = chat({
  adapter: openai(),
  messages: [...],
  model: "gpt-4o",
});
const readableStream = toServerSentEventsStream(stream);
```

### Parameters

- `stream` - Async iterable of `StreamChunk`
- `abortController?` - Optional AbortController to abort when stream is cancelled

### Returns

A `ReadableStream<Uint8Array>` in Server-Sent Events format. Each chunk is:
- Prefixed with `"data: "`
- Followed by `"\n\n"`
- Stream ends with `"data: [DONE]\n\n"`

## `toStreamResponse(stream, init?)`

Converts a stream to an HTTP Response with proper SSE headers.

```typescript
import { toStreamResponse, chat } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";

const stream = chat({
  adapter: openai(),
  messages: [...],
  model: "gpt-4o",
});
return toStreamResponse(stream);
```

### Parameters

- `stream` - Async iterable of `StreamChunk`
- `init?` - Optional ResponseInit options (including `abortController`)

### Returns

A `Response` object suitable for HTTP endpoints with SSE headers (`Content-Type: text/event-stream`, `Cache-Control: no-cache`, `Connection: keep-alive`).

## `maxIterations(count)`

Creates an agent loop strategy that limits iterations.

```typescript
import { maxIterations, chat } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";

const stream = chat({
  adapter: openai(),
  messages: [...],
  model: "gpt-4o",
  agentLoopStrategy: maxIterations(20),
});
```

### Parameters

- `count` - Maximum number of tool execution iterations

### Returns

An `AgentLoopStrategy` object.

## Types

### `ModelMessage`

```typescript
interface ModelMessage {
  role: "user" | "assistant" | "system" | "tool";
  content: string;
  toolCallId?: string;
}
```

### `StreamChunk`

```typescript
type StreamChunk =
  | ContentStreamChunk
  | ThinkingStreamChunk
  | ToolCallStreamChunk
  | ToolResultStreamChunk
  | DoneStreamChunk
  | ErrorStreamChunk;

interface ThinkingStreamChunk {
  type: "thinking";
  id: string;
  model: string;
  timestamp: number;
  delta?: string; // Incremental thinking token
  content: string; // Accumulated thinking content
}
```

Stream chunks represent different types of data in the stream:

- **Content chunks** - Text content being generated
- **Thinking chunks** - Model's reasoning process (when supported by the model)
- **Tool call chunks** - When the model calls a tool
- **Tool result chunks** - Results from tool execution
- **Done chunks** - Stream completion
- **Error chunks** - Stream errors

### `Tool`

```typescript
interface Tool {
  type: "function";
  function: {
    name: string;
    description: string;
    parameters: Record<string, any>;
  };
  execute?: (args: any) => Promise<any> | any;
  needsApproval?: boolean;
}
```

## Usage Examples

```typescript
import { chat, summarize, embedding } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";

const adapter = openai();

// Streaming chat
const stream = chat({
  adapter,
  messages: [{ role: "user", content: "Hello!" }],
  model: "gpt-4o",
});

// Summarization
const summary = await summarize({
  adapter,
  model: "gpt-4o",
  text: "Long text to summarize...",
  maxLength: 100,
});

// Embeddings
const embeddings = await embedding({
  adapter,
  model: "text-embedding-3-small",
  input: "Text to embed",
});
```

## Next Steps

- [Getting Started](../getting-started/quick-start) - Learn the basics
- [Tools Guide](../guides/tools) - Learn about tools
- [Adapters](../adapters/openai) - Explore adapter options


## Links discovered
- [Getting Started](https://github.com/tanstack/ai/blob/main/docs/getting-started/quick-start.md)
- [Tools Guide](https://github.com/tanstack/ai/blob/main/docs/guides/tools.md)
- [Adapters](https://github.com/tanstack/ai/blob/main/docs/adapters/openai.md)

--- docs/api/ai-client.md ---
---
title: TanStack AI Client API
slug: /api/ai-client
---

Framework-agnostic headless client for managing chat state and streaming.

## Installation

```bash
npm install @tanstack/ai-client
```

## `ChatClient`

The main client class for managing chat state.

```typescript
import { ChatClient, fetchServerSentEvents } from "@tanstack/ai-client";

const client = new ChatClient({
  connection: fetchServerSentEvents("/api/chat"),
  initialMessages: [],
  onMessagesChange: (messages) => {
    console.log("Messages updated:", messages);
  },
  onToolCall: async ({ toolName, input }) => {
    // Handle client tool execution
    return { result: "..." };
  },
});
```

### Constructor Options

- `connection` - Connection adapter for streaming
- `initialMessages?` - Initial messages array
- `id?` - Unique identifier for this chat instance
- `body?` - Additional body parameters to send
- `onResponse?` - Callback when response is received
- `onChunk?` - Callback when stream chunk is received
- `onFinish?` - Callback when response finishes
- `onError?` - Callback when error occurs
- `onMessagesChange?` - Callback when messages change
- `onLoadingChange?` - Callback when loading state changes
- `onErrorChange?` - Callback when error state changes
- `onToolCall?` - Callback for client-side tool execution
- `streamProcessor?` - Stream processing configuration

### Methods

#### `sendMessage(content: string)`

Sends a user message and gets a response.

```typescript
await client.sendMessage("Hello!");
```

#### `append(message: ModelMessage | UIMessage)`

Appends a message to the conversation.

```typescript
await client.append({
  role: "user",
  content: "Additional context",
});
```

#### `reload()`

Reloads the last assistant message.

```typescript
await client.reload();
```

#### `stop()`

Stops the current response generation.

```typescript
client.stop();
```

#### `clear()`

Clears all messages.

```typescript
client.clear();
```

#### `setMessagesManually(messages: UIMessage[])`

Manually sets the messages array.

```typescript
client.setMessagesManually([...newMessages]);
```

#### `addToolResult(result)`

Adds the result of a client-side tool execution.

```typescript
await client.addToolResult({
  toolCallId: "call_123",
  tool: "toolName",
  output: { result: "..." },
  state: "output-available",
});
```

#### `addToolApprovalResponse(response)`

Responds to a tool approval request.

```typescript
await client.addToolApprovalResponse({
  id: "approval_123",
  approved: true,
});
```

### Properties

- `messages: UIMessage[]` - Current messages
- `isLoading: boolean` - Whether a response is being generated
- `error: Error | undefined` - Current error, if any

## Connection Adapters

### `fetchServerSentEvents(url, options?)`

Creates an SSE connection adapter.

```typescript
import { fetchServerSentEvents } from "@tanstack/ai-client";

const adapter = fetchServerSentEvents("/api/chat", {
  headers: {
    Authorization: "Bearer token",
  },
});
```

### `fetchHttpStream(url, options?)`

Creates an HTTP stream connection adapter.

```typescript
import { fetchHttpStream } from "@tanstack/ai-client";

const adapter = fetchHttpStream("/api/chat");
```

### `stream(connectFn)`

Creates a custom connection adapter.

```typescript
import { stream } from "@tanstack/ai-client";

const adapter = stream(async (messages, data, signal) => {
  // Custom implementation
  const response = await fetch("/api/chat", {
    method: "POST",
    body: JSON.stringify({ messages, ...data }),
    signal,
  });
  return processStream(response);
});
```

## Helper Functions

### `clientTools(...tools)`

Creates a typed array of client tools with proper type inference. This eliminates the need for `as const` when defining tool arrays and enables proper discriminated union type narrowing.

```typescript
import { clientTools } from "@tanstack/ai-client";
import { myTool1, myTool2 } from "./tools";

// Create client implementations
const tool1Client = myTool1.client((input) => {
  // Implementation
  return { result: "..." };
});

const tool2Client = myTool2.client((input) => {
  // Implementation
  return { result: "..." };
});

// Create typed tools array (no 'as const' needed!)
const tools = clientTools(tool1Client, tool2Client);

// Now when you use these tools in chat options:
const chatOptions = createChatClientOptions({
  connection: fetchServerSentEvents("/api/chat"),
  tools, // Fully typed with literal tool names
});

// In your component:
messages.forEach((message) => {
  message.parts.forEach((part) => {
    if (part.type === "tool-call" && part.name === "myTool1") {
      // ✅ TypeScript knows part.name is literally "myTool1"
      // ✅ part.input is typed from myTool1's input schema
      // ✅ part.output is typed from myTool1's output schema
    }
  });
});
```

### `createChatClientOptions(options)`

Helper function to create typed chat client options with proper type inference.

```typescript
import { createChatClientOptions, clientTools } from "@tanstack/ai-client";

const tools = clientTools(tool1, tool2);

const chatOptions = createChatClientOptions({
  connection: fetchServerSentEvents("/api/chat"),
  tools,
});

// Use InferChatMessages to extract message types
type ChatMessages = InferChatMessages<typeof chatOptions>;
```

## Types

### `UIMessage`

```typescript
interface UIMessage {
  id: string;
  role: "user" | "assistant";
  parts: MessagePart[];
  createdAt?: Date;
}
```

### `MessagePart`

```typescript
type MessagePart = TextPart | ThinkingPart | ToolCallPart | ToolResultPart;
```

### `TextPart`

```typescript
interface TextPart {
  type: "text";
  content: string;
}
```

### `ThinkingPart`

```typescript
interface ThinkingPart {
  type: "thinking";
  content: string;
}
```

Thinking parts represent the model's internal reasoning process. They are typically displayed in a collapsible format and automatically collapse when the response text appears. Thinking parts are UI-only and are not sent back to the model in subsequent requests.

**Note:** Thinking parts are only available when using models that support reasoning/thinking (e.g., Anthropic Claude with thinking enabled, OpenAI GPT-5 with reasoning enabled).

### `ToolCallPart`

```typescript
interface ToolCallPart {
  type: "tool-call";
  id: string;
  name: string;
  arguments: string; // JSON string (may be incomplete during streaming)
  input?: any; // Parsed tool input (typed from tool's inputSchema)
  state: ToolCallState;
  approval?: ApprovalRequest;
  output?: any; // Tool execution output (typed from tool's outputSchema)
}
```

When using typed tools with `clientTools()` and `createChatClientOptions()`, the `input` and `output` fields are automatically typed based on your tool's Zod schemas, and `name` becomes a discriminated union enabling type narrowing.

### `ToolResultPart`

```typescript
interface ToolResultPart {
  type: "tool-result";
  id: string;
  toolCallId: string;
  tool: string;
  output: any;
  state: ToolResultState;
  errorText?: string;
}
```

### `ToolCallState`

```typescript
type ToolCallState =
  | "pending"
  | "approval-requested"
  | "executing"
  | "output-available"
  | "output-error"
  | "cancelled";
```

### `ToolResultState`

```typescript
type ToolResultState =
  | "pending"
  | "executing"
  | "output-available"
  | "output-error";
```

## Stream Processing

Configure stream processing with chunk strategies:

```typescript
import { ImmediateStrategy, fetchServerSentEvents } from "@tanstack/ai-client";

const client = new ChatClient({
  connection: fetchServerSentEvents("/api/chat"),
  streamProcessor: {
    chunkStrategy: new ImmediateStrategy(), // Emit every chunk
  },
});
```

## Next Steps

- [Getting Started](../getting-started/quick-start) - Learn the basics
- [Connection Adapters](../guides/connection-adapters) - Learn about adapters
- [@tanstack/ai-react API](./ai-react) - React hooks wrapper


## Links discovered
- [Getting Started](https://github.com/tanstack/ai/blob/main/docs/getting-started/quick-start.md)
- [Connection Adapters](https://github.com/tanstack/ai/blob/main/docs/guides/connection-adapters.md)
- [@tanstack/ai-react API](https://github.com/tanstack/ai/blob/main/docs/api/ai-react.md)

--- docs/api/ai-react.md ---
---
title: TanStack AI React API
slug: /api/ai-react
---

React hooks for TanStack AI, providing convenient React bindings for the headless client.

## Installation

```bash
npm install @tanstack/ai-react
```

## `useChat(options?)`

Main hook for managing chat state in React with full type safety.

```typescript
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
import { 
  clientTools, 
  createChatClientOptions, 
  type InferChatMessages 
} from "@tanstack/ai-client";

function ChatComponent() {
  // Create client tool implementations
  const updateUI = updateUIDef.client((input) => {
    setNotification(input.message);
    return { success: true };
  });

  // Create typed tools array (no 'as const' needed!)
  const tools = clientTools(updateUI);

  const chatOptions = createChatClientOptions({
    connection: fetchServerSentEvents("/api/chat"),
    tools,
  });

  // Fully typed messages!
  type ChatMessages = InferChatMessages<typeof chatOptions>;

  const { messages, sendMessage, isLoading, error, addToolApprovalResponse } =
    useChat(chatOptions);

  return <div>{/* Chat UI with typed messages */}</div>;
}
```

### Options

Extends `ChatClientOptions` from `@tanstack/ai-client`:

- `connection` - Connection adapter (required)
- `tools?` - Array of client tool implementations (with `.client()` method)
- `initialMessages?` - Initial messages array
- `id?` - Unique identifier for this chat instance
- `body?` - Additional body parameters to send
- `onResponse?` - Callback when response is received
- `onChunk?` - Callback when stream chunk is received
- `onFinish?` - Callback when response finishes
- `onError?` - Callback when error occurs
- `streamProcessor?` - Stream processing configuration

**Note:** Client tools are now automatically executed - no `onToolCall` callback needed!

### Returns

```typescript
interface UseChatReturn {
  messages: UIMessage[];
  sendMessage: (content: string) => Promise<void>;
  append: (message: ModelMessage | UIMessage) => Promise<void>;
  addToolResult: (result: {
    toolCallId: string;
    tool: string;
    output: any;
    state?: "output-available" | "output-error";
    errorText?: string;
  }) => Promise<void>;
  addToolApprovalResponse: (response: {
    id: string;
    approved: boolean;
  }) => Promise<void>;
  reload: () => Promise<void>;
  stop: () => void;
  isLoading: boolean;
  error: Error | undefined;
  setMessages: (messages: UIMessage[]) => void;
  clear: () => void;
}
```

## Connection Adapters

Re-exported from `@tanstack/ai-client` for convenience:

```typescript
import {
  fetchServerSentEvents,
  fetchHttpStream,
  stream,
  type ConnectionAdapter,
} from "@tanstack/ai-react";
```

## Example: Basic Chat

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

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

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

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (input.trim() && !isLoading) {
      sendMessage(input);
      setInput("");
    }
  };

  return (
    <div>
      <div>
        {messages.map((message) => (
          <div key={message.id}>
            <strong>{message.role}:</strong>
            {message.parts.map((part, idx) => {
              if (part.type === "thinking") {
                return (
                  <div key={idx} className="text-sm text-gray-500 italic">
                    💭 Thinking: {part.content}
                  </div>
                );
              }
              if (part.type === "text") {
                return <span key={idx}>{part.content}</span>;
              }
              return null;
            })}
          </div>
        ))}
      </div>
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          disabled={isLoading}
        />
        <button type="submit" disabled={isLoading}>
          Send
        </button>
      </form>
    </div>
  );
}
```

## Example: Tool Approval

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

export function ChatWithApproval() {
  const { messages, sendMessage, addToolApprovalResponse } = useChat({
    connection: fetchServerSentEvents("/api/chat"),
  });

  return (
    <div>
      {messages.map((message) =>
        message.parts.map((part) => {
          if (
            part.type === "tool-call" &&
            part.state === "approval-requested" &&
            part.approval
          ) {
            return (
              <div key={part.id}>
                <p>Approve: {part.name}</p>
                <button
                  onClick={() =>
                    addToolApprovalResponse({
                      id: part.approval!.id,
                      approved: true,
                    })
                  }
                >
                  Approve
                </button>
                <button
                  onClick={() =>
                    addToolApprovalResponse({
                      id: part.approval!.id,
                      approved: false,
                    })
                  }
                >
                  Deny
                </button>
              </div>
            );
          }
          return null;
        })
      )}
    </div>
  );
}
```

## Example: Client Tools with Type Safety

```typescript
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
import { 
  clientTools, 
  createChatClientOptions, 
  type InferChatMessages 
} from "@tanstack/ai-client";
import { updateUIDef, saveToStorageDef } from "./tool-definitions";
import { useState } from "react";

export function ChatWithClientTools() {
  const [notification, setNotification] = useState(null);

  // Create client implementations
  const updateUI = updateUIDef.client((input) => {
    // ✅ input is fully typed!
    setNotification({ message: input.message, type: input.type });
    return { success: true };
  });

  const saveToStorage = saveToStorageDef.client((input) => {
    localStorage.setItem(input.key, input.value);
    return { saved: true };
  });

  // Create typed tools array (no 'as const' needed!)
  const tools = clientTools(updateUI, saveToStorage);

  const { messages, sendMessage } = useChat({
    connection: fetchServerSentEvents("/api/chat"),
    tools, // ✅ Automatic execution, full type safety
  });

  return (
    <div>
      {messages.map((message) =>
        message.parts.map((part) => {
          if (part.type === "tool-call" && part.name === "updateUI") {
            // ✅ part.input and part.output are fully typed!
            return <div>Tool executed: {part.name}</div>;
          }
        })
      )}
    </div>
  );
}
```

## `createChatClientOptions(options)`

Helper to create typed chat options (re-exported from `@tanstack/ai-client`).

```typescript
import { 
  clientTools, 
  createChatClientOptions, 
  type InferChatMessages 
} from "@tanstack/ai-client";

// Create typed tools array (no 'as const' needed!)
const tools = clientTools(tool1, tool2);

const chatOptions = createChatClientOptions({
  connection: fetchServerSentEvents("/api/chat"),
  tools,
});

type Messages = InferChatMessages<typeof chatOptions>;
```

## Types

Re-exported from `@tanstack/ai-client`:

- `UIMessage<TTools>` - Message type with tool type parameter
- `MessagePart<TTools>` - Message part with tool type parameter
- `TextPart` - Text content part
- `ThinkingPart` - Thinking content part
- `ToolCallPart<TTools>` - Tool call part (discriminated union)
- `ToolResultPart` - Tool result part
- `ChatClientOptions<TTools>` - Chat client options
- `ConnectionAdapter` - Connection adapter interface
- `InferChatMessages<T>` - Extract message type from options

Re-exported from `@tanstack/ai`:

- `toolDefinition()` - Create isomorphic tool definition
- `ToolDefinitionInstance` - Tool definition type
- `ClientTool` - Client tool type
- `ServerTool` - Server tool type

## Next Steps

- [Getting Started](../getting-started/quick-start) - Learn the basics
- [Tools Guide](../guides/tools) - Learn about the isomorphic tool system
- [Client Tools](../guides/client-tools) - Learn about client-side tools


## Links discovered
- [Getting Started](https://github.com/tanstack/ai/blob/main/docs/getting-started/quick-start.md)
- [Tools Guide](https://github.com/tanstack/ai/blob/main/docs/guides/tools.md)
- [Client Tools](https://github.com/tanstack/ai/blob/main/docs/guides/client-tools.md)

--- docs/api/ai-solid.md ---
---
title: Tanstack AI Solid API
slug: /api/ai-solid
---

SolidJS primitives for TanStack AI, providing convenient SolidJS bindings for the headless client.

## Installation

```bash
npm install @tanstack/ai-solid
```

## `useChat(options?)`

Main primitive for managing chat state in SolidJS with full type safety.

```typescript
import { useChat, fetchServerSentEvents } from "@tanstack/ai-solid";
import { 
  clientTools, 
  createChatClientOptions, 
  type InferChatMessages 
} from "@tanstack/ai-client";

function ChatComponent() {
  // Create client tool implementations
  const updateUI = updateUIDef.client((input) => {
    setNotification(input.message);
    return { success: true };
  });

  // Create typed tools array (no 'as const' needed!)
  const tools = clientTools(updateUI);

  const chatOptions = createChatClientOptions({
    connection: fetchServerSentEvents("/api/chat"),
    tools,
  });

  // Fully typed messages!
  type ChatMessages = InferChatMessages<typeof chatOptions>;

  const { messages, sendMessage, isLoading, error, addToolApprovalResponse } =
    useChat(chatOptions);

  return <div>{/* Chat UI with typed messages */}</div>;
}
```

### Options

Extends `ChatClientOptions` from `@tanstack/ai-client`:

- `connection` - Connection adapter (required)
- `tools?` - Array of client tool implementations (with `.client()` method)
- `initialMessages?` - Initial messages array
- `id?` - Unique identifier for this chat instance
- `body?` - Additional body parameters to send
- `onResponse?` - Callback when response is received
- `onChunk?` - Callback when stream chunk is received
- `onFinish?` - Callback when response finishes
- `onError?` - Callback when error occurs
- `streamProcessor?` - Stream processing configuration

**Note:** Client tools are now automatically executed - no `onToolCall` callback needed!

### Returns

```typescript
interface UseChatReturn {
  messages: Accessor<UIMessage[]>;
  sendMessage: (content: string) => Promise<void>;
  append: (message: ModelMessage | UIMessage) => Promise<void>;
  addToolResult: (result: {
    toolCallId: string;
    tool: string;
    output: any;
    state?: "output-available" | "output-error";
    errorText?: string;
  }) => Promise<void>;
  addToolApprovalResponse: (response: {
    id: string;
    approved: boolean;
  }) => Promise<void>;
  reload: () => Promise<void>;
  stop: () => void;
  isLoading: Accessor<boolean>;
  error: Accessor<Error | undefined>;
  setMessages: (messages: UIMessage[]) => void;
  clear: () => void;
}
```

**Note:** Unlike React, `messages`, `isLoading`, and `error` are SolidJS `Accessor` functions, so you need to call them to get their values (e.g., `messages()` instead of just `messages`).

## Connection Adapters

Re-exported from `@tanstack/ai-client` for convenience:

```typescript
import {
  fetchServerSentEvents,
  fetchHttpStream,
  stream,
  type ConnectionAdapter,
} from "@tanstack/ai-solid";
```

## Example: Basic Chat

```typescript
import { createSignal, For } from "solid-js";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-solid";

export function Chat() {
  const [input, setInput] = createSignal("");

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

  const handleSubmit = (e: Event) => {
    e.preventDefault();
    if (input().trim() && !isLoading()) {
      sendMessage(input());
      setInput("");
    }
  };

  return (
    <div>
      <div>
        <For each={messages()}>
          {(message) => (
            <div>
              <strong>{message.role}:</strong>
              <For each={message.parts}>
                {(part) => {
                  if (part.type === "thinking") {
                    return (
                      <div class="text-sm text-gray-500 italic">
                        💭 Thinking: {part.content}
                      </div>
                    );
                  }
                  if (part.type === "text") {
                    return <span>{part.content}</span>;
                  }
                  return null;
                }}
              </For>
            </div>
          )}
        </For>
      </div>
      <form onSubmit={handleSubmit}>
        <input
          value={input()}
          onInput={(e) => setInput(e.currentTarget.value)}
          disabled={isLoading()}
        />
        <button type="submit" disabled={isLoading()}>
          Send
        </button>
      </form>
    </div>
  );
}
```

## Example: Tool Approval

```typescript
import { For, Show } from "solid-js";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-solid";

export function ChatWithApproval() {
  const { messages, sendMessage, addToolApprovalResponse } = useChat({
    connection: fetchServerSentEvents("/api/chat"),
  });

  return (
    <div>
      <For each={messages()}>
        {(message) => (
          <For each={message.parts}>
            {(part) => (
              <Show
                when={
                  part.type === "tool-call" &&
                  part.state === "approval-requested" &&
                  part.approval
                }
              >
                <div>
                  <p>Approve: {part.name}</p>
                  <button
                    onClick={() =>
                      addToolApprovalResponse({
                        id: part.approval!.id,
                        approved: true,
                      })
                    }
                  >
                    Approve
                  </button>
                  <button
                    onClick={() =>
                      addToolApprovalResponse({
                        id: part.approval!.id,
                        approved: false,
                      })
                    }
                  >
                    Deny
                  </button>
                </div>
              </Show>
            )}
          </For>
        )}
      </For>
    </div>
  );
}
```

## Example: Client Tools with Type Safety

```typescript
import { useChat, fetchServerSentEvents } from "@tanstack/ai-solid";
import { 
  clientTools, 
  createChatClientOptions, 
  type InferChatMessages 
} from "@tanstack/ai-client";
import { updateUIDef, saveToStorageDef } from "./tool-definitions";
import { createSignal, For } from "solid-js";

export function ChatWithClientTools() {
  const [notification, setNotification] = createSignal(null);

  // Create client implementations
  const updateUI = updateUIDef.client((input) => {
    // ✅ input is fully typed!
    setNotification({ message: input.message, type: input.type });
    return { success: true };
  });

  const saveToStorage = saveToStorageDef.client((input) => {
    localStorage.setItem(input.key, input.value);
    return { saved: true };
  });

  // Create typed tools array (no 'as const' needed!)
  const tools = clientTools(updateUI, saveToStorage);

  const { messages, sendMessage } = useChat({
    connection: fetchServerSentEvents("/api/chat"),
    tools, // ✅ Automatic execution, full type safety
  });

  return (
    <div>
      <For each={messages()}>
        {(message) => (
          <For each={message.parts}>
            {(part) => {
              if (part.type === "tool-call" && part.name === "updateUI") {
                // ✅ part.input and part.output are fully typed!
                return <div>Tool executed: {part.name}</div>;
              }
            }}
          </For>
        )}
      </For>
    </div>
  );
}
```

## `createChatClientOptions(options)`

Helper to create typed chat options (re-exported from `@tanstack/ai-client`).

```typescript
import { 
  clientTools, 
  createChatClientOptions, 
  type InferChatMessages 
} from "@tanstack/ai-client";

// Create typed tools array (no 'as const' needed!)
const tools = clientTools(tool1, tool2);

const chatOptions = createChatClientOptions({
  connection: fetchServerSentEvents("/api/chat"),
  tools,
});

type Messages = InferChatMessages<typeof chatOptions>;
```

## Types

Re-exported from `@tanstack/ai-client`:

- `UIMessage<TTools>` - Message type with tool type parameter
- `MessagePart<TTools>` - Message part with tool type parameter
- `TextPart` - Text content part
- `ThinkingPart` - Thinking content part
- `ToolCallPart<TTools>` - Tool call part (discriminated union)
- `ToolResultPart` - Tool result part
- `ChatClientOptions<TTools>` - Chat client options
- `ConnectionAdapter` - Connection adapter interface
- `InferChatMessages<T>` - Extract message type from options
- `ChatRequestBody` - Request body type

Re-exported from `@tanstack/ai`:

- `toolDefinition()` - Create isomorphic tool definition
- `ToolDefinitionInstance` - Tool definition type
- `ClientTool` - Client tool type
- `ServerTool` - Server tool type

## Next Steps

- [Getting Started](../getting-started/quick-start) - Learn the basics
- [Tools Guide](../guides/tools) - Learn about the isomorphic tool system
- [Client Tools](../guides/client-tools) - Learn about client-side tools


## Links discovered
- [Getting Started](https://github.com/tanstack/ai/blob/main/docs/getting-started/quick-start.md)
- [Tools Guide](https://github.com/tanstack/ai/blob/main/docs/guides/tools.md)
- [Client Tools](https://github.com/tanstack/ai/blob/main/docs/guides/client-tools.md)

--- examples/README.md ---
# TanStack AI Examples

This directory contains comprehensive examples demonstrating TanStack AI across multiple languages and frameworks.

## Quick Start

Choose an example based on your use case:

- **Want a full-stack TypeScript app?** → [TanStack Chat (ts-react-chat)](#tanstack-chat-ts-react-chat)
- **Need a vanilla JS frontend?** → [Vanilla Chat](#vanilla-chat)
- **Building a Python backend?** → [Python FastAPI Server](#python-fastapi-server)
- **Building a PHP backend?** → [PHP Slim Framework Server](#php-slim-framework-server)
- **Multi-User TypeScript chat app?** → [Group Chat (ts-group-chat)](#group-chat-ts-group-chat)

## TypeScript Examples

### TanStack Chat (ts-react-chat)

A full-featured chat application built with the TanStack ecosystem.

**Tech Stack:**

- TanStack Start (full-stack React framework)
- TanStack Router (type-safe routing)
- TanStack Store (state management)
- `@tanstack/ai` (AI backend)
- `@tanstack/ai-react` (React hooks)
- `@tanstack/ai-client` (headless client)

**Features:**

- ✅ Real-time streaming with OpenAI GPT-4o
- ✅ Automatic tool execution loop
- ✅ Rich markdown rendering
- ✅ Conversation management
- ✅ Modern UI with Tailwind CSS

**Getting Started:**

```bash
cd examples/ts-react-chat
pnpm install
cp env.example .env
# Edit .env and add your OPENAI_API_KEY
pnpm start
```

📖 [Full Documentation](ts-react-chat/README.md)

---

### Group Chat (ts-group-chat)

A real-time multi-user chat application with AI integration, demonstrating WebSocket-based communication and TanStack AI.

**Tech Stack:**

- TanStack Start (full-stack React framework)
- TanStack Router (type-safe routing)
- Cap'n Web RPC (bidirectional WebSocket RPC)
- `@tanstack/ai` (AI backend)
- `@tanstack/ai-anthropic` (Claude adapter)
- `@tanstack/ai-client` (headless client)
- `@tanstack/ai-react` (React hooks)

**Features:**

- ✅ Real-time multi-user chat with WebSocket
- ✅ Online presence tracking
- ✅ AI assistant (Claude) integration with queuing
- ✅ Message broadcasting to all users
- ✅ Modern chat UI (iMessage-style)
- ✅ Username-based authentication (no registration)

**Getting Started:**

```bash
cd examples/ts-group-chat
pnpm install
cp .env.example .env
# Edit .env and add your ANTHROPIC_API_KEY
pnpm dev
```

Open `http://localhost:4000` in multiple browser tabs to test multi-user functionality.

**Key Concepts:**

- **WebSocket RPC**: Uses Cap'n Web RPC for type-safe bidirectional communication
- **AI Queuing**: Claude requests are queued and processed sequentially
- **Real-time Updates**: Messages and online users update in real-time
- **Message Broadcasting**: Server broadcasts messages to all connected clients

📖 [Full Documentation](ts-group-chat/README.md)

---

### Vanilla Chat

A framework-free chat application using pure JavaScript and `@tanstack/ai-client`. Works with both PHP and Python backends.

**Tech Stack:**

- Vanilla JavaScript (no frameworks!)
- `@tanstack/ai-client` (headless client)
- Vite (dev server)
- Compatible with PHP Slim or Python FastAPI backends

**Features:**

- ✅ Pure vanilla JavaScript
- ✅ Real-time streaming messages
- ✅ Beautiful, responsive UI
- ✅ No framework dependencies
- ✅ Works with multiple backend languages

**Getting Started:**

**Option 1: With Python Backend**

```bash
# Start the Python backend first
cd examples/python-fastapi
python anthropic-server.py

# Then start the frontend
cd examples/vanilla-chat
pnpm install
pnpm start
```

**Option 2: With PHP Backend**

```bash
# Start the PHP backend and UI together
cd examples/php-slim
pnpm install
composer install
cp env.example .env
# Edit .env and add your ANTHROPIC_API_KEY
pnpm start
```

Open `http://localhost:3001` (UI) - connects to backend on port 8000

📖 [Full Documentation](vanilla-chat/README.md)

---

## Python Examples

### Python FastAPI Server

A FastAPI server that streams AI responses in Server-Sent Events (SSE) format, compatible with TanStack AI clients.

**Features:**

- ✅ FastAPI with SSE streaming
- ✅ Converts Anthropic/OpenAI events to `StreamChunk` format
- ✅ Compatible with `@tanstack/ai-client`
- ✅ Tool call support
- ✅ Type-safe with Pydantic

**Getting Started:**

```bash
cd examples/python-fastapi

# Create virtual environment
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install dependencies
pip install -r requirements.txt

# Set up environment
cp env.example .env
# Edit .env and add your ANTHROPIC_API_KEY or OPENAI_API_KEY

# Run the server
python anthropic-server.py  # or openai-server.py
```

**API Endpoints:**

- `POST /chat` - Stream chat responses in SSE format
- `GET /health` - Health check

**Usage with TypeScript Client:**

```typescript
import { ChatClient, fetchServerSentEvents } from '@tanstack/ai-client'

const client = new ChatClient({
  connection: fetchServerSentEvents('http://localhost:8000/chat'),
})

await client.sendMessage('Hello!')
```

📖 [Full Documentation](python-fastapi/README.md)

---

## PHP Examples

### PHP Slim Framework Server

A PHP Slim Framework server that streams AI responses in SSE format, with support for both Anthropic and OpenAI.

**Features:**

- ✅ Slim Framework with SSE streaming
- ✅ Converts Anthropic/OpenAI events to `StreamChunk` format
- ✅ Compatible with `@tanstack/ai-client`
- ✅ Tool call support
- ✅ PHP 8.1+ with type safety

**Getting Started:**

```bash
cd examples/php-slim

# Install dependencies
composer install

# Set up environment
cp env.example .env
# Edit .env and add your ANTHROPIC_API_KEY and/or OPENAI_API_KEY

# Run the server
composer start-anthropic  # Runs on port 8000
# or
composer start-openai     # Runs on port 8001
```

**API Endpoints:**

- `POST /chat` - Stream chat responses in SSE format
- `GET /health` - Health check

**Usage with TypeScript Client:**

```typescript
import { ChatClient, fetchServerSentEvents } from '@tanstack/ai-client'

const client = new ChatClient({
  connection: fetchServerSentEvents('http://localhost:8000/chat'),
})

await client.sendMessage('Hello!')
```

📖 [Full Documentation](php-slim/README.md)

---

## Architecture Patterns

### Full-Stack TypeScript

Use TanStack AI end-to-end in TypeScript:

```
Frontend (React)
  ↓ (useChat hook)
@tanstack/ai-react
  ↓ (ChatClient)
@tanstack/ai-client
  ↓ (SSE/HTTP)
Backend (TanStack Start API Route)
  ↓ (chat() function)
@tanstack/ai
  ↓ (adapter)
AI Provider (OpenAI/Anthropic/etc.)
```

**Example:** [TanStack Chat (ts-react-chat)](ts-react-chat/README.md)

### Multi-Language Backend

Use Python or PHP for the backend, TypeScript for the frontend:

```
Frontend (Vanilla JS/React/Vue/etc.)
  ↓ (ChatClient)
@tanstack/ai-client
  ↓ (SSE/HTTP)
Backend (Python FastAPI or PHP Slim)
  ↓ (tanstack-ai or tanstack/ai)
Stream Conversion & Message Formatting
  ↓ (provider SDK)
AI Provider (OpenAI/Anthropic/etc.)
```

**Examples:**

- [Python FastAPI](python-fastapi/README.md) + [Vanilla Chat](vanilla-chat/README.md)
- [PHP Slim](php-slim/README.md) + [Vanilla Chat](vanilla-chat/README.md)
- [PHP Slim](php-slim/README.md) + any frontend with `@tanstack/ai-client`

## Common Patterns

### Server-Sent Events (SSE) Streaming

All examples use SSE for real-time streaming:

**Backend (TypeScript):**

```typescript
import { chat, toStreamResponse } from '@tanstack/ai'
import { openai } from '@tanstack/ai-openai'

const stream = chat({
  adapter: openai(),
  model: 'gpt-4o',
  messages,
})

return toStreamResponse(stream)
```

**Backend (Python):**

```python
from tanstack_ai import StreamChunkConverter, format_sse_chunk

async for event in anthropic_stream:
    chunks = await converter.convert_event(event)
    for chunk in chunks:
        yield format_sse_chunk(chunk)
```

**Backend (PHP):**

```php
use TanStack\AI\StreamChunkConverter;
use TanStack\AI\SSEFormatter;

foreach ($anthropicStream as $event) {
    $chunks = $converter->convertEvent($event);
    foreach ($chunks as $chunk) {
        echo SSEFormatter::formatChunk($chunk);
    }
}
```

**Frontend:**

```typescript
import { ChatClient, fetchServerSentEvents } from '@tanstack/ai-client'

const client = new ChatClient({
  connection: fetchServerSentEvents('/api/chat'),
})
```

### Automatic Tool Execution

The TypeScript backend (`@tanstack/ai`) automatically handles tool execution:

```typescript
import { chat, toolDefinition } from '@tanstack/ai'
import { z } from 'zod'

// Step 1: Define the tool schema
const weatherToolDef = toolDefinition({
  name: 'getWeather',
  description: 'Get weather for a location',
  inputSchema: z.object({
    location: z.string().describe('The city and state, e.g. San Francisco, CA'),
  }),
  outputSchema: z.object({
    temp: z.number(),
    condition: z.string(),
  }),
})

// Step 2: Create server implementation
const weatherTool = weatherToolDef.server(async ({ location }) => {
  // This is called automatically by the SDK
  return { temp: 72, condition: 'sunny' }
})

const stream = chat({
  adapter: openai(),
  model: 'gpt-4o',
  messages,
  tools: [weatherTool], // SDK executes these automatically
})
```

Clients receive:

- `content` chunks - text from the model
- `tool_call` chunks - when the model calls a tool
- `tool_result` chunks - results from tool execution
- `done` chunk - conversation complete

---

## Development Tips

### Running Multiple Examples

You can run backend and frontend examples together:

```bash
# Option 1: Python backend + Vanilla Chat frontend
# Terminal 1: Start Python backend
cd examples/python-fastapi
python anthropic-server.py

# Terminal 2: Start vanilla frontend
cd examples/vanilla-chat
pnpm start

# Option 2: PHP backend + Vanilla Chat frontend (runs together)
cd examples/php-slim
pnpm start  # Starts both PHP server and vanilla-chat UI

# Option 3: Full-stack TypeScript
cd examples/ts-react-chat
pnpm start
```

### Environment Variables

Each example has an `env.example` file. Copy it to `.env` and add your API keys:

```bash
# TypeScript examples
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...

# Python examples
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...

# PHP examples
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
```

### Building for Production

**TypeScript:**

```bash
pnpm build
```

**Python:**

```bash
# Use a production ASGI server
uvicorn anthropic-server:app --host 0.0.0.0 --port 8000
```

**PHP:**

```bash
# Use a production web server (Apache, Nginx, etc.)
# See php-slim/README.md for deployment details
```

---

## Contributing

When adding new examples:

1. **Create a README.md** with setup instructions
2. **Add an env.example** file with required environment variables
3. **Document the tech stack** and key features
4. **Include usage examples** with code snippets
5. **Update this README** to list your example

---

## Learn More

- 📖 [Main README](../README.md) - Project overview
- 📖 [Documentation](../docs/) - Comprehensive guides
- 📖 [TypeScript Packages](../packages/typescript/) - Core libraries
- 📖 [Python Package](../packages/python/tanstack-ai/) - Python utilities
- 📖 [PHP Package](../packages/php/tanstack-ai/) - PHP utilities

---

Built with ❤️ by the TanStack community


## Links discovered
- [Full Documentation](https://github.com/tanstack/ai/blob/main/examples/ts-react-chat/README.md)
- [Full Documentation](https://github.com/tanstack/ai/blob/main/examples/ts-group-chat/README.md)
- [Full Documentation](https://github.com/tanstack/ai/blob/main/examples/vanilla-chat/README.md)
- [Full Documentation](https://github.com/tanstack/ai/blob/main/examples/python-fastapi/README.md)
- [Full Documentation](https://github.com/tanstack/ai/blob/main/examples/php-slim/README.md)
- [TanStack Chat (ts-react-chat)](https://github.com/tanstack/ai/blob/main/examples/ts-react-chat/README.md)
- [Python FastAPI](https://github.com/tanstack/ai/blob/main/examples/python-fastapi/README.md)
- [Vanilla Chat](https://github.com/tanstack/ai/blob/main/examples/vanilla-chat/README.md)
- [PHP Slim](https://github.com/tanstack/ai/blob/main/examples/php-slim/README.md)
- [Main README](https://github.com/tanstack/ai/blob/main/README.md)
- [Documentation](https://github.com/tanstack/ai/blob/main/docs.md)
- [TypeScript Packages](https://github.com/tanstack/ai/blob/main/packages/typescript.md)
- [Python Package](https://github.com/tanstack/ai/blob/main/packages/python/tanstack-ai.md)
- [PHP Package](https://github.com/tanstack/ai/blob/main/packages/php/tanstack-ai.md)

--- examples/php-slim/README.md ---
# TanStack AI PHP Slim Framework Example

This is a PHP Slim Framework server example that demonstrates how to stream Anthropic and OpenAI API events in Server-Sent Events (SSE) format compatible with the TanStack AI client.

## Features

- PHP Slim Framework server with SSE streaming support
- Converts Anthropic and OpenAI API events to TanStack AI `StreamChunk` format
- Compatible with `@tanstack/ai-client`'s `fetchServerSentEvents` adapter
- Supports tool calls and function calling
- Type-safe request/response handling

## Setup

### Prerequisites

- PHP 8.1 or higher
- Composer (PHP dependency manager)

### Step-by-Step Setup

1. **Navigate to the project directory:**

```bash
cd examples/php-slim
```

2. **Install dependencies:**

Install PHP dependencies:

```bash
composer install
```

This will install all required packages (Slim Framework, Anthropic SDK, OpenAI SDK, etc.).

Install Node.js dependencies (for running the UI):

```bash
pnpm install
```

Or if you're at the repository root, you can install all dependencies at once:

```bash
pnpm install
```

3. **Set up environment variables:**

Copy `env.example` to `.env` and add your API keys:

```bash
cp env.example .env
# Edit .env and add your ANTHROPIC_API_KEY and/or OPENAI_API_KEY
```

4. **Run the server and UI:**

**For Anthropic:**

```bash
pnpm start
```

Or using npm:

```bash
npm start
```

This will start both the PHP server and the vanilla-chat UI concurrently.

**For OpenAI:**

```bash
pnpm start-openai
```

Or using npm:

```bash
npm start-openai
```

The servers will start on:

- PHP Server (Anthropic): `http://localhost:8000`
- PHP Server (OpenAI): `http://localhost:8001`
- UI: `http://localhost:3001`

**Note:** The UI (vanilla-chat) is required to interact with the PHP server. The `start` script runs both servers together.

## API Endpoints

### POST `/chat`

Streams chat responses in SSE format.

**Request Body:**

```json
{
  "messages": [
    {
      "role": "user",
      "content": "Hello!"
    }
  ]
}
```

**Response:** Server-Sent Events stream with `StreamChunk` format:

```
data: {"type":"content","id":"...","model":"claude-3-haiku-20240307","timestamp":1234567890,"delta":"Hello","content":"Hello","role":"assistant"}

data: {"type":"content","id":"...","model":"claude-3-haiku-20240307","timestamp":1234567890,"delta":" world","content":"Hello world","role":"assistant"}

data: {"type":"done","id":"...","model":"claude-3-haiku-20240307","timestamp":1234567890,"finishReason":"stop","usage":{"promptTokens":10,"completionTokens":2,"totalTokens":12}}

data: [DONE]
```

### GET `/health`

Health check endpoint.

## Usage with TanStack AI Client

This server is compatible with the TypeScript TanStack AI client:

```typescript
import { ChatClient, fetchServerSentEvents } from '@tanstack/ai-client'

const client = new ChatClient({
  connection: fetchServerSentEvents('http://localhost:8000/chat'),
})

await client.sendMessage('Hello!')
```

## StreamChunk Format

The `tanstack/ai` package converts provider events to the following `StreamChunk` types:

- **`content`**: Text content updates with delta and accumulated content
- **`tool_call`**: Tool/function call events with incremental arguments
- **`done`**: Stream completion with finish reason and usage stats
- **`error`**: Error events

See `packages/typescript/ai/src/types.ts` for the full TypeScript type definitions.

## Supported Providers

The converter currently supports:

- ✅ **Anthropic** (Claude models) - fully implemented
- ✅ **OpenAI** (GPT models) - fully implemented

## Project Structure

```
php-slim/
├── public/
│   ├── anthropic-server.php  # Anthropic server
│   └── openai-server.php     # OpenAI server
├── composer.json              # PHP dependencies
├── env.example                # Environment variables template
└── README.md                  # This file
```

## Architecture

The server uses the `tanstack/ai` package located at `packages/php/tanstack-ai/`:

- **`anthropic-server.php`** / **`openai-server.php`**: Handles Slim setup, provider client initialization, and HTTP endpoints
- **`tanstack/ai` package**: Provides `StreamChunkConverter`, message formatters, and SSE utilities for converting provider events to TanStack AI format

The converter package is installed as a local dependency, making it easy to develop and test changes.

## Notes

- The server uses CORS middleware allowing all origins (configure for production)
- Default Anthropic model is `claude-3-haiku-20240307` (can be made configurable)
- Default OpenAI model is `gpt-4o` (can be made configurable)
- Supports system messages, tool calls, and tool results
- Error handling converts exceptions to error StreamChunks
- Uses PHP 8.1+ features including named arguments and match expressions

## Development

To use the local `tanstack/ai` package during development:

1. Add to `composer.json`:

```json
{
  "repositories": [
    {
      "type": "path",
      "url": "../../packages/php/tanstack-ai"
    }
  ],
  "require": {
    "tanstack/ai": "@dev"
  }
}
```

2. Run:

```bash
composer update tanstack/ai
```


--- examples/python-fastapi/README.md ---
# TanStack AI Python FastAPI Example

This is a Python FastAPI server example that demonstrates agentic workflows with automatic tool execution using TanStack AI's Python SDK.

## Features

- 🤖 **Agentic workflows** with automatic tool execution
- 🔧 **Built-in tools**: Weather lookup and timezone information
- 📡 **SSE streaming** support for real-time responses
- 🔄 **Agent loop strategies** with configurable iteration limits
- ✅ Compatible with `@tanstack/ai-client`'s `fetchServerSentEvents` adapter
- 📝 Type-safe request/response models using Pydantic

## Setup

### Prerequisites

- Python 3.8 or higher
- pip (Python package installer)

### Step-by-Step Setup

1. **Navigate to the project directory:**

```bash
cd examples/python-fastapi
```

2. **Create a virtual environment (recommended):**

A virtual environment keeps dependencies isolated from your system Python installation.

```bash
python3 -m venv venv
```

3. **Activate the virtual environment:**
   - **On macOS/Linux:**

     ```bash
     source venv/bin/activate
     ```

   - **On Windows:**
     ```bash
     venv\Scripts\activate
     ```

   You should see `(venv)` in your terminal prompt when activated.

4. **Install dependencies:**

```bash
pip install -r requirements.txt
```

This will install all required packages (FastAPI, Anthropic SDK, Pydantic, etc.).

5. **Set up environment variables:**

Copy `env.example` to `.env` and add your Anthropic API key:

```bash
cp env.example .env
# Edit .env and add your ANTHROPIC_API_KEY
```

6. **Run the server:**

```bash
python anthropic-server.py
```

Or using uvicorn directly:

```bash
uvicorn anthropic-server:app --reload --port 8000
```

The server will start on `http://localhost:8000`

### Deactivating the Virtual Environment

When you're done, you can deactivate the virtual environment:

```bash
deactivate
```

**Note:** The `venv/` directory is already included in `.gitignore`, so it won't be committed to version control.

## API Endpoints

### POST `/chat`

Streams chat responses in SSE format with automatic tool execution.

**Request Body:**

```json
{
  "messages": [
    {
      "role": "user",
      "content": "What's the weather in San Francisco?"
    }
  ],
  "data": {
    "model": "claude-3-5-sonnet-20241022"
  }
}
```

**Response:** Server-Sent Events stream with `StreamChunk` format:

```
data: {"type":"content","id":"...","model":"claude-3-5-sonnet-20241022","timestamp":1234567890,"delta":"Let","content":"Let","role":"assistant"}

data: {"type":"tool_call","id":"...","model":"claude-3-5-sonnet-20241022","timestamp":1234567890,"toolCall":{"id":"call_123","type":"function","function":{"name":"get_weather","arguments":"{\"location\":\"San Francisco\"}"}},"index":0}

data: {"type":"tool_result","id":"...","model":"claude-3-5-sonnet-20241022","timestamp":1234567891,"toolCallId":"call_123","content":"{\"temperature\":62,\"conditions\":\"Foggy\",\"location\":\"San Francisco\"}"}

data: {"type":"content","id":"...","model":"claude-3-5-sonnet-20241022","timestamp":1234567892,"delta":"The","content":"The","role":"assistant"}

data: {"type":"done","id":"...","model":"claude-3-5-sonnet-20241022","timestamp":1234567893,"finishReason":"stop","usage":{"promptTokens":150,"completionTokens":75,"totalTokens":225}}

data: [DONE]
```

### GET `/health`

Health check endpoint.

## Available Tools

The server includes two built-in tools that are automatically executed:

### 1. `get_weather`

Get weather information for a city (returns static demo data).

**Parameters:**

- `location` (required): City name (e.g., "San Francisco", "New York", "London")
- `unit` (optional): Temperature unit - "celsius" or "fahrenheit" (default: fahrenheit)

**Supported Cities:**

- San Francisco, New York, London, Tokyo, Paris, Sydney

**Example:**

```json
{
  "messages": [
    {
      "role": "user",
      "content": "What's the weather in Tokyo in celsius?"
    }
  ]
}
```

### 2. `get_time`

Get current time in a specific timezone (returns static demo data).

**Parameters:**

- `timezone` (required): Timezone code (e.g., "PST", "EST", "UTC")

**Supported Timezones:**

- UTC, PST, EST, GMT, JST, AEST

**Example:**

```json
{
  "messages": [
    {
      "role": "user",
      "content": "What time is it in Tokyo?"
    }
  ]
}
```

## Usage with TanStack AI Client

This server is compatible with the TypeScript TanStack AI client:

```typescript
import { ChatClient, fetchServerSentEvents } from '@tanstack/ai-client'

const client = new ChatClient({
  connection: fetchServerSentEvents('http://localhost:8000/chat'),
})

await client.sendMessage('Hello!')
```

## StreamChunk Format

The `tanstack-ai` package emits the following `StreamChunk` types:

- **`content`**: Text content updates with delta and accumulated content
- **`tool_call`**: Tool/function call events with arguments
- **`tool_result`**: Results from tool execution
- **`done`**: Stream completion with finish reason and usage stats
- **`error`**: Error events
- **`tool-input-available`**: Tool inputs ready for client-side execution
- **`approval-requested`**: Tool requiring user approval

See `packages/python/tanstack-ai/src/tanstack_ai/types.py` for full type definitions.

## Agentic Flow

The server uses TanStack AI's agentic flow features:

1. **Tool Registration**: Tools are defined with JSON Schema and execute functions
2. **Automatic Execution**: When Claude calls a tool, it's automatically executed
3. **Result Injection**: Tool results are added to the conversation
4. **Loop Control**: Agent can iterate up to 5 times (configurable)
5. **Streaming**: All events (content, tool calls, results) are streamed to the client

```python
# Example tool definition
weather_tool = tool(
    name="get_weather",
    description="Get the current weather for a location",
    input_schema={
        "type": "object",
        "properties": {
            "location": {"type": "string"},
        },
        "required": ["location"],
    },
    execute=get_weather_impl,
)

# Use in chat with automatic execution
async for chunk in chat(
    adapter=adapter,
    model="claude-3-5-sonnet-20241022",
    messages=messages,
    tools=[weather_tool],
    agent_loop_strategy=max_iterations(5),
):
    yield format_sse_chunk(chunk)
```

## Supported Models

- ✅ **Anthropic** (Claude models) - fully implemented
  - claude-3-5-sonnet-20241022 (recommended for tool calling)
  - claude-3-5-haiku-20241022
  - claude-3-opus-20240229
  - And more...

## Project Structure

```
python-fastapi/
├── anthropic-server.py  # FastAPI server example
├── requirements.txt      # Python dependencies (includes tanstack-ai package)
├── env.example       # Environment variables template
└── README.md         # This file
```

## Architecture

The server uses the `tanstack-ai` Python SDK located at `packages/python/tanstack-ai/`:

```
FastAPI Server (anthropic-server.py)
    ↓
TanStack AI SDK (tanstack-ai package)
    ↓
AnthropicAdapter
    ↓
Claude API
```

**Key Components:**

- **`anthropic-server.py`**: FastAPI endpoints and HTTP streaming
- **`AnthropicAdapter`**: Converts Anthropic events to StreamChunks
- **`ChatEngine`**: Orchestrates the agentic loop
- **`ToolCallManager`**: Manages tool execution
- **`chat()` function**: Main entry point for agentic chat

The package is installed as an editable dependency, making development easy.

## Testing the Server

### With curl:

```bash
curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "role": "user",
        "content": "What'\''s the weather in San Francisco and what time is it in Tokyo?"
      }
    ]
  }'
```

### With the TanStack AI Client:

```typescript
import { ChatClient, fetchServerSentEvents } from '@tanstack/ai-client'

const client = new ChatClient({
  connection: fetchServerSentEvents('http://localhost:8000/chat'),
})

await client.sendMessage("What's the weather in New York?")
```

## Notes

- The server uses CORS middleware allowing all origins (configure for production)
- Default model is `claude-3-5-sonnet-20241022` (better for tool calling)
- Tools are automatically executed on the server side
- Agent loop allows up to 5 iterations (configurable via `max_iterations`)
- Error handling converts exceptions to error StreamChunks
- All tool executions are logged for debugging


--- examples/ts-group-chat/README.md ---
# Cap'n Web Chat Demo

A real-time multi-user chat application demonstrating the integration of **Cap'n Web RPC** with **TanStack Start**. This project showcases bidirectional WebSocket communication, server-side rendering, and modern React patterns.

## 🎥 Demo

![Demo video](./assets/demo.gif)

## ✨ Features

- **Real-time messaging** - Instant message delivery across all connected users
- **Online presence** - See who's currently in the chat
- **Auto-connection** - Seamlessly connects when the page loads
- **Responsive design** - Works on desktop and mobile devices
- **Modern chat UI** - Messages appear like iMessage/WhatsApp (yours on right, others on left)
- **Username-based** - Simple username entry, no registration required

## 🏗️ Architecture

This demo combines several powerful technologies:

- **[Cap'n Web RPC](https://github.com/cloudflare/capnweb)** - Bidirectional RPC over WebSockets
- **[TanStack Start](https://tanstack.com/start)** - Full-stack React framework
- **[TanStack Router](https://tanstack.com/router)** - Type-safe routing
- **[Tailwind CSS](https://tailwindcss.com/)** - Utility-first styling
- **WebSocket Server** - Real-time communication layer

### Key Components

```
chat-server/
├── chat-logic.ts      # Core chat business logic
├── capnweb-rpc.ts     # Cap'n Web RPC server implementation
└── vite-plugin.ts     # WebSocket integration with Vite

src/
├── components/
│   ├── ChatInterface.tsx    # Main chat message display
│   ├── OnlineUsers.tsx      # Online user list
│   └── UsernameInput.tsx    # Username entry
├── hooks/
│   ├── useChatConnection.ts # WebSocket connection management
│   └── useChatMessages.ts   # Message state and polling
└── routes/
    └── index.tsx           # Main chat page
```

## 🚀 Getting Started

### Prerequisites

- Node.js 18+
- pnpm (recommended) or npm

### Installation

```bash
# Clone the repository
git clone <repo-url>
cd capnweb-chat-demo

# Install dependencies
pnpm install
```

### Development

```bash
# Start the development server
pnpm dev
```

Open [http://localhost:3000](http://localhost:3000) in multiple browser tabs to test the chat functionality.

### Building for Production

```bash
# Build the application
pnpm build

# Start production server
pnpm start
```

## 💬 How It Works

### 1. **Connection Flow**

- User opens the chat page
- Application auto-connects to WebSocket server via Cap'n Web RPC
- Connection status is managed transparently

### 2. **Chat Flow**

- User enters a username
- Application automatically joins the chat room
- Messages are sent via RPC calls to the server
- Server broadcasts messages to all connected users
- Client polls for new messages every second

### 3. **Message Broadcasting**

```typescript
// Server-side message broadcasting
await ChatServer.broadcastToAll({
  type: 'message',
  message: message.message,
  username: message.username,
  timestamp: message.timestamp,
  id: message.id,
})
```

### 4. **Real-time Updates**

- **Messages**: Polled every 1 second
- **Online users**: Updated every 5 seconds
- **Connection status**: Real-time via WebSocket events

## 🔧 Configuration

### WebSocket Setup

The WebSocket server is configured in `chat-server/vite-plugin.ts`:

```typescript
// WebSocket endpoint: /api/websocket
wss.handleUpgrade(request, socket, head, (ws) => {
  const chatServer = new ChatServer()
  chatServer.setWebSocket(ws)
  newWebSocketRpcSession(ws, chatServer)
})
```

### Message Queue System

Messages are queued per user to ensure delivery:

```typescript
// Each user gets their own message queue
export const userMessageQueues = new Map<string, Array<any>>()

// Messages are polled and cleared
const messages = userMessageQueues.get(username) || []
userMessageQueues.set(username, []) // Clear after reading
```

## 🧪 Testing

```bash
# Run tests
pnpm test
```

To test the chat functionality:

1. Open multiple browser tabs to [http://localhost:3000](http://localhost:3000)
2. Enter different usernames in each tab
3. Send messages and observe real-time delivery
4. Check the online users list updates

## 📚 Key Technologies Explained

### Cap'n Web RPC

- **Bidirectional**: Both client and server can call methods on each other
- **Type-safe**: Full TypeScript support for RPC calls
- **WebSocket-based**: Built on standard WebSocket protocol
- **Efficient**: Binary protocol with message queuing

### TanStack Start

- **Full-stack**: Single framework for client and server
- **Server-side rendering**: Pages render on the server first
- **File-based routing**: Routes defined by file structure
- **Type-safe**: End-to-end TypeScript support

### Message Flow

```
User types message
    ↓
useChatMessages.sendMessage()
    ↓
Cap'n Web RPC call
    ↓
ChatServer.sendMessage()
    ↓
ChatLogic.sendMessage()
    ↓
Broadcast to all users
    ↓
Message queues updated
    ↓
Clients poll for updates
    ↓
UI updates with new message
```

## 🎯 Why This Demo?

This application demonstrates:

1. **Real-world WebSocket usage** - Not just a simple echo server
2. **RPC over WebSockets** - More structured than plain message passing
3. **Modern React patterns** - Hooks, effects, and clean component architecture
4. **Full-stack TypeScript** - Shared types between client and server
5. **Production-ready patterns** - Error handling, reconnection, message queuing

Perfect for learning how to build real-time applications with modern web technologies!

## 🤝 Contributing

This is a demonstration project, but improvements are welcome:

- Enhanced error handling
- Message persistence
- User authentication
- Private messaging
- File sharing
- Emoji reactions

## 📄 License

This project is open source and available under the [MIT License](LICENSE).

---

**Built with ❤️ using Cap'n Web RPC and TanStack Start**


## Links discovered
- [Demo video](https://github.com/tanstack/ai/blob/main/examples/ts-group-chat/assets/demo.gif)
- [Cap'n Web RPC](https://github.com/cloudflare/capnweb)
- [TanStack Start](https://tanstack.com/start)
- [TanStack Router](https://tanstack.com/router)
- [Tailwind CSS](https://tailwindcss.com/)
- [http://localhost:3000](http://localhost:3000)
- [MIT License](https://github.com/tanstack/ai/blob/main/examples/ts-group-chat/LICENSE.md)

--- examples/ts-react-chat/README.md ---
Welcome to your new TanStack app!

# Getting Started

To run this application:

```bash
pnpm install
pnpm dev
```

# Building For Production

To build this application for production:

```bash
pnpm build
```

## Testing

This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with:

```bash
pnpm test
```

## Styling

This project uses [Tailwind CSS](https://tailwindcss.com/) for styling.

# TanStack Chat Application

An example chat application built with TanStack Start, TanStack Store, and **TanStack AI** (our open-source AI SDK).

## .env Updates

```env
OPENAI_API_KEY=your_openai_api_key
```

## ✨ Features

### AI Capabilities

- 🤖 Powered by **TanStack AI** with OpenAI GPT-4o
- 📝 Rich markdown formatting with syntax highlighting
- 🎯 Customizable system prompts for tailored AI behavior
- 🔄 Real-time streaming responses with Server-Sent Events
- 🔌 **Connection adapters** - flexible streaming architecture
- 🛠️ **Automatic tool execution loop** - tools are executed automatically by the SDK
- 🎸 Tool/function calling with guitar recommendations

### User Experience

- 🎨 Modern UI with Tailwind CSS and Lucide icons
- 🔍 Conversation management and history
- 🔐 Secure API key management
- 📋 Markdown rendering with code highlighting

### Technical Features

- 📦 Centralized state management with TanStack Store
- 🔌 Extensible architecture for multiple AI providers
- 🛠️ TypeScript for type safety

## Architecture

### Tech Stack

- **Frontend Framework**: TanStack Start
- **Routing**: TanStack Router
- **State Management**: TanStack Store
- **Styling**: Tailwind CSS
- **AI Integration**: TanStack AI with OpenAI GPT-4o
- **Chat Client**: `@tanstack/ai-react` with connection adapters
- **Streaming**: Server-Sent Events via `fetchServerSentEvents`
- **Tool Execution**: Automatic loop with `ToolCallManager`

## Routing

This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as files in `src/routes`.

### Adding A Route

To add a new route to your application just add another a new file in the `./src/routes` directory.

TanStack will automatically generate the content of the route file for you.

Now that you have two routes you can use a `Link` component to navigate between them.

### Adding Links

To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`.

```tsx
import { Link } from '@tanstack/react-router'
```

Then anywhere in your JSX you can use it like so:

```tsx
<Link to="/about">About</Link>
```

This will create a link that will navigate to the `/about` route.

More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent).

### Using A Layout

In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you use the `<Outlet />` component.

Here is an example layout that includes a header:

```tsx
import { Outlet, createRootRoute } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'

import { Link } from '@tanstack/react-router'

export const Route = createRootRoute({
  component: () => (
    <>
      <header>
        <nav>
          <Link to="/">Home</Link>
          <Link to="/about">About</Link>
        </nav>
      </header>
      <Outlet />
      <TanStackRouterDevtools />
    </>
  ),
})
```

The `<TanStackRouterDevtools />` component is not required so you can remove it if you don't want it in your layout.

More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts).

## Data Fetching

There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered.

For example:

```tsx
const peopleRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/people',
  loader: async () => {
    const response = await fetch('https://swapi.dev/api/people')
    return response.json() as Promise<{
      results: {
        name: string
      }[]
    }>
  },
  component: () => {
    const data = peopleRoute.useLoaderData()
    return (
      <ul>
        {data.results.map((person) => (
          <li key={person.name}>{person.name}</li>
        ))}
      </ul>
    )
  },
})
```

Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters).

### React-Query

React-Query is an excellent addition or alternative to route loading and integrating it into you application is a breeze.

First add your dependencies:

```bash
pnpm add @tanstack/react-query @tanstack/react-query-devtools
```

Next we'll need to create a query client and provider. We recommend putting those in `main.tsx`.

```tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

// ...

const queryClient = new QueryClient()

// ...

if (!rootElement.innerHTML) {
  const root = ReactDOM.createRoot(rootElement)

  root.render(
    <QueryClientProvider client={queryClient}>
      <RouterProvider router={router} />
    </QueryClientProvider>,
  )
}
```

You can also add TanStack Query Devtools to the root route (optional).

```tsx
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

const rootRoute = createRootRoute({
  component: () => (
    <>
      <Outlet />
      <ReactQueryDevtools buttonPosition="top-right" />
      <TanStackRouterDevtools />
    </>
  ),
})
```

Now you can use `useQuery` to fetch your data.

```tsx
import { useQuery } from '@tanstack/react-query'

import './App.css'

function App() {
  const { data } = useQuery({
    queryKey: ['people'],
    queryFn: () =>
      fetch('https://swapi.dev/api/people')
        .then((res) => res.json())
        .then((data) => data.results as { name: string }[]),
    initialData: [],
  })

  return (
    <div>
      <ul>
        {data.map((person) => (
          <li key={person.name}>{person.name}</li>
        ))}
      </ul>
    </div>
  )
}

export default App
```

You can find out everything you need to know on how to use React-Query in the [React-Query documentation](https://tanstack.com/query/latest/docs/framework/react/overview).

## State Management

Another common requirement for React applications is state management. There are many options for state management in React. TanStack Store provides a great starting point for your project.

First you need to add TanStack Store as a dependency:

```bash
pnpm add @tanstack/store
```

Now let's create a simple counter in the `src/App.tsx` file as a demonstration.

```tsx
import { useStore } from '@tanstack/react-store'
import { Store } from '@tanstack/store'
import './App.css'

const countStore = new Store(0)

function App() {
  const count = useStore(countStore)
  return (
    <div>
      <button onClick={() => countStore.setState((n) => n + 1)}>
        Increment - {count}
      </button>
    </div>
  )
}

export default App
```

One of the many nice features of TanStack Store is the ability to derive state from other state. That derived state will update when the base state updates.

Let's check this out by doubling the count using derived state.

```tsx
import { useStore } from '@tanstack/react-store'
import { Store, Derived } from '@tanstack/store'
import './App.css'

const countStore = new Store(0)

const doubledStore = new Derived({
  fn: () => countStore.state * 2,
  deps: [countStore],
})
doubledStore.mount()

function App() {
  const count = useStore(countStore)
  const doubledCount = useStore(doubledStore)

  return (
    <div>
      <button onClick={() => countStore.setState((n) => n + 1)}>
        Increment - {count}
      </button>
      <div>Doubled - {doubledCount}</div>
    </div>
  )
}

export default App
```

We use the `Derived` class to create a new store that is derived from another store. The `Derived` class has a `mount` method that will start the derived store updating.

Once we've created the derived store we can use it in the `App` component just like we would any other store using the `useStore` hook.

You can find out everything you need to know on how to use TanStack Store in the [TanStack Store documentation](https://tanstack.com/store/latest).

# Demo files

Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed.

# Learn More

You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com).


## Links discovered
- [Vitest](https://vitest.dev/)
- [Tailwind CSS](https://tailwindcss.com/)
- [TanStack Router](https://tanstack.com/router)
- [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent)
- [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts)
- [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters)
- [React-Query documentation](https://tanstack.com/query/latest/docs/framework/react/overview)
- [TanStack Store documentation](https://tanstack.com/store/latest)
- [TanStack documentation](https://tanstack.com)

--- examples/ts-solid-chat/README.md ---
Welcome to your new TanStack app!

# Getting Started

To run this application:

```bash
pnpm install
pnpm start
```

# Building For Production

To build this application for production:

```bash
pnpm build
```

## Testing

This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with:

```bash
pnpm test
```

## Styling

This project uses [Tailwind CSS](https://tailwindcss.com/) for styling.

# TanStack Chat Application

An example chat application built with TanStack Start, TanStack Store, and **TanStack AI** (our open-source AI SDK).

## .env Updates

```env
OPENAI_API_KEY=your_openai_api_key
```

## ✨ Features

### AI Capabilities

- 🤖 Powered by **TanStack AI** with OpenAI GPT-4o
- 📝 Rich markdown formatting with syntax highlighting
- 🎯 Customizable system prompts for tailored AI behavior
- 🔄 Real-time streaming responses with Server-Sent Events
- 🔌 **Connection adapters** - flexible streaming architecture
- 🛠️ **Automatic tool execution loop** - tools are executed automatically by the SDK
- 🎸 Tool/function calling with guitar recommendations

### User Experience

- 🎨 Modern UI with Tailwind CSS and Lucide icons
- 🔍 Conversation management and history
- 🔐 Secure API key management
- 📋 Markdown rendering with code highlighting

### Technical Features

- 📦 Centralized state management with TanStack Store
- 🔌 Extensible architecture for multiple AI providers
- 🛠️ TypeScript for type safety

## Architecture

### Tech Stack

- **Frontend Framework**: TanStack Start
- **Routing**: TanStack Router
- **State Management**: TanStack Store
- **Styling**: Tailwind CSS
- **AI Integration**: TanStack AI with OpenAI GPT-4o
- **Chat Client**: `@tanstack/ai-solid` with connection adapters
- **Streaming**: Server-Sent Events via `fetchServerSentEvents`
- **Tool Execution**: Automatic loop with `ToolCallManager`

## Routing

This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as files in `src/routes`.

### Adding A Route

To add a new route to your application just add another a new file in the `./src/routes` directory.

TanStack will automatically generate the content of the route file for you.

Now that you have two routes you can use a `Link` component to navigate between them.

### Adding Links

To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/solid-router`.

```tsx
import { Link } from '@tanstack/solid-router'
```

Then anywhere in your JSX you can use it like so:

```tsx
<Link to="/about">About</Link>
```

This will create a link that will navigate to the `/about` route.

More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/solid/api/router/linkComponent).

### Using A Layout

In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you use the `<Outlet />` component.

Here is an example layout that includes a header:

```tsx
import { Outlet, createRootRoute } from '@tanstack/solid-router'
import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools'

import { Link } from '@tanstack/solid-router'

export const Route = createRootRoute({
  component: () => (
    <>
      <header>
        <nav>
          <Link to="/">Home</Link>
          <Link to="/about">About</Link>
        </nav>
      </header>
      <Outlet />
      <TanStackRouterDevtools />
    </>
  ),
})
```

The `<TanStackRouterDevtools />` component is not required so you can remove it if you don't want it in your layout.

More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/solid/guide/routing-concepts#layouts).

## Data Fetching

There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered.

For example:

```tsx
const peopleRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/people',
  loader: async () => {
    const response = await fetch('https://swapi.dev/api/people')
    return response.json() as Promise<{
      results: {
        name: string
      }[]
    }>
  },
  component: () => {
    const data = peopleRoute.useLoaderData()
    return (
      <ul>
        {data.results.map((person) => (
          <li key={person.name}>{person.name}</li>
        ))}
      </ul>
    )
  },
})
```

Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/solid/guide/data-loading#loader-parameters).

### Solid-Query

Solid-Query is an excellent addition or alternative to route loading and integrating it into you application is a breeze.

First add your dependencies:

```bash
pnpm add @tanstack/solid-query @tanstack/solid-query-devtools
```

Next we'll need to create a query client and provider. We recommend putting those in `main.tsx`.

```tsx
import { QueryClient, QueryClientProvider } from '@tanstack/solid-query'

// ...

const queryClient = new QueryClient()

// ...
```

You can also add TanStack Query Devtools to the root route (optional).

```tsx
import { SolidQueryDevtools } from '@tanstack/solid-query-devtools'

const rootRoute = createRootRoute({
  component: () => (
    <>
      <Outlet />
      <SolidQueryDevtools buttonPosition="top-right" />
      <TanStackRouterDevtools />
    </>
  ),
})
```

Now you can use `useQuery` to fetch your data.

```tsx
import { useQuery } from '@tanstack/solid-query'

import './App.css'

function App() {
  const { data } = useQuery({
    queryKey: ['people'],
    queryFn: () =>
      fetch('https://swapi.dev/api/people')
        .then((res) => res.json())
        .then((data) => data.results as { name: string }[]),
    initialData: [],
  })

  return (
    <div>
      <ul>
        {data.map((person) => (
          <li key={person.name}>{person.name}</li>
        ))}
      </ul>
    </div>
  )
}

export default App
```

You can find out everything you need to know on how to use Solid-Query in the [Solid-Query documentation](https://tanstack.com/query/latest/docs/framework/solid/overview).

## State Management

Another common requirement for Solid applications is state management. There are many options for state management in Solid. TanStack Store provides a great starting point for your project.

First you need to add TanStack Store as a dependency:

```bash
pnpm add @tanstack/store
```

Now let's create a simple counter in the `src/App.tsx` file as a demonstration.

```tsx
import { useStore } from '@tanstack/solid-store'
import { Store } from '@tanstack/store'
import './App.css'

const countStore = new Store(0)

function App() {
  const count = useStore(countStore)
  return (
    <div>
      <button onClick={() => countStore.setState((n) => n + 1)}>
        Increment - {count}
      </button>
    </div>
  )
}

export default App
```

One of the many nice features of TanStack Store is the ability to derive state from other state. That derived state will update when the base state updates.

Let's check this out by doubling the count using derived state.

```tsx
import { useStore } from '@tanstack/solid-store'
import { Store, Derived } from '@tanstack/store'
import './App.css'

const countStore = new Store(0)

const doubledStore = new Derived({
  fn: () => countStore.state * 2,
  deps: [countStore],
})
doubledStore.mount()

function App() {
  const count = useStore(countStore)
  const doubledCount = useStore(doubledStore)

  return (
    <div>
      <button onClick={() => countStore.setState((n) => n + 1)}>
        Increment - {count}
      </button>
      <div>Doubled - {doubledCount}</div>
    </div>
  )
}

export default App
```

We use the `Derived` class to create a new store that is derived from another store. The `Derived` class has a `mount` method that will start the derived store updating.

Once we've created the derived store we can use it in the `App` component just like we would any other store using the `useStore` hook.

You can find out everything you need to know on how to use TanStack Store in the [TanStack Store documentation](https://tanstack.com/store/latest).

# Demo files

Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed.

# Learn More

You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com).


## Links discovered
- [Vitest](https://vitest.dev/)
- [Tailwind CSS](https://tailwindcss.com/)
- [TanStack Router](https://tanstack.com/router)
- [Link documentation](https://tanstack.com/router/v1/docs/framework/solid/api/router/linkComponent)
- [Layouts documentation](https://tanstack.com/router/latest/docs/framework/solid/guide/routing-concepts#layouts)
- [Loader documentation](https://tanstack.com/router/latest/docs/framework/solid/guide/data-loading#loader-parameters)
- [Solid-Query documentation](https://tanstack.com/query/latest/docs/framework/solid/overview)
- [TanStack Store documentation](https://tanstack.com/store/latest)
- [TanStack documentation](https://tanstack.com)

--- examples/ts-svelte-chat/README.md ---
# ts-svelte-chat

A SvelteKit chat application powered by TanStack AI.

## Features

- 🎯 Multiple AI providers (OpenAI, Anthropic, Gemini, Ollama)
- 🛠️ Tool execution (client-side and server-side)
- ✅ Tool approval workflow
- 🎸 Guitar recommendation example
- 💭 Thinking process visualization
- 🎨 Modern UI with Tailwind CSS

## Setup

1. Install dependencies:

```bash
pnpm install
```

2. Set up environment variables:

If `.env` doesn't exist, copy from the example:

```bash
cp env.example .env
```

Then edit `.env` and add your API keys:

```bash
OPENAI_API_KEY=sk-your-actual-key-here
ANTHROPIC_API_KEY=sk-ant-your-actual-key-here
GEMINI_API_KEY=your-actual-key-here
```

**Important Notes:**

- The `.env` file must be in the project root (`examples/ts-svelte-chat/.env`)
- You must **restart the dev server** after creating or modifying `.env`
- Environment variables in SvelteKit are loaded at server startup
- These are server-side only variables (not exposed to the browser)

3. Start (or restart) the development server:

```bash
pnpm dev
```

4. Open [http://localhost:3000](http://localhost:3000)

## Architecture

This example demonstrates:

- **@tanstack/ai-svelte**: Svelte 5 hooks for chat functionality
- **SvelteKit**: Full-stack framework with API routes
- **Streaming**: Real-time response streaming
- **Tools**: Both client-side and server-side tool execution
- **Approvals**: Tool approval workflow

## License

MIT


## Links discovered
- [http://localhost:3000](http://localhost:3000)

--- examples/ts-vue-chat/README.md ---
# TanStack AI - Vue Chat Example

A Vue 3 chat application demonstrating the use of `@tanstack/ai-vue`.

## Setup

1. Copy `env.example` to `.env` and add your API keys:

```bash
cp env.example .env
```

2. Install dependencies:

```bash
pnpm install
```

3. Start the development server:

```bash
pnpm dev
```

4. Open [http://localhost:5173](http://localhost:5173) in your browser.

## Features

- Real-time streaming chat with multiple AI providers
- Support for OpenAI, Anthropic, Gemini, and Ollama
- Client-side and server-side tools
- Tool approval workflow
- Guitar recommendation demo

## Project Structure

```
src/
├── main.ts           # App entry point
├── App.vue           # Root component
├── router.ts         # Vue Router setup
├── styles.css        # Global styles
├── components/       # UI components
│   ├── Header.vue
│   ├── ChatInput.vue
│   ├── Messages.vue
│   ├── ThinkingPart.vue
│   └── GuitarRecommendation.vue
├── lib/              # Utilities
│   ├── model-selection.ts
│   └── guitar-tools.ts
├── data/             # Example data
│   └── guitars.ts
└── views/            # Page components
    ├── ChatView.vue
    └── GuitarDetailView.vue
```

## Tech Stack

- Vue 3 with Composition API
- TypeScript
- Vite
- Tailwind CSS
- @tanstack/ai-vue


## Links discovered
- [http://localhost:5173](http://localhost:5173)

--- examples/vanilla-chat/README.md ---
# Vanilla Chat Example

A simple vanilla JavaScript chat interface using `@tanstack/ai-client` and the Python FastAPI server.

## Features

- ✅ Pure vanilla JavaScript (no frameworks)
- ✅ Uses `@tanstack/ai-client` for chat functionality
- ✅ Connects to API servers on port 8000
- ✅ Real-time streaming messages
- ✅ Beautiful, responsive UI :)

## Setup

1. **Install dependencies:**

```bash
cd examples/vanilla-chat
npm install
# or
pnpm install
```

2. **Make sure the FastAPI server is running:**

```bash
cd ../python-fastapi
python main.py
```

The FastAPI server should be running on `http://localhost:8080`

3. **Start the Vite dev server:**

```bash
npm run dev
# or
pnpm dev
```

The app will be available at `http://localhost:3001`

## Usage

1. Open `http://localhost:3001` in your browser
2. Type a message and press Enter (or click Send)
3. Watch the AI response stream in real-time!

## Project Structure

```
vanilla-chat/
├── index.html      # Main HTML file
├── src/
│   ├── main.js    # Chat client logic
│   └── style.css   # Styles
├── package.json    # Dependencies
└── vite.config.ts # Vite configuration
```

## How It Works

The app uses `ChatClient` from `@tanstack/ai-client` with the `fetchServerSentEvents` connection adapter to connect to the FastAPI server:

```javascript
import { ChatClient, fetchServerSentEvents } from '@tanstack/ai-client'

const client = new ChatClient({
  connection: fetchServerSentEvents('http://localhost:8080/chat'),
  onMessagesChange: (messages) => {
    // Update UI when messages change
  },
  onLoadingChange: (isLoading) => {
    // Update loading state
  },
})
```

The FastAPI server streams responses in Server-Sent Events (SSE) format, which the client automatically parses and displays.


--- examples/ts-vue-chat/index.html ---
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/x-icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>TanStack AI - Vue Chat</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>


--- testing/panel/src/routes/api.chat.ts ---
import * as path from 'node:path'
import * as fs from 'node:fs'
import { createFileRoute } from '@tanstack/react-router'
import { chat, maxIterations, toStreamResponse } from '@tanstack/ai'
import { anthropic } from '@tanstack/ai-anthropic'
import { gemini } from '@tanstack/ai-gemini'
import { openai } from '@tanstack/ai-openai'
import { ollama } from '@tanstack/ai-ollama'
import type { AIAdapter, ChatOptions, StreamChunk } from '@tanstack/ai'
import type { ChunkRecording } from '@/lib/recording'
import {
  addToCartToolDef,
  addToWishListToolDef,
  getGuitars,
  getPersonalGuitarPreferenceToolDef,
  recommendGuitarToolDef,
} from '@/lib/guitar-tools'

const SYSTEM_PROMPT = `You are a helpful assistant for a guitar store.

CRITICAL INSTRUCTIONS - YOU MUST FOLLOW THIS EXACT WORKFLOW:

When a user asks for a guitar recommendation:
1. FIRST: Use the getGuitars tool (no parameters needed)
2. SECOND: Use the recommendGuitar tool with the ID of the guitar you want to recommend
3. NEVER write a recommendation directly - ALWAYS use the recommendGuitar tool

IMPORTANT:
- The recommendGuitar tool will display the guitar in a special, appealing format
- You MUST use recommendGuitar for ANY guitar recommendation
- ONLY recommend guitars from our inventory (use getGuitars first)
- The recommendGuitar tool has a buy button - this is how customers purchase
- Do NOT describe the guitar yourself - let the recommendGuitar tool do it

Example workflow:
User: "I want an acoustic guitar"
Step 1: Call getGuitars()
Step 2: Call recommendGuitar(id: "6") 
Step 3: Done - do NOT add any text after calling recommendGuitar
`
const addToCartToolServer = addToCartToolDef.server((args) => ({
  success: true,
  cartId: 'CART_' + Date.now(),
  guitarId: args.guitarId,
  quantity: args.quantity,
  totalItems: args.quantity,
}))

type Provider = 'openai' | 'anthropic' | 'gemini' | 'ollama'

/**
 * Wraps an adapter to intercept chatStream and record raw chunks from the adapter
 * before they're processed by the stream processor.
 */
function wrapAdapterForRecording<TAdapter extends AIAdapter>(
  adapter: TAdapter,
  recordingFilePath: string,
  model: string,
  provider: string,
): TAdapter {
  const originalChatStream = adapter.chatStream.bind(adapter)

  // Track chunks for recording
  const chunks: Array<{
    chunk: StreamChunk
    timestamp: number
    index: number
  }> = []
  let chunkIndex = 0

  // Create a wrapper that intercepts chatStream
  const wrappedAdapter = {
    ...adapter,
    chatStream: async function* (
      options: ChatOptions<string, any>,
    ): AsyncIterable<StreamChunk> {
      const startTime = Date.now()

      try {
        // Iterate over chunks from the original adapter
        for await (const chunk of originalChatStream(options)) {
          const timestamp = Date.now()
          const index = chunkIndex++

          // Record the chunk
          chunks.push({
            chunk,
            timestamp,
            index,
          })

          // Yield the chunk to continue normal processing
          yield chunk
        }
      } finally {
        // Save recording when stream completes
        try {
          const recording: ChunkRecording = {
            version: '1.0',
            timestamp: startTime,
            model,
            provider,
            chunks,
          }

          // Ensure directory exists
          const dir = path.dirname(recordingFilePath)
          if (!fs.existsSync(dir)) {
            fs.mkdirSync(dir, { recursive: true })
          }

          // Write recording
          fs.writeFileSync(
            recordingFilePath,
            JSON.stringify(recording, null, 2),
            'utf-8',
          )

          console.log(`Adapter chunks recorded to: ${recordingFilePath}`)
        } catch (error) {
          console.error('Failed to save adapter chunk recording:', error)
        }
      }
    },
  } as TAdapter

  return wrappedAdapter
}

export const Route = createFileRoute('/api/chat')({
  server: {
    handlers: {
      POST: async ({ request }) => {
        // Capture request signal before reading body (it may be aborted after body is consumed)
        const requestSignal = request.signal

        // If request is already aborted, return early
        if (requestSignal.aborted) {
          return new Response(null, { status: 499 }) // 499 = Client Closed Request
        }

        const abortController = new AbortController()

        const body = await request.json()
        const messages = body.messages
        const data = body.data || {}

        // Extract provider, model, and traceId from data
        const provider: Provider = data.provider || 'openai'
        const model: string | undefined = data.model
        const traceId: string | undefined = data.traceId

        try {
          // Select adapter based on provider
          let adapter
          let defaultModel

          switch (provider) {
            case 'anthropic':
              adapter = anthropic()
              defaultModel = 'claude-sonnet-4-5-20250929'
              break
            case 'gemini':
              adapter = gemini()
              defaultModel = 'gemini-2.0-flash-exp'
              break
            case 'ollama':
              adapter = ollama()
              defaultModel = 'mistral:7b'
              break
            case 'openai':
            default:
              adapter = openai()
              defaultModel = 'gpt-4o'
              break
          }

          // Determine model - use provided model or default based on provider
          const selectedModel = model || defaultModel

          console.log(`>> model: ${selectedModel} on provider: ${provider}`)

          // If we have a traceId, wrap the adapter to record raw chunks from chatStream
          if (traceId) {
            const traceDir = path.join(process.cwd(), 'test-traces')
            if (!fs.existsSync(traceDir)) {
              fs.mkdirSync(traceDir, { recursive: true })
            }
            const traceFile = path.join(traceDir, `${traceId}.json`)
            adapter = wrapAdapterForRecording(
              adapter,
              traceFile,
              selectedModel,
              provider,
            )
          }

          // Use the stream abort signal for proper cancellation handling
          const stream = chat({
            adapter: adapter as any,
            model: selectedModel as any,
            tools: [
              getGuitars, // Server tool
              recommendGuitarToolDef, // No server execute - client will handle
              addToCartToolServer,
              addToWishListToolDef,
              getPersonalGuitarPreferenceToolDef,
            ],
            systemPrompts: [SYSTEM_PROMPT],
            agentLoopStrategy: maxIterations(20),
            messages,
            providerOptions: {
              // Enable reasoning for OpenAI (gpt-5, o3 models):
              // reasoning: {
              //   effort: "medium", // or "low", "high", "minimal", "none" (for gpt-5.1)
              // },
              // Enable thinking for Anthropic:
              /*   thinking: {
                  type: "enabled",
                  budget_tokens: 2048,
                }, */
            },
            abortController,
          })

          return toStreamResponse(stream, { abortController })
        } catch (error: any) {
          console.error('[API Route] Error in chat request:', {
            message: error?.message,
            name: error?.name,
            status: error?.status,
            statusText: error?.statusText,
            code: error?.code,
            type: error?.type,
            stack: error?.stack,
            error: error,
          })
          // If request was aborted, return early (don't send error response)
          if (error.name === 'AbortError' || abortController.signal.aborted) {
            return new Response(null, { status: 499 }) // 499 = Client Closed Request
          }
          return new Response(
            JSON.stringify({
              error: error.message || 'An error occurred',
            }),
            {
              status: 500,
              headers: { 'Content-Type': 'application/json' },
            },
          )
        }
      },
    },
  },
})


--- testing/panel/src/routes/api.list-traces.ts ---
import { createFileRoute } from '@tanstack/react-router'
import * as fs from 'node:fs'
import * as path from 'node:path'

export const Route = createFileRoute('/api/list-traces')({
  server: {
    handlers: {
      GET: async () => {
        try {
          const traceDir = path.join(process.cwd(), 'test-traces')

          // Check if directory exists
          if (!fs.existsSync(traceDir)) {
            return new Response(JSON.stringify({ traces: [] }), {
              status: 200,
              headers: { 'Content-Type': 'application/json' },
            })
          }

          // Read all files in the directory
          const files = fs.readdirSync(traceDir)

          // Filter for JSON files and extract metadata
          const traces = files
            .filter((file) => file.endsWith('.json'))
            .map((file) => {
              try {
                const filePath = path.join(traceDir, file)
                const stats = fs.statSync(filePath)
                const content = fs.readFileSync(filePath, 'utf-8')
                const data = JSON.parse(content)

                return {
                  id: data.id || file.replace('.json', ''),
                  filename: file,
                  timestamp: data.timestamp || stats.mtime.toISOString(),
                  provider: data.provider,
                  model: data.model,
                  size: stats.size,
                  chunkCount: data.chunks?.length || 0,
                }
              } catch (error) {
                console.error(`Failed to read trace file ${file}:`, error)
                return null
              }
            })
            .filter((trace) => trace !== null)
            .sort((a, b) => {
              // Sort by timestamp, newest first
              return (
                new Date(b.timestamp).getTime() -
                new Date(a.timestamp).getTime()
              )
            })

          return new Response(JSON.stringify({ traces }), {
            status: 200,
            headers: { 'Content-Type': 'application/json' },
          })
        } catch (error: any) {
          console.error('[API] Error listing traces:', error)
          return new Response(
            JSON.stringify({ error: error.message || 'Failed to list traces' }),
            {
              status: 500,
              headers: { 'Content-Type': 'application/json' },
            },
          )
        }
      },
    },
  },
})


--- testing/panel/src/routes/api.load-trace.ts ---
import { createFileRoute } from '@tanstack/react-router'
import * as fs from 'node:fs'
import * as path from 'node:path'

export const Route = createFileRoute('/api/load-trace')({
  server: {
    handlers: {
      GET: async ({ request }) => {
        try {
          const url = new URL(request.url)
          const traceId = url.searchParams.get('id')

          if (!traceId) {
            return new Response(JSON.stringify({ error: 'Missing trace ID' }), {
              status: 400,
              headers: { 'Content-Type': 'application/json' },
            })
          }

          const traceDir = path.join(process.cwd(), 'test-traces')
          const traceFile = path.join(traceDir, `${traceId}.json`)

          if (!fs.existsSync(traceFile)) {
            return new Response(
              JSON.stringify({ error: 'Trace file not found' }),
              {
                status: 404,
                headers: { 'Content-Type': 'application/json' },
              },
            )
          }

          const traceData = fs.readFileSync(traceFile, 'utf-8')
          return new Response(traceData, {
            status: 200,
            headers: { 'Content-Type': 'application/json' },
          })
        } catch (error: any) {
          console.error('[API] Error loading trace:', error)
          return new Response(
            JSON.stringify({ error: error.message || 'Failed to load trace' }),
            {
              status: 500,
              headers: { 'Content-Type': 'application/json' },
            },
          )
        }
      },
    },
  },
})


--- packages/typescript/smoke-tests/e2e/src/routes/api.tanchat.ts ---
import { createFileRoute } from '@tanstack/react-router'
import { chat, toStreamResponse, maxIterations } from '@tanstack/ai'
import { openai } from '@tanstack/ai-openai'

export const Route = createFileRoute('/api/tanchat')({
  server: {
    handlers: {
      POST: async ({ request }) => {
        const requestSignal = request.signal

        if (requestSignal?.aborted) {
          return new Response(null, { status: 499 })
        }

        const abortController = new AbortController()

        const { messages } = await request.json()
        try {
          const stream = chat({
            adapter: openai(),
            model: 'gpt-4o-mini',
            systemPrompts: [
              'You are a helpful assistant. Provide clear and concise answers.',
            ],
            agentLoopStrategy: maxIterations(20),
            messages,
            abortController,
          })

          return toStreamResponse(stream, { abortController })
        } catch (error: any) {
          console.error('[API Route] Error in chat request:', {
            message: error?.message,
            name: error?.name,
            status: error?.status,
            statusText: error?.statusText,
            code: error?.code,
            type: error?.type,
            stack: error?.stack,
            error: error,
          })
          if (error.name === 'AbortError' || abortController.signal.aborted) {
            return new Response(null, { status: 499 })
          }
          return new Response(
            JSON.stringify({
              error: error.message || 'An error occurred',
            }),
            {
              status: 500,
              headers: { 'Content-Type': 'application/json' },
            },
          )
        }
      },
    },
  },
})


--- CHANGELOG.md ---
# Changelog

## Reverts

### Revert: ci: sync config between projects (#53)

**Reverted:** Commit [349c24c](https://github.com/TanStack/ai/commit/349c24cf58e59f956e10137b6b6d5516399c0931)

This commit was reverted due to a regression that was breaking the main branch. The changes affected CI configuration syncing between projects.

**Changes reverted:**

- Reverted workflow changes in `.github/workflows/autofix.yml`
- Reverted nx.json configuration changes
- Reverted package.json script changes
- Reverted ai-solid package changes (tsconfig, test utilities, package scripts)
- Restored `scripts/clean.sh`
- Renamed `scripts/generate-docs.ts` back to `scripts/generateDocs.ts`
- Restored size-limit configuration and dependencies
- Restored pnpm overrides

## Recent Refactoring (November 2025)

### New Packages

#### @tanstack/ai-client

**New Package:** Framework-agnostic headless client for TanStack AI chat functionality.

**Installation:**

```bash
npm install @tanstack/ai-client
```

**Features:**

- ✅ Framework-agnostic (works with React, Vue, Svelte, vanilla JS, etc.)
- ✅ Headless client with state management
- ✅ Connection adapters for SSE, HTTP streams, and server functions
- ✅ Stream processing with smart chunking strategies
- ✅ Automatic tool call handling

**See:** [Package Documentation](packages/typescript/ai-client/README.md)

#### @tanstack/ai-react-ui

**New Package:** Pre-built React UI components for chat interfaces.

**Installation:**

```bash
npm install @tanstack/ai-react-ui
```

**Features:**

- ✅ Pre-built chat UI components
- ✅ Customizable styling
- ✅ Works with `@tanstack/ai-react`

#### tanstack-ai (Python)

**New Package:** Python utilities for converting AI provider events to TanStack AI StreamChunk format.

**Installation:**

```bash
pip install tanstack-ai
```

**Features:**

- ✅ Message formatting for Anthropic and OpenAI
- ✅ Stream chunk conversion from provider events
- ✅ SSE formatting utilities
- ✅ Type-safe with Pydantic models

**Usage:**

```python
from tanstack_ai import StreamChunkConverter, format_sse_chunk

converter = StreamChunkConverter(model="claude-3-haiku-20240307", provider="anthropic")

async for event in anthropic_stream:
    chunks = await converter.convert_event(event)
    for chunk in chunks:
        yield format_sse_chunk(chunk)
```

**See:** [Package Documentation](packages/python/tanstack-ai/README.md) | [Python FastAPI Example](examples/python-fastapi/README.md)

#### tanstack/ai (PHP)

**New Package:** PHP utilities for converting AI provider events to TanStack AI StreamChunk format.

**Installation:**

```bash
composer require tanstack/ai
```

**Features:**

- ✅ Message formatting for Anthropic and OpenAI
- ✅ Stream chunk conversion from provider events
- ✅ SSE formatting utilities
- ✅ PHP 8.1+ with type safety

**Usage:**

```php
use TanStack\AI\StreamChunkConverter;
use TanStack\AI\SSEFormatter;

$converter = new StreamChunkConverter(
    model: "claude-3-haiku-20240307",
    provider: "anthropic"
);

foreach ($anthropicStream as $event) {
    $chunks = $converter->convertEvent($event);
    foreach ($chunks as $chunk) {
        echo SSEFormatter::formatChunk($chunk);
    }
}
```

**See:** [Package Documentation](packages/php/tanstack-ai/README.md) | [PHP Slim Example](examples/php-slim/README.md)

### New Examples

#### Vanilla Chat Example

**New Example:** Framework-free chat application using pure JavaScript and `@tanstack/ai-client`.

**Features:**

- ✅ Pure vanilla JavaScript (no frameworks!)
- ✅ Real-time streaming with `@tanstack/ai-client`
- ✅ Beautiful, responsive UI
- ✅ Connects to Python FastAPI backend

**See:** [Vanilla Chat Example](examples/vanilla-chat/README.md)

#### Python FastAPI Server Example

**New Example:** FastAPI server that streams AI responses in SSE format.

**Features:**

- ✅ FastAPI with SSE streaming
- ✅ Converts Anthropic/OpenAI events to StreamChunk format
- ✅ Compatible with `@tanstack/ai-client`
- ✅ Tool call support

**See:** [Python FastAPI Example](examples/python-fastapi/README.md)

#### PHP Slim Framework Server Example

**New Example:** PHP Slim Framework server with Anthropic and OpenAI support.

**Features:**

- ✅ Slim Framework with SSE streaming
- ✅ Converts Anthropic/OpenAI events to StreamChunk format
- ✅ Compatible with `@tanstack/ai-client`
- ✅ PHP 8.1+ with type safety

**See:** [PHP Slim Example](examples/php-slim/README.md)

### Stream Processing Features

**New Feature:** Smart chunking strategies for optimal UX in `@tanstack/ai-client`.

**Built-in Strategies:**

- `ImmediateStrategy` - Emit content immediately
- `PunctuationStrategy` - Emit at sentence boundaries
- `BatchStrategy` - Batch N characters before emitting
- `WordBoundaryStrategy` - Emit at word boundaries
- `CompositeStrategy` - Combine multiple strategies

**Usage:**

```typescript
import {
  ChatClient,
  fetchServerSentEvents,
  PunctuationStrategy,
} from '@tanstack/ai-client'

const client = new ChatClient({
  connection: fetchServerSentEvents('/api/chat'),
  chunkingStrategy: new PunctuationStrategy(),
})
```

**See:** [Stream Processing Quick Start](packages/typescript/ai-client/docs/STREAM_QUICKSTART.md)

### Connection Adapters Added

**New Feature:** `@tanstack/ai-client` now uses flexible connection adapters for streaming.

**API:**

```typescript
import { ChatClient, fetchServerSentEvents } from '@tanstack/ai-client'

const client = new ChatClient({
  connection: fetchServerSentEvents('/api/chat', {
    headers: { Authorization: 'Bearer token' },
  }),
})
```

**Benefits:**

- ✅ Support SSE, HTTP streams, WebSockets, server functions, etc.
- ✅ Easy to test with custom adapters
- ✅ Extensible for any streaming scenario

**Built-in Adapters:**

- `fetchServerSentEvents(url, options)` - For SSE (default)
- `fetchHttpStream(url, options)` - For newline-delimited JSON
- `stream(factory)` - For direct async iterables (server functions)

**With React:**

```typescript
import { useChat, fetchServerSentEvents } from '@tanstack/ai-react'

const chat = useChat({
  connection: fetchServerSentEvents('/api/chat'),
})
```

**Create Custom Adapters:**

```typescript
import type { ConnectionAdapter } from '@tanstack/ai-client'

const wsAdapter: ConnectionAdapter = {
  async *connect(messages, data) {
    const ws = new WebSocket('wss://api.example.com')
    // ... WebSocket logic
  },
  abort() {
    ws.close()
  },
}

const chat = useChat({ connection: wsAdapter })
```

**Documentation:**

- 📖 [Connection Adapters Guide](docs/CONNECTION_ADAPTERS_GUIDE.md) - Complete guide
- 📖 [Connection Adapters API](packages/ai-client/CONNECTION_ADAPTERS.md) - API reference

### Agent Loop Strategies

**New Feature:** `agentLoopStrategy` parameter replaces `maxIterations` with a flexible strategy pattern.

**Before:**

```typescript
const stream = ai.chat({
  model: "gpt-4",
  messages: [...],
  tools: [...],
  maxIterations: 5,
});
```

**After:**

```typescript
import { maxIterations, untilFinishReason, combineStrategies } from "@tanstack/ai";

const stream = ai.chat({
  model: "gpt-4",
  messages: [...],
  tools: [...],
  agentLoopStrategy: maxIterations(5), // Or custom strategy
});
```

**Built-in Strategies:**

- `maxIterations(max)` - Continue for max iterations
- `untilFinishReason(reasons)` - Stop on specific finish reasons
- `combineStrategies(strategies)` - Combine multiple strategies

### ToolCallManager Class

**Refactoring:** Tool execution logic extracted into separate `ToolCallManager` class.

**Benefits:**

- ✅ Reduced `chat()` method size from ~180 lines to ~85 lines
- ✅ Independently testable
- ✅ Cleaner separation of concerns

## Previous Refactoring (October 2025)

### Breaking Changes

#### Chat API Refactored

The `chat()` method has been split into two distinct methods with different behaviors:

**Before:**

```typescript
// Promise mode
const result = await ai.chat({
  model: "gpt-4",
  messages: [...],
  as: "promise"
});

// Stream mode
const stream = ai.chat({
  model: "gpt-4",
  messages: [...],
  as: "stream"
});

// Response mode
const response = ai.chat({
  model: "gpt-4",
  messages: [...],
  as: "response"
});
```

**After:**

```typescript
// Promise-based completion (no automatic tool execution)
const result = await ai.chatCompletion({
  model: "gpt-4",
  messages: [...]
});

// Streaming with automatic tool execution loop
const stream = ai.chat({
  model: "gpt-4",
  messages: [...],
  tools: [weatherTool] // Auto-executed when called
});

// HTTP streaming
const stream = ai.chat({
  model: "gpt-4",
  messages: [...]
});
return toStreamResponse(stream); // Exported from @tanstack/ai
```

### New Features

#### 1. Automatic Tool Execution Loop

The `chat()` method now includes an automatic tool execution loop:

```typescript
import { chat, tool, maxIterations } from '@tanstack/ai'
import { openai } from '@tanstack/ai-openai'

const stream = chat({
  adapter: openai(),
  model: 'gpt-4o',
  messages: [{ role: 'user', content: "What's the weather in Paris?" }],
  tools: [weatherTool],
  agentLoopStrategy: maxIterations(5), // Optional: control loop
})

// SDK automatically:
// 1. Detects tool calls from model
// 2. Executes tool.execute() functions
// 3. Adds results to conversation
// 4. Continues conversation with model
// 5. Emits tool_call and tool_result chunks
```

**New Chunk Types:**

- `tool_call` - Model is calling a tool
- `tool_result` - Tool execution result (new!)

#### 2. Agent Loop Strategies

Control the tool execution loop with flexible strategies:

```typescript
import {
  maxIterations,
  untilFinishReason,
  combineStrategies,
} from '@tanstack/ai'

// Built-in strategies
agentLoopStrategy: maxIterations(10)
agentLoopStrategy: untilFinishReason(['stop', 'length'])
agentLoopStrategy: combineStrategies([
  maxIterations(10),
  ({ messages }) => messages.length < 100,
])

// Custom strategy
agentLoopStrategy: ({ iterationCount, messages, finishReason }) => {
  return iterationCount < 10 && messages.length < 50
}
```

#### 3. ToolCallManager Class

Tool execution logic extracted into a testable class:

```typescript
import { ToolCallManager } from '@tanstack/ai'

const manager = new ToolCallManager(tools)

// Accumulate tool calls from stream
manager.addToolCallChunk(chunk)

// Check if tools need execution
if (manager.hasToolCalls()) {
  const results = yield * manager.executeTools(doneChunk)
}

// Clear for next iteration
manager.clear()
```

#### 4. Explicit Server-Sent Events Helpers

```typescript
import { toStreamResponse, toServerSentEventsStream } from '@tanstack/ai'

// Full HTTP Response with SSE headers
return toStreamResponse(stream)

// Just the ReadableStream (for custom response)
return new Response(toServerSentEventsStream(stream), {
  headers: { 'X-Custom': 'value' },
})
```

### New Exports

```typescript
// From @tanstack/ai
export { chat, chatCompletion } // Separate streaming and promise methods
export { toStreamResponse, toServerSentEventsStream } // HTTP helpers
export { ToolCallManager } // Tool execution manager
export { maxIterations, untilFinishReason, combineStrategies } // Loop strategies
export type { AgentLoopStrategy, AgentLoopState } // Strategy types
export type { ToolResultStreamChunk } // New chunk type
```

### Migration Guide

See [docs/MIGRATION_UNIFIED_CHAT.md](docs/MIGRATION_UNIFIED_CHAT.md) for complete migration guide.

**Quick migration:**

1. Replace `chat({ as: "promise" })` with `chatCompletion()`
2. Replace `chat({ as: "stream" })` with `chat()`
3. Replace `chat({ as: "response" })` with `chat()` + `toStreamResponse()`
4. Import `toStreamResponse` from `@tanstack/ai` (not subpath)
5. Update `maxIterations: 5` to `agentLoopStrategy: maxIterations(5)` (optional)

### Architecture Improvements

- **Smaller chat() method**: Reduced from ~180 lines to ~85 lines
- **Testable components**: ToolCallManager and strategies have unit tests (23 tests, all passing)
- **Separation of concerns**: Tool execution logic isolated from chat logic
- **Strategy pattern**: Flexible control over tool execution loop
- **Better documentation**: Comprehensive guides for all features

### Documentation

New documentation:

- [Tool Execution Loop](docs/TOOL_EXECUTION_LOOP.md) - How automatic execution works
- [Agent Loop Strategies](docs/AGENT_LOOP_STRATEGIES.md) - Controlling the loop
- [Unified Chat API](docs/UNIFIED_CHAT_API.md) - Updated API reference

### Testing

```bash
# Run all tests
pnpm test

# Run tests for @tanstack/ai
cd packages/ai && pnpm test

# Test coverage:
# - ToolCallManager: 7 tests
# - Agent Loop Strategies: 16 tests
# - Total: 23 tests, all passing
```

### Backwards Compatibility

- `maxIterations` as a number still works (converted to strategy automatically)
- All existing functionality preserved
- Gradual migration path available

### Breaking Changes Summary

1. **`chat()` method**:
   - No longer accepts `as` option
   - Now streaming-only
   - Includes automatic tool execution loop

2. **New `chatCompletion()` method**:
   - Promise-based
   - Supports structured output
   - No automatic tool execution

3. **Import changes**:
   - `toStreamResponse` now from `@tanstack/ai` (not subpath)

### Benefits

✅ **Clearer API** - Method names indicate behavior  
✅ **Automatic tool execution** - No manual management  
✅ **Flexible control** - Strategy pattern for loops  
✅ **Better organized** - Tool logic in separate class  
✅ **Well tested** - 23 unit tests  
✅ **Better docs** - Comprehensive guides  
✅ **Type-safe** - Full TypeScript support

---

For questions or issues, see the [documentation](docs/) or [examples](examples/).


## Links discovered
- [349c24c](https://github.com/TanStack/ai/commit/349c24cf58e59f956e10137b6b6d5516399c0931)
- [Package Documentation](https://github.com/tanstack/ai/blob/main/packages/typescript/ai-client/README.md)
- [Package Documentation](https://github.com/tanstack/ai/blob/main/packages/python/tanstack-ai/README.md)
- [Python FastAPI Example](https://github.com/tanstack/ai/blob/main/examples/python-fastapi/README.md)
- [Package Documentation](https://github.com/tanstack/ai/blob/main/packages/php/tanstack-ai/README.md)
- [PHP Slim Example](https://github.com/tanstack/ai/blob/main/examples/php-slim/README.md)
- [Vanilla Chat Example](https://github.com/tanstack/ai/blob/main/examples/vanilla-chat/README.md)
- [Stream Processing Quick Start](https://github.com/tanstack/ai/blob/main/packages/typescript/ai-client/docs/STREAM_QUICKSTART.md)
- [Connection Adapters Guide](https://github.com/tanstack/ai/blob/main/docs/CONNECTION_ADAPTERS_GUIDE.md)
- [Connection Adapters API](https://github.com/tanstack/ai/blob/main/packages/ai-client/CONNECTION_ADAPTERS.md)
- [docs/MIGRATION_UNIFIED_CHAT.md](https://github.com/tanstack/ai/blob/main/docs/MIGRATION_UNIFIED_CHAT.md)
- [Tool Execution Loop](https://github.com/tanstack/ai/blob/main/docs/TOOL_EXECUTION_LOOP.md)
- [Agent Loop Strategies](https://github.com/tanstack/ai/blob/main/docs/AGENT_LOOP_STRATEGIES.md)
- [Unified Chat API](https://github.com/tanstack/ai/blob/main/docs/UNIFIED_CHAT_API.md)
- [documentation](https://github.com/tanstack/ai/blob/main/docs.md)
- [examples](https://github.com/tanstack/ai/blob/main/examples.md)

--- packages/typescript/ai-anthropic/CHANGELOG.md ---
# @tanstack/ai-anthropic

## 0.0.3

### Patch Changes

- Updated dependencies [[`52c3172`](https://github.com/TanStack/ai/commit/52c317244294a75b0c7f5e6cafc8583fbb6abfb7)]:
  - @tanstack/ai@0.0.3

## 0.0.2

### Patch Changes

- added text metadata support for message inputs ([#95](https://github.com/TanStack/ai/pull/95))

- Updated dependencies [[`64fda55`](https://github.com/TanStack/ai/commit/64fda55f839062bc67b8c24850123e879fdbf0b3)]:
  - @tanstack/ai@0.0.2

## 0.0.1

### Patch Changes

- Initial release of TanStack AI ([#72](https://github.com/TanStack/ai/pull/72))

- Updated dependencies [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)]:
  - @tanstack/ai@0.0.1


## Links discovered
- [[`52c3172`](https://github.com/TanStack/ai/commit/52c317244294a75b0c7f5e6cafc8583fbb6abfb7)
- [#95](https://github.com/TanStack/ai/pull/95)
- [[`64fda55`](https://github.com/TanStack/ai/commit/64fda55f839062bc67b8c24850123e879fdbf0b3)
- [#72](https://github.com/TanStack/ai/pull/72)
- [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)

--- packages/typescript/ai-client/CHANGELOG.md ---
# @tanstack/ai-client

## 0.0.3

### Patch Changes

- Updated dependencies [[`52c3172`](https://github.com/TanStack/ai/commit/52c317244294a75b0c7f5e6cafc8583fbb6abfb7)]:
  - @tanstack/ai@0.0.3

## 0.0.2

### Patch Changes

- Made the fetch client used by the default connection adapters configurable. ([#80](https://github.com/TanStack/ai/pull/80))

- Updated dependencies [[`64fda55`](https://github.com/TanStack/ai/commit/64fda55f839062bc67b8c24850123e879fdbf0b3)]:
  - @tanstack/ai@0.0.2

## 0.0.1

### Patch Changes

- Initial release of TanStack AI ([#72](https://github.com/TanStack/ai/pull/72))

- Updated dependencies [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)]:
  - @tanstack/ai@0.0.1


## Links discovered
- [[`52c3172`](https://github.com/TanStack/ai/commit/52c317244294a75b0c7f5e6cafc8583fbb6abfb7)
- [#80](https://github.com/TanStack/ai/pull/80)
- [[`64fda55`](https://github.com/TanStack/ai/commit/64fda55f839062bc67b8c24850123e879fdbf0b3)
- [#72](https://github.com/TanStack/ai/pull/72)
- [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)

--- packages/typescript/ai-devtools/CHANGELOG.md ---
# @tanstack/ai-devtools-core

## 0.0.3

### Patch Changes

- update event client ([#128](https://github.com/TanStack/ai/pull/128))

- Updated dependencies [[`52c3172`](https://github.com/TanStack/ai/commit/52c317244294a75b0c7f5e6cafc8583fbb6abfb7)]:
  - @tanstack/ai@0.0.3

## 0.0.2

### Patch Changes

- Updated dependencies [[`64fda55`](https://github.com/TanStack/ai/commit/64fda55f839062bc67b8c24850123e879fdbf0b3)]:
  - @tanstack/ai@0.0.2

## 0.0.1

### Patch Changes

- Initial release of TanStack AI ([#72](https://github.com/TanStack/ai/pull/72))

- Updated dependencies [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)]:
  - @tanstack/ai@0.0.1


## Links discovered
- [#128](https://github.com/TanStack/ai/pull/128)
- [[`52c3172`](https://github.com/TanStack/ai/commit/52c317244294a75b0c7f5e6cafc8583fbb6abfb7)
- [[`64fda55`](https://github.com/TanStack/ai/commit/64fda55f839062bc67b8c24850123e879fdbf0b3)
- [#72](https://github.com/TanStack/ai/pull/72)
- [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)

--- packages/typescript/ai-gemini/CHANGELOG.md ---
# @tanstack/ai-gemini

## 0.0.3

### Patch Changes

- Updated dependencies [[`52c3172`](https://github.com/TanStack/ai/commit/52c317244294a75b0c7f5e6cafc8583fbb6abfb7)]:
  - @tanstack/ai@0.0.3

## 0.0.2

### Patch Changes

- added text metadata support for message inputs ([#95](https://github.com/TanStack/ai/pull/95))

- Updated dependencies [[`64fda55`](https://github.com/TanStack/ai/commit/64fda55f839062bc67b8c24850123e879fdbf0b3)]:
  - @tanstack/ai@0.0.2

## 0.0.1

### Patch Changes

- Initial release of TanStack AI ([#72](https://github.com/TanStack/ai/pull/72))

- Updated dependencies [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)]:
  - @tanstack/ai@0.0.1


## Links discovered
- [[`52c3172`](https://github.com/TanStack/ai/commit/52c317244294a75b0c7f5e6cafc8583fbb6abfb7)
- [#95](https://github.com/TanStack/ai/pull/95)
- [[`64fda55`](https://github.com/TanStack/ai/commit/64fda55f839062bc67b8c24850123e879fdbf0b3)
- [#72](https://github.com/TanStack/ai/pull/72)
- [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)

--- packages/typescript/ai-ollama/CHANGELOG.md ---
# @tanstack/ai-ollama

## 0.0.3

### Patch Changes

- Updated dependencies [[`52c3172`](https://github.com/TanStack/ai/commit/52c317244294a75b0c7f5e6cafc8583fbb6abfb7)]:
  - @tanstack/ai@0.0.3

## 0.0.2

### Patch Changes

- Updated dependencies [[`64fda55`](https://github.com/TanStack/ai/commit/64fda55f839062bc67b8c24850123e879fdbf0b3)]:
  - @tanstack/ai@0.0.2

## 0.0.1

### Patch Changes

- Initial release of TanStack AI ([#72](https://github.com/TanStack/ai/pull/72))

- Updated dependencies [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)]:
  - @tanstack/ai@0.0.1


## Links discovered
- [[`52c3172`](https://github.com/TanStack/ai/commit/52c317244294a75b0c7f5e6cafc8583fbb6abfb7)
- [[`64fda55`](https://github.com/TanStack/ai/commit/64fda55f839062bc67b8c24850123e879fdbf0b3)
- [#72](https://github.com/TanStack/ai/pull/72)
- [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)

--- packages/typescript/ai-openai/CHANGELOG.md ---
# @tanstack/ai-openai

## 0.0.3

### Patch Changes

- Fix reasoning token streaming for `gpt-5-mini` and `gpt-5-nano` models ([#94](https://github.com/TanStack/ai/pull/94))
  - Added `OpenAIReasoningOptions` to type definitions for `gpt-5-mini` and `gpt-5-nano` models
  - Fixed `summary` option placement in `OpenAIReasoningOptions` (moved inside `reasoning` object to match OpenAI SDK)
  - Added handler for `response.reasoning_summary_text.delta` events to stream reasoning summaries
  - Added model-specific `reasoning.summary` types: `concise` only available for `computer-use-preview`
  - Added `OpenAIReasoningOptionsWithConcise` for `computer-use-preview` model

- Updated dependencies [[`52c3172`](https://github.com/TanStack/ai/commit/52c317244294a75b0c7f5e6cafc8583fbb6abfb7)]:
  - @tanstack/ai@0.0.3

## 0.0.2

### Patch Changes

- added text metadata support for message inputs ([#95](https://github.com/TanStack/ai/pull/95))

- Updated dependencies [[`64fda55`](https://github.com/TanStack/ai/commit/64fda55f839062bc67b8c24850123e879fdbf0b3)]:
  - @tanstack/ai@0.0.2

## 0.0.1

### Patch Changes

- Initial release of TanStack AI ([#72](https://github.com/TanStack/ai/pull/72))

- Updated dependencies [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)]:
  - @tanstack/ai@0.0.1


## Links discovered
- [#94](https://github.com/TanStack/ai/pull/94)
- [[`52c3172`](https://github.com/TanStack/ai/commit/52c317244294a75b0c7f5e6cafc8583fbb6abfb7)
- [#95](https://github.com/TanStack/ai/pull/95)
- [[`64fda55`](https://github.com/TanStack/ai/commit/64fda55f839062bc67b8c24850123e879fdbf0b3)
- [#72](https://github.com/TanStack/ai/pull/72)
- [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)

--- packages/typescript/ai-react-ui/CHANGELOG.md ---
# @tanstack/ai-react-ui

## 0.0.3

### Patch Changes

- Updated dependencies []:
  - @tanstack/ai-client@0.0.3
  - @tanstack/ai-react@0.0.3

## 0.0.2

### Patch Changes

- Updated dependencies [[`a7bd563`](https://github.com/TanStack/ai/commit/a7bd5639eb2fbf1b4169eb307f77149f4a85a915)]:
  - @tanstack/ai-client@0.0.2
  - @tanstack/ai-react@0.0.2

## 0.0.1

### Patch Changes

- initial release of AI ([#76](https://github.com/TanStack/ai/pull/76))

## 0.0.1

### Patch Changes

- Initial release of TanStack AI ([#72](https://github.com/TanStack/ai/pull/72))

- Updated dependencies [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)]:
  - @tanstack/ai-client@0.0.1
  - @tanstack/ai-react@0.0.1


## Links discovered
- [[`a7bd563`](https://github.com/TanStack/ai/commit/a7bd5639eb2fbf1b4169eb307f77149f4a85a915)
- [#76](https://github.com/TanStack/ai/pull/76)
- [#72](https://github.com/TanStack/ai/pull/72)
- [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)

--- packages/typescript/ai-react/CHANGELOG.md ---
# @tanstack/ai-react

## 0.0.3

### Patch Changes

- Updated dependencies [[`52c3172`](https://github.com/TanStack/ai/commit/52c317244294a75b0c7f5e6cafc8583fbb6abfb7)]:
  - @tanstack/ai@0.0.3
  - @tanstack/ai-client@0.0.3

## 0.0.2

### Patch Changes

- Updated dependencies [[`a7bd563`](https://github.com/TanStack/ai/commit/a7bd5639eb2fbf1b4169eb307f77149f4a85a915), [`64fda55`](https://github.com/TanStack/ai/commit/64fda55f839062bc67b8c24850123e879fdbf0b3)]:
  - @tanstack/ai-client@0.0.2
  - @tanstack/ai@0.0.2

## 0.0.1

### Patch Changes

- Initial release of TanStack AI ([#72](https://github.com/TanStack/ai/pull/72))

- Updated dependencies [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)]:
  - @tanstack/ai-client@0.0.1
  - @tanstack/ai@0.0.1


## Links discovered
- [[`52c3172`](https://github.com/TanStack/ai/commit/52c317244294a75b0c7f5e6cafc8583fbb6abfb7)
- [[`a7bd563`](https://github.com/TanStack/ai/commit/a7bd5639eb2fbf1b4169eb307f77149f4a85a915)
- [`64fda55`](https://github.com/TanStack/ai/commit/64fda55f839062bc67b8c24850123e879fdbf0b3)
- [#72](https://github.com/TanStack/ai/pull/72)
- [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)

--- packages/typescript/ai-solid-ui/CHANGELOG.md ---
# @tanstack/ai-solid-ui

## 0.0.3

### Patch Changes

- Updated dependencies []:
  - @tanstack/ai-client@0.0.3
  - @tanstack/ai-solid@0.0.3

## 0.0.2

### Patch Changes

- Updated dependencies [[`a7bd563`](https://github.com/TanStack/ai/commit/a7bd5639eb2fbf1b4169eb307f77149f4a85a915)]:
  - @tanstack/ai-client@0.0.2
  - @tanstack/ai-solid@0.0.2

## 0.0.1

### Patch Changes

- initial release of AI ([#76](https://github.com/TanStack/ai/pull/76))

## 0.0.1

### Patch Changes

- Initial release of TanStack AI ([#72](https://github.com/TanStack/ai/pull/72))

- Updated dependencies [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)]:
  - @tanstack/ai-client@0.0.1
  - @tanstack/ai-solid@0.0.1


## Links discovered
- [[`a7bd563`](https://github.com/TanStack/ai/commit/a7bd5639eb2fbf1b4169eb307f77149f4a85a915)
- [#76](https://github.com/TanStack/ai/pull/76)
- [#72](https://github.com/TanStack/ai/pull/72)
- [[`a9b54c2`](https://github.com/TanStack/ai/commit/a9b54c21282d16036a427761e0784b159a6f2d99)

--- CLAUDE.md ---
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Repository Overview

TanStack AI is a type-safe, provider-agnostic AI SDK for building AI-powered applications. The repository is a **pnpm monorepo** managed with **Nx** that includes TypeScript, PHP, and Python packages, plus multiple framework examples.

## Package Manager & Tooling

- **Package Manager**: pnpm@10.17.0 (required)
- **Build System**: Nx for task orchestration and caching
- **TypeScript**: 5.9.3
- **Testing**: Vitest for unit tests
- **Linting**: ESLint with custom TanStack config
- **Formatting**: Prettier

## Common Commands

### Testing

```bash
# Run all tests (full CI suite)
pnpm test

# Run tests for affected packages only (for PRs)
pnpm test:pr

# Run specific test suites
pnpm test:lib              # Run unit tests for affected packages
pnpm test:lib:dev          # Watch mode for unit tests
pnpm test:eslint           # Lint affected packages
pnpm test:types            # Type check affected packages
pnpm test:build            # Verify build artifacts with publint
pnpm test:coverage         # Generate coverage reports
pnpm test:knip             # Check for unused dependencies
pnpm test:sherif           # Check pnpm workspace consistency
pnpm test:docs             # Verify documentation links
```

### Testing Individual Packages

```bash
# Navigate to package directory and run tests
cd packages/typescript/ai
pnpm test:lib              # Run tests for this package
pnpm test:lib:dev          # Watch mode
pnpm test:types            # Type check
pnpm test:eslint           # Lint
```

### Building

```bash
# Build affected packages
pnpm build

# Build all packages
pnpm build:all

# Watch mode (build + watch for changes)
pnpm watch
pnpm dev  # alias for watch
```

### Code Quality

```bash
pnpm format                # Format all files with Prettier
```

### Changesets (Release Management)

```bash
pnpm changeset             # Create a new changeset
pnpm changeset:version     # Bump versions based on changesets
pnpm changeset:publish     # Publish to npm
```

## Architecture

### Monorepo Structure

```
packages/
├── typescript/           # TypeScript packages (main implementation)
│   ├── ai/              # Core AI library (@tanstack/ai)
│   ├── ai-client/       # Framework-agnostic chat client
│   ├── ai-react/        # React hooks (useChat)
│   ├── ai-solid/        # Solid hooks
│   ├── ai-svelte/       # Svelte integration
│   ├── ai-vue/          # Vue integration
│   ├── ai-openai/       # OpenAI adapter
│   ├── ai-anthropic/    # Anthropic/Claude adapter
│   ├── ai-gemini/       # Google Gemini adapter
│   ├── ai-ollama/       # Ollama adapter
│   ├── ai-devtools/     # DevTools integration
│   ├── react-ai-devtools/ # React DevTools component
│   └── solid-ai-devtools/ # Solid DevTools component
├── php/                 # PHP packages (future)
└── python/              # Python packages (future)

examples/                # Example applications
├── ts-react-chat/       # React chat example
├── ts-solid-chat/       # Solid chat example
├── ts-vue-chat/         # Vue chat example
├── ts-svelte-chat/      # Svelte chat example
├── ts-group-chat/       # Multi-user group chat
├── vanilla-chat/        # Vanilla JS example
├── php-slim/            # PHP Slim framework example
└── python-fastapi/      # Python FastAPI example
```

### Core Architecture Concepts

#### 1. Adapter System (Tree-Shakeable)

The library uses a **tree-shakeable adapter architecture** where each provider (OpenAI, Anthropic, Gemini, Ollama) exports multiple specialized adapters:

- **Text adapters** (`openaiText`, `anthropicText`) - Chat/completion
- **Embedding adapters** (`openaiEmbed`) - Text embeddings
- **Summarize adapters** (`openaiSummarize`) - Summarization
- **Image adapters** (`openaiImage`) - Image generation

Each adapter is a separate import to minimize bundle size:

```typescript
import { openaiText } from '@tanstack/ai-openai/adapters'
import { ai } from '@tanstack/ai'

const textAdapter = openaiText()
const result = ai({ adapter: textAdapter, model: 'gpt-4o', messages: [...] })
```

#### 2. Core Functions

The `@tanstack/ai` package provides core functions:

- **`ai()`** / **`generate()`** - Unified generation function for any adapter type
- **`chat()`** - Chat completion with streaming, tools, and agent loops
- **`embedding()`** - Generate embeddings
- **`summarize()`** - Summarize text
- Legacy adapters (monolithic, deprecated) use `openai()`, `anthropic()`, etc.

#### 3. Isomorphic Tool System

Tools are defined once with `toolDefinition()` and can have `.server()` or `.client()` implementations:

```typescript
const tool = toolDefinition({
  name: 'getTodos',
  inputSchema: z.object({ userId: z.string() }),
  outputSchema: z.array(z.object({ id: z.string(), title: z.string() })),
})

// Server implementation (runs on server)
const serverTool = tool.server(async ({ userId }) => db.todos.find({ userId }))

// Client implementation (runs in browser)
const clientTool = tool.client(async ({ userId }) =>
  fetch(`/api/todos/${userId}`),
)
```

#### 4. Framework Integrations

- **`@tanstack/ai-client`** - Headless chat state management with connection adapters (SSE, HTTP stream, custom)
- **`@tanstack/ai-react`** - `useChat` hook for React
- **`@tanstack/ai-solid`** - `useChat` hook for Solid
- **`@tanstack/ai-vue`** - Vue integration
- **`@tanstack/ai-svelte`** - Svelte integration

Each framework integration uses the headless `ai-client` under the hood.

#### 5. Type Safety Features

- **Per-model type safety** - Provider options are typed based on selected model
- **Multimodal content** - Type-safe image, audio, video, document support based on model capabilities
- **Zod schema inference** - Tools use Zod for runtime validation and type inference
- **`InferChatMessages`** - Type inference for message types based on tools and configuration

### Key Files & Directories

#### Core Package (`packages/typescript/ai/src/`)

- **`index.ts`** - Main exports (chat, embedding, summarize, toolDefinition, etc.)
- **`types.ts`** - Core type definitions (ModelMessage, ContentPart, StreamChunk, etc.)
- **`core/`** - Core functions (chat.ts, generate.ts, embedding.ts, summarize.ts)
- **`adapters/`** - Base adapter classes and interfaces
- **`tools/`** - Tool definition system and Zod converter
- **`stream/`** - Stream processing (StreamProcessor, chunking strategies, partial JSON parsing)
- **`utilities/`** - Helpers (message converters, agent loop strategies, SSE utilities)

#### Provider Adapters (e.g., `packages/typescript/ai-openai/src/`)

- **`index.ts`** - Exports tree-shakeable adapters (openaiText, openaiEmbed, etc.)
- **`adapters/`** - Individual adapter implementations (text.ts, embed.ts, summarize.ts, image.ts)
- **`model-meta.ts`** - Model metadata for type safety (provider options per model)
- **`openai-adapter.ts`** - Legacy monolithic adapter (deprecated)

## Development Workflow

### Adding a New Feature

1. Create a changeset: `pnpm changeset`
2. Make changes in the appropriate package(s)
3. Run tests: `pnpm test:lib` (or package-specific tests)
4. Run type checks: `pnpm test:types`
5. Run linter: `pnpm test:eslint`
6. Format code: `pnpm format`
7. Verify build: `pnpm test:build` or `pnpm build`

### Working with Examples

Examples are not built by Nx. To run an example:

```bash
cd examples/ts-react-chat
pnpm install  # if needed
pnpm dev      # start dev server
```

### Nx Workspace

- Uses Nx affected commands to only test/build changed packages
- Nx caching speeds up builds and tests
- `nx.json` configures Nx behavior
- Use `nx run-many` to run commands across multiple packages

## Important Conventions

### Workspace Dependencies

- Use `workspace:*` protocol for internal package dependencies in `package.json`
- Example: `"@tanstack/ai": "workspace:*"`

### Tree-Shakeable Exports

When adding new functionality to provider adapters, create separate adapters rather than adding to monolithic ones. Export from `/adapters` subpath.

### Exports Field

Each package uses `exports` field in package.json for subpath exports (e.g., `@tanstack/ai/adapters`, `@tanstack/ai/event-client`)

### Testing Strategy

- Unit tests in `*.test.ts` files alongside source
- Uses Vitest with happy-dom for DOM testing
- Test coverage via `pnpm test:coverage`

### Documentation

- Docs are in `docs/` directory (Markdown)
- Auto-generated docs via `pnpm generate-docs` (TypeDoc)
- Link verification via `pnpm test:docs`

## Key Dependencies

### Core Runtime Dependencies

- `zod` - Schema validation (peer dependency)
- `@alcyone-labs/zod-to-json-schema` - Convert Zod schemas to JSON Schema for LLM tools
- `partial-json` - Parse incomplete JSON from streaming responses

### Provider SDKs (in adapter packages)

- `openai` - OpenAI SDK
- `@anthropic-ai/sdk` - Anthropic SDK
- `@google/generative-ai` - Gemini SDK
- `ollama` - Ollama SDK

### DevTools

- `@tanstack/devtools-event-client` - TanStack DevTools integration


--- CODE_OF_CONDUCT.md ---
---
title: Code of Conduct
id: code-of-conduct
---

# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

- The use of sexualized language or imagery and unwelcome sexual attention or
  advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
  address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at TANNERLINSLEY@GMAIL.COM. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq


--- README.md ---
<div align="center">
  <img src="./media/header_ai.png" >
</div>

<br />

<div align="center">
<a href="https://npmjs.com/package/@tanstack/ai" target="\_parent">
  <img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />
</a>
<a href="https://github.com/TanStack/ai" target="\_parent">
	  <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
</a>
<a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent">
  <img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />
</a>
</div>

<div align="center">
<a href="#badge">
  <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
	<a href="#badge">
		<img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
	</a>
<a href="https://twitter.com/tan_stack">
  <img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
</a>
</div>

<div align="center">
  
### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
</div>

# TanStack AI

A powerful, type-safe AI SDK for building AI-powered applications.

- Provider-agnostic adapters (OpenAI, Anthropic, Gemini, Ollama, etc.)
- **Multimodal content support** - Send images, audio, video, and documents
- Chat completion, streaming, and agent loop strategies
- Headless chat state management with adapters (SSE, HTTP stream, custom)
- Isomorphic type-safe tools with server/client execution
- **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions

### <a href="https://tanstack.com/ai">Read the docs →</b></a>

## Bonus: TanStack Start Integration

TanStack AI works with **any** framework (Next.js, Express, Remix, etc.).

**With TanStack Start**, you get a bonus: share implementations between AI tools and server functions with `createServerFnTool`:

```typescript
import { createServerFnTool } from '@tanstack/ai-react'

// Define once, get AI tool AND server function (TanStack Start only)
const getProducts = createServerFnTool({
  name: 'getProducts',
  inputSchema: z.object({ query: z.string() }),
  execute: async ({ query }) => db.products.search(query),
})

// Use in AI chat
chat({ tools: [getProducts.server] })

// Call directly from components (no API endpoint needed!)
const products = await getProducts.serverFn({ query: 'laptop' })
```

No duplicate logic, full type safety, automatic validation. The `serverFn` feature requires TanStack Start. See [docs](https://tanstack.com/ai) for details.

## Get Involved

- We welcome issues and pull requests!
- Participate in [GitHub discussions](https://github.com/TanStack/ai/discussions)
- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions

## Partners

<table align="center">
  <tr>
    <td>
      <a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" />
          <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
        </picture>
      </a>
    </td>
    <td>
      <a href="https://www.cloudflare.com?utm_source=tanstack">
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" />
          <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
        </picture>
      </a>
    </td>
  </tr>
</table>

<div align="center">
<img src="./media/partner_logo.svg" alt="AI & you?" height="65">
<p>
We're looking for TanStack AI Partners to join our mission! Partner with us to push the boundaries of TanStack AI and build amazing things together.
</p>
<a href="mailto:partners@tanstack.com?subject=TanStack AI Partnership"><b>LET'S CHAT</b></a>
</div>

## Explore the TanStack Ecosystem

- <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages
- <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store
- <a href="https://github.com/tanstack/devtools"><b>TanStack Devtools</b></a> – Unified devtools panel
- <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state
- <a href="https://github.com/tanstack/pacer"><b>TanStack Pacer</b></a> – Debouncing, throttling, batching
- <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching
- <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives
- <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state
- <a href="https://github.com/tanstack/router"><b>TanStack Start</b></a> – Full‑stack SSR & streaming
- <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store
- <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids
- <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering

… and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a>

<!-- USE THE FORCE LUKE -->


## Links discovered
- [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
- [docs](https://tanstack.com/ai)
- [GitHub discussions](https://github.com/TanStack/ai/discussions)
- [Discord](https://discord.com/invite/WrRKjPJ)
- [CONTRIBUTING.md](https://github.com/tanstack/ai/blob/main/CONTRIBUTING.md)
- [<img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />](https://npmjs.com/package/@tanstack/ai)
- [<img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />](https://github.com/TanStack/ai)
- [<img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />](https://bundlephobia.com/result?p=@tanstack/ai@latest)
- [<img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>](https://twitter.com/tan_stack)
- [Read the docs →</b>](https://tanstack.com/ai)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" /> <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" /> </picture>](https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" /> <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" /> </picture>](https://www.cloudflare.com?utm_source=tanstack)
- [<b>TanStack Config</b>](https://github.com/tanstack/config)
- [<b>TanStack DB</b>](https://github.com/tanstack/db)
- [<b>TanStack Devtools</b>](https://github.com/tanstack/devtools)
- [<b>TanStack Form</b>](https://github.com/tanstack/form)
- [<b>TanStack Pacer</b>](https://github.com/tanstack/pacer)
- [<b>TanStack Query</b>](https://github.com/tanstack/query)
- [<b>TanStack Ranger</b>](https://github.com/tanstack/ranger)
- [<b>TanStack Router</b>](https://github.com/tanstack/router)
- [<b>TanStack Start</b>](https://github.com/tanstack/router)
- [<b>TanStack Store</b>](https://github.com/tanstack/store)
- [<b>TanStack Table</b>](https://github.com/tanstack/table)
- [<b>TanStack Virtual</b>](https://github.com/tanstack/virtual)
- [<b>TanStack.com »</b>](https://tanstack.com)

--- eslint.config.js ---
// @ts-check

import { tanstackConfig } from '@tanstack/eslint-config'
import unusedImports from 'eslint-plugin-unused-imports'

/** @type {import('eslint').Linter.Config[]} */
const config = [
  ...tanstackConfig,
  {
    name: 'tanstack/temp',
    plugins: {
      'unused-imports': unusedImports,
    },
    rules: {
      'no-case-declarations': 'off',
      'no-shadow': 'off',
      'unused-imports/no-unused-imports': 'warn',
      'pnpm/enforce-catalog': 'off',
      'pnpm/json-enforce-catalog': 'off',
    },
  },
]

export default config


--- prettier.config.js ---
// @ts-check

/** @type {import('prettier').Config} */
const config = {
  semi: false,
  singleQuote: true,
  trailingComma: 'all',
  plugins: ['prettier-plugin-svelte'],
  overrides: [{ files: '*.svelte', options: { parser: 'svelte' } }],
}

export default config


--- scripts/verify-links.ts ---
import { existsSync, readFileSync, statSync } from 'node:fs'
import { extname, resolve } from 'node:path'
import { glob } from 'tinyglobby'
// @ts-ignore Could not find a declaration file for module 'markdown-link-extractor'.
import markdownLinkExtractor from 'markdown-link-extractor'

const errors: Array<{
  file: string
  link: string
  resolvedPath: string
  reason: string
}> = []

function isRelativeLink(link: string) {
  return (
    !link.startsWith('/') &&
    !link.startsWith('http://') &&
    !link.startsWith('https://') &&
    !link.startsWith('//') &&
    !link.startsWith('#') &&
    !link.startsWith('mailto:')
  )
}

/** Remove any trailing .md */
function stripExtension(p: string): string {
  return p.replace(`${extname(p)}`, '')
}

function relativeLinkExists(link: string, file: string): boolean {
  // Remove hash if present
  const linkWithoutHash = link.split('#')[0]
  // If the link is empty after removing hash, it's not a file
  if (!linkWithoutHash) return false

  // Strip the file/link extensions
  const filePath = stripExtension(file)
  const linkPath = stripExtension(linkWithoutHash)

  // Resolve the path relative to the markdown file's directory
  // Nav up a level to simulate how links are resolved on the web
  let absPath = resolve(filePath, '..', linkPath)

  // Ensure the resolved path is within /docs
  const docsRoot = resolve('docs')
  if (!absPath.startsWith(docsRoot)) {
    errors.push({
      link,
      file,
      resolvedPath: absPath,
      reason: 'Path outside /docs',
    })
    return false
  }

  // Check if this is an example path
  const isExample = absPath.includes('/examples/')

  let exists = false

  if (isExample) {
    // Transform /docs/framework/{framework}/examples/ to /examples/{framework}/
    absPath = absPath.replace(
      /\/docs\/framework\/([^/]+)\/examples\//,
      '/examples/$1/',
    )
    // For examples, we want to check if the directory exists
    exists = existsSync(absPath) && statSync(absPath).isDirectory()
  } else {
    // For non-examples, we want to check if the .md file exists
    if (!absPath.endsWith('.md')) {
      absPath = `${absPath}.md`
    }
    exists = existsSync(absPath)
  }

  if (!exists) {
    errors.push({
      link,
      file,
      resolvedPath: absPath,
      reason: 'Not found',
    })
  }
  return exists
}

async function verifyMarkdownLinks() {
  // Find all markdown files in docs directory
  const markdownFiles = await glob('docs/**/*.md', {
    ignore: ['**/node_modules/**'],
  })

  console.log(`Found ${markdownFiles.length} markdown files\n`)

  // Process each file
  for (const file of markdownFiles) {
    const content = readFileSync(file, 'utf-8')
    const links: Array<string> = markdownLinkExtractor(content)

    const relativeLinks = links.filter((link: string) => {
      return isRelativeLink(link)
    })

    if (relativeLinks.length > 0) {
      relativeLinks.forEach((link) => {
        relativeLinkExists(link, file)
      })
    }
  }

  if (errors.length > 0) {
    console.log(`\n❌ Found ${errors.length} broken links:`)
    errors.forEach((err) => {
      console.log(
        `${err.file}\n  link:      ${err.link}\n  resolved:  ${err.resolvedPath}\n  why:       ${err.reason}\n`,
      )
    })
    process.exit(1)
  } else {
    console.log('\n✅ No broken links found!')
  }
}

verifyMarkdownLinks().catch(console.error)


--- vitest.workspace.js ---
// @ts-check

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    projects: [
      './packages/ai/vite.config.ts',
      './packages/ai-lite/vite.config.ts',
      './packages/react-ai/vite.config.ts',
      './packages/react-ai-devtools/vite.config.ts',
      './packages/solid-ai/vite.config.ts',
      './packages/solid-ai-devtools/vite.config.ts',
    ],
  },
})


--- .github/pull_request_template.md ---
## 🎯 Changes

<!-- What changes are made in this PR? Describe the change and its motivation. -->

## ✅ Checklist

- [ ] I have followed the steps in the [Contributing guide](https://github.com/TanStack/ai/blob/main/CONTRIBUTING.md).
- [ ] I have tested this code locally with `pnpm run test:pr`.

## 🚀 Release Impact

- [ ] This change affects published code, and I have generated a [changeset](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).
- [ ] This change is docs/CI/dev-only (no release).


## Links discovered
- [Contributing guide](https://github.com/TanStack/ai/blob/main/CONTRIBUTING.md)
- [changeset](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md)

--- .github/instructions/copilot-instructions.md ---
---
applyTo: '**'
---

Provide project context and coding guidelines that AI should follow when generating code, answering questions, or reviewing changes.

Whenever you want to build the packages to test if they work you should run `pnpm run build` from the root of the repository.

If you want to check if the examples work you need to go to `examples/<example-name>` and run `pnpm run dev`.

When writing code, please follow these guidelines:

- Use TypeScript for all new code.
- Ensure all new code is covered by tests.
- Do not use `any` type; prefer specific types or generics.
- Follow existing code style and conventions.

If you get an error "address already in use :::42069 you should kill the process using that port.

If we add a new functionality add a section about it in the `docs/` folder explaining how to use it and update the `README.md` file to mention it.

Write tests for any new functionality.

When defining new types, first check if the types exist somewhere and re-use them, do not create new types that are similar to existing ones.

When modifying existing functionality, ensure backward compatibility unless there's a strong reason to introduce breaking changes. If breaking changes are necessary, document them clearly in the relevant documentation files.
 

Under no circumstances should casting `as any` be used in the codebase. Always strive to find or create the appropriate type definitions. Avoid casting unless absolutely neccessary, and even then, prefer using `satisfies` for type assertions to maintain type safety.

Do not add ` Select-Object -Last X` at the end of PowerShell commands in the CI scripts. This can hide important error messages and make debugging more difficult and it also makes the
process hang

--- packages/php/tanstack-ai/README.md ---
# TanStack AI PHP

PHP utilities for converting AI provider events to TanStack AI StreamChunk format and formatting messages between TanStack AI and provider formats.

## Installation

```bash
composer require tanstack/ai
```

Or install from source:

```bash
cd packages/php/tanstack-ai
composer install
```

## Usage

### StreamChunkConverter

Convert provider streaming events to TanStack AI StreamChunk format:

```php
use TanStack\AI\StreamChunkConverter;

$converter = new StreamChunkConverter(
    model: "claude-3-haiku-20240307",
    provider: "anthropic"
);

foreach ($anthropicStream as $event) {
    $chunks = $converter->convertEvent($event);
    foreach ($chunks as $chunk) {
        // Process StreamChunk
    }
}
```

### Message Formatters

Convert TanStack AI messages to provider formats:

```php
use TanStack\AI\MessageFormatters;

// Convert to Anthropic format
[$systemMessage, $anthropicMessages] = MessageFormatters::formatMessagesForAnthropic($messages);

// Convert to OpenAI format
$openaiMessages = MessageFormatters::formatMessagesForOpenAI($messages);
```

### SSE Formatting Utilities

Format StreamChunk arrays as Server-Sent Events (SSE) for HTTP responses:

```php
use TanStack\AI\SSEFormatter;

// Format a chunk
$sseData = SSEFormatter::formatChunk($chunk); // Returns "data: {...}\n\n"

// Format completion marker
$sseDone = SSEFormatter::formatDone(); // Returns "data: [DONE]\n\n"

// Format an error
$sseError = SSEFormatter::formatError($exception); // Returns formatted error chunk
```

Example usage in Slim Framework:

```php
use TanStack\AI\StreamChunkConverter;
use TanStack\AI\SSEFormatter;

function generateStream($stream, $converter) {
    foreach ($stream as $event) {
        $chunks = $converter->convertEvent($event);
        foreach ($chunks as $chunk) {
            yield SSEFormatter::formatChunk($chunk);
        }
    }
    yield SSEFormatter::formatDone();
}
```

## Supported Providers

- Anthropic (Claude models)
- OpenAI (GPT models)

## License

MIT


--- packages/python/tanstack-ai/README.md ---
# TanStack AI Python SDK

Python SDK for building AI applications with streaming, tool calling, and agentic workflows. Provides adapters for AI providers (Anthropic, OpenAI, etc.) and utilities for message formatting and SSE streaming.

## Features

- 🤖 **Agentic Workflows** - Automatic tool execution loops with customizable strategies
- 🔧 **Tool Calling** - Define and execute tools with JSON Schema validation
- 📡 **Streaming** - Full support for streaming chat completions
- 🔌 **Multiple Adapters** - Support for Anthropic (Claude), with more providers coming soon
- 🛡️ **Type Safety** - Comprehensive type hints using TypedDict and dataclasses
- 📋 **Protocol Compliant** - Follows the TanStack AI StreamChunk protocol

## Installation

```bash
pip install tanstack-ai anthropic
```

Or install from source:

```bash
cd packages/python/tanstack-ai
pip install -e .
```

## Quick Start

### Basic Chat

```python
import asyncio
from tanstack_ai import AnthropicAdapter, AIAdapterConfig, chat

async def main():
    adapter = AnthropicAdapter(
        AIAdapterConfig(api_key="your-api-key")
    )

    async for chunk in chat(
        adapter=adapter,
        model="claude-3-5-sonnet-20241022",
        messages=[{"role": "user", "content": "Hello!"}],
    ):
        if chunk["type"] == "content":
            print(chunk["delta"], end="", flush=True)

asyncio.run(main())
```

### Chat with Tools (Agentic Flow)

```python
from tanstack_ai import chat, tool, max_iterations

# Define a tool
weather_tool = tool(
    name="get_weather",
    description="Get the current weather for a location",
    input_schema={
        "type": "object",
        "properties": {
            "location": {"type": "string"},
            "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
        },
        "required": ["location"],
    },
    execute=lambda args: {
        "temperature": 72,
        "conditions": "sunny",
        "location": args["location"],
    },
)

# Use the tool in chat
async for chunk in chat(
    adapter=adapter,
    model="claude-3-5-sonnet-20241022",
    messages=[
        {"role": "user", "content": "What's the weather in San Francisco?"}
    ],
    tools=[weather_tool],
    agent_loop_strategy=max_iterations(5),
):
    if chunk["type"] == "content":
        print(chunk["delta"], end="", flush=True)
    elif chunk["type"] == "tool_call":
        print(f"\n[Calling: {chunk['toolCall']['function']['name']}]")
```

See `example_usage.py` and `AGENTIC_FEATURES.md` for more examples.

## Usage

### StreamChunkConverter

Convert provider streaming events to TanStack AI StreamChunk format:

```python
from tanstack_ai import StreamChunkConverter

converter = StreamChunkConverter(model="claude-3-haiku-20240307", provider="anthropic")

async for event in anthropic_stream:
    chunks = await converter.convert_event(event)
    for chunk in chunks:
        # Process StreamChunk
        pass
```

### Message Formatters

Convert TanStack AI messages to provider formats:

```python
from tanstack_ai import format_messages_for_anthropic, format_messages_for_openai

# Convert to Anthropic format
system_message, anthropic_messages = format_messages_for_anthropic(messages)

# Convert to OpenAI format
openai_messages = format_messages_for_openai(messages)
```

### SSE Formatting Utilities

Format StreamChunk dictionaries as Server-Sent Events (SSE) for HTTP responses:

```python
from tanstack_ai import format_sse_chunk, format_sse_done, format_sse_error

# Format a chunk
sse_data = format_sse_chunk(chunk)  # Returns "data: {...}\n\n"

# Format completion marker
sse_done = format_sse_done()  # Returns "data: [DONE]\n\n"

# Format an error
sse_error = format_sse_error(exception)  # Returns formatted error chunk
```

Example usage in FastAPI:

```python
async def generate_stream():
    async for event in stream:
        chunks = await converter.convert_event(event)
        for chunk in chunks:
            yield format_sse_chunk(chunk)
    yield format_sse_done()
```

## Core API

### chat()

Stream chat completions with automatic tool execution:

```python
async for chunk in chat(
    adapter=adapter,           # AI adapter instance
    model="model-name",        # Model identifier
    messages=[...],            # Conversation messages
    tools=[...],               # Optional tools (auto-executed)
    agent_loop_strategy=...,   # Optional loop control strategy
    options={...},             # Common options (temperature, etc.)
    provider_options={...},    # Provider-specific options
):
    # Handle StreamChunk
    pass
```

### tool()

Define a tool with JSON Schema:

```python
my_tool = tool(
    name="tool_name",
    description="What the tool does",
    input_schema={...},        # JSON Schema for inputs
    execute=lambda args: ...,  # Function to execute
    needs_approval=False,      # Require user approval
)
```

### Agent Loop Strategies

Control how the agent loop behaves:

```python
from tanstack_ai import (
    max_iterations,           # Limit iterations
    until_finish_reason,      # Stop on specific reasons
    combine_strategies,       # Combine multiple strategies
)

# Max 10 iterations
strategy = max_iterations(10)

# Stop on specific finish reasons
strategy = until_finish_reason(["stop", "length"])

# Custom logic
strategy = lambda state: len(state["messages"]) < 50

# Combine strategies
strategy = combine_strategies([
    max_iterations(10),
    until_finish_reason(["stop"]),
])
```

## Adapters

### AnthropicAdapter

```python
from tanstack_ai import AnthropicAdapter, AIAdapterConfig

adapter = AnthropicAdapter(
    AIAdapterConfig(
        api_key="your-api-key",
        base_url=None,           # Optional custom base URL
        timeout=None,            # Optional timeout
        max_retries=None,        # Optional max retries
    )
)
```

**Supported Models:**

- claude-3-5-sonnet-20241022
- claude-3-5-sonnet-20240620
- claude-3-5-haiku-20241022
- claude-3-opus-20240229
- claude-3-sonnet-20240229
- claude-3-haiku-20240307

## StreamChunk Protocol

All streaming responses emit `StreamChunk` objects with these types:

- `content` - Text content being generated
- `thinking` - Model's reasoning process (when supported)
- `tool_call` - Model calling a tool
- `tool_result` - Result from tool execution
- `tool-input-available` - Tool inputs ready for client execution
- `approval-requested` - Tool requires approval
- `done` - Stream completion
- `error` - Error occurred

See `docs/protocol/chunk-definitions.md` for detailed specifications.

## Supported Providers

- ✅ Anthropic (Claude models)
- 🔄 OpenAI (coming soon)
- 🔄 Gemini (coming soon)
- 🔄 Ollama (coming soon)

## Examples

See these files for complete examples:

- `example_usage.py` - Basic usage examples
- `AGENTIC_FEATURES.md` - Detailed documentation of agentic features

## License

MIT


--- packages/typescript/ai-client/README.md ---
<div align="center">
  <img src="./media/header_ai.png" >
</div>

<br />

<div align="center">
<a href="https://npmjs.com/package/@tanstack/ai" target="\_parent">
  <img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />
</a>
<a href="https://github.com/TanStack/ai" target="\_parent">
	  <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
</a>
<a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent">
  <img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />
</a>
</div>

<div align="center">
<a href="#badge">
  <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
	<a href="#badge">
		<img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
	</a>
<a href="https://twitter.com/tan_stack">
  <img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
</a>
</div>

<div align="center">
  
### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
</div>

# TanStack AI

A powerful, type-safe AI SDK for building AI-powered applications.

- Provider-agnostic adapters (OpenAI, Anthropic, Gemini, Ollama, etc.)
- **Multimodal content support** - Send images, audio, video, and documents
- Chat completion, streaming, and agent loop strategies
- Headless chat state management with adapters (SSE, HTTP stream, custom)
- Isomorphic type-safe tools with server/client execution
- **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions

### <a href="https://tanstack.com/ai">Read the docs →</b></a>

## Bonus: TanStack Start Integration

TanStack AI works with **any** framework (Next.js, Express, Remix, etc.).

**With TanStack Start**, you get a bonus: share implementations between AI tools and server functions with `createServerFnTool`:

```typescript
import { createServerFnTool } from '@tanstack/ai-react'

// Define once, get AI tool AND server function (TanStack Start only)
const getProducts = createServerFnTool({
  name: 'getProducts',
  inputSchema: z.object({ query: z.string() }),
  execute: async ({ query }) => db.products.search(query),
})

// Use in AI chat
chat({ tools: [getProducts.server] })

// Call directly from components (no API endpoint needed!)
const products = await getProducts.serverFn({ query: 'laptop' })
```

No duplicate logic, full type safety, automatic validation. The `serverFn` feature requires TanStack Start. See [docs](https://tanstack.com/ai) for details.

## Get Involved

- We welcome issues and pull requests!
- Participate in [GitHub discussions](https://github.com/TanStack/ai/discussions)
- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions

## Partners

<table align="center">
  <tr>
    <td>
      <a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" />
          <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
        </picture>
      </a>
    </td>
    <td>
      <a href="https://www.cloudflare.com?utm_source=tanstack">
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" />
          <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
        </picture>
      </a>
    </td>
  </tr>
</table>

<div align="center">
<img src="./media/partner_logo.svg" alt="AI & you?" height="65">
<p>
We're looking for TanStack AI Partners to join our mission! Partner with us to push the boundaries of TanStack AI and build amazing things together.
</p>
<a href="mailto:partners@tanstack.com?subject=TanStack AI Partnership"><b>LET'S CHAT</b></a>
</div>

## Explore the TanStack Ecosystem

- <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages
- <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store
- <a href="https://github.com/tanstack/devtools"><b>TanStack Devtools</b></a> – Unified devtools panel
- <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state
- <a href="https://github.com/tanstack/pacer"><b>TanStack Pacer</b></a> – Debouncing, throttling, batching
- <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching
- <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives
- <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state
- <a href="https://github.com/tanstack/router"><b>TanStack Start</b></a> – Full‑stack SSR & streaming
- <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store
- <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids
- <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering

… and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a>

<!-- USE THE FORCE LUKE -->


## Links discovered
- [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
- [docs](https://tanstack.com/ai)
- [GitHub discussions](https://github.com/TanStack/ai/discussions)
- [Discord](https://discord.com/invite/WrRKjPJ)
- [CONTRIBUTING.md](https://github.com/tanstack/ai/blob/main/packages/typescript/ai-client/CONTRIBUTING.md)
- [<img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />](https://npmjs.com/package/@tanstack/ai)
- [<img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />](https://github.com/TanStack/ai)
- [<img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />](https://bundlephobia.com/result?p=@tanstack/ai@latest)
- [<img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>](https://twitter.com/tan_stack)
- [Read the docs →</b>](https://tanstack.com/ai)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" /> <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" /> </picture>](https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" /> <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" /> </picture>](https://www.cloudflare.com?utm_source=tanstack)
- [<b>TanStack Config</b>](https://github.com/tanstack/config)
- [<b>TanStack DB</b>](https://github.com/tanstack/db)
- [<b>TanStack Devtools</b>](https://github.com/tanstack/devtools)
- [<b>TanStack Form</b>](https://github.com/tanstack/form)
- [<b>TanStack Pacer</b>](https://github.com/tanstack/pacer)
- [<b>TanStack Query</b>](https://github.com/tanstack/query)
- [<b>TanStack Ranger</b>](https://github.com/tanstack/ranger)
- [<b>TanStack Router</b>](https://github.com/tanstack/router)
- [<b>TanStack Start</b>](https://github.com/tanstack/router)
- [<b>TanStack Store</b>](https://github.com/tanstack/store)
- [<b>TanStack Table</b>](https://github.com/tanstack/table)
- [<b>TanStack Virtual</b>](https://github.com/tanstack/virtual)
- [<b>TanStack.com »</b>](https://tanstack.com)

--- packages/typescript/ai-devtools/README.md ---
<div align="center">
  <img src="./media/header_ai.png" >
</div>

<br />

<div align="center">
<a href="https://npmjs.com/package/@tanstack/ai" target="\_parent">
  <img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />
</a>
<a href="https://github.com/TanStack/ai" target="\_parent">
	  <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
</a>
<a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent">
  <img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />
</a>
</div>

<div align="center">
<a href="#badge">
  <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
	<a href="#badge">
		<img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
	</a>
<a href="https://twitter.com/tan_stack">
  <img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
</a>
</div>

<div align="center">
  
### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
</div>

# TanStack AI

A powerful, type-safe AI SDK for building AI-powered applications.

- Provider-agnostic adapters (OpenAI, Anthropic, Gemini, Ollama, etc.)
- **Multimodal content support** - Send images, audio, video, and documents
- Chat completion, streaming, and agent loop strategies
- Headless chat state management with adapters (SSE, HTTP stream, custom)
- Isomorphic type-safe tools with server/client execution
- **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions

### <a href="https://tanstack.com/ai">Read the docs →</b></a>

## Bonus: TanStack Start Integration

TanStack AI works with **any** framework (Next.js, Express, Remix, etc.).

**With TanStack Start**, you get a bonus: share implementations between AI tools and server functions with `createServerFnTool`:

```typescript
import { createServerFnTool } from '@tanstack/ai-react'

// Define once, get AI tool AND server function (TanStack Start only)
const getProducts = createServerFnTool({
  name: 'getProducts',
  inputSchema: z.object({ query: z.string() }),
  execute: async ({ query }) => db.products.search(query),
})

// Use in AI chat
chat({ tools: [getProducts.server] })

// Call directly from components (no API endpoint needed!)
const products = await getProducts.serverFn({ query: 'laptop' })
```

No duplicate logic, full type safety, automatic validation. The `serverFn` feature requires TanStack Start. See [docs](https://tanstack.com/ai) for details.

## Get Involved

- We welcome issues and pull requests!
- Participate in [GitHub discussions](https://github.com/TanStack/ai/discussions)
- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions

## Partners

<table align="center">
  <tr>
    <td>
      <a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" />
          <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
        </picture>
      </a>
    </td>
    <td>
      <a href="https://www.cloudflare.com?utm_source=tanstack">
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" />
          <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
        </picture>
      </a>
    </td>
  </tr>
</table>

<div align="center">
<img src="./media/partner_logo.svg" alt="AI & you?" height="65">
<p>
We're looking for TanStack AI Partners to join our mission! Partner with us to push the boundaries of TanStack AI and build amazing things together.
</p>
<a href="mailto:partners@tanstack.com?subject=TanStack AI Partnership"><b>LET'S CHAT</b></a>
</div>

## Explore the TanStack Ecosystem

- <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages
- <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store
- <a href="https://github.com/tanstack/devtools"><b>TanStack Devtools</b></a> – Unified devtools panel
- <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state
- <a href="https://github.com/tanstack/pacer"><b>TanStack Pacer</b></a> – Debouncing, throttling, batching
- <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching
- <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives
- <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state
- <a href="https://github.com/tanstack/router"><b>TanStack Start</b></a> – Full‑stack SSR & streaming
- <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store
- <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids
- <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering

… and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a>

<!-- USE THE FORCE LUKE -->


## Links discovered
- [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
- [docs](https://tanstack.com/ai)
- [GitHub discussions](https://github.com/TanStack/ai/discussions)
- [Discord](https://discord.com/invite/WrRKjPJ)
- [CONTRIBUTING.md](https://github.com/tanstack/ai/blob/main/packages/typescript/ai-devtools/CONTRIBUTING.md)
- [<img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />](https://npmjs.com/package/@tanstack/ai)
- [<img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />](https://github.com/TanStack/ai)
- [<img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />](https://bundlephobia.com/result?p=@tanstack/ai@latest)
- [<img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>](https://twitter.com/tan_stack)
- [Read the docs →</b>](https://tanstack.com/ai)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" /> <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" /> </picture>](https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" /> <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" /> </picture>](https://www.cloudflare.com?utm_source=tanstack)
- [<b>TanStack Config</b>](https://github.com/tanstack/config)
- [<b>TanStack DB</b>](https://github.com/tanstack/db)
- [<b>TanStack Devtools</b>](https://github.com/tanstack/devtools)
- [<b>TanStack Form</b>](https://github.com/tanstack/form)
- [<b>TanStack Pacer</b>](https://github.com/tanstack/pacer)
- [<b>TanStack Query</b>](https://github.com/tanstack/query)
- [<b>TanStack Ranger</b>](https://github.com/tanstack/ranger)
- [<b>TanStack Router</b>](https://github.com/tanstack/router)
- [<b>TanStack Start</b>](https://github.com/tanstack/router)
- [<b>TanStack Store</b>](https://github.com/tanstack/store)
- [<b>TanStack Table</b>](https://github.com/tanstack/table)
- [<b>TanStack Virtual</b>](https://github.com/tanstack/virtual)
- [<b>TanStack.com »</b>](https://tanstack.com)

--- packages/typescript/ai-gemini/README.md ---
<div align="center">
  <img src="./media/header_ai.png" >
</div>

<br />

<div align="center">
<a href="https://npmjs.com/package/@tanstack/ai" target="\_parent">
  <img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />
</a>
<a href="https://github.com/TanStack/ai" target="\_parent">
	  <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
</a>
<a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent">
  <img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />
</a>
</div>

<div align="center">
<a href="#badge">
  <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
	<a href="#badge">
		<img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
	</a>
<a href="https://twitter.com/tan_stack">
  <img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
</a>
</div>

<div align="center">
  
### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
</div>

# TanStack AI

A powerful, type-safe AI SDK for building AI-powered applications.

- Provider-agnostic adapters (OpenAI, Anthropic, Gemini, Ollama, etc.)
- **Multimodal content support** - Send images, audio, video, and documents
- Chat completion, streaming, and agent loop strategies
- Headless chat state management with adapters (SSE, HTTP stream, custom)
- Isomorphic type-safe tools with server/client execution
- **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions

### <a href="https://tanstack.com/ai">Read the docs →</b></a>

## Bonus: TanStack Start Integration

TanStack AI works with **any** framework (Next.js, Express, Remix, etc.).

**With TanStack Start**, you get a bonus: share implementations between AI tools and server functions with `createServerFnTool`:

```typescript
import { createServerFnTool } from '@tanstack/ai-react'

// Define once, get AI tool AND server function (TanStack Start only)
const getProducts = createServerFnTool({
  name: 'getProducts',
  inputSchema: z.object({ query: z.string() }),
  execute: async ({ query }) => db.products.search(query),
})

// Use in AI chat
chat({ tools: [getProducts.server] })

// Call directly from components (no API endpoint needed!)
const products = await getProducts.serverFn({ query: 'laptop' })
```

No duplicate logic, full type safety, automatic validation. The `serverFn` feature requires TanStack Start. See [docs](https://tanstack.com/ai) for details.

## Get Involved

- We welcome issues and pull requests!
- Participate in [GitHub discussions](https://github.com/TanStack/ai/discussions)
- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions

## Partners

<table align="center">
  <tr>
    <td>
      <a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" />
          <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
        </picture>
      </a>
    </td>
    <td>
      <a href="https://www.cloudflare.com?utm_source=tanstack">
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" />
          <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
        </picture>
      </a>
    </td>
  </tr>
</table>

<div align="center">
<img src="./media/partner_logo.svg" alt="AI & you?" height="65">
<p>
We're looking for TanStack AI Partners to join our mission! Partner with us to push the boundaries of TanStack AI and build amazing things together.
</p>
<a href="mailto:partners@tanstack.com?subject=TanStack AI Partnership"><b>LET'S CHAT</b></a>
</div>

## Explore the TanStack Ecosystem

- <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages
- <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store
- <a href="https://github.com/tanstack/devtools"><b>TanStack Devtools</b></a> – Unified devtools panel
- <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state
- <a href="https://github.com/tanstack/pacer"><b>TanStack Pacer</b></a> – Debouncing, throttling, batching
- <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching
- <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives
- <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state
- <a href="https://github.com/tanstack/router"><b>TanStack Start</b></a> – Full‑stack SSR & streaming
- <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store
- <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids
- <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering

… and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a>

<!-- USE THE FORCE LUKE -->


## Links discovered
- [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
- [docs](https://tanstack.com/ai)
- [GitHub discussions](https://github.com/TanStack/ai/discussions)
- [Discord](https://discord.com/invite/WrRKjPJ)
- [CONTRIBUTING.md](https://github.com/tanstack/ai/blob/main/packages/typescript/ai-gemini/CONTRIBUTING.md)
- [<img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />](https://npmjs.com/package/@tanstack/ai)
- [<img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />](https://github.com/TanStack/ai)
- [<img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />](https://bundlephobia.com/result?p=@tanstack/ai@latest)
- [<img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>](https://twitter.com/tan_stack)
- [Read the docs →</b>](https://tanstack.com/ai)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" /> <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" /> </picture>](https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" /> <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" /> </picture>](https://www.cloudflare.com?utm_source=tanstack)
- [<b>TanStack Config</b>](https://github.com/tanstack/config)
- [<b>TanStack DB</b>](https://github.com/tanstack/db)
- [<b>TanStack Devtools</b>](https://github.com/tanstack/devtools)
- [<b>TanStack Form</b>](https://github.com/tanstack/form)
- [<b>TanStack Pacer</b>](https://github.com/tanstack/pacer)
- [<b>TanStack Query</b>](https://github.com/tanstack/query)
- [<b>TanStack Ranger</b>](https://github.com/tanstack/ranger)
- [<b>TanStack Router</b>](https://github.com/tanstack/router)
- [<b>TanStack Start</b>](https://github.com/tanstack/router)
- [<b>TanStack Store</b>](https://github.com/tanstack/store)
- [<b>TanStack Table</b>](https://github.com/tanstack/table)
- [<b>TanStack Virtual</b>](https://github.com/tanstack/virtual)
- [<b>TanStack.com »</b>](https://tanstack.com)

--- packages/typescript/ai-ollama/README.md ---
<div align="center">
  <img src="./media/header_ai.png" >
</div>

<br />

<div align="center">
<a href="https://npmjs.com/package/@tanstack/ai" target="\_parent">
  <img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />
</a>
<a href="https://github.com/TanStack/ai" target="\_parent">
	  <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
</a>
<a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent">
  <img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />
</a>
</div>

<div align="center">
<a href="#badge">
  <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
	<a href="#badge">
		<img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
	</a>
<a href="https://twitter.com/tan_stack">
  <img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
</a>
</div>

<div align="center">
  
### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
</div>

# TanStack AI

A powerful, type-safe AI SDK for building AI-powered applications.

- Provider-agnostic adapters (OpenAI, Anthropic, Gemini, Ollama, etc.)
- **Multimodal content support** - Send images, audio, video, and documents
- Chat completion, streaming, and agent loop strategies
- Headless chat state management with adapters (SSE, HTTP stream, custom)
- Isomorphic type-safe tools with server/client execution
- **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions

### <a href="https://tanstack.com/ai">Read the docs →</b></a>

## Bonus: TanStack Start Integration

TanStack AI works with **any** framework (Next.js, Express, Remix, etc.).

**With TanStack Start**, you get a bonus: share implementations between AI tools and server functions with `createServerFnTool`:

```typescript
import { createServerFnTool } from '@tanstack/ai-react'

// Define once, get AI tool AND server function (TanStack Start only)
const getProducts = createServerFnTool({
  name: 'getProducts',
  inputSchema: z.object({ query: z.string() }),
  execute: async ({ query }) => db.products.search(query),
})

// Use in AI chat
chat({ tools: [getProducts.server] })

// Call directly from components (no API endpoint needed!)
const products = await getProducts.serverFn({ query: 'laptop' })
```

No duplicate logic, full type safety, automatic validation. The `serverFn` feature requires TanStack Start. See [docs](https://tanstack.com/ai) for details.

## Get Involved

- We welcome issues and pull requests!
- Participate in [GitHub discussions](https://github.com/TanStack/ai/discussions)
- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions

## Partners

<table align="center">
  <tr>
    <td>
      <a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" />
          <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
        </picture>
      </a>
    </td>
    <td>
      <a href="https://www.cloudflare.com?utm_source=tanstack">
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" />
          <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
        </picture>
      </a>
    </td>
  </tr>
</table>

<div align="center">
<img src="./media/partner_logo.svg" alt="AI & you?" height="65">
<p>
We're looking for TanStack AI Partners to join our mission! Partner with us to push the boundaries of TanStack AI and build amazing things together.
</p>
<a href="mailto:partners@tanstack.com?subject=TanStack AI Partnership"><b>LET'S CHAT</b></a>
</div>

## Explore the TanStack Ecosystem

- <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages
- <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store
- <a href="https://github.com/tanstack/devtools"><b>TanStack Devtools</b></a> – Unified devtools panel
- <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state
- <a href="https://github.com/tanstack/pacer"><b>TanStack Pacer</b></a> – Debouncing, throttling, batching
- <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching
- <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives
- <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state
- <a href="https://github.com/tanstack/router"><b>TanStack Start</b></a> – Full‑stack SSR & streaming
- <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store
- <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids
- <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering

… and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a>

<!-- USE THE FORCE LUKE -->


## Links discovered
- [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
- [docs](https://tanstack.com/ai)
- [GitHub discussions](https://github.com/TanStack/ai/discussions)
- [Discord](https://discord.com/invite/WrRKjPJ)
- [CONTRIBUTING.md](https://github.com/tanstack/ai/blob/main/packages/typescript/ai-ollama/CONTRIBUTING.md)
- [<img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />](https://npmjs.com/package/@tanstack/ai)
- [<img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />](https://github.com/TanStack/ai)
- [<img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />](https://bundlephobia.com/result?p=@tanstack/ai@latest)
- [<img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>](https://twitter.com/tan_stack)
- [Read the docs →</b>](https://tanstack.com/ai)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" /> <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" /> </picture>](https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" /> <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" /> </picture>](https://www.cloudflare.com?utm_source=tanstack)
- [<b>TanStack Config</b>](https://github.com/tanstack/config)
- [<b>TanStack DB</b>](https://github.com/tanstack/db)
- [<b>TanStack Devtools</b>](https://github.com/tanstack/devtools)
- [<b>TanStack Form</b>](https://github.com/tanstack/form)
- [<b>TanStack Pacer</b>](https://github.com/tanstack/pacer)
- [<b>TanStack Query</b>](https://github.com/tanstack/query)
- [<b>TanStack Ranger</b>](https://github.com/tanstack/ranger)
- [<b>TanStack Router</b>](https://github.com/tanstack/router)
- [<b>TanStack Start</b>](https://github.com/tanstack/router)
- [<b>TanStack Store</b>](https://github.com/tanstack/store)
- [<b>TanStack Table</b>](https://github.com/tanstack/table)
- [<b>TanStack Virtual</b>](https://github.com/tanstack/virtual)
- [<b>TanStack.com »</b>](https://tanstack.com)

--- packages/typescript/ai-openai/README.md ---
<div align="center">
  <img src="./media/header_ai.png" >
</div>

<br />

<div align="center">
<a href="https://npmjs.com/package/@tanstack/ai" target="\_parent">
  <img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />
</a>
<a href="https://github.com/TanStack/ai" target="\_parent">
	  <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
</a>
<a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent">
  <img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />
</a>
</div>

<div align="center">
<a href="#badge">
  <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
	<a href="#badge">
		<img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
	</a>
<a href="https://twitter.com/tan_stack">
  <img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
</a>
</div>

<div align="center">
  
### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
</div>

# TanStack AI

A powerful, type-safe AI SDK for building AI-powered applications.

- Provider-agnostic adapters (OpenAI, Anthropic, Gemini, Ollama, etc.)
- **Multimodal content support** - Send images, audio, video, and documents
- Chat completion, streaming, and agent loop strategies
- Headless chat state management with adapters (SSE, HTTP stream, custom)
- Isomorphic type-safe tools with server/client execution
- **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions

### <a href="https://tanstack.com/ai">Read the docs →</b></a>

## Bonus: TanStack Start Integration

TanStack AI works with **any** framework (Next.js, Express, Remix, etc.).

**With TanStack Start**, you get a bonus: share implementations between AI tools and server functions with `createServerFnTool`:

```typescript
import { createServerFnTool } from '@tanstack/ai-react'

// Define once, get AI tool AND server function (TanStack Start only)
const getProducts = createServerFnTool({
  name: 'getProducts',
  inputSchema: z.object({ query: z.string() }),
  execute: async ({ query }) => db.products.search(query),
})

// Use in AI chat
chat({ tools: [getProducts.server] })

// Call directly from components (no API endpoint needed!)
const products = await getProducts.serverFn({ query: 'laptop' })
```

No duplicate logic, full type safety, automatic validation. The `serverFn` feature requires TanStack Start. See [docs](https://tanstack.com/ai) for details.

## Get Involved

- We welcome issues and pull requests!
- Participate in [GitHub discussions](https://github.com/TanStack/ai/discussions)
- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions

## Partners

<table align="center">
  <tr>
    <td>
      <a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" />
          <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
        </picture>
      </a>
    </td>
    <td>
      <a href="https://www.cloudflare.com?utm_source=tanstack">
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" />
          <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
        </picture>
      </a>
    </td>
  </tr>
</table>

<div align="center">
<img src="./media/partner_logo.svg" alt="AI & you?" height="65">
<p>
We're looking for TanStack AI Partners to join our mission! Partner with us to push the boundaries of TanStack AI and build amazing things together.
</p>
<a href="mailto:partners@tanstack.com?subject=TanStack AI Partnership"><b>LET'S CHAT</b></a>
</div>

## Explore the TanStack Ecosystem

- <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages
- <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store
- <a href="https://github.com/tanstack/devtools"><b>TanStack Devtools</b></a> – Unified devtools panel
- <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state
- <a href="https://github.com/tanstack/pacer"><b>TanStack Pacer</b></a> – Debouncing, throttling, batching
- <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching
- <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives
- <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state
- <a href="https://github.com/tanstack/router"><b>TanStack Start</b></a> – Full‑stack SSR & streaming
- <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store
- <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids
- <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering

… and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a>

<!-- USE THE FORCE LUKE -->


## Links discovered
- [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
- [docs](https://tanstack.com/ai)
- [GitHub discussions](https://github.com/TanStack/ai/discussions)
- [Discord](https://discord.com/invite/WrRKjPJ)
- [CONTRIBUTING.md](https://github.com/tanstack/ai/blob/main/packages/typescript/ai-openai/CONTRIBUTING.md)
- [<img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />](https://npmjs.com/package/@tanstack/ai)
- [<img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />](https://github.com/TanStack/ai)
- [<img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />](https://bundlephobia.com/result?p=@tanstack/ai@latest)
- [<img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>](https://twitter.com/tan_stack)
- [Read the docs →</b>](https://tanstack.com/ai)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" /> <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" /> </picture>](https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" /> <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" /> </picture>](https://www.cloudflare.com?utm_source=tanstack)
- [<b>TanStack Config</b>](https://github.com/tanstack/config)
- [<b>TanStack DB</b>](https://github.com/tanstack/db)
- [<b>TanStack Devtools</b>](https://github.com/tanstack/devtools)
- [<b>TanStack Form</b>](https://github.com/tanstack/form)
- [<b>TanStack Pacer</b>](https://github.com/tanstack/pacer)
- [<b>TanStack Query</b>](https://github.com/tanstack/query)
- [<b>TanStack Ranger</b>](https://github.com/tanstack/ranger)
- [<b>TanStack Router</b>](https://github.com/tanstack/router)
- [<b>TanStack Start</b>](https://github.com/tanstack/router)
- [<b>TanStack Store</b>](https://github.com/tanstack/store)
- [<b>TanStack Table</b>](https://github.com/tanstack/table)
- [<b>TanStack Virtual</b>](https://github.com/tanstack/virtual)
- [<b>TanStack.com »</b>](https://tanstack.com)

--- packages/typescript/ai-react-ui/README.md ---
<div align="center">
  <img src="./media/header_ai.png" >
</div>

<br />

<div align="center">
<a href="https://npmjs.com/package/@tanstack/ai" target="\_parent">
  <img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />
</a>
<a href="https://github.com/TanStack/ai" target="\_parent">
	  <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
</a>
<a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent">
  <img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />
</a>
</div>

<div align="center">
<a href="#badge">
  <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
	<a href="#badge">
		<img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
	</a>
<a href="https://twitter.com/tan_stack">
  <img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
</a>
</div>

<div align="center">
  
### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
</div>

# TanStack AI

A powerful, type-safe AI SDK for building AI-powered applications.

- Provider-agnostic adapters (OpenAI, Anthropic, Gemini, Ollama, etc.)
- **Multimodal content support** - Send images, audio, video, and documents
- Chat completion, streaming, and agent loop strategies
- Headless chat state management with adapters (SSE, HTTP stream, custom)
- Isomorphic type-safe tools with server/client execution
- **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions

### <a href="https://tanstack.com/ai">Read the docs →</b></a>

## Bonus: TanStack Start Integration

TanStack AI works with **any** framework (Next.js, Express, Remix, etc.).

**With TanStack Start**, you get a bonus: share implementations between AI tools and server functions with `createServerFnTool`:

```typescript
import { createServerFnTool } from '@tanstack/ai-react'

// Define once, get AI tool AND server function (TanStack Start only)
const getProducts = createServerFnTool({
  name: 'getProducts',
  inputSchema: z.object({ query: z.string() }),
  execute: async ({ query }) => db.products.search(query),
})

// Use in AI chat
chat({ tools: [getProducts.server] })

// Call directly from components (no API endpoint needed!)
const products = await getProducts.serverFn({ query: 'laptop' })
```

No duplicate logic, full type safety, automatic validation. The `serverFn` feature requires TanStack Start. See [docs](https://tanstack.com/ai) for details.

## Get Involved

- We welcome issues and pull requests!
- Participate in [GitHub discussions](https://github.com/TanStack/ai/discussions)
- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions

## Partners

<table align="center">
  <tr>
    <td>
      <a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" />
          <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
        </picture>
      </a>
    </td>
    <td>
      <a href="https://www.cloudflare.com?utm_source=tanstack">
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" />
          <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
        </picture>
      </a>
    </td>
  </tr>
</table>

<div align="center">
<img src="./media/partner_logo.svg" alt="AI & you?" height="65">
<p>
We're looking for TanStack AI Partners to join our mission! Partner with us to push the boundaries of TanStack AI and build amazing things together.
</p>
<a href="mailto:partners@tanstack.com?subject=TanStack AI Partnership"><b>LET'S CHAT</b></a>
</div>

## Explore the TanStack Ecosystem

- <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages
- <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store
- <a href="https://github.com/tanstack/devtools"><b>TanStack Devtools</b></a> – Unified devtools panel
- <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state
- <a href="https://github.com/tanstack/pacer"><b>TanStack Pacer</b></a> – Debouncing, throttling, batching
- <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching
- <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives
- <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state
- <a href="https://github.com/tanstack/router"><b>TanStack Start</b></a> – Full‑stack SSR & streaming
- <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store
- <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids
- <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering

… and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a>

<!-- USE THE FORCE LUKE -->


## Links discovered
- [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
- [docs](https://tanstack.com/ai)
- [GitHub discussions](https://github.com/TanStack/ai/discussions)
- [Discord](https://discord.com/invite/WrRKjPJ)
- [CONTRIBUTING.md](https://github.com/tanstack/ai/blob/main/packages/typescript/ai-react-ui/CONTRIBUTING.md)
- [<img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />](https://npmjs.com/package/@tanstack/ai)
- [<img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />](https://github.com/TanStack/ai)
- [<img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />](https://bundlephobia.com/result?p=@tanstack/ai@latest)
- [<img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>](https://twitter.com/tan_stack)
- [Read the docs →</b>](https://tanstack.com/ai)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" /> <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" /> </picture>](https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" /> <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" /> </picture>](https://www.cloudflare.com?utm_source=tanstack)
- [<b>TanStack Config</b>](https://github.com/tanstack/config)
- [<b>TanStack DB</b>](https://github.com/tanstack/db)
- [<b>TanStack Devtools</b>](https://github.com/tanstack/devtools)
- [<b>TanStack Form</b>](https://github.com/tanstack/form)
- [<b>TanStack Pacer</b>](https://github.com/tanstack/pacer)
- [<b>TanStack Query</b>](https://github.com/tanstack/query)
- [<b>TanStack Ranger</b>](https://github.com/tanstack/ranger)
- [<b>TanStack Router</b>](https://github.com/tanstack/router)
- [<b>TanStack Start</b>](https://github.com/tanstack/router)
- [<b>TanStack Store</b>](https://github.com/tanstack/store)
- [<b>TanStack Table</b>](https://github.com/tanstack/table)
- [<b>TanStack Virtual</b>](https://github.com/tanstack/virtual)
- [<b>TanStack.com »</b>](https://tanstack.com)

--- packages/typescript/ai-react/README.md ---
<div align="center">
  <img src="./media/header_ai.png" >
</div>

<br />

<div align="center">
<a href="https://npmjs.com/package/@tanstack/ai" target="\_parent">
  <img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />
</a>
<a href="https://github.com/TanStack/ai" target="\_parent">
	  <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
</a>
<a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent">
  <img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />
</a>
</div>

<div align="center">
<a href="#badge">
  <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
	<a href="#badge">
		<img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
	</a>
<a href="https://twitter.com/tan_stack">
  <img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
</a>
</div>

<div align="center">
  
### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
</div>

# TanStack AI

A powerful, type-safe AI SDK for building AI-powered applications.

- Provider-agnostic adapters (OpenAI, Anthropic, Gemini, Ollama, etc.)
- **Multimodal content support** - Send images, audio, video, and documents
- Chat completion, streaming, and agent loop strategies
- Headless chat state management with adapters (SSE, HTTP stream, custom)
- Isomorphic type-safe tools with server/client execution
- **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions

### <a href="https://tanstack.com/ai">Read the docs →</b></a>

## Bonus: TanStack Start Integration

TanStack AI works with **any** framework (Next.js, Express, Remix, etc.).

**With TanStack Start**, you get a bonus: share implementations between AI tools and server functions with `createServerFnTool`:

```typescript
import { createServerFnTool } from '@tanstack/ai-react'

// Define once, get AI tool AND server function (TanStack Start only)
const getProducts = createServerFnTool({
  name: 'getProducts',
  inputSchema: z.object({ query: z.string() }),
  execute: async ({ query }) => db.products.search(query),
})

// Use in AI chat
chat({ tools: [getProducts.server] })

// Call directly from components (no API endpoint needed!)
const products = await getProducts.serverFn({ query: 'laptop' })
```

No duplicate logic, full type safety, automatic validation. The `serverFn` feature requires TanStack Start. See [docs](https://tanstack.com/ai) for details.

## Get Involved

- We welcome issues and pull requests!
- Participate in [GitHub discussions](https://github.com/TanStack/ai/discussions)
- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions

## Partners

<table align="center">
  <tr>
    <td>
      <a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" />
          <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
        </picture>
      </a>
    </td>
    <td>
      <a href="https://www.cloudflare.com?utm_source=tanstack">
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" />
          <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
        </picture>
      </a>
    </td>
  </tr>
</table>

<div align="center">
<img src="./media/partner_logo.svg" alt="AI & you?" height="65">
<p>
We're looking for TanStack AI Partners to join our mission! Partner with us to push the boundaries of TanStack AI and build amazing things together.
</p>
<a href="mailto:partners@tanstack.com?subject=TanStack AI Partnership"><b>LET'S CHAT</b></a>
</div>

## Explore the TanStack Ecosystem

- <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages
- <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store
- <a href="https://github.com/tanstack/devtools"><b>TanStack Devtools</b></a> – Unified devtools panel
- <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state
- <a href="https://github.com/tanstack/pacer"><b>TanStack Pacer</b></a> – Debouncing, throttling, batching
- <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching
- <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives
- <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state
- <a href="https://github.com/tanstack/router"><b>TanStack Start</b></a> – Full‑stack SSR & streaming
- <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store
- <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids
- <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering

… and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a>

<!-- USE THE FORCE LUKE -->


## Links discovered
- [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
- [docs](https://tanstack.com/ai)
- [GitHub discussions](https://github.com/TanStack/ai/discussions)
- [Discord](https://discord.com/invite/WrRKjPJ)
- [CONTRIBUTING.md](https://github.com/tanstack/ai/blob/main/packages/typescript/ai-react/CONTRIBUTING.md)
- [<img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />](https://npmjs.com/package/@tanstack/ai)
- [<img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />](https://github.com/TanStack/ai)
- [<img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />](https://bundlephobia.com/result?p=@tanstack/ai@latest)
- [<img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>](https://twitter.com/tan_stack)
- [Read the docs →</b>](https://tanstack.com/ai)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" /> <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" /> </picture>](https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" /> <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" /> </picture>](https://www.cloudflare.com?utm_source=tanstack)
- [<b>TanStack Config</b>](https://github.com/tanstack/config)
- [<b>TanStack DB</b>](https://github.com/tanstack/db)
- [<b>TanStack Devtools</b>](https://github.com/tanstack/devtools)
- [<b>TanStack Form</b>](https://github.com/tanstack/form)
- [<b>TanStack Pacer</b>](https://github.com/tanstack/pacer)
- [<b>TanStack Query</b>](https://github.com/tanstack/query)
- [<b>TanStack Ranger</b>](https://github.com/tanstack/ranger)
- [<b>TanStack Router</b>](https://github.com/tanstack/router)
- [<b>TanStack Start</b>](https://github.com/tanstack/router)
- [<b>TanStack Store</b>](https://github.com/tanstack/store)
- [<b>TanStack Table</b>](https://github.com/tanstack/table)
- [<b>TanStack Virtual</b>](https://github.com/tanstack/virtual)
- [<b>TanStack.com »</b>](https://tanstack.com)

--- packages/typescript/ai-solid-ui/README.md ---
<div align="center">
  <img src="./media/header_ai.png" >
</div>

<br />

<div align="center">
<a href="https://npmjs.com/package/@tanstack/ai" target="\_parent">
  <img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />
</a>
<a href="https://github.com/TanStack/ai" target="\_parent">
	  <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
</a>
<a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent">
  <img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />
</a>
</div>

<div align="center">
<a href="#badge">
  <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
	<a href="#badge">
		<img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
	</a>
<a href="https://twitter.com/tan_stack">
  <img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
</a>
</div>

<div align="center">
  
### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
</div>

# TanStack AI

A powerful, type-safe AI SDK for building AI-powered applications.

- Provider-agnostic adapters (OpenAI, Anthropic, Gemini, Ollama, etc.)
- Chat completion, streaming, and agent loop strategies
- Headless chat state management with adapters (SSE, HTTP stream, custom)
- Type-safe tools with server/client execution

### <a href="https://tanstack.com/ai">Read the docs →</b></a>

## Get Involved

- We welcome issues and pull requests!
- Participate in [GitHub discussions](https://github.com/TanStack/ai/discussions)
- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions

## Partners

<table align="center">
  <tr>
    <td>
      <a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" />
          <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
        </picture>
      </a>
    </td>
    <td>
      <a href="https://www.cloudflare.com?utm_source=tanstack">
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" />
          <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" />
          <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
        </picture>
      </a>
    </td>
  </tr>
</table>

<div align="center">
<img src="./media/partner_logo.svg" alt="AI & you?" height="65">
<p>
We're looking for TanStack AI Partners to join our mission! Partner with us to push the boundaries of TanStack AI and build amazing things together.
</p>
<a href="mailto:partners@tanstack.com?subject=TanStack AI Partnership"><b>LET'S CHAT</b></a>
</div>

## Explore the TanStack Ecosystem

- <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages
- <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store
- <a href="https://github.com/tanstack/devtools">TanStack Devtools</a> – Unified devtools panel
- <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state
- <a href="https://github.com/tanstack/pacer"><b>TanStack Pacer</b></a> – Debouncing, throttling, batching
- <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching
- <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives
- <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state
- <a href="https://github.com/tanstack/router"><b>TanStack Start</b></a> – Full‑stack SSR & streaming
- <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store
- <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids
- <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering

… and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a>

<!-- USE THE FORCE LUKE -->


## Links discovered
- [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
- [GitHub discussions](https://github.com/TanStack/ai/discussions)
- [Discord](https://discord.com/invite/WrRKjPJ)
- [CONTRIBUTING.md](https://github.com/tanstack/ai/blob/main/packages/typescript/ai-solid-ui/CONTRIBUTING.md)
- [<img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />](https://npmjs.com/package/@tanstack/ai)
- [<img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />](https://github.com/TanStack/ai)
- [<img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />](https://bundlephobia.com/result?p=@tanstack/ai@latest)
- [<img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>](https://twitter.com/tan_stack)
- [Read the docs →</b>](https://tanstack.com/ai)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" /> <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" /> </picture>](https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS)
- [<picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" /> <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" /> </picture>](https://www.cloudflare.com?utm_source=tanstack)
- [<b>TanStack Config</b>](https://github.com/tanstack/config)
- [<b>TanStack DB</b>](https://github.com/tanstack/db)
- [TanStack Devtools](https://github.com/tanstack/devtools)
- [<b>TanStack Form</b>](https://github.com/tanstack/form)
- [<b>TanStack Pacer</b>](https://github.com/tanstack/pacer)
- [<b>TanStack Query</b>](https://github.com/tanstack/query)
- [<b>TanStack Ranger</b>](https://github.com/tanstack/ranger)
- [<b>TanStack Router</b>](https://github.com/tanstack/router)
- [<b>TanStack Start</b>](https://github.com/tanstack/router)
- [<b>TanStack Store</b>](https://github.com/tanstack/store)
- [<b>TanStack Table</b>](https://github.com/tanstack/table)
- [<b>TanStack Virtual</b>](https://github.com/tanstack/virtual)
- [<b>TanStack.com »</b>](https://tanstack.com)

--- testing/panel/README.md ---
# Stream Processor Test Panel

A visual testing tool for validating the TanStack AI stream processor.

## Features

- **Drop Zone**: Drag & drop JSON trace files to test
- **Sample Traces**: Pre-loaded examples from unit tests
- **Step-through Mode**: Process chunks one at a time
- **Side-by-side View**: See raw chunks and parsed output

## Usage

```bash
# From workspace root
pnpm install
cd testing/panel
pnpm dev
```

Then open http://localhost:3001

## Creating Trace Files

Trace files are automatically created when you use the chat interface with a `traceId`. The panel subscribes to `aiEventClient` events to record all stream activity.

You can also create trace files programmatically by subscribing to events:

```typescript
import { createEventRecording } from '@/lib/recording'

// Create a recording instance that listens to aiEventClient events
const recording = createEventRecording('tmp/my-trace.json', 'my-trace-id')

// When done, clean up
recording.stop()
```

The recording utility automatically captures:

- Stream chunks (content, tool calls, tool results, done, errors, thinking)
- Final accumulated content
- Tool calls and their results
- Finish reason

Or capture traces from the test panel and save them.

## Trace Format

```json
{
  "version": "1.0",
  "timestamp": 1234567890,
  "chunks": [
    {
      "chunk": { "type": "content", "delta": "Hello", ... },
      "timestamp": 1234567891,
      "index": 0
    }
  ],
  "result": {
    "content": "Hello world",
    "toolCalls": [],
    "finishReason": "stop"
  }
}
```


--- testing/panel/app.config.ts ---
import { defineConfig } from '@tanstack/react-start/config'
import viteTsConfigPaths from 'vite-tsconfig-paths'

export default defineConfig({
  vite: {
    plugins: () => [
      viteTsConfigPaths({
        projects: ['./tsconfig.json'],
      }),
    ],
  },
})


--- testing/panel/vite.config.ts ---
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import viteReact from '@vitejs/plugin-react'
import viteTsConfigPaths from 'vite-tsconfig-paths'
import tailwindcss from '@tailwindcss/vite'
import { nitroV2Plugin } from '@tanstack/nitro-v2-vite-plugin'

const config = defineConfig({
  plugins: [
    nitroV2Plugin(),
    viteTsConfigPaths({
      projects: ['./tsconfig.json'],
    }),
    tailwindcss(),
    tanstackStart(),
    viteReact(),
  ],
})

export default config


--- testing/panel/src/routeTree.gen.ts ---
/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from './routes/__root'
import { Route as StreamDebuggerRouteImport } from './routes/stream-debugger'
import { Route as IndexRouteImport } from './routes/index'
import { Route as ApiLoadTraceRouteImport } from './routes/api.load-trace'
import { Route as ApiListTracesRouteImport } from './routes/api.list-traces'
import { Route as ApiChatRouteImport } from './routes/api.chat'

const StreamDebuggerRoute = StreamDebuggerRouteImport.update({
  id: '/stream-debugger',
  path: '/stream-debugger',
  getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
  id: '/',
  path: '/',
  getParentRoute: () => rootRouteImport,
} as any)
const ApiLoadTraceRoute = ApiLoadTraceRouteImport.update({
  id: '/api/load-trace',
  path: '/api/load-trace',
  getParentRoute: () => rootRouteImport,
} as any)
const ApiListTracesRoute = ApiListTracesRouteImport.update({
  id: '/api/list-traces',
  path: '/api/list-traces',
  getParentRoute: () => rootRouteImport,
} as any)
const ApiChatRoute = ApiChatRouteImport.update({
  id: '/api/chat',
  path: '/api/chat',
  getParentRoute: () => rootRouteImport,
} as any)

export interface FileRoutesByFullPath {
  '/': typeof IndexRoute
  '/stream-debugger': typeof StreamDebuggerRoute
  '/api/chat': typeof ApiChatRoute
  '/api/list-traces': typeof ApiListTracesRoute
  '/api/load-trace': typeof ApiLoadTraceRoute
}
export interface FileRoutesByTo {
  '/': typeof IndexRoute
  '/stream-debugger': typeof StreamDebuggerRoute
  '/api/chat': typeof ApiChatRoute
  '/api/list-traces': typeof ApiListTracesRoute
  '/api/load-trace': typeof ApiLoadTraceRoute
}
export interface FileRoutesById {
  __root__: typeof rootRouteImport
  '/': typeof IndexRoute
  '/stream-debugger': typeof StreamDebuggerRoute
  '/api/chat': typeof ApiChatRoute
  '/api/list-traces': typeof ApiListTracesRoute
  '/api/load-trace': typeof ApiLoadTraceRoute
}
export interface FileRouteTypes {
  fileRoutesByFullPath: FileRoutesByFullPath
  fullPaths:
    | '/'
    | '/stream-debugger'
    | '/api/chat'
    | '/api/list-traces'
    | '/api/load-trace'
  fileRoutesByTo: FileRoutesByTo
  to:
    | '/'
    | '/stream-debugger'
    | '/api/chat'
    | '/api/list-traces'
    | '/api/load-trace'
  id:
    | '__root__'
    | '/'
    | '/stream-debugger'
    | '/api/chat'
    | '/api/list-traces'
    | '/api/load-trace'
  fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
  IndexRoute: typeof IndexRoute
  StreamDebuggerRoute: typeof StreamDebuggerRoute
  ApiChatRoute: typeof ApiChatRoute
  ApiListTracesRoute: typeof ApiListTracesRoute
  ApiLoadTraceRoute: typeof ApiLoadTraceRoute
}

declare module '@tanstack/react-router' {
  interface FileRoutesByPath {
    '/stream-debugger': {
      id: '/stream-debugger'
      path: '/stream-debugger'
      fullPath: '/stream-debugger'
      preLoaderRoute: typeof StreamDebuggerRouteImport
      parentRoute: typeof rootRouteImport
    }
    '/': {
      id: '/'
      path: '/'
      fullPath: '/'
      preLoaderRoute: typeof IndexRouteImport
      parentRoute: typeof rootRouteImport
    }
    '/api/load-trace': {
      id: '/api/load-trace'
      path: '/api/load-trace'
      fullPath: '/api/load-trace'
      preLoaderRoute: typeof ApiLoadTraceRouteImport
      parentRoute: typeof rootRouteImport
    }
    '/api/list-traces': {
      id: '/api/list-traces'
      path: '/api/list-traces'
      fullPath: '/api/list-traces'
      preLoaderRoute: typeof ApiListTracesRouteImport
      parentRoute: typeof rootRouteImport
    }
    '/api/chat': {
      id: '/api/chat'
      path: '/api/chat'
      fullPath: '/api/chat'
      preLoaderRoute: typeof ApiChatRouteImport
      parentRoute: typeof rootRouteImport
    }
  }
}

const rootRouteChildren: RootRouteChildren = {
  IndexRoute: IndexRoute,
  StreamDebuggerRoute: StreamDebuggerRoute,
  ApiChatRoute: ApiChatRoute,
  ApiListTracesRoute: ApiListTracesRoute,
  ApiLoadTraceRoute: ApiLoadTraceRoute,
}
export const routeTree = rootRouteImport
  ._addFileChildren(rootRouteChildren)
  ._addFileTypes<FileRouteTypes>()

import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
  interface Register {
    ssr: true
    router: Awaited<ReturnType<typeof getRouter>>
  }
}


--- testing/panel/src/lib/guitar-tools.ts ---
import { toolDefinition } from '@tanstack/ai'
import { z } from 'zod'
import guitars from '@/data/example-guitars'

// Tool definition for getting guitars
export const getGuitarsToolDef = toolDefinition({
  name: 'getGuitars',
  description: 'Get all products from the database',
  inputSchema: z.object({}),
  outputSchema: z.array(
    z.object({
      id: z.number(),
      name: z.string(),
      image: z.string(),
      description: z.string(),
      shortDescription: z.string(),
      price: z.number(),
    }),
  ),
})

// Server implementation
export const getGuitars = getGuitarsToolDef.server(() => guitars)

// Tool definition for guitar recommendation
export const recommendGuitarToolDef = toolDefinition({
  name: 'recommendGuitar',
  description:
    'REQUIRED tool to display a guitar recommendation to the user. This tool MUST be used whenever recommending a guitar - do NOT write recommendations yourself. This displays the guitar in a special appealing format with a buy button.',
  inputSchema: z.object({
    id: z
      .union([z.string(), z.number()])
      .describe(
        'The ID of the guitar to recommend (from the getGuitars results)',
      ),
  }),
  outputSchema: z.object({
    id: z.number(),
  }),
})

// Tool definition for personal preference
export const getPersonalGuitarPreferenceToolDef = toolDefinition({
  name: 'getPersonalGuitarPreference',
  description:
    "Get the user's guitar preference from their local browser storage",
  inputSchema: z.object({}),
  outputSchema: z.object({
    preference: z.string(),
  }),
})

// Tool definition for wish list (needs approval)
export const addToWishListToolDef = toolDefinition({
  name: 'addToWishList',
  description: "Add a guitar to the user's wish list (requires approval)",
  inputSchema: z.object({
    guitarId: z.string(),
  }),
  outputSchema: z.object({
    success: z.boolean(),
    guitarId: z.string(),
    totalItems: z.number(),
  }),
  needsApproval: true,
})

// Tool definition for add to cart (server + client)
export const addToCartToolDef = toolDefinition({
  name: 'addToCart',
  description: 'Add a guitar to the shopping cart (requires approval)',
  inputSchema: z.object({
    guitarId: z.string(),
    quantity: z.number(),
  }),
  outputSchema: z.object({
    success: z.boolean(),
    cartId: z.string(),
    guitarId: z.string(),
    quantity: z.number(),
    totalItems: z.number(),
  }),
  needsApproval: true,
})


--- testing/panel/src/traces/index.ts ---
/**
 * Sample trace files for testing the stream processor
 *
 * These are extracted from unit tests in @tanstack/ai-client
 */

import type { ChunkRecording } from '@tanstack/ai'

// Helper to create a base recording
function createRecording(chunks: any[]): ChunkRecording {
  return {
    version: '1.0',
    timestamp: Date.now(),
    chunks: chunks.map((chunk, index) => ({
      chunk,
      timestamp: Date.now() + index * 10,
      index,
    })),
  }
}

/**
 * Simple text streaming - "Hello world!" character by character
 */
export const textSimple = createRecording([
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'H',
    content: 'H',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'e',
    content: 'He',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'l',
    content: 'Hel',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'l',
    content: 'Hell',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'o',
    content: 'Hello',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: ' ',
    content: 'Hello ',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'w',
    content: 'Hello w',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'o',
    content: 'Hello wo',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'r',
    content: 'Hello wor',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'l',
    content: 'Hello worl',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'd',
    content: 'Hello world',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: '!',
    content: 'Hello world!',
    role: 'assistant',
  },
  {
    type: 'done',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    finishReason: 'stop',
  },
])

/**
 * Text with punctuation - multiple sentences
 */
export const textPunctuation = createRecording([
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'Hello',
    content: 'Hello',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: ' world',
    content: 'Hello world',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: '!',
    content: 'Hello world!',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: ' How',
    content: 'Hello world! How',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: ' are',
    content: 'Hello world! How are',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: ' you?',
    content: 'Hello world! How are you?',
    role: 'assistant',
  },
  {
    type: 'done',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    finishReason: 'stop',
  },
])

/**
 * Single tool call with streaming arguments
 */
export const toolCallSingle = createRecording([
  {
    type: 'tool_call',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    toolCall: {
      id: 'call_1',
      type: 'function',
      function: { name: 'getWeather', arguments: '{"lo' },
    },
    index: 0,
  },
  {
    type: 'tool_call',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    toolCall: {
      id: 'call_1',
      type: 'function',
      function: { name: 'getWeather', arguments: 'cation":' },
    },
    index: 0,
  },
  {
    type: 'tool_call',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    toolCall: {
      id: 'call_1',
      type: 'function',
      function: { name: 'getWeather', arguments: ' "Paris"}' },
    },
    index: 0,
  },
  {
    type: 'done',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    finishReason: 'tool_calls',
  },
])

/**
 * Multiple parallel tool calls
 */
export const toolCallParallel = createRecording([
  {
    type: 'tool_call',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    toolCall: {
      id: 'call_1',
      type: 'function',
      function: { name: 'getWeather', arguments: '{"lo' },
    },
    index: 0,
  },
  {
    type: 'tool_call',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    toolCall: {
      id: 'call_2',
      type: 'function',
      function: { name: 'getTime', arguments: '{"ci' },
    },
    index: 1,
  },
  {
    type: 'tool_call',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    toolCall: {
      id: 'call_1',
      type: 'function',
      function: { name: 'getWeather', arguments: 'cation":"Paris"}' },
    },
    index: 0,
  },
  {
    type: 'tool_call',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    toolCall: {
      id: 'call_2',
      type: 'function',
      function: { name: 'getTime', arguments: 'ty":"Tokyo"}' },
    },
    index: 1,
  },
  {
    type: 'done',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    finishReason: 'tool_calls',
  },
])

/**
 * Tool call followed by text (tool result response)
 */
export const toolCallWithText = createRecording([
  {
    type: 'tool_call',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    toolCall: {
      id: 'call_1',
      type: 'function',
      function: { name: 'getWeather', arguments: '{"location":"Paris"}' },
    },
    index: 0,
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'The weather in Paris is',
    content: 'The weather in Paris is',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: ' sunny',
    content: 'The weather in Paris is sunny',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: ' and warm.',
    content: 'The weather in Paris is sunny and warm.',
    role: 'assistant',
  },
  {
    type: 'done',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    finishReason: 'stop',
  },
])

/**
 * Tool result chunk
 */
export const toolResult = createRecording([
  {
    type: 'tool_call',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    toolCall: {
      id: 'call_1',
      type: 'function',
      function: { name: 'calculate', arguments: '{"expression":"2+2"}' },
    },
    index: 0,
  },
  {
    type: 'done',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    finishReason: 'tool_calls',
  },
  {
    type: 'tool_result',
    id: 'msg-2',
    model: 'test',
    timestamp: Date.now(),
    toolCallId: 'call_1',
    content: '4',
  },
  {
    type: 'content',
    id: 'msg-2',
    model: 'test',
    timestamp: Date.now(),
    delta: 'The result is 4.',
    content: 'The result is 4.',
    role: 'assistant',
  },
  {
    type: 'done',
    id: 'msg-2',
    model: 'test',
    timestamp: Date.now(),
    finishReason: 'stop',
  },
])

/**
 * Thinking/reasoning chunks (Claude-style)
 */
export const thinking = createRecording([
  {
    type: 'thinking',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'Let me think about this...',
    content: 'Let me think about this...',
  },
  {
    type: 'thinking',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: ' I need to consider the weather patterns.',
    content:
      'Let me think about this... I need to consider the weather patterns.',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'Based on my analysis, ',
    content: 'Based on my analysis, ',
    role: 'assistant',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'it looks like rain.',
    content: 'Based on my analysis, it looks like rain.',
    role: 'assistant',
  },
  {
    type: 'done',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    finishReason: 'stop',
  },
])

/**
 * Approval requested flow
 */
export const approvalRequested = createRecording([
  {
    type: 'tool_call',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    toolCall: {
      id: 'call_1',
      type: 'function',
      function: { name: 'deleteFile', arguments: '{"path":"/tmp/test.txt"}' },
    },
    index: 0,
  },
  {
    type: 'done',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    finishReason: 'tool_calls',
  },
  {
    type: 'approval-requested',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    toolCallId: 'call_1',
    toolName: 'deleteFile',
    input: { path: '/tmp/test.txt' },
    approval: { id: 'approval_call_1', needsApproval: true },
  },
])

/**
 * Client tool input available
 */
export const toolInputAvailable = createRecording([
  {
    type: 'tool_call',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    toolCall: {
      id: 'call_1',
      type: 'function',
      function: { name: 'getUserLocation', arguments: '{}' },
    },
    index: 0,
  },
  {
    type: 'done',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    finishReason: 'tool_calls',
  },
  {
    type: 'tool-input-available',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    toolCallId: 'call_1',
    toolName: 'getUserLocation',
    input: {},
  },
])

/**
 * Error chunk
 */
export const error = createRecording([
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'Starting...',
    content: 'Starting...',
    role: 'assistant',
  },
  {
    type: 'error',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    error: { message: 'Rate limit exceeded', code: 'rate_limit' },
  },
])

/**
 * Delta-only chunks (no accumulated content)
 */
export const deltaOnly = createRecording([
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: 'Hello',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: ' world',
  },
  {
    type: 'content',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    delta: '!',
  },
  {
    type: 'done',
    id: 'msg-1',
    model: 'test',
    timestamp: Date.now(),
    finishReason: 'stop',
  },
])


--- testing/panel/src/lib/model-selection.ts ---
export type Provider = 'openai' | 'anthropic' | 'gemini' | 'ollama'

export interface ModelOption {
  provider: Provider
  model: string
  label: string
}

export const MODEL_OPTIONS: Array<ModelOption> = [
  // OpenAI
  { provider: 'openai', model: 'gpt-4o', label: 'OpenAI - GPT-4o' },
  { provider: 'openai', model: 'gpt-4o-mini', label: 'OpenAI - GPT-4o Mini' },
  { provider: 'openai', model: 'gpt-5', label: 'OpenAI - GPT-5' },

  // Anthropic
  {
    provider: 'anthropic',
    model: 'claude-sonnet-4-5-20250929',
    label: 'Anthropic - Claude Sonnet 4.5',
  },
  {
    provider: 'anthropic',
    model: 'claude-opus-4-5-20251101',
    label: 'Anthropic - Claude Opus 4.5',
  },
  {
    provider: 'anthropic',
    model: 'claude-haiku-4-0-20250514',
    label: 'Anthropic - Claude Haiku 4.0',
  },

  // Gemini
  {
    provider: 'gemini',
    model: 'gemini-2.0-flash-exp',
    label: 'Gemini - 2.0 Flash',
  },
  {
    provider: 'gemini',
    model: 'gemini-exp-1206',
    label: 'Gemini - Exp 1206 (Pro)',
  },

  // Ollama
  {
    provider: 'ollama',
    model: 'mistral:7b',
    label: 'Ollama - Mistral 7B',
  },
  {
    provider: 'ollama',
    model: 'mistral',
    label: 'Ollama - Mistral',
  },
  {
    provider: 'ollama',
    model: 'gpt-oss:20b',
    label: 'Ollama - GPT-OSS 20B',
  },
  {
    provider: 'ollama',
    model: 'granite4:3b',
    label: 'Ollama - Granite4 3B',
  },
  {
    provider: 'ollama',
    model: 'smollm',
    label: 'Ollama - SmolLM',
  },
]

const STORAGE_KEY = 'tanstack-ai-model-preference'

export function getStoredModelPreference(): ModelOption | null {
  if (typeof window === 'undefined') return null

  try {
    const stored = localStorage.getItem(STORAGE_KEY)
    if (!stored) return null

    const parsed = JSON.parse(stored) as { provider: Provider; model: string }
    const option = MODEL_OPTIONS.find(
      (opt) => opt.provider === parsed.provider && opt.model === parsed.model,
    )

    return option || null
  } catch {
    return null
  }
}

export function setStoredModelPreference(option: ModelOption): void {
  if (typeof window === 'undefined') return

  try {
    localStorage.setItem(
      STORAGE_KEY,
      JSON.stringify({ provider: option.provider, model: option.model }),
    )
  } catch {
    // Ignore storage errors
  }
}

export function getDefaultModelOption(): ModelOption {
  const stored = getStoredModelPreference()
  return stored || MODEL_OPTIONS[0]
}


--- testing/panel/src/lib/recording.ts ---
import * as fs from 'node:fs/promises'
import * as path from 'node:path'
import { aiEventClient } from '@tanstack/ai/event-client'
import type { StreamChunk, ToolCall } from '@tanstack/ai'

/**
 * Recording data structure matching the old format
 */
export interface ChunkRecording {
  version: '1.0'
  timestamp: number
  model: string
  provider: string
  chunks: Array<{
    chunk: StreamChunk
    timestamp: number
    index: number
  }>
  result?: {
    content: string
    toolCalls: Array<ToolCall>
    finishReason: string | null
  }
}

/**
 * Creates an event-based recording that subscribes to aiEventClient events
 * and saves recordings to a file when a stream completes.
 *
 * @param filePath - Path where the recording will be saved
 * @param traceId - Optional trace ID to filter events (if not provided, records all streams)
 * @returns Object with stop() method to unsubscribe from events
 *
 * @example
 * const recording = createEventRecording('tmp/recording.json', 'trace_123')
 * // Recording automatically starts listening to events for this traceId
 * // Call recording.stop() when done to unsubscribe
 */
export function createEventRecording(
  filePath: string,
  traceId?: string,
): {
  stop: () => void
  getStreamId: () => string | undefined
} {
  // Track active streams and their data
  const activeStreams = new Map<
    string,
    {
      streamId: string
      requestId: string
      model: string
      provider: string
      chunks: Array<{
        chunk: StreamChunk
        timestamp: number
        index: number
      }>
      accumulatedContent: string
      toolCalls: Map<string, ToolCall>
      finishReason: string | null
      traceId?: string
    }
  >()

  // Track which streamId belongs to this recording (if traceId is provided)
  let recordingStreamId: string | undefined

  let chunkIndex = 0

  // Helper to reconstruct StreamChunk from events
  const createContentChunk = (
    content: string,
    delta?: string,
  ): StreamChunk => ({
    type: 'content',
    content,
    delta,
  })

  const createToolCallChunk = (
    toolCallId: string,
    toolName: string,
    index: number,
    arguments_: string,
  ): StreamChunk => ({
    type: 'tool_call',
    toolCall: {
      id: toolCallId,
      type: 'function',
      function: {
        name: toolName,
        arguments: arguments_,
      },
    },
    index,
  })

  const createToolResultChunk = (
    toolCallId: string,
    result: string,
  ): StreamChunk => ({
    type: 'tool_result',
    toolCallId,
    content: result,
  })

  const createDoneChunk = (
    finishReason: string | null,
    usage?: {
      promptTokens: number
      completionTokens: number
      totalTokens: number
    },
  ): StreamChunk => ({
    type: 'done',
    finishReason: finishReason as any,
    usage,
  })

  const createErrorChunk = (error: string): StreamChunk => ({
    type: 'error',
    error: {
      message: error,
    },
  })

  const createThinkingChunk = (
    content: string,
    delta?: string,
  ): StreamChunk => ({
    type: 'thinking',
    content,
    delta,
  })

  // Subscribe to stream:started to initialize recording
  const unsubscribeStarted = aiEventClient.on(
    'stream:started',
    (event) => {
      const { streamId, model, provider } = event.payload
      // If traceId is provided, we'll track this streamId when we see chat:started
      // For now, track all streams (we'll filter later)
      activeStreams.set(streamId, {
        streamId,
        requestId: '', // Will be set from chat:started
        model,
        provider,
        chunks: [],
        accumulatedContent: '',
        toolCalls: new Map(),
        finishReason: null,
        traceId: undefined,
      })
    },
    { withEventTarget: false },
  )

  // Subscribe to chat:started to get requestId and check if we should record
  const unsubscribeChatStarted = aiEventClient.on(
    'chat:started',
    (event) => {
      const { streamId, requestId, providerOptions } = event.payload
      const stream = activeStreams.get(streamId)
      if (stream) {
        stream.requestId = requestId
        // Check if providerOptions contain traceId matching our filter
        // If traceId is provided, only record streams that match
        if (
          traceId &&
          providerOptions &&
          (providerOptions as any).traceId === traceId
        ) {
          stream.traceId = traceId
          recordingStreamId = streamId
        } else if (!traceId) {
          // If no traceId filter, record all streams
          recordingStreamId = streamId
        }
      }
    },
    { withEventTarget: false },
  )

  // Helper to check if we should record this stream
  const shouldRecord = (streamId: string): boolean => {
    if (!traceId) return true // Record all if no filter
    return streamId === recordingStreamId
  }

  // Subscribe to content chunks
  const unsubscribeContent = aiEventClient.on(
    'stream:chunk:content',
    (event) => {
      const { streamId, content, delta, timestamp } = event.payload
      if (!shouldRecord(streamId)) return
      const stream = activeStreams.get(streamId)
      if (stream) {
        stream.accumulatedContent = content
        stream.chunks.push({
          chunk: createContentChunk(content, delta),
          timestamp,
          index: chunkIndex++,
        })
      }
    },
    { withEventTarget: false },
  )

  // Subscribe to tool call chunks
  const unsubscribeToolCall = aiEventClient.on(
    'stream:chunk:tool-call',
    (event) => {
      const {
        streamId,
        toolCallId,
        toolName,
        index,
        arguments: args,
        timestamp,
      } = event.payload
      if (!shouldRecord(streamId)) return
      const stream = activeStreams.get(streamId)
      if (stream) {
        stream.chunks.push({
          chunk: createToolCallChunk(toolCallId, toolName, index, args),
          timestamp,
          index: chunkIndex++,
        })
        // Store tool call info for final recording (update arguments as they stream)
        const existing = stream.toolCalls.get(toolCallId)
        if (existing) {
          existing.arguments = args
        } else {
          stream.toolCalls.set(toolCallId, {
            id: toolCallId,
            name: toolName,
            arguments: args,
            result: undefined,
          } as ToolCall)
        }
      }
    },
    { withEventTarget: false },
  )

  // Subscribe to tool result chunks
  const unsubscribeToolResult = aiEventClient.on(
    'stream:chunk:tool-result',
    (event) => {
      const { streamId, toolCallId, result, timestamp } = event.payload
      if (!shouldRecord(streamId)) return
      const stream = activeStreams.get(streamId)
      if (stream) {
        stream.chunks.push({
          chunk: createToolResultChunk(toolCallId, result),
          timestamp,
          index: chunkIndex++,
        })
      }
    },
    { withEventTarget: false },
  )

  // Subscribe to done chunks
  const unsubscribeDone = aiEventClient.on(
    'stream:chunk:done',
    (event) => {
      const { streamId, finishReason, usage, timestamp } = event.payload
      if (!shouldRecord(streamId)) return
      const stream = activeStreams.get(streamId)
      if (stream) {
        stream.finishReason = finishReason || null
        stream.chunks.push({
          chunk: createDoneChunk(finishReason, usage),
          timestamp,
          index: chunkIndex++,
        })
      }
    },
    { withEventTarget: false },
  )

  // Subscribe to error chunks
  const unsubscribeError = aiEventClient.on(
    'stream:chunk:error',
    (event) => {
      const { streamId, error, timestamp } = event.payload
      if (!shouldRecord(streamId)) return
      const stream = activeStreams.get(streamId)
      if (stream) {
        stream.chunks.push({
          chunk: createErrorChunk(error),
          timestamp,
          index: chunkIndex++,
        })
      }
    },
    { withEventTarget: false },
  )

  // Subscribe to thinking chunks
  const unsubscribeThinking = aiEventClient.on(
    'stream:chunk:thinking',
    (event) => {
      const { streamId, content, delta, timestamp } = event.payload
      if (!shouldRecord(streamId)) return
      const stream = activeStreams.get(streamId)
      if (stream) {
        stream.chunks.push({
          chunk: createThinkingChunk(content, delta),
          timestamp,
          index: chunkIndex++,
        })
      }
    },
    { withEventTarget: false },
  )

  // Subscribe to chat:completed to get final tool calls
  const unsubscribeChatCompleted = aiEventClient.on(
    'chat:completed',
    async (event) => {
      const { streamId, content, finishReason } = event.payload
      if (!shouldRecord(streamId)) return
      const stream = activeStreams.get(streamId)
      if (stream) {
        stream.accumulatedContent = content
        stream.finishReason = finishReason || null
      }
    },
    { withEventTarget: false },
  )

  // Subscribe to tool:call-completed to update tool call results
  const unsubscribeToolCompleted = aiEventClient.on(
    'tool:call-completed',
    (event) => {
      const { streamId, toolCallId, toolName, result } = event.payload
      if (!shouldRecord(streamId)) return
      const stream = activeStreams.get(streamId)
      if (stream) {
        // Update tool call result (arguments should already be set from tool-call chunks)
        const existing = stream.toolCalls.get(toolCallId)
        if (existing) {
          existing.result = result
        } else {
          // Fallback if we missed the tool-call chunk
          stream.toolCalls.set(toolCallId, {
            id: toolCallId,
            name: toolName,
            arguments: '',
            result,
          } as ToolCall)
        }
      }
    },
    { withEventTarget: false },
  )

  // Subscribe to stream:ended to save recording
  const unsubscribeStreamEnded = aiEventClient.on(
    'stream:ended',
    async (event) => {
      const { streamId } = event.payload
      if (!shouldRecord(streamId)) return
      const stream = activeStreams.get(streamId)
      if (!stream) {
        return
      }

      try {
        // Ensure directory exists
        const dir = path.dirname(filePath)
        await fs.mkdir(dir, { recursive: true })

        // Build recording object
        const recording: ChunkRecording = {
          version: '1.0',
          timestamp: Date.now(),
          model: stream.model,
          provider: stream.provider,
          chunks: stream.chunks,
          result: {
            content: stream.accumulatedContent,
            toolCalls: Array.from(stream.toolCalls.values()),
            finishReason: stream.finishReason,
          },
        }

        // Write recording
        await fs.writeFile(
          filePath,
          JSON.stringify(recording, null, 2),
          'utf-8',
        )

        console.log(`Recording saved to: ${filePath}`)

        // Clean up
        activeStreams.delete(streamId)
      } catch (error) {
        console.error('Failed to save recording:', error)
      }
    },
    { withEventTarget: false },
  )

  // Return cleanup function
  return {
    stop: () => {
      unsubscribeStarted()
      unsubscribeChatStarted()
      unsubscribeContent()
      unsubscribeToolCall()
      unsubscribeToolResult()
      unsubscribeDone()
      unsubscribeError()
      unsubscribeThinking()
      unsubscribeChatCompleted()
      unsubscribeToolCompleted()
      unsubscribeStreamEnded()
      activeStreams.clear()
    },
    getStreamId: () => recordingStreamId,
  }
}
