Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .agent/domain-map.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"packages/ai-llmgateway",
"packages/ai-lovable",
"packages/ai-mistral",
"packages/ai-neon",
"packages/ai-ollaya",
"packages/ai-openrouter",
"packages/ai-perplexity",
Expand Down
7 changes: 7 additions & 0 deletions .changeset/neon-adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@tanstack/ai': patch
'@tanstack/ai-neon': minor
'@tanstack/openai-base': patch
---

Add `@tanstack/ai-neon` for the Neon AI Gateway: `neonText` sends Claude to the Anthropic Messages route, codex and `*-pro` models to the Responses route, and every other model to Chat Completions, with `neonEmbedding` and `neonSummarize`. `@tanstack/openai-base` gets a `streamOptions(model)` hook, so a Chat Completions backend that rejects `stream_options` can leave it out.
5 changes: 3 additions & 2 deletions .github/workflows/sync-models.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add packages/ scripts/openrouter.models.json scripts/openrouter.video-models.json scripts/vercel-gateway.models.json scripts/lovable-gateway.models.json scripts/llmgateway.models.json scripts/.sync-models-last-run .changeset/
git add packages/ scripts/openrouter.models.json scripts/openrouter.video-models.json scripts/vercel-gateway.models.json scripts/lovable-gateway.models.json scripts/llmgateway.models.json scripts/neon.models.json scripts/.sync-models-last-run .changeset/
git commit -m "chore: sync model metadata"
# GITHUB_TOKEN pushes do not start the PR Test / E2E workflows.
# After this job, a maintainer must run those checks from the
Expand All @@ -87,12 +87,13 @@ jobs:

if [ -z "$EXISTING_PR" ] || [ "$EXISTING_PR" = "null" ]; then
BODY=$(cat <<'PRBODY'
Automated daily sync of model metadata from OpenRouter, Vercel AI Gateway, Lovable AI Gateway, LLM Gateway, and modelschemas (ElevenLabs text-to-speech, music, sound effects, and voice design).
Automated daily sync of model metadata from OpenRouter, Vercel AI Gateway, Lovable AI Gateway, LLM Gateway, Neon AI Gateway, and modelschemas (ElevenLabs text-to-speech, music, sound effects, and voice design).

- Fetches the latest model list from OpenRouter (chat + `GET /api/v1/videos/models`)
- Fetches the latest model list from Vercel AI Gateway
- Fetches the latest model list from Lovable AI Gateway
- Fetches the latest model list from LLM Gateway (`GET /v1/models`)
- Fetches the latest model list from Neon AI Gateway
- Converts to the internal adapter format
- Syncs provider-specific model metadata for affected packages
- Inserts new ElevenLabs text-to-speech, music, sound-effect, and voice-design models from modelschemas
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ For deeper architecture details (adapter system, isomorphic tools, framework int

`pnpm generate:models` is the maintainer command behind the daily **Sync Model Metadata** workflow (branch `automated/sync-models`). It:

1. Fetches OpenRouter, Vercel AI Gateway, and Lovable AI Gateway catalogs.
2. Regenerates `packages/ai-openrouter/src/model-meta.ts` and the Vercel Gateway model list.
1. Fetches OpenRouter, Vercel AI Gateway, Lovable AI Gateway, and Neon AI Gateway catalogs.
2. Regenerates `packages/ai-openrouter/src/model-meta.ts` and the Vercel Gateway and Neon model lists.
3. Inserts **new** native-provider models into `packages/ai-openai`, `ai-anthropic`, `ai-gemini`, and `ai-grok`.
4. Inserts **new** ElevenLabs text-to-speech, music, sound-effect, and voice-design ids from [modelschemas](https://modelschemas.com/v1/schemas/elevenlabs) into `packages/ai-elevenlabs/src/model-meta.ts`. Transcription ids stay hand-maintained.
5. Writes a patch changeset for the packages that changed.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ Official adapters include:
| [`@tanstack/ai-reactor`](https://tanstack.com/ai/latest/docs/adapters/reactor) | Reactor live world and video generation (Orbis, Happy Oyster, LingBot, Helios, FastH3) |
| [`@tanstack/ai-worldlabs`](https://tanstack.com/ai/latest/docs/adapters/worldlabs) | World Labs Marble persistent 3D world generation |
| [`@tanstack/ai-cloudflare`](https://tanstack.com/ai/latest/docs/adapters/cloudflare) | Cloudflare Workers AI chat, embeddings, image, speech, transcription, and AI Gateway |
| [`@tanstack/ai-neon`](https://tanstack.com/ai/latest/docs/adapters/neon) | Neon AI Gateway chat and embeddings for Claude, GPT, Gemini, and open models |

The adapter system is tree-shakeable by activity. Import `openaiText` for chat,
`openaiImage` for images, `falVideo` for video, `geminiSpeech` for TTS, and so
Expand Down
246 changes: 246 additions & 0 deletions docs/adapters/neon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
---
title: Neon AI Gateway
id: neon-adapter
description: "Call Claude, GPT, Gemini, and open models with one Neon credential through the Neon AI Gateway. Chat, embeddings, and summarize work with @tanstack/ai-neon."
keywords:
- tanstack ai
- neon
- ai gateway
- adapter
- claude
- gemini
- embeddings
- pgvector
---

You want Claude, GPT, and Gemini in your app, and you do not want an account and a key for each provider. The Neon AI Gateway gives your Neon branch one credential for every model in its catalog.

Install `@tanstack/ai-neon`. Then call `neonText` with a model id such as `claude-opus-5-5`.

## Installation

<!-- ::start:tabs variant="package-manager" mode="install" -->

react: @tanstack/ai-neon
vue: @tanstack/ai-neon
solid: @tanstack/ai-neon
svelte: @tanstack/ai-neon
preact: @tanstack/ai-neon
angular: @tanstack/ai-neon
vanilla: @tanstack/ai-neon
octane: @tanstack/ai-neon

<!-- ::end:tabs -->

## Auth

The adapter reads two variables:

- `NEON_AI_GATEWAY_TOKEN`: the Neon credential (`nt_live_...`).
- `NEON_AI_GATEWAY_BASE_URL`: the gateway host of your branch, with no path.

`neon env pull` writes both to `.env`. A deployed Neon Function gets both automatically.

```bash
NEON_AI_GATEWAY_TOKEN=nt_live_...
NEON_AI_GATEWAY_BASE_URL=https://br-winter-pond-aptw82ef-api.ai.c-2.us-east-2.aws.neon.tech
```

You can also pass the token to a `create*` factory. `baseURL` is the same bare host:

```typescript
import { createNeonText } from "@tanstack/ai-neon"

const adapter = createNeonText("claude-opus-5-5", process.env.NEON_AI_GATEWAY_TOKEN!, {
baseURL: process.env.NEON_AI_GATEWAY_BASE_URL,
})
```

## Chat

Model ids are the catalog ids, with no provider prefix.

**Server.** An endpoint that streams the reply over SSE:

```typescript
import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from "@tanstack/ai"
import { neonText } from "@tanstack/ai-neon"

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

const stream = chat({
adapter: neonText("claude-opus-5-5"),
messages,
threadId,
runId,
})

return toServerSentEventsResponse(stream)
}
```

**Client.** The same `useChat` hook as every other provider:

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

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

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

return (
<div>
{messages.map((message) => (
<div key={message.id}>
<strong>{message.role}</strong>
{message.parts.map((part, index) =>
part.type === "text" ? <p key={index}>{part.content}</p> : null,
)}
</div>
))}

<form
onSubmit={(event) => {
event.preventDefault()
if (!input.trim() || isLoading) return
sendMessage(input)
setInput("")
}}
>
<input value={input} onChange={(event) => setInput(event.target.value)} />
<button type="submit" disabled={isLoading}>
Send
</button>
</form>
</div>
)
}
```

To change the model, change the id. The rest of the code stays the same.

## Which endpoint each model uses

The gateway has one endpoint per API style. `neonText` sends each model to the endpoint that serves it best:

| Models | Endpoint | What you get |
| --- | --- | --- |
| Claude (`claude-*`) | `/anthropic/v1/messages` | The [Anthropic adapter](./anthropic), with thinking budgets and prompt caching |
| `gpt-5-3-codex`, `gpt-5-5-pro` | `/openai/v1/responses` | The OpenAI Responses API, the only API that serves these models |
| All other models | `/v1/chat/completions` | Chat Completions, which serves every model in the catalog |

The types follow the endpoint. For example, a Claude model takes the Anthropic model options.

Chat Completions accepts text and images through this adapter. For documents, use Claude or a GPT model with `api: "responses"`. Audio and video inputs are not supported.

Other GPT models can also use the Responses API. Pass `{ api: "responses" }`:

```typescript
import { chat } from "@tanstack/ai"
import { neonText } from "@tanstack/ai-neon"

const stream = chat({
adapter: neonText("gpt-6-1-sol", { api: "responses" }),
messages: [{ role: "user", content: "Hello" }],
threadId: "thread-1",
})
```

`NEON_RESPONSES_MODELS` lists the models that take `api: "responses"`. TypeScript rejects it for any other model.

Need a header on each request, or a log of each model call? The chat adapters on this page support [`wrapFetch`](../advanced/middleware#change-the-http-requests-of-a-call).

## Reasoning

Set `reasoning` on `chat()`. The types take the levels of each model:

```typescript
import { chat } from "@tanstack/ai"
import { neonText } from "@tanstack/ai-neon"

const stream = chat({
adapter: neonText("gemini-3-8-flash"),
messages: [{ role: "user", content: "Plan a database migration." }],
threadId: "thread-1",
reasoning: "high",
})
```

- Chat Completions sends `reasoning_effort`. Responses sends `reasoning.effort`. Neither takes `budgetTokens`.
- Claude takes the same levels as on [Anthropic](./anthropic#reasoning), and older Claude models also take `budgetTokens`.

## Embeddings

Embed text with the same credential. Then keep the vectors in your branch, next to the rows they describe:

```typescript
import { embed } from "@tanstack/ai"
import { neonEmbedding } from "@tanstack/ai-neon"

const result = await embed({
adapter: neonEmbedding("qwen3-embedding-0-6b"),
input: ["Neon branches a database like code."],
})

console.log(result.embeddings[0]?.vector.length) // 1024
```

All three embedding models (`qwen3-embedding-0-6b`, `gte-large-en`, `bge-large-en`) return 1024 numbers. A pgvector column of the same size stores them:

```sql
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE documents (id serial PRIMARY KEY, body text, embedding vector(1024));
```

## Summarize

```typescript
import { summarize } from "@tanstack/ai"
import { neonSummarize } from "@tanstack/ai-neon"

const result = await summarize({
adapter: neonSummarize("gpt-6-1-sol"),
threadId: "thread-1",
text: "The Fender Stratocaster is a versatile electric guitar.",
maxLength: 60,
})
```

Summarize uses the same endpoint and route options as `neonText` for the model. With `api: "responses"`, `modelOptions` accepts Responses options such as `max_output_tokens`.

## Models

Pass a model id from the catalog. TypeScript rejects an id that is not on a list.

- `NEON_CHAT_MODELS`: every chat model.
- `NEON_ANTHROPIC_MODELS`: the Claude models that go to the Anthropic endpoint.
- `NEON_RESPONSES_MODELS`: the models that take `api: "responses"`.
- `NEON_EMBEDDING_MODELS`: the embedding models.

The lists come from the [Neon model catalog](https://neon.com/models). Your account can have a smaller set. To see the models your branch can call, ask the gateway:

```bash
curl "$NEON_AI_GATEWAY_BASE_URL/v1/models" \
-H "Authorization: Bearer $NEON_AI_GATEWAY_TOKEN"
```

A model with `"enabled": false` in that list is in the catalog, but your account cannot call it yet.

## Errors

- `401`: the token is missing, wrong, or revoked.
- `403 credential not authorized for ai gateway`: the credential has no `ai_gateway:invoke` scope. Make a new credential with that scope.
- `403 model requires a verified account`: request access to the model on the AI Gateway page of your branch in the Neon Console.
- `429`: your account quota or the upstream provider limit is reached. The response has a `Retry-After` header.

The gateway needs a paid Neon plan and a project in a supported region. See [Neon AI Gateway](https://neon.com/docs/ai-gateway/overview) for regions, pricing, and limits.
1 change: 1 addition & 0 deletions docs/adapters/openai-compatible.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ If a dedicated adapter exists, use it, because it has curated metadata for each
- [OpenAI](./openai), [Grok](./grok), [Groq](./groq), and [OpenRouter](./openrouter).
- Vercel AI Gateway: `vercelGatewayText` from `@tanstack/ai-vercel-gateway`. See [Vercel AI Gateway](./vercel-gateway.md).
- Lovable AI Gateway: `lovableText` (plus image, video, embeddings, and speech factories) from `@tanstack/ai-lovable`. See [Lovable AI Gateway](./lovable.md).
- Neon AI Gateway: `neonText` from `@tanstack/ai-neon`. See [Neon AI Gateway](./neon.md).

Perplexity Sonar chat uses this adapter. [`@tanstack/ai-perplexity`](./perplexity.md) is for search and grounding only. To send the `X-Pplx-Integration` attribution header of Perplexity, pass `defaultHeaders: getPerplexityIntegrationHeaders()` from that package.

Expand Down
6 changes: 3 additions & 3 deletions docs/chat/reasoning.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,9 @@ const lowEffort: ChatMiddleware = {

| Adapter | Sent as |
| --- | --- |
| OpenAI, Azure OpenAI, Grok, Bedrock Responses | `reasoning: { effort, summary }` |
| OpenAI Chat Completions, Groq, LLM Gateway, Lovable, Bedrock chat, Cloudflare | `reasoning_effort` |
| Anthropic | adaptive thinking with the effort, or a thinking budget |
| OpenAI, Azure OpenAI, Grok, Bedrock Responses, Neon Responses | `reasoning: { effort, summary }` |
| OpenAI Chat Completions, Groq, LLM Gateway, Lovable, Bedrock chat, Cloudflare, Neon Chat Completions | `reasoning_effort` |
| Anthropic, Neon Claude | adaptive thinking with the effort, or a thinking budget |
| Gemini | `thinkingConfig.thinkingLevel` or `thinkingConfig.thinkingBudget` |
| OpenRouter, Vercel AI Gateway | the gateway's `reasoning` object |
| Mistral | `reasoning_effort` or `prompt_mode` |
Expand Down
5 changes: 5 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1829,6 +1829,11 @@
"addedAt": "2026-09-03",
"updatedAt": "2026-10-22"
},
{
"label": "Neon AI Gateway",
"to": "adapters/neon",
"addedAt": "2026-10-21"
},
{
"label": "Claude Code",
"to": "adapters/claude-code",
Expand Down
16 changes: 15 additions & 1 deletion docs/embeddings.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Embeddings
id: embeddings
order: 1
description: "Turn text and images into vectors with embed(). Use them for semantic search, RAG, and clustering with OpenAI, Cohere, Gemini, Mistral, Amazon Bedrock, Ollama, or Vercel AI Gateway."
description: "Turn text and images into vectors with embed(). Use them for semantic search, RAG, and clustering with OpenAI, Cohere, Gemini, Mistral, Amazon Bedrock, Ollama, Vercel AI Gateway, or Neon AI Gateway."
keywords:
- tanstack ai
- embeddings
Expand All @@ -18,6 +18,7 @@ keywords:
- bedrock
- ollama
- vercel ai gateway
- neon ai gateway
---

`embed()` turns text into a vector. Search, RAG, and clustering then compare those vectors.
Expand Down Expand Up @@ -83,6 +84,7 @@ You now have one `number[]` vector per input string. Store them in your vector d
| Amazon Bedrock | `bedrockEmbedding` (`@tanstack/ai-bedrock`) | Titan Text Embeddings V2 (text), Titan Multimodal Embeddings G1 (text + image), Cohere Embed v3 (text) | text, or text + image |
| Ollama | `ollamaEmbedding` (`@tanstack/ai-ollama`) | `nomic-embed-text`, `mxbai-embed-large`, and any local embedding model | text |
| Vercel AI Gateway | `vercelGatewayEmbedding` (`@tanstack/ai-vercel-gateway`) | OpenAI-compatible models such as `openai/text-embedding-3-small` | text |
| Neon AI Gateway | `neonEmbedding` (`@tanstack/ai-neon`) | `qwen3-embedding-0-6b`, `gte-large-en`, `bge-large-en` (1024 dimensions) | text |

Vercel AI Gateway takes a `creator/model` id:

Expand All @@ -96,6 +98,18 @@ const result = await embed({
});
```

Neon AI Gateway uses the same call. The vectors fit a `vector(1024)` pgvector column in your Neon branch:

```typescript
import { embed } from "@tanstack/ai";
import { neonEmbedding } from "@tanstack/ai-neon";

const result = await embed({
adapter: neonEmbedding("qwen3-embedding-0-6b"),
input: "a red guitar",
});
```

## Pick the vector size

Some models (Matryoshka models) let you choose the number of dimensions. Pass `dimensions`:
Expand Down
1 change: 1 addition & 0 deletions docs/getting-started/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ Each adapter is its own package. Install only the ones you use.
| `@tanstack/ai-worldlabs` | World Labs Marble (persistent 3D world generation). |
| `@tanstack/ai-llmgateway` | LLM Gateway. Hundreds of models through one OpenAI-compatible endpoint. You can host it yourself. |
| `@tanstack/ai-cloudflare` | Cloudflare Workers AI (chat, embeddings, image, speech, transcription, evaluate) and AI Gateway routing. |
| `@tanstack/ai-neon` | Neon AI Gateway (Claude, GPT, Gemini, and open models, plus embeddings, through one Neon credential). |
| `@tanstack/ai-typesafe` | TypeSafe Jev (typed evaluate decisions). |
| `@tanstack/ai-ollaya` | Local Ollaya `laya` models for typed evaluate decisions. No API key. |
| `@tanstack/ai-lovable` | Lovable AI Gateway (Google and OpenAI chat, image, video, embeddings, and speech through one project key). |
Expand Down
1 change: 1 addition & 0 deletions docs/structured-outputs/streaming.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ Streaming structured output works with every adapter. Only some send the JSON as
| `@tanstack/ai-byteplus` | Native stream in one request on supported models. Other models emit `RUN_ERROR`. |
| `@tanstack/ai-llmgateway` | Native stream in one request (Chat Completions, `response_format: json_schema`) |
| `@tanstack/ai-cloudflare` | Native stream in one request (Chat Completions, `response_format: json_schema`) |
| `@tanstack/ai-neon` | Native stream in one request (Chat Completions, `response_format: json_schema`). Claude uses the Anthropic adapter. |
| `@tanstack/ai-lovable` | Native stream in one request (Responses or Chat Completions) |
| `@tanstack/ai-gemini` | Native stream in one request (`generateContentStream`, `responseSchema`) |
| Other adapters (Anthropic, Ollama, and more) | Runs `structuredOutput` without streaming, then emits the object as one `structured-output.complete` event |
Expand Down
Loading
Loading