SolidJS primitives for TanStack AI, providing convenient SolidJS bindings for the headless client.
npm install @tanstack/ai-solid
npm install @tanstack/ai-solid
Main primitive for managing chat state in SolidJS with full type safety.
import { useChat, fetchServerSentEvents } from "@tanstack/ai-solid";
import {
clientTools,
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
function ChatComponent() {
// Create client tool implementations
const updateUI = updateUIDef.client((input) => {
setNotification(input.message);
return { success: true };
});
// Create typed tools array (no 'as const' needed!)
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);
return <div>{/* Chat UI with typed messages */}</div>;
}
import { useChat, fetchServerSentEvents } from "@tanstack/ai-solid";
import {
clientTools,
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
function ChatComponent() {
// Create client tool implementations
const updateUI = updateUIDef.client((input) => {
setNotification(input.message);
return { success: true };
});
// Create typed tools array (no 'as const' needed!)
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);
return <div>{/* Chat UI with typed messages */}</div>;
}
Extends ChatClientOptions from @tanstack/ai-client:
Note: Client tools are now automatically executed - no onToolCall callback needed!
interface UseChatReturn {
messages: Accessor<UIMessage[]>;
sendMessage: (content: string) => 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: Accessor<boolean>;
error: Accessor<Error | undefined>;
setMessages: (messages: UIMessage[]) => void;
clear: () => void;
}
interface UseChatReturn {
messages: Accessor<UIMessage[]>;
sendMessage: (content: string) => 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: Accessor<boolean>;
error: Accessor<Error | undefined>;
setMessages: (messages: UIMessage[]) => void;
clear: () => void;
}
Note: Unlike React, messages, isLoading, and error are SolidJS Accessor functions, so you need to call them to get their values (e.g., messages() instead of just messages).
Re-exported from @tanstack/ai-client for convenience:
import {
fetchServerSentEvents,
fetchHttpStream,
stream,
type ConnectionAdapter,
} from "@tanstack/ai-solid";
import {
fetchServerSentEvents,
fetchHttpStream,
stream,
type ConnectionAdapter,
} from "@tanstack/ai-solid";
import { createSignal, For } from "solid-js";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-solid";
export function Chat() {
const [input, setInput] = createSignal("");
const { messages, sendMessage, isLoading } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});
const handleSubmit = (e: Event) => {
e.preventDefault();
if (input().trim() && !isLoading()) {
sendMessage(input());
setInput("");
}
};
return (
<div>
<div>
<For each={messages()}>
{(message) => (
<div>
<strong>{message.role}:</strong>
<For each={message.parts}>
{(part) => {
if (part.type === "thinking") {
return (
<div class="text-sm text-gray-500 italic">
💠Thinking: {part.content}
</div>
);
}
if (part.type === "text") {
return <span>{part.content}</span>;
}
return null;
}}
</For>
</div>
)}
</For>
</div>
<form onSubmit={handleSubmit}>
<input
value={input()}
onInput={(e) => setInput(e.currentTarget.value)}
disabled={isLoading()}
/>
<button type="submit" disabled={isLoading()}>
Send
</button>
</form>
</div>
);
}
import { createSignal, For } from "solid-js";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-solid";
export function Chat() {
const [input, setInput] = createSignal("");
const { messages, sendMessage, isLoading } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});
const handleSubmit = (e: Event) => {
e.preventDefault();
if (input().trim() && !isLoading()) {
sendMessage(input());
setInput("");
}
};
return (
<div>
<div>
<For each={messages()}>
{(message) => (
<div>
<strong>{message.role}:</strong>
<For each={message.parts}>
{(part) => {
if (part.type === "thinking") {
return (
<div class="text-sm text-gray-500 italic">
💠Thinking: {part.content}
</div>
);
}
if (part.type === "text") {
return <span>{part.content}</span>;
}
return null;
}}
</For>
</div>
)}
</For>
</div>
<form onSubmit={handleSubmit}>
<input
value={input()}
onInput={(e) => setInput(e.currentTarget.value)}
disabled={isLoading()}
/>
<button type="submit" disabled={isLoading()}>
Send
</button>
</form>
</div>
);
}
import { For, Show } from "solid-js";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-solid";
export function ChatWithApproval() {
const { messages, sendMessage, addToolApprovalResponse } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});
return (
<div>
<For each={messages()}>
{(message) => (
<For each={message.parts}>
{(part) => (
<Show
when={
part.type === "tool-call" &&
part.state === "approval-requested" &&
part.approval
}
>
<div>
<p>Approve: {part.name}</p>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval!.id,
approved: true,
})
}
>
Approve
</button>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval!.id,
approved: false,
})
}
>
Deny
</button>
</div>
</Show>
)}
</For>
)}
</For>
</div>
);
}
import { For, Show } from "solid-js";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-solid";
export function ChatWithApproval() {
const { messages, sendMessage, addToolApprovalResponse } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});
return (
<div>
<For each={messages()}>
{(message) => (
<For each={message.parts}>
{(part) => (
<Show
when={
part.type === "tool-call" &&
part.state === "approval-requested" &&
part.approval
}
>
<div>
<p>Approve: {part.name}</p>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval!.id,
approved: true,
})
}
>
Approve
</button>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval!.id,
approved: false,
})
}
>
Deny
</button>
</div>
</Show>
)}
</For>
)}
</For>
</div>
);
}
import { useChat, fetchServerSentEvents } from "@tanstack/ai-solid";
import {
clientTools,
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
import { updateUIDef, saveToStorageDef } from "./tool-definitions";
import { createSignal, For } from "solid-js";
export function ChatWithClientTools() {
const [notification, setNotification] = createSignal(null);
// Create client implementations
const updateUI = updateUIDef.client((input) => {
// ✅ input is fully typed!
setNotification({ 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
});
return (
<div>
<For each={messages()}>
{(message) => (
<For each={message.parts}>
{(part) => {
if (part.type === "tool-call" && part.name === "updateUI") {
// ✅ part.input and part.output are fully typed!
return <div>Tool executed: {part.name}</div>;
}
}}
</For>
)}
</For>
</div>
);
}
import { useChat, fetchServerSentEvents } from "@tanstack/ai-solid";
import {
clientTools,
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
import { updateUIDef, saveToStorageDef } from "./tool-definitions";
import { createSignal, For } from "solid-js";
export function ChatWithClientTools() {
const [notification, setNotification] = createSignal(null);
// Create client implementations
const updateUI = updateUIDef.client((input) => {
// ✅ input is fully typed!
setNotification({ 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
});
return (
<div>
<For each={messages()}>
{(message) => (
<For each={message.parts}>
{(part) => {
if (part.type === "tool-call" && part.name === "updateUI") {
// ✅ part.input and part.output are fully typed!
return <div>Tool executed: {part.name}</div>;
}
}}
</For>
)}
</For>
</div>
);
}
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>;
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:
