Skip to content
Merged
14 changes: 14 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ each of these and confirm they're green before pushing:

Do **not** rely on CI as your first signal. Run locally, fix, then push.

## Documentation

When editing docs under `docs/`:

- **No `as` type-assertion casts in code samples.** Examples must type-check
without `as SomeType` — narrow `unknown` values with `typeof` / `in`
checks, type guards, or Standard Schema validation instead. (`as const` is
fine — it's a const assertion, not a type cast.)
- **Show both sides of the coin.** When a doc spans server and client,
include snippets for both halves (server endpoint AND client consumption).
- **Use the latest model per provider**, sourced from each adapter's
`model-meta.ts` (newest `gpt-*`, `claude-*`, `gemini-*`, …), in example code.
- Run `pnpm test:docs` (link verification) before pushing.

## Everything Else

For package manager (`pnpm@10.17.0`), monorepo layout, adapter architecture,
Expand Down
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,18 @@ OPENAI_API_KEY=sk-... pnpm --filter @tanstack/ai-e2e record
- Docs are in `docs/` directory (Markdown)
- Auto-generated docs via `pnpm generate-docs` (TypeDoc)
- Link verification via `pnpm test:docs`
- **No `as` type-assertion casts in doc code samples.** Examples must
type-check without `as SomeType`. To use a value typed `unknown` (a raw
JSON Schema tool input, `request.json()`, `JSON.parse`, custom-event
values, etc.), narrow it with a `typeof` / `in` check or a type guard, or
validate it with a Standard Schema library — never `as`. (`as const` is
allowed; it's a const assertion, not a type cast.)
- **Show both sides of the coin.** When a doc spans both server and client,
include snippets for **both** halves (the server endpoint AND the client
consumption), not just one.
- **Use the latest model per provider in examples**, sourced from each
adapter's `model-meta.ts` (the newest `gpt-*`, `claude-*`, `gemini-*`,
etc.). Don't introduce superseded model ids in new or edited samples.

## Key Dependencies

Expand Down
36 changes: 18 additions & 18 deletions docs/adapters/anthropic.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";

const stream = chat({
adapter: anthropicText("claude-sonnet-4-5"),
adapter: anthropicText("claude-sonnet-4-6"),
messages: [{ role: "user", content: "Hello!" }],
});
```
Expand All @@ -39,7 +39,7 @@ const stream = chat({
import { chat } from "@tanstack/ai";
import { createAnthropicChat } from "@tanstack/ai-anthropic";

const adapter = createAnthropicChat("claude-sonnet-4-5", process.env.ANTHROPIC_API_KEY!, {
const adapter = createAnthropicChat("claude-sonnet-4-6", process.env.ANTHROPIC_API_KEY!, {
// ... your config options
});

Expand All @@ -58,7 +58,7 @@ const config: Omit<AnthropicTextConfig, "apiKey"> = {
baseURL: "https://api.anthropic.com", // Optional, for custom endpoints
};

const adapter = createAnthropicChat("claude-sonnet-4-5", process.env.ANTHROPIC_API_KEY!, config);
const adapter = createAnthropicChat("claude-sonnet-4-6", process.env.ANTHROPIC_API_KEY!, config);
```


Expand All @@ -72,7 +72,7 @@ export async function POST(request: Request) {
const { messages } = await request.json();

const stream = chat({
adapter: anthropicText("claude-sonnet-4-5"),
adapter: anthropicText("claude-sonnet-4-6"),
messages,
});

Expand Down Expand Up @@ -101,7 +101,7 @@ const searchDatabase = searchDatabaseDef.server(async ({ query }) => {
});

const stream = chat({
adapter: anthropicText("claude-sonnet-4-5"),
adapter: anthropicText("claude-sonnet-4-6"),
messages,
tools: [searchDatabase],
});
Expand All @@ -113,7 +113,7 @@ Anthropic supports various provider-specific options. Sampling parameters live h

```typescript
const stream = chat({
adapter: anthropicText("claude-sonnet-4-5"),
adapter: anthropicText("claude-sonnet-4-6"),
messages,
modelOptions: {
max_tokens: 4096,
Expand All @@ -140,15 +140,15 @@ modelOptions: {
}
```

**Note:** `max_tokens` must be greater than `budget_tokens`. The adapter automatically adjusts `max_tokens` if needed.
**Note:** `budget_tokens` must be less than `modelOptions.max_tokens` — set `max_tokens` high enough to leave room for the visible response alongside the thinking budget, or the request is rejected.

### Prompt Caching

Cache prompts for better performance and reduced costs:

```typescript
const stream = chat({
adapter: anthropicText("claude-sonnet-4-5"),
adapter: anthropicText("claude-sonnet-4-6"),
messages: [
{
role: "user",
Expand Down Expand Up @@ -177,7 +177,7 @@ import { summarize } from "@tanstack/ai";
import { anthropicSummarize } from "@tanstack/ai-anthropic";

const result = await summarize({
adapter: anthropicSummarize("claude-sonnet-4-5"),
adapter: anthropicSummarize("claude-sonnet-4-6"),
text: "Your long text to summarize...",
maxLength: 100,
style: "concise", // "concise" | "bullet-points" | "paragraph"
Expand All @@ -204,7 +204,7 @@ Creates an Anthropic chat adapter.

**Parameters:**

- `model` - Claude model id (e.g. `"claude-sonnet-4-5"`, `"claude-opus-4-6"`)
- `model` - Claude model id (e.g. `"claude-sonnet-4-6"`, `"claude-opus-4.8"`)
- `config?.baseURL` - Custom base URL (optional)

### `anthropicSummarize(model, config?)` / `createAnthropicSummarize(model, apiKey, config?)`
Expand Down Expand Up @@ -242,7 +242,7 @@ import { anthropicText } from "@tanstack/ai-anthropic";
import { webSearchTool } from "@tanstack/ai-anthropic/tools";

const stream = chat({
adapter: anthropicText("claude-opus-4-6"),
adapter: anthropicText("claude-opus-4.8"),
messages: [{ role: "user", content: "What's new in AI this week?" }],
tools: [
webSearchTool({
Expand All @@ -269,7 +269,7 @@ import { anthropicText } from "@tanstack/ai-anthropic";
import { webFetchTool } from "@tanstack/ai-anthropic/tools";

const stream = chat({
adapter: anthropicText("claude-sonnet-4-5"),
adapter: anthropicText("claude-sonnet-4-6"),
messages: [{ role: "user", content: "Summarise https://example.com" }],
tools: [webFetchTool()],
});
Expand All @@ -289,7 +289,7 @@ import { anthropicText } from "@tanstack/ai-anthropic";
import { codeExecutionTool } from "@tanstack/ai-anthropic/tools";

const stream = chat({
adapter: anthropicText("claude-sonnet-4-5"),
adapter: anthropicText("claude-sonnet-4-6"),
messages: [{ role: "user", content: "Plot a histogram of [1,2,2,3,3,3]" }],
tools: [
codeExecutionTool({ name: "code_execution", type: "code_execution_20250825" }),
Expand All @@ -311,7 +311,7 @@ import { anthropicText } from "@tanstack/ai-anthropic";
import { computerUseTool } from "@tanstack/ai-anthropic/tools";

const stream = chat({
adapter: anthropicText("claude-sonnet-4-5"),
adapter: anthropicText("claude-sonnet-4-6"),
messages: [{ role: "user", content: "Open the browser and go to example.com" }],
tools: [
computerUseTool({
Expand All @@ -338,7 +338,7 @@ import { anthropicText } from "@tanstack/ai-anthropic";
import { bashTool } from "@tanstack/ai-anthropic/tools";

const stream = chat({
adapter: anthropicText("claude-sonnet-4-5"),
adapter: anthropicText("claude-sonnet-4-6"),
messages: [{ role: "user", content: "List all TypeScript files in src/" }],
tools: [bashTool({ name: "bash", type: "bash_20250124" })],
});
Expand All @@ -358,7 +358,7 @@ import { anthropicText } from "@tanstack/ai-anthropic";
import { textEditorTool } from "@tanstack/ai-anthropic/tools";

const stream = chat({
adapter: anthropicText("claude-sonnet-4-5"),
adapter: anthropicText("claude-sonnet-4-6"),
messages: [{ role: "user", content: "Fix the bug in src/index.ts" }],
tools: [
textEditorTool({ type: "text_editor_20250124", name: "str_replace_editor" }),
Expand All @@ -380,7 +380,7 @@ import { anthropicText } from "@tanstack/ai-anthropic";
import { memoryTool } from "@tanstack/ai-anthropic/tools";

const stream = chat({
adapter: anthropicText("claude-sonnet-4-5"),
adapter: anthropicText("claude-sonnet-4-6"),
messages: [{ role: "user", content: "Remember that I prefer metric units" }],
tools: [memoryTool()],
});
Expand All @@ -402,7 +402,7 @@ import { customTool } from "@tanstack/ai-anthropic/tools";
import { z } from "zod";

const stream = chat({
adapter: anthropicText("claude-sonnet-4-5"),
adapter: anthropicText("claude-sonnet-4-6"),
messages: [{ role: "user", content: "Look up user 42" }],
tools: [
customTool(
Expand Down
12 changes: 6 additions & 6 deletions docs/adapters/elevenlabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The ElevenLabs adapter is **voice-focused**. It exposes four capabilities:

It does not support text `chat()` or `summarize()` — use OpenAI, Anthropic, or Gemini for those.

The realtime adapter uses an **agent-based architecture** where you configure your conversational AI agent in the [ElevenLabs dashboard](https://elevenlabs.io/) (voice, personality, knowledge base, tools) and then connect to it at runtime. The adapter wraps the `@11labs/client` SDK for seamless integration with `useRealtimeChat` and `RealtimeClient`.
The realtime adapter uses an **agent-based architecture** where you configure your conversational AI agent in the [ElevenLabs dashboard](https://elevenlabs.io/) (voice, personality, knowledge base, tools) and then connect to it at runtime. The adapter wraps the `@elevenlabs/client` SDK for seamless integration with `useRealtimeChat` and `RealtimeClient`.

## Installation

Expand Down Expand Up @@ -184,7 +184,7 @@ const chat = useRealtimeChat({
})
```

Tool results are automatically serialized to strings and returned to the ElevenLabs agent. The adapter converts TanStack tool definitions into the `@11labs/client` clientTools format internally.
Tool results are automatically serialized to strings and returned to the ElevenLabs agent. The adapter converts TanStack tool definitions into the `@elevenlabs/client` clientTools format internally.

## Configuration

Expand All @@ -194,7 +194,7 @@ Used on the **server** to generate a signed WebSocket URL.

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `agentId` | `string` | Yes | Agent ID configured in the ElevenLabs dashboard |
| `agentId` | `string` | No\* | Agent ID configured in the ElevenLabs dashboard. \*Falls back to `ELEVENLABS_AGENT_ID`; required only if that env var is unset |
| `overrides.voiceId` | `string` | No | Custom voice ID to override the agent's default voice |
| `overrides.systemPrompt` | `string` | No | Custom system prompt to override the agent's default |
| `overrides.firstMessage` | `string` | No | First message the agent speaks when the session starts |
Expand All @@ -207,7 +207,7 @@ Used on the **client** to establish the connection.
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `connectionMode` | `'websocket' \| 'webrtc'` | auto-detect | Transport protocol for the connection |
| `debug` | `boolean` | `false` | Enable debug logging |
| `debug` | `boolean \| DebugConfig` | `false` | Enable debug logging — pass `true` for all categories, or a `DebugConfig` to select categories/sink |

## Differences from OpenAI Realtime

Expand All @@ -218,7 +218,7 @@ ElevenLabs and OpenAI take different approaches to realtime voice:
| **Configuration** | Agent-based. Configure voice, personality, and knowledge in the ElevenLabs dashboard or via `overrides` at token time. | Session-based. Configure `instructions`, `voice`, `temperature`, etc. per session via `useRealtimeChat` options. |
| **Token type** | Signed WebSocket URL (valid 30 minutes) | Ephemeral API token (valid ~10 minutes) |
| **Transport** | WebSocket (default) or WebRTC | WebRTC |
| **Audio handling** | `@11labs/client` SDK manages audio capture and playback automatically | TanStack AI manages WebRTC peer connection and audio tracks |
| **Audio handling** | `@elevenlabs/client` SDK manages audio capture and playback automatically | TanStack AI manages WebRTC peer connection and audio tracks |
| **VAD** | Handled by ElevenLabs server-side | Supports `server`, `semantic`, and `manual` modes |
| **Runtime updates** | Session config is set at creation time and cannot be changed mid-session | Supports `updateSession()` for mid-session config changes |
| **Image input** | Not supported | Supported via `sendImage()` |
Expand Down Expand Up @@ -293,7 +293,7 @@ const music = await generateAudio({

// Sound effects
const sfx = await generateAudio({
adapter: elevenlabsAudio("sound_effects_v1"),
adapter: elevenlabsAudio("eleven_text_to_sound_v2"),
prompt: "A glass shattering on concrete",
});
```
Expand Down
13 changes: 8 additions & 5 deletions docs/adapters/gemini.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,14 @@ for await (const chunk of chat({
adapter: geminiTextInteractions("gemini-3.5-flash"),
messages: [{ role: "user", content: "Hi, my name is Amir." }],
})) {
if (chunk.type === "CUSTOM" && chunk.name === "gemini.interactionId") {
const value = chunk.value as GeminiInteractionsCustomEventValue<
"gemini.interactionId"
>;
interactionId = value.interactionId;
if (
chunk.type === "CUSTOM" &&
chunk.name === "gemini.interactionId" &&
chunk.value &&
typeof chunk.value === "object" &&
"interactionId" in chunk.value
) {
interactionId = String(chunk.value.interactionId);
}
}

Expand Down
Loading
Loading