AI Docs
Chat & Streaming
Transports
Tools
Middleware
Chat UI Recipes
Interrupts
MCP
- MCP Server Tools
- Managed MCP with chat()Updated
- Manual MCPUpdated
- MCP Type Generation
- MCP Client Input
- MCP Server Tools: Advanced
- MCP Events
- MCP AppsUpdated
- Serve Tools over HTTP
- Serve Resources and Prompts
- Ask for Input
- Draft Text from a Tool
- MCP Server TasksUpdated
- MCP Server Auth
- MCP Server Sessions
- MCP Server on stdio
Resumable Streams
Persistence
Persistence (Advanced)
Media
Embeddings
Reranking
Evaluate
Code Mode
Harness
- Build your first harness
- Host options
- Send and show media
- Connect clientsUpdated
- 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 toolsUpdated
- 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)
Sandboxes
- Overview
- Quick Start
- ProvidersUpdated
- 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
- EventsUpdated
Sandboxes (Advanced)
Advanced
- Runtime Context
- Bring Your Own Key
- Keep Keys on the Server
- Debug Logging
- Multimodal Content
- Files APIUpdated
- Per-Model Type Safety
- Runtime Adapter Switching
- Tree-ShakingUpdated
- Extend Adapter
- Extend Adapter: Advanced
- Typed Pre-Configured Options
- Test with a Fake Model
- Prompt Caching
- Mid-Conversation Changes
- Approval Flow Processing
AI/LLM: This documentation page is available in plain markdown format at/ai/latest/docs/effect/index.md
Effect
Effect
Your server code uses Effect 4. Core chat() returns an async iterable and throws plain errors, so it does not fit in your Effect program.
@tanstack/ai-effect wraps TanStack AI for Effect. AI.chat returns an Effect Stream of chunks. Its errors are typed, and interruption stops the model request.
1. Install the packages#
shell
npm i @tanstack/ai @tanstack/ai-effect @tanstack/ai-openai @tanstack/ai-react effectshell
pnpm add @tanstack/ai @tanstack/ai-effect @tanstack/ai-openai @tanstack/ai-react effectshell
yarn add @tanstack/ai @tanstack/ai-effect @tanstack/ai-openai @tanstack/ai-react effectshell
bun add @tanstack/ai @tanstack/ai-effect @tanstack/ai-openai @tanstack/ai-react effectThe package needs Effect 4 (effect 4.1 or later).
2. Stream the chat from your server route#
ts
import { chatParamsFromRequest, toServerSentEventsResponse } from '@tanstack/ai'
import * as AI from '@tanstack/ai-effect'
import { openaiText } from '@tanstack/ai-openai'
import { Stream } from 'effect'
export async function POST(request: Request) {
const params = await chatParamsFromRequest(request)
const stream = AI.chat({
adapter: openaiText('gpt-6.1-sol'),
messages: params.messages,
threadId: params.threadId,
runId: params.runId,
})
return toServerSentEventsResponse(Stream.toAsyncIterable(stream))
}What each piece does:
- AI.chat takes the options of core chat(). It returns a Stream<StreamChunk, AIError>.
- Stream.toAsyncIterable turns the Stream into the async iterable that toServerSentEventsResponse reads.
- If the browser closes the request, the stream stops. Then AI.chat aborts the request to the model.
3. Show the chat on the client#
The client does not use Effect. It reads the Server-Sent Events with useChat:
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: 'effect-chat',
})
return (
<div>
{messages.map((message) => (
<p key={message.id}>
{message.role}:{' '}
{message.parts.map((part) =>
part.type === 'text' ? part.content : null,
)}
</p>
))}
<form
onSubmit={(event) => {
event.preventDefault()
sendMessage(input)
setInput('')
}}
>
<input value={input} onChange={(event) => setInput(event.target.value)} />
<button type="submit" disabled={isLoading}>
Send
</button>
</form>
</div>
)
}Send a message. The reply streams in token by token from your Effect server.
Get the whole reply as an Effect#
If you do not need tokens as they arrive, pass stream: false. Then AI.chat returns an Effect of { text, chunks }:
ts
import * as AI from '@tanstack/ai-effect'
import { openaiText } from '@tanstack/ai-openai'
import { Effect } from 'effect'
const program = Effect.gen(function* () {
const { text } = yield* AI.chat({
adapter: openaiText('gpt-6.1-sol'),
messages: [{ role: 'user', content: 'Write a haiku about rivers.' }],
threadId: 'haiku',
stream: false,
})
return text
})
const haiku = await Effect.runPromise(program)With outputSchema, AI.chat returns an Effect of the parsed object. See Structured Outputs for the schema options.
Next#
- Build the full app step by step: Effect Chat tutorial.
- Give your tools Effect services: Effect tools.
- Run images, embeddings, and speech as Effects: Effect activities.