webSocket() opens one socket for the conversation. A durable run reconnects on that socket.
On Cloudflare Workers or Durable Objects, use toWebSocketResponse:
// worker.ts
import { chat, memoryStream, toWebSocketResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
export default {
fetch(request: Request): Response {
return toWebSocketResponse(request, {
durability: (ctx) => memoryStream(ctx.request),
onRun: ({ messages, threadId, runId }) =>
chat({
adapter: openaiText("gpt-6.1-sol"),
messages,
threadId,
runId,
}),
});
},
};On other hosts, such as Node, accept the socket yourself. Then pass it to toWebSocketStream. See Resumable Streams: WebSockets.
import { useChat, webSocket } from "@tanstack/ai-react";
const connection = webSocket("/api/chat-ws");
export function Chat() {
const { messages, sendMessage } = useChat({ connection, threadId: "support-chat" });
return (
<div>
{messages.map((message) => (
<p key={message.id}>
{message.parts.map((part) =>
part.type === "text" ? part.content : "",
)}
</p>
))}
<button onClick={() => void sendMessage("Hello")}>Send</button>
</div>
);
}Send two messages. Both replies stream over the same socket.
For the wire protocol, the onRun context, client options, and the reconnect details, see Resumable Streams: WebSockets.
To use your own wire format or a server that you do not control, write a custom transport. See Custom Transports.