Svelte 5 bindings for TanStack AI, providing reactive factory functions for the headless client using Svelte runes.
npm install @tanstack/ai-svelte
Factory function for managing chat state in Svelte 5 with full type safety.
import { createChat, fetchServerSentEvents } from "@tanstack/ai-svelte";
import {
clientTools,
createChatClientOptions,
type InferChatMessages,
} from "@tanstack/ai-client";
// In <script> block
const updateUI = updateUIDef.client((input) => {
notification = input.message;
return { success: true };
});
const tools = clientTools(updateUI);
const chatOptions = createChatClientOptions({
connection: fetchServerSentEvents("/api/chat"),
tools,
});
// Fully typed messages!
type ChatMessages = InferChatMessages<typeof chatOptions>;
const chat = createChat(chatOptions);
// Access: chat.messages, chat.sendMessage, chat.isLoading, chat.error
Extends ChatClientOptions from @tanstack/ai-client (minus internal state callbacks):
Note: Client tools are now automatically executed - no onToolCall callback needed!
interface CreateChatReturn {
readonly messages: UIMessage[];
sendMessage: (content: string | MultimodalContent) => Promise<void>;
append: (message: ModelMessage | UIMessage) => Promise<void>;
addToolResult: (result: {
toolCallId: string;
tool: string;
output: any;
state?: "output-available" | "output-error";
errorText?: string;
}) => Promise<void>;
addToolApprovalResponse: (response: {
id: string;
approved: boolean;
}) => Promise<void>;
reload: () => Promise<void>;
stop: () => void;
readonly isLoading: boolean;
readonly error: Error | undefined;
readonly status: ChatClientState;
readonly isSubscribed: boolean;
readonly connectionStatus: ConnectionStatus;
readonly sessionGenerating: boolean;
setMessages: (messages: UIMessage[]) => void;
clear: () => void;
updateBody: (body: Record<string, any>) => void;
}
Key differences from React/Vue:
Re-exported from @tanstack/ai-client for convenience:
import {
fetchServerSentEvents,
fetchHttpStream,
stream,
type ConnectionAdapter,
} from "@tanstack/ai-svelte";
<script lang="ts">
import { createChat, fetchServerSentEvents } from "@tanstack/ai-svelte";
let input = $state("");
const chat = createChat({
connection: fetchServerSentEvents("/api/chat"),
});
const handleSubmit = (e: Event) => {
e.preventDefault();
if (input.trim() && !chat.isLoading) {
chat.sendMessage(input);
input = "";
}
};
</script>
<div>
<div>
{#each chat.messages as message (message.id)}
<div>
<strong>{message.role}:</strong>
{#each message.parts as part, idx}
{#if part.type === "thinking"}
<div class="text-sm text-gray-500 italic">
Thinking: {part.content}
</div>
{:else if part.type === "text"}
<span>{part.content}</span>
{/if}
{/each}
</div>
{/each}
</div>
<form onsubmit={handleSubmit}>
<input bind:value={input} disabled={chat.isLoading} />
<button type="submit" disabled={chat.isLoading}>Send</button>
</form>
</div>
<script lang="ts">
import { createChat, fetchServerSentEvents } from "@tanstack/ai-svelte";
const chat = createChat({
connection: fetchServerSentEvents("/api/chat"),
});
</script>
<div>
{#each chat.messages as message (message.id)}
{#each message.parts as part}
{#if part.type === "tool-call" && part.state === "approval-requested" && part.approval}
<div>
<p>Approve: {part.name}</p>
<button
onclick={() =>
chat.addToolApprovalResponse({
id: part.approval.id,
approved: true,
})}
>
Approve
</button>
<button
onclick={() =>
chat.addToolApprovalResponse({
id: part.approval.id,
approved: false,
})}
>
Deny
</button>
</div>
{/if}
{/each}
{/each}
</div>
<script lang="ts">
import { createChat, fetchServerSentEvents } from "@tanstack/ai-svelte";
import {
clientTools,
createChatClientOptions,
type InferChatMessages,
} from "@tanstack/ai-client";
import { updateUIDef, saveToStorageDef } from "./tool-definitions";
let notification = $state(null);
// Create client implementations
const updateUI = updateUIDef.client((input) => {
// input is fully typed!
notification = { message: input.message, type: input.type };
return { success: true };
});
const saveToStorage = saveToStorageDef.client((input) => {
localStorage.setItem(input.key, input.value);
return { saved: true };
});
// Create typed tools array (no 'as const' needed!)
const tools = clientTools(updateUI, saveToStorage);
const chat = createChat({
connection: fetchServerSentEvents("/api/chat"),
tools, // Automatic execution, full type safety
});
</script>
<div>
{#each chat.messages as message (message.id)}
{#each message.parts as part}
{#if part.type === "tool-call" && part.name === "updateUI"}
<div>Tool executed: {part.name}</div>
{/if}
{/each}
{/each}
</div>
Factory functions for one-shot generation tasks (images, speech, transcription, summarization, video). All share the same pattern: provide a connection or fetcher, call generate(), and read reactive state.
Base factory for custom generation types. All specialized functions below are built on this.
import { createGeneration, fetchServerSentEvents } from "@tanstack/ai-svelte";
const gen = createGeneration({
connection: fetchServerSentEvents("/api/generate/custom"),
});
// gen.generate({ prompt: 'Hello' })
// gen.result, gen.isLoading, gen.error, gen.status
Options: connection?, fetcher?, id?, body?, onResult?, onError?, onProgress?, onChunk?
Returns: generate, result, isLoading, error, status, stop, reset, updateBody -- all state properties are reactive getters.
Image generation factory. generate() accepts ImageGenerateInput, result is ImageGenerationResult.
Text-to-speech factory. generate() accepts SpeechGenerateInput, result is TTSResult.
Audio transcription factory. generate() accepts TranscriptionGenerateInput, result is TranscriptionResult.
Text summarization factory. generate() accepts SummarizeGenerateInput, result is SummarizationResult.
Video generation factory with job polling. Returns additional jobId and videoStatus reactive getters. Accepts extra onJobCreated? and onStatusUpdate? callbacks.
No generation function includes automatic cleanup. Call .stop() manually when done.
Helper to create typed chat options (re-exported from @tanstack/ai-client).
import {
clientTools,
createChatClientOptions,
type InferChatMessages,
} from "@tanstack/ai-client";
// Create typed tools array (no 'as const' needed!)
const tools = clientTools(tool1, tool2);
const chatOptions = createChatClientOptions({
connection: fetchServerSentEvents("/api/chat"),
tools,
});
type Messages = InferChatMessages<typeof chatOptions>;
Re-exported from @tanstack/ai-client:
Re-exported from @tanstack/ai: