Skip to content
Open
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
26 changes: 26 additions & 0 deletions .changeset/react-native-ai-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
'@tanstack/ai': minor
'@tanstack/ai-client': minor
'@tanstack/ai-react': minor
'@tanstack/ai-preact': minor
'@tanstack/ai-solid': minor
'@tanstack/ai-svelte': minor
'@tanstack/ai-vue': minor
---

Add React Native support for chat clients and framework hooks, including
client-safe streaming utilities and connection adapters that work in mobile
environments.

The `fetcher` option is now available on `ChatClient` and the framework chat
hooks (`useChat` / `createChat`), mirroring the generation hooks. Pass either
`connection` or `fetcher` -- the XOR is enforced at the type level via
`ChatTransport`. Fetchers may return either a `Response` (parsed as SSE) or an
`AsyncIterable<StreamChunk>` (yielded directly).

The client-safe `@tanstack/ai/client` subpath is now public for framework
packages and mobile bundles. `stream()`, `fetchServerSentEvents`,
`fetchHttpStream`, `rpcStream`, `xhrServerSentEvents`, and `xhrHttpStream` are
available from the client package and framework re-exports. React Native docs,
an Expo chat example, and smoke tests are included for the supported mobile
setup.
6 changes: 5 additions & 1 deletion docs/advanced/tree-shaking.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,11 @@ Modern bundlers (Vite, Webpack, Rollup, esbuild) can easily eliminate unused cod
2. **Use specific adapter functions** - Import `openaiText` not `openai`
3. **Separate activities by route** - Different API routes can use different activities
4. **Lazy load when possible** - Use dynamic imports for code-split routes
5. **Keep mobile chat bundles client-only** - React Native and Expo chat screens
should import `useChat` and chat connection adapters, not provider SDKs,
server response helpers, React DOM UI, devtools UI, or other framework
packages. See [Quick Start: React Native](../getting-started/quick-start-react-native)
for the server-only provider boundary and mobile transport setup.

```ts
// ✅ Good - Only imports chat
Expand Down Expand Up @@ -295,4 +300,3 @@ TanStack AI's tree-shakeable design means:
- ✅ **Flexibility** - Mix and match activities and adapters as needed

The functional, modular architecture ensures that modern bundlers can eliminate unused code effectively, resulting in optimal bundle sizes for your application.

69 changes: 68 additions & 1 deletion docs/api/ai-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ await client.addToolApprovalResponse({

## Connection Adapters

For a complete transport walkthrough, see
[Connection Adapters](../chat/connection-adapters). For React Native and Expo,
see [Quick Start: React Native](../getting-started/quick-start-react-native).

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

Creates an SSE connection adapter.
Expand All @@ -160,14 +164,77 @@ const adapter = fetchServerSentEvents("/api/chat", {

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

Creates an HTTP stream connection adapter.
Creates a newline-delimited JSON HTTP stream connection adapter. Pair it with
`toHttpResponse()` on the server.

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

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

`fetchHttpStream()` requires a runtime with streaming `fetch`,
`Response.body.getReader()`, and `TextDecoder`. If the runtime cannot expose an
incremental response body, it throws `UnsupportedResponseStreamError`; use the
XHR adapters in React Native or Expo.

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

Creates an `XMLHttpRequest`-backed newline-delimited JSON stream adapter. This
is the recommended default for React Native and Expo chat screens. Pair it with
`toHttpResponse()` on the server.

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

const adapter = xhrHttpStream("http://192.168.1.10:8787/chat/http", {
headers: { Authorization: "Bearer token" },
withCredentials: true,
});
```

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

Creates an `XMLHttpRequest`-backed SSE adapter for runtimes where XHR progress
events are more reliable than streaming `fetch`. Pair it with
`toServerSentEventsResponse()` on the server.

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

const adapter = xhrServerSentEvents("http://192.168.1.10:8787/chat/sse");
```

### Adapter options

Fetch adapters accept:

- `headers?: Record<string, string> | Headers`
- `credentials?: RequestCredentials`
- `signal?: AbortSignal`
- `body?: Record<string, any>`
- `fetchClient?: typeof globalThis.fetch`

XHR adapters accept:

- `headers?: Record<string, string> | Headers`
- `withCredentials?: boolean`
- `signal?: AbortSignal`
- `body?: Record<string, any>`
- `xhrFactory?: () => XMLHttpRequest`

`body` is merged into the AG-UI `forwardedProps` payload. Values from
`forwardedProps` on the client and per-message `sendMessage(..., data)` calls
override static adapter `body` values.

### Stream errors

- `UnsupportedResponseStreamError` - thrown by fetch-based adapters when
`Response.body`, `Response.body.getReader()`, or `TextDecoder` is missing.
- `StreamTruncatedError` - thrown when an SSE or NDJSON stream ends with
unterminated trailing data, usually because the server, proxy, or network cut
the connection mid-line.

### `stream(connectFn)`

Creates a custom connection adapter.
Expand Down
25 changes: 25 additions & 0 deletions docs/api/ai-react.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ keywords:
---

React hooks for TanStack AI, providing convenient React bindings for the headless client.
For React Native, the documented support surface is narrow: `useChat` with chat
connection adapters. React DOM-specific UI packages and TanStack AI devtools UI
are not part of the React Native support surface.

For a complete native journey, see
[Quick Start: React Native](../getting-started/quick-start-react-native).

## Installation

Expand Down Expand Up @@ -111,11 +117,30 @@ Re-exported from `@tanstack/ai-client` for convenience:
import {
fetchServerSentEvents,
fetchHttpStream,
xhrServerSentEvents,
xhrHttpStream,
stream,
type ConnectionAdapter,
type FetchConnectionOptions,
type XhrConnectionOptions,
} from "@tanstack/ai-react";
```

For React Native or Expo chat screens, use an absolute server URL and prefer
`xhrHttpStream()` with a server route that returns `toHttpResponse()`. Use
`xhrServerSentEvents()` with `toServerSentEventsResponse()` when you want SSE.
Use `fetchHttpStream()` only when the runtime supports streaming `fetch`,
`Response.body.getReader()`, and `TextDecoder`; otherwise it throws
`UnsupportedResponseStreamError`.

XHR adapter options include `headers`, `withCredentials`, `signal`, `body`, and
`xhrFactory`. Fetch adapter options include `headers`, `credentials`, `signal`,
`body`, and `fetchClient`. Both option objects may be provided directly or as a
function that resolves per request.

For error narrowing, import `UnsupportedResponseStreamError` and
`StreamTruncatedError` from `@tanstack/ai-client`.

## Example: Basic Chat

```typescript
Expand Down
68 changes: 67 additions & 1 deletion docs/chat/connection-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ This page covers every supported transport, when to pick which, and how to build
| You have… | Use |
| --- | --- |
| A normal HTTP server and want the default | [`fetchServerSentEvents`](#server-sent-events-sse) |
| An environment that blocks SSE (some edge runtimes, RN, strict proxies) | [`fetchHttpStream`](#http-streaming-ndjson) |
| An environment that blocks SSE (some edge runtimes, strict proxies) | [`fetchHttpStream`](#http-streaming-ndjson) |
| React Native or Expo | [`xhrHttpStream`](#react-native-and-expo) by default, [`xhrServerSentEvents`](#react-native-and-expo) for SSE, or [`fetchHttpStream`](#http-streaming-ndjson) only when streaming `fetch` is available |
| A TanStack Start (or other) server function that already returns an async iterable | [`stream`](#server-functions-and-direct-async-iterables) |
| An RPC framework like Cap'n Web, gRPC-Web, or tRPC | [`rpcStream`](#rpc-streams) |
| A single long-lived WebSocket (or BroadcastChannel, postMessage, shared worker) serving many runs | [Custom `subscribe` / `send` adapter](#persistent-transports-websockets-and-friends) |
Expand Down Expand Up @@ -90,6 +91,71 @@ const { messages } = useChat({

Server-side, write each chunk as `JSON.stringify(chunk) + "\n"` to the response body. Options (`url`, `headers`, `body`, `fetchClient`, dynamic functions) match `fetchServerSentEvents` exactly.

## React Native and Expo

You have a native app that needs to call your own backend rather than a
same-origin browser route. Use `useChat` from `@tanstack/ai-react` with an
explicit chat transport and an absolute URL. By the end of this section, the
client adapter and server response helper will be paired correctly for React
Native or Expo.

```typescript
const baseUrl =
process.env.EXPO_PUBLIC_TANSTACK_AI_BASE_URL ??
'http://127.0.0.1:8787'
const httpUrl = `${baseUrl}/chat/http`
const sseUrl = `${baseUrl}/chat/sse`
```

Use the URL your runtime can reach. iOS simulators can often use `localhost` or
`127.0.0.1`, Android emulators commonly use `10.0.2.2` to reach the host
machine, and physical devices need a LAN or tunneled URL.

Prefer `xhrHttpStream()` for Expo and React Native. It pairs with
`toHttpResponse()` and reads newline-delimited JSON through incremental XHR
progress events:

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

const chat = useChat({
connection: xhrHttpStream(httpUrl),
});
```

Use `xhrServerSentEvents()` when your server returns `text/event-stream` via
`toServerSentEventsResponse()`:

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

const chat = useChat({
connection: xhrServerSentEvents(sseUrl),
});
```

Only use `fetchHttpStream()` if your exact React Native runtime exposes
streaming `fetch` responses, `Response.body.getReader()`, and `TextDecoder`.
The server still returns newline-delimited JSON with `toHttpResponse()`:

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

const chat = useChat({
connection: fetchHttpStream(httpUrl),
});
```

If one of those fetch-streaming APIs is missing, `fetchHttpStream()` throws
`UnsupportedResponseStreamError`. A polyfill that buffers the response does not
make fetch streaming compatible; the adapter needs incremental bytes. Switch to
`xhrHttpStream()` or `xhrServerSentEvents()` instead.

Keep provider SDKs and server helpers on your backend. The React Native bundle
should import hooks and connection adapters, not OpenAI/Anthropic/Gemini SDKs,
React DOM UI, devtools UI, or other framework packages. For a complete mobile
walkthrough, see [Quick Start: React Native](../getting-started/quick-start-react-native).

## Server Functions and Direct Async Iterables

When your client can call into your server without going over HTTP — TanStack Start server functions, RSC streams, in-process tests — skip the transport entirely. `stream()` takes a factory that returns an `AsyncIterable<StreamChunk>` and wires it straight into the client:
Expand Down
4 changes: 4 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
"label": "Quick Start: React",
"to": "getting-started/quick-start"
},
{
"label": "Quick Start: React Native",
"to": "getting-started/quick-start-react-native"
},
{
"label": "Devtools",
"to": "getting-started/devtools"
Expand Down
2 changes: 2 additions & 0 deletions docs/getting-started/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ The framework-agnostic core of TanStack AI provides the building blocks for crea

- **Next.js** - API routes and App Router
- **TanStack Start** - React Start or Solid Start (recommended!)
- **React Native / Expo** - Native chat screens with `useChat`, absolute server URLs, and XHR streaming transports
- **Express** - Node.js server
- **React Router v7** - Loaders and actions

Expand Down Expand Up @@ -114,5 +115,6 @@ With the help of adapters, TanStack AI can connect to various LLM providers. Ava
## Next Steps

- [Quick Start Guide](./quick-start) - Get up and running in minutes
- [Quick Start: React Native](./quick-start-react-native) - Add mobile chat with Expo and a server-owned provider boundary
- [Tools Guide](../tools/tools) - Learn about the isomorphic tool system
- [API Reference](../api/ai) - Explore the full API
Loading