본문으로 건너뛰기

@tanstack/ai-preact

TanStack AI를 위한 Preact 훅으로, 헤드리스 클라이언트에 편리한 Preact 바인딩을 제공합니다.

설치

npm install @tanstack/ai-preact

useChat(options?)

완전한 타입 안전성으로 Preact에서 채팅 상태를 관리하는 기본 훅입니다.

import { useChat, fetchServerSentEvents } from "@tanstack/ai-preact";
import {
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
import { useState } from "preact/hooks";

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

function ChatComponent() {
const [, setNotification] = useState<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 { UIMessage } from "@tanstack/ai-preact";
import type { ModelMessage } from "@tanstack/ai/client";
import type {
MultimodalContent,
SendMessageOptions,
} from "@tanstack/ai-client";

interface UseChatReturn {
messages: 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: boolean;
error: Error | undefined;
setMessages: (messages: UIMessage[]) => void;
clear: () => void;
}

useByok(client)

Preact에서 ByokClient 스냅샷을 구독합니다.

import { useByok } from "@tanstack/ai-preact";
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-preact";

예시: 기본 채팅

import { useState } from "preact/hooks";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-preact";

export function Chat() {
const [input, setInput] = useState("");

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>
{messages.map((message) => (
<div key={message.id}>
<strong>{message.role}:</strong>
{message.parts.map((part, idx) => {
if (part.type === "thinking") {
return (
<div key={idx} class="text-sm text-gray-500 italic">
💭 Thinking: {part.content}
</div>
);
}
if (part.type === "text") {
return <span key={idx}>{part.content}</span>;
}
return null;
})}
</div>
))}
</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 { useChat, fetchServerSentEvents } from "@tanstack/ai-preact";

export function ChatWithApproval() {
const { messages, sendMessage, addToolApprovalResponse } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});

return (
<div>
{messages.map((message) =>
message.parts.map((part) => {
if (
part.type === "tool-call" &&
part.state === "approval-requested" &&
part.approval
) {
return (
<div key={part.id}>
<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;
})
)}
</div>
);
}

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

import { useChat, fetchServerSentEvents } from "@tanstack/ai-preact";
import {
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
import { useState } from "preact/hooks";

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] = useState<{ 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>
{messages.map((message) =>
message.parts.map((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;
})
)}
</div>
);
}

createChatClientOptions(options)

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

import { 
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
import { fetchServerSentEvents } from "@tanstack/ai-preact";
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> - 옵션에서 메시지 타입 추출

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

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

다음 단계