본문으로 건너뛰기

@tanstack/ai-solid

헤드리스 클라이언트에 편리한 SolidJS 바인딩을 제공하는 TanStack AI용 SolidJS 프리미티브입니다.

설치

npm install @tanstack/ai-solid

useChat(options?)

완전한 타입 안전성을 갖추고 SolidJS에서 채팅 상태를 관리하는 기본 프리미티브입니다.

import { useChat, fetchServerSentEvents } from "@tanstack/ai-solid";
import {
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
import { createSignal } from "solid-js";

const updateUIDef = toolDefinition({
name: "updateUI",
description: "Show a notification in the UI",
inputSchema: z.object({ message: z.string() }),
});

function ChatComponent() {
const [, setNotification] = createSignal<string | null>(null);
// 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 = [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>;
}

옵션

@tanstack/ai-clientChatClientOptions를 확장합니다.

  • connection - 연결 어댑터(필수)
  • tools? - 클라이언트 도구 구현의 배열(.client() 메서드 포함)
  • initialMessages? - 초기 메시지 배열
  • threadId? - 이 채팅의 유일한 식별자입니다. 영속성이 활성화된 경우 필요합니다. 생략하면 마운트 후 생성됩니다.
  • forwardedProps? - AG-UI RunAgentInput.forwardedProps 필드에서 서버로 전달되는 클라이언트 제어 JSON입니다(예: { provider: 'openai', model: 'gpt-5.5' }).
  • body? - 사용 중단 예정입니다. 대신 forwardedProps를 사용합니다. 하위 호환성을 위해 계속 작동하며, 값은 전송 시 forwardedProps에 병합됩니다.
  • byok? - defineByok에서 가져오는 선택적 BYOK 키링입니다. 전송할 때마다 클라이언트가 확인된 provider를 준비하고 x-byok-* 요청 헤더를 설정합니다. 키는 절대 body에 포함되지 않습니다.
  • byokProvider? - 이 채팅의 provider slug를 반환하는 선택적 함수입니다. slug를 반환하면 해당 키만 준비하여 전송합니다. 그렇지 않으면 forwardedProps, body, 호출별 sendMessage body에서 병합된 provider를 사용합니다. 나중의 소스가 우선합니다. slug가 확인되지 않으면 저장된 모든 키를 첨부하는 대신 전송에서 오류가 발생합니다.
  • context? - 클라이언트 도구 구현에 전달되는 타입이 지정된 클라이언트 로컬 런타임 컨텍스트입니다. 이 값은 서버로 직렬화되지 않습니다.
  • onResponse? - 응답을 받았을 때 호출되는 콜백
  • onChunk? - 스트림 청크를 받았을 때 호출되는 콜백
  • onFinish? - 응답이 완료되었을 때 호출되는 콜백
  • onError? - 오류가 발생했을 때 호출되는 콜백
  • onInterruptStateChange? - 인터럽트 상태가 변경될 때 호출되는 콜백입니다. 컨텍스트 소스는 복원된 상태에서는 hydrate, 스트리밍 또는 클라이언트가 시작한 업데이트에서는 live입니다.
  • streamProcessor? - 스트림 처리 구성

참고: 클라이언트 도구는 이제 자동으로 실행되므로 onToolCall 콜백이 필요하지 않습니다.

반환값

import type { Accessor } from "solid-js";
import type { UIMessage } from "@tanstack/ai-solid";
import type { ModelMessage } from "@tanstack/ai/client";
import type {
MultimodalContent,
SendMessageOptions,
} from "@tanstack/ai-client";

interface UseChatReturn {
messages: Accessor<UIMessage[]>;
sendMessage: (
content: string | MultimodalContent,
options?: SendMessageOptions,
) => 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;
}

참고: React와 달리 messages, isLoading, error는 SolidJS의 Accessor 함수이므로 값을 가져오려면 호출해야 합니다(예: messages가 아니라 messages()).

useByok(client)

Solid에서 ByokClient 스냅샷을 구독합니다. 반환값은 Accessor입니다.

import { useByok } from "@tanstack/ai-solid";
import { byok } from "./byok";

export function KeyStatus() {
const snapshot = useByok(byok);
const openai = snapshot().status.openai;
const last4 = openai && "masked" in openai ? openai.masked : "No key";
return <p>{last4}</p>;
}

snapshot()에는 status, locked, prompt가 있습니다. 자체 UI에서 byok.update(provider, value)를 호출하여 키를 저장합니다. Bring Your Own Key를 참고합니다.

연결 어댑터

편의를 위해 @tanstack/ai-client에서 다시 내보냅니다.

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 { 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) => {
if (
part.type === "tool-call" &&
part.state === "approval-requested" &&
part.approval
) {
return (
<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>
);
}
return null;
}}
</For>
)}
</For>
</div>
);
}

예시: 타입 안전성을 갖춘 클라이언트 도구

import { useChat, fetchServerSentEvents } from "@tanstack/ai-solid";
import {
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
import { createSignal, For } from "solid-js";

const updateUIDef = toolDefinition({
name: "updateUI",
description: "Show a notification in the UI",
inputSchema: z.object({ message: z.string(), type: z.string() }),
});

const saveToStorageDef = toolDefinition({
name: "saveToStorage",
description: "Save a value to localStorage",
inputSchema: z.object({ key: z.string(), value: z.string() }),
});

export function ChatWithClientTools() {
const [notification, setNotification] = createSignal<{ message: string; type: string } | null>(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 = [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>;
}
return null;
}}
</For>
)}
</For>
</div>
);
}

createChatClientOptions(options)

타입이 지정된 채팅 옵션을 생성하는 헬퍼입니다(@tanstack/ai-client에서 다시 내보냄).

import { 
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
import { fetchServerSentEvents } from "@tanstack/ai-solid";
import { tool1, tool2 } from "./tools";

// Create typed tools array (no 'as const' needed!)
const tools = [tool1, tool2];

const chatOptions = createChatClientOptions({
connection: fetchServerSentEvents("/api/chat"),
tools,
});

type Messages = InferChatMessages<typeof chatOptions>;

타입

@tanstack/ai-client에서 다시 내보냅니다.

  • UIMessage<TTools> - 도구 타입 매개변수를 사용하는 메시지 타입
  • MessagePart<TTools> - 도구 타입 매개변수를 사용하는 메시지 파트
  • TextPart - 텍스트 콘텐츠 파트
  • ThinkingPart - 사고 콘텐츠 파트
  • ToolCallPart<TTools> - 도구 호출 파트(판별된 유니온)
  • ToolResultPart - 도구 결과 파트
  • ChatClientOptions<TTools, TContext> - 타입이 지정된 클라이언트 런타임 컨텍스트를 사용하는 채팅 클라이언트 옵션
  • ConnectionAdapter - 연결 어댑터 인터페이스
  • InferChatMessages<T> - 옵션에서 메시지 타입 추출
  • ChatRequestBody - 요청 본문 타입

@tanstack/ai에서 다시 내보냅니다.

  • toolDefinition() - 동형 도구 정의 생성
  • ToolDefinitionInstance - 도구 정의 타입
  • ClientTool - 클라이언트 도구 타입
  • ServerTool - 서버 도구 타입

다음 단계