Persistence (Advanced)
- Build your first harness
- Host options
- Send and show media
- Connect clients
- Write a client in any language
- Access and tenants
- Run in the terminal
- Deploy a harness
- Send inputs safely
- Run a tool without a model
- Durable sessions
- Run background work on workers
- List, rename, and fork sessions
- Lifecycle and retention
- Run side effects once
- Wait for a person
- Build on the session log
- Control how a turn ends
- Store settings per thread
- Compact a long session
- Fork and reset a thread
- One log for a session tree
- Write a plugin
- Track usage and cost
- Ask before risky tool calls
- Give your agent coding tools
- Sandbox and extend the coding tools
- Run the coding tools for many threads
- Undo a turn
- Build a coding agent
- Delegate to coding agents
- Switch between agent profiles
- Auth and connectors
- Share a thread between people
- Connect model providers
- Build your own UI
- Code mode in a harness
- Work until a goal is met
- Run agents from a harness
- Hand off a conversation
- Add skills
- Use MCP servers
- Give your agent a computer
- Use from any MCP client
- Use a harness from an editor (ACP)
- Overview
- Quick Start
- Providers
- Harnesses
- Harness Auth
- Workspace
- Tools
- Policy
- Lifecycle & Snapshots
- Portable Snapshots
- Keep Files After Reload
- Save a Named Version
- Branch From a Version
- Send a Frozen File
- Let the Agent Save and Fork
- Pick Which Files to Keep
- What a Snapshot Stores
- Instance Durability
- Durable Runs
- Events
Sandboxes (Advanced)
- Runtime Context
- Bring Your Own Key
- Keep Keys on the Server
- Debug Logging
- Multimodal Content
- Files API
- Per-Model Type Safety
- Runtime Adapter Switching
- Tree-Shaking
- Extend Adapter
- Extend Adapter: Advanced
- Typed Pre-Configured Options
- Test with a Fake Model
- Prompt Caching
- Mid-Conversation Changes
- Approval Flow Processing
React Native
Use useChat from @tanstack/ai-react with xhrHttpStream() and an absolute URL. XHR progress events carry the stream when fetch streaming is missing.
Quick setup#
1. Return NDJSON from the server#
Your backend returns the stream with toHttpResponse():
// server route: POST /chat/http
import { chat, chatParamsFromRequest, toHttpResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
export async function POST(request: Request) {
const { messages, threadId, runId } = await chatParamsFromRequest(request);
const stream = chat({
adapter: openaiText("gpt-6.1-sol"),
messages,
threadId,
runId,
});
return toHttpResponse(stream);
}2. Set the backend URL#
Put the backend address in a public Expo variable:
EXPO_PUBLIC_TANSTACK_AI_BASE_URL=http://127.0.0.1:8787Use a URL that your runtime can reach:
- iOS simulator: usually localhost or 127.0.0.1.
- Android emulator: usually 10.0.2.2, to reach the host machine.
- A physical device: a LAN URL or a tunnel URL.
3. Connect with xhrHttpStream#
import { Button, Text, View } from "react-native";
import { useChat, xhrHttpStream } from "@tanstack/ai-react";
const baseUrl =
process.env.EXPO_PUBLIC_TANSTACK_AI_BASE_URL ?? "http://127.0.0.1:8787";
export function ChatScreen() {
const { messages, sendMessage } = useChat({
connection: xhrHttpStream(`${baseUrl}/chat/http`),
threadId: "support-chat",
});
return (
<View>
{messages.map((message) => (
<Text key={message.id}>
{message.parts.map((part) =>
part.type === "text" ? part.content : "",
)}
</Text>
))}
<Button title="Send" onPress={() => void sendMessage("Hello")} />
</View>
);
}Tap Send in the simulator. The reply streams into messages as the model writes it.
For a full mobile walkthrough with a Hono server and a text input, see Quick Start: React Native.
How it works#
- sendMessage posts the messages to your backend through XHR.
- The server writes each chunk as one line of JSON.
- xhrHttpStream() reads new lines from each XHR progress event and updates messages.
Mobile connections drop often. If the server adds a durability adapter, both XHR transports reconnect and support joinRun. See Resumable Streams.
Advanced#
Use SSE on mobile#
If your server returns text/event-stream with toServerSentEventsResponse() (see Server-Sent Events), use xhrServerSentEvents():
import { xhrServerSentEvents } from "@tanstack/ai-react";
const baseUrl =
process.env.EXPO_PUBLIC_TANSTACK_AI_BASE_URL ?? "http://127.0.0.1:8787";
export const connection = xhrServerSentEvents(`${baseUrl}/chat/sse`);Pass this connection to useChat.
Use fetchHttpStream on mobile#
fetchHttpStream() works only if your React Native runtime has all of these:
- Streaming fetch responses.
- Response.body.getReader().
- TextDecoder.
The server still returns newline-delimited JSON with toHttpResponse():
import { fetchHttpStream } from "@tanstack/ai-react";
const baseUrl =
process.env.EXPO_PUBLIC_TANSTACK_AI_BASE_URL ?? "http://127.0.0.1:8787";
export const connection = fetchHttpStream(`${baseUrl}/chat/http`);If one of these APIs is missing, fetchHttpStream() throws UnsupportedResponseStreamError. A polyfill that buffers the response does not help, because the transport needs incremental bytes. Use xhrHttpStream() or xhrServerSentEvents().
Request options on mobile#
The XHR transports take headers and body, like the fetch transports. For cookies, they use withCredentials, not credentials. See Request Options.
Keep the bundle small#
Keep provider SDKs and server helpers on your backend. Import only hooks and connection adapters in the React Native bundle. Do not import these:
- OpenAI, Anthropic, or Gemini SDKs.
- React DOM UI or devtools UI.
- Packages for other frameworks.