@tanstack/ai-react
TanStack AI를 위한 React 훅으로, 헤드리스 클라이언트에 편리한 React 바인딩을 제공합니다.
React Native에서 문서화된 지원 범위는 좁으며, 채팅 연결 어댑터와 함께 사용하는 useChat으로
제한됩니다. React DOM 전용 UI 패키지와 TanStack AI devtools UI는 React Native 지원 범위에
포함되지 않습니다.
완전한 네이티브 사용 과정은 빠른 시작: React Native를 참고하세요.
설치
npm install @tanstack/ai-react
useChat(options?)
완전한 타입 안전성을 갖추고 React에서 채팅 상태를 관리하는 주요 훅입니다.
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
import {
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
import { useState } from "react";
const updateUIDef = toolDefinition({
name: "updateUI",
description: "Update the UI with a notification",
inputSchema: z.object({
message: z.string(),
}),
outputSchema: z.object({ success: z.boolean() }),
});
function ChatComponent() {
const [notification, 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-client의 ChatClientOptions를 확장합니다.
connection- Connection adapter (필수)tools?- 클라이언트 도구 구현의 배열(.client()메서드 포함)initialMessages?- 초기 메시지 배열threadId?- 이 채팅의 유일한 식별자입니다. 영속성이 켜져 있으면 필수입니다. 생략하면 마운트 후 생성됩니다.forwardedProps?- AG-UIRunAgentInput.forwardedProps필드로 서버에 전달되는 클라이언트가 제어하는 임의의 JSON입니다(예:{ provider: 'openai', model: 'gpt-5.5' }).body?- 사용 중단 예정입니다. 대신forwardedProps를 사용하세요. 이전 버전과의 호환성을 위해 계속 작동하며, 값은 wire에서forwardedProps에 병합됩니다.byok?-defineByok에서 가져오는 선택적 BYOK 키링입니다. 전송할 때마다 클라이언트가 확인된 provider를 준비하고x-byok-*요청 헤더를 추가합니다. 키는 절대 body에 포함되지 않습니다.byokProvider?- 이 채팅의 provider slug를 반환하는 선택적 함수입니다. slug를 반환하면 해당 키만 준비하여 전송합니다. 그렇지 않으면forwardedProps,body, 호출별sendMessagebody에서 병합된provider를 사용합니다. 나중의 소스가 우선합니다. slug가 확인되지 않으면 저장된 모든 키를 첨부하는 대신 전송에서 오류가 발생합니다.context?- 클라이언트 도구 구현에 전달되는 타입이 지정된 클라이언트 로컬 런타임 컨텍스트입니다. 이 값은 서버로 직렬화되지 않습니다.onResponse?- 응답을 받았을 때 호출되는 콜백onChunk?- 스트림 청크를 받았을 때 호출되는 콜백onFinish?- 응답이 완료되었을 때 호출되는 콜백onError?- 오류가 발생했을 때 호출되는 콜백onInterruptStateChange?- 인터럽트 상태가 변경될 때 호출되는 콜백입니다. 컨텍스트 소스는 복원된 상태의 경우hydrate, 스트리밍 또는 클라이언트가 시작한 업데이트의 경우live입니다.streamProcessor?- 스트림 처리 구성
참고: 클라이언트 도구는 이제 자동으로 실행되므로 onToolCall 콜백이 필요하지 않습니다.
반환값
import type { UIMessage } from "@tanstack/ai-react";
import type { ModelMessage } from "@tanstack/ai";
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)
React에서 ByokClient 스냅샷을 구독합니다.
import { useByok } from "@tanstack/ai-react";
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,
xhrServerSentEvents,
xhrHttpStream,
stream,
type ConnectionAdapter,
type FetchConnectionOptions,
type XhrConnectionOptions,
} from "@tanstack/ai-react";
React Native 또는 Expo 채팅 화면에서는 절대 서버 URL을 사용하고, toHttpResponse()를 반환하는 서버 라우트와 함께
xhrHttpStream()을 사용하는 것이 좋습니다. SSE가 필요하면 toServerSentEventsResponse()와 함께
xhrServerSentEvents()를 사용하세요. 런타임이 스트리밍 fetch, Response.body.getReader(),
TextDecoder를 지원하는 경우에만 fetchHttpStream()을 사용하세요. 그렇지 않으면
UnsupportedResponseStreamError가 발생합니다.
XHR 어댑터 옵션에는 headers, withCredentials, signal, body, xhrFactory가 포함됩니다.
Fetch 어댑터 옵션에는 headers, credentials, signal, body, fetchClient가 포함됩니다.
두 옵션 객체 모두 직접 제공하거나 요청별로 확인되는 함수로 제공할 수 있습니다.
오류 타입을 좁히려면 @tanstack/ai-client에서 UnsupportedResponseStreamError와
StreamTruncatedError를 가져오세요.
예시: 기본 채팅
import { useState } from "react";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
export function Chat() {
const [input, setInput] = useState("");
const { messages, sendMessage, isLoading } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});
const handleSubmit = (e: React.FormEvent) => {
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} className="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}
onChange={(e) => setInput(e.target.value)}
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
Send
</button>
</form>
</div>
);
}
예시: 도구 승인
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
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-react";
import {
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
import { useState } from "react";
const updateUIDef = toolDefinition({
name: "updateUI",
description: "Update the UI with a notification",
inputSchema: z.object({
message: z.string(),
type: z.string(),
}),
outputSchema: z.object({ success: z.boolean() }),
});
const saveToStorageDef = toolDefinition({
name: "saveToStorage",
description: "Save a value to storage",
inputSchema: z.object({
key: z.string(),
value: z.string(),
}),
outputSchema: z.object({ saved: z.boolean() }),
});
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 key={part.id}>Tool executed: {part.name}</div>;
}
return null;
})
)}
</div>
);
}
createChatClientOptions(options)
타입이 지정된 채팅 옵션을 생성하는 도우미입니다(@tanstack/ai-client에서 다시 내보냅니다).
import {
createChatClientOptions,
fetchServerSentEvents,
type InferChatMessages
} from "@tanstack/ai-client";
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- 서버 도구 타입
다음 단계
- 시작하기 - 기본 사항을 알아봅니다.
- Bring Your Own Key - 키를 저장하고
byok를useChat에 전달합니다. - 도구 가이드 - 동형 도구 시스템을 알아봅니다.
- 클라이언트 도구 - 클라이언트 측 도구를 알아봅니다.