Vue composables for TanStack AI, providing convenient Vue 3 bindings for the headless client.
npm install @tanstack/ai-vue
Main composable for managing chat state in Vue with full type safety.
import { useChat, fetchServerSentEvents } from "@tanstack/ai-vue";
import {
clientTools,
createChatClientOptions,
type InferChatMessages,
} from "@tanstack/ai-client";
// In <script setup>
const updateUI = updateUIDef.client((input) => {
notification.value = 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 { messages, sendMessage, isLoading, error, addToolApprovalResponse } =
useChat(chatOptions);
Extends ChatClientOptions from @tanstack/ai-client (minus internal state callbacks):
Note: Client tools are now automatically executed - no onToolCall callback needed!
interface UseChatReturn {
messages: DeepReadonly<ShallowRef<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;
isLoading: DeepReadonly<ShallowRef<boolean>>;
error: DeepReadonly<ShallowRef<Error | undefined>>;
status: DeepReadonly<ShallowRef<ChatClientState>>;
isSubscribed: DeepReadonly<ShallowRef<boolean>>;
connectionStatus: DeepReadonly<ShallowRef<ConnectionStatus>>;
sessionGenerating: DeepReadonly<ShallowRef<boolean>>;
setMessages: (messages: UIMessage[]) => void;
clear: () => void;
}
Note: Reactive state (messages, isLoading, error, status, isSubscribed, connectionStatus, sessionGenerating) is wrapped in DeepReadonly<ShallowRef<T>>. Access values with .value (e.g., messages.value). Cleanup is automatic via onScopeDispose.
Re-exported from @tanstack/ai-client for convenience:
import {
fetchServerSentEvents,
fetchHttpStream,
stream,
type ConnectionAdapter,
} from "@tanstack/ai-vue";
<script setup lang="ts">
import { ref } from "vue";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-vue";
const input = ref("");
const { messages, sendMessage, isLoading } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});
const handleSubmit = () => {
if (input.value.trim() && !isLoading.value) {
sendMessage(input.value);
input.value = "";
}
};
</script>
<template>
<div>
<div>
<div v-for="message in messages.value" :key="message.id">
<strong>{{ message.role }}:</strong>
<template v-for="(part, idx) in message.parts" :key="idx">
<div
v-if="part.type === 'thinking'"
class="text-sm text-gray-500 italic"
>
Thinking: {{ part.content }}
</div>
<span v-else-if="part.type === 'text'">{{ part.content }}</span>
</template>
</div>
</div>
<form @submit.prevent="handleSubmit">
<input v-model="input" :disabled="isLoading.value" />
<button type="submit" :disabled="isLoading.value">Send</button>
</form>
</div>
</template>
<script setup lang="ts">
import { useChat, fetchServerSentEvents } from "@tanstack/ai-vue";
const { messages, sendMessage, addToolApprovalResponse } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});
</script>
<template>
<div>
<template v-for="message in messages.value" :key="message.id">
<template v-for="part in message.parts" :key="part.id">
<div
v-if="
part.type === 'tool-call' &&
part.state === 'approval-requested' &&
part.approval
"
>
<p>Approve: {{ part.name }}</p>
<button
@click="
addToolApprovalResponse({
id: part.approval!.id,
approved: true,
})
"
>
Approve
</button>
<button
@click="
addToolApprovalResponse({
id: part.approval!.id,
approved: false,
})
"
>
Deny
</button>
</div>
</template>
</template>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-vue";
import {
clientTools,
createChatClientOptions,
type InferChatMessages,
} from "@tanstack/ai-client";
import { updateUIDef, saveToStorageDef } from "./tool-definitions";
const notification = ref(null);
// Create client implementations
const updateUI = updateUIDef.client((input) => {
// input is fully typed!
notification.value = { 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 { messages, sendMessage } = useChat({
connection: fetchServerSentEvents("/api/chat"),
tools, // Automatic execution, full type safety
});
</script>
<template>
<div>
<template v-for="message in messages.value" :key="message.id">
<template v-for="(part, idx) in message.parts" :key="idx">
<div v-if="part.type === 'tool-call' && part.name === 'updateUI'">
Tool executed: {{ part.name }}
</div>
</template>
</template>
</div>
</template>
Vue composables 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 composable for custom generation types. All specialized composables below are built on this.
import { useGeneration } from "@tanstack/ai-vue";
import { fetchServerSentEvents } from "@tanstack/ai-client";
const { generate, result, isLoading, error, status, stop, reset } =
useGeneration({
connection: fetchServerSentEvents("/api/generate/custom"),
});
Options: connection?, fetcher?, id?, body?, onResult?, onError?, onProgress?, onChunk?
Returns: generate, result, isLoading, error, status, stop, reset -- all reactive state is DeepReadonly<ShallowRef<T>>.
Image generation composable. generate() accepts ImageGenerateInput, result is ImageGenerationResult.
Text-to-speech composable. generate() accepts SpeechGenerateInput, result is TTSResult.
Audio transcription composable. generate() accepts TranscriptionGenerateInput, result is TranscriptionResult.
Text summarization composable. generate() accepts SummarizeGenerateInput, result is SummarizationResult.
Video generation composable with job polling. Returns additional jobId and videoStatus refs. Accepts extra onJobCreated? and onStatusUpdate? callbacks.
All generation composables automatically clean up via onScopeDispose.
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: