TanStack AI Integration
Learn how to use oRPC as a transport for TanStack AI chat streams, through the oRPC client or as a plain Server-Sent Events endpoint.
Transport
TanStack AI’s chat returns an AsyncIteratorObject of stream chunks, so a procedure can return it directly and oRPC streams every chunk to the client.
Server
import type { UIMessage } from '@tanstack/ai'
import { os, type } from '@orpc/server'
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
export const streamChat = os
.input(type<{ messages: UIMessage[] }>())
.handler(({ input, signal }) => {
const abortController = new AbortController()
signal?.addEventListener('abort', () => abortController.abort(), { once: true })
return chat({
adapter: openaiText('gpt-5.5'),
systemPrompts: ['You are a helpful assistant.'],
messages: input.messages,
abortController,
})
})
Client
Pass an oRPC client call as the fetcher of useChat. The fetcher option accepts a promise of an AsyncIterable of stream chunks, which is exactly what an oRPC client call returns.
import { useState } from 'react'
import { useChat } from '@tanstack/ai-react'
import { client } from './client'
export function Example() {
const { messages, sendMessage, isLoading } = useChat({
fetcher: ({ messages }, { signal }) =>
client.streamChat({ messages }, { signal }),
})
const [input, setInput] = useState('')
return (
<>
{messages.map(message => (
<div key={message.id}>
{message.role === 'user' ? 'User: ' : 'AI: '}
{message.parts.map((part, index) =>
part.type === 'text' ? <span key={index}>{part.content}</span> : null,
)}
</div>
))}
<form
onSubmit={(e) => {
e.preventDefault()
if (input.trim() && !isLoading) {
sendMessage(input)
setInput('')
}
}}
>
<input
value={input}
onChange={e => setInput(e.target.value)}
disabled={isLoading}
placeholder="Say something..."
/>
<button type="submit" disabled={isLoading}>
Submit
</button>
</form>
</>
)
}