@tanstack/ai-client
채팅 상태와 스트리밍을 관리하는 프레임워크 독립적 헤드리스 클라이언트입니다.
설치
npm install @tanstack/ai-client
ChatClient
채팅 상태를 관리하는 기본 클라이언트 클래스입니다.
import {
ChatClient,
fetchServerSentEvents,
type UIMessage,
} from "@tanstack/ai-client";
import { myClientTool } from "./tools";
const client = new ChatClient({
connection: fetchServerSentEvents("/api/chat"),
initialMessages: [],
tools: [myClientTool],
onMessagesChange: (messages: UIMessage[]) => {
console.log("Messages updated:", messages);
},
});
// A new client is IDLE. Attach it when your view appears, detach when it goes.
client.attach();
수명 주기: attach() 및 detach()
한 페이지에 여러 채팅을 둘 수 있습니다. 브라우저는 하나의 origin에 약 6개의 연결만 허용하며, 실행을 추적하는 채팅은 실행이 지속되는 동안 연결 하나를 점유합니다. 모든 채팅이 연결을 유지하면 열린 뷰 몇 개만으로 모든 슬롯이 사용되고, 메시지를 로드하는 요청을 포함한 다른 모든 요청이 대기열에 들어갑니다.
따라서 연결은 뷰를 따릅니다. 새 클라이언트는 연결을 보유하지 않으며, attach()가
연결을 시작하고 detach()가 중지합니다.
프레임워크 패키지(@tanstack/ai-react, -vue, -solid, -svelte, -preact,
-angular)를 사용하면 훅이 이미 이 작업을 수행합니다. 뷰가 마운트될 때 연결하고
마운트 해제될 때 연결을 해제합니다. ChatClient를 직접 사용할 때만 이 메서드를
직접 호출합니다.
import { ChatClient, fetchServerSentEvents } from "@tanstack/ai-client";
const client = new ChatClient({
connection: fetchServerSentEvents("/api/chat"),
threadId: "thread-1",
persistence: true,
});
client.attach(); // start: rejoin a run in progress, and load the thread
client.detach(); // stop: drop the connection, keep messages and the run pointer
각 메서드는 다음을 보장합니다.
attach()는 여러 번 호출해도 안전합니다. 이미 연결된 클라이언트에 연결하면 아무 작업도 하지 않습니다.detach()는 대화 기록, 재개 포인터와 실행 ID를 유지합니다. 아무도 지켜보지 않는 동안에도 서버에서 실행은 계속되므로, 다시 연결하면 즉시 다시 표시하고 영속 로그에서 이어서 처리합니다.detach()는 실행을 종료하는stop()도 클라이언트를 종료하는dispose()도 아닙니다. 현재 어떤 뷰도 지켜보고 있지 않다는 뜻일 뿐입니다.- 영속성이 없는 채팅에는 재개 포인터와 저장된 스레드가 없으므로
attach()는 요청을 전혀 보내지 않습니다.
생성자 추적에서 마이그레이션
이전 버전은 생성자 내부에서 추적을 시작했습니다. ChatClient를 직접 생성한다면 뷰가
나타나는 곳에 client.attach()를 추가하고 뷰가 사라지는 곳에 client.detach()를
추가합니다. 프레임워크 훅 사용자는 변경할 필요가 없습니다.
생성자 옵션
connection- 스트리밍용 연결 어댑터initialMessages?- 초기 메시지 배열threadId?- 이 채팅의 유일한 식별자입니다. 영속성이 켜져 있으면 필수입니다. 생략하면 마운트 후 생성됩니다.forwardedProps?- 클라이언트가 제어하는 임의의 JSON을 AG-UIRunAgentInput.forwardedProps필드로 서버에 전달합니다.body?- 더 이상 권장되지 않습니다. 대신forwardedProps를 사용합니다. 계속 작동하지만, 전송 시 값이forwardedProps에 병합되고 이전 버전과의 호환성을 위해 레거시data필드에도 반영됩니다.byok?-defineByok에서 가져오는 선택적 BYOK 키링입니다. 전송할 때마다 클라이언트가 확인된 provider를 준비하고x-byok-*요청 헤더를 추가합니다. 키는 본문에 절대 포함되지 않습니다.byokProvider?- 이 채팅의 provider slug를 반환하는 선택적 함수입니다. slug를 반환하면 해당 키만 준비하여 전송합니다. 그렇지 않으면forwardedProps,body및 호출별sendMessagebody에서 병합된provider를 사용합니다. 나중 소스가 우선합니다. slug가 확인되지 않으면 저장된 모든 키를 첨부하는 대신 전송에서 예외가 발생합니다.context?- 클라이언트 도구 구현에 전달되는 타입 지정 클라이언트 로컬 런타임 컨텍스트입니다. 이 값은 서버로 직렬화되지 않습니다.tools?- 등록된.client()도구 구현입니다. 모델이 도구를 호출하면 클라이언트가 일치하는 도구를 자동으로 실행합니다.onResponse?- 응답을 수신했을 때 호출되는 콜백onChunk?- 스트림 청크를 수신했을 때 호출되는 콜백onFinish?- 응답이 완료되었을 때 호출되는 콜백onError?- 오류가 발생했을 때 호출되는 콜백onInterruptStateChange?- 인터럽트 상태가 변경되었을 때 호출되는 콜백입니다. 컨텍스트 소스는 복원된 상태에서는hydrate, 스트리밍 또는 클라이언트가 시작한 업데이트에서는live입니다.onMessagesChange?- 메시지가 변경되었을 때 호출되는 콜백onLoadingChange?- 로딩 상태가 변경되었을 때 호출되는 콜백onErrorChange?- 오류 상태가 변경되었을 때 호출되는 콜백streamProcessor?- 스트림 처리 구성
메서드
sendMessage(content: string | MultimodalContent, body?, sendOptions?)
사용자 메시지를 보내고 실행을 시작합니다.
MultimodalContent는 { content, id?, metadata? }입니다. 문자열 형식에는 메타데이터가 없습니다. 객체 형식을 전달하면 사용자 UIMessage에 metadata가 기록됩니다. TanStack은 tanstack 키를 기록하며, 다른 키는 가방의 최상위에 유지됩니다.
두 번째 인수는 이 요청의 forwardedProps에 병합되는 추가 JSON입니다. 세 번째 인수는 SendMessageOptions입니다. { whenBusy }는 이 전송의 대기열 정책을 재정의합니다. { body }도 추가 JSON입니다.
채팅 수준의 body, 위치 인수와 sendOptions.body는 얕게 병합됩니다. 키가 충돌하면 sendOptions.body가 우선합니다.
프레임워크 훅은 위치 인수인 본문 없이 sendMessage(content, sendOptions)를 노출합니다. 이때 { body }를 전달합니다. 훅의 body와 같은 방식으로 병합됩니다.
import { client } from "./client";
await client.sendMessage("Hello!");
await client.sendMessage({
content: "Show me failed logins",
metadata: { author: { id: "user-42", name: "Dana" } },
});
await client.sendMessage("Summarize the attached files", undefined, {
body: { attachmentIds: ["att_1", "att_2"] },
whenBusy: "interrupt",
});
서버는 chatParamsFromRequest에서 병합된 JSON을 읽습니다.
import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
export async function POST(request: Request) {
const { messages, forwardedProps } = await chatParamsFromRequest(request);
const stream = chat({
adapter: openaiText("gpt-5.5"),
messages,
});
if (
forwardedProps &&
typeof forwardedProps === "object" &&
"attachmentIds" in forwardedProps
) {
const { attachmentIds } = forwardedProps;
if (Array.isArray(attachmentIds) && attachmentIds.length > 0) {
// Look up the uploads. Do not add them to `messages`.
}
}
return toServerSentEventsResponse(stream);
}
append(message: ModelMessage | UIMessage)
대화에 메시지를 추가합니다. UIMessage를 전달하면 append가 uiMessage.metadata를 저장된 메시지에 복사합니다.
이 호출이 스트림을 시작하면 전체 HTTP 응답이 처리된 후 append()가 완료됩니다. 이미 스트림이 진행 중이면 이 호출은 전송을 대기열에 넣으며, 반환된 프로미스가 해당 대기 중인 응답이 처리되기 전에 완료될 수 있습니다. 해당 응답에서 에이전트 루프가 계속될 때는 RUN_FINISHED에서 finishReason: "tool_calls"가 대기를 끝내지 않습니다.
import { client } from "./client";
import type { UIMessage } from "@tanstack/ai-client";
await client.append({
role: "user",
content: "Additional context",
});
const stamped: UIMessage = {
id: "user-1",
role: "user",
parts: [{ type: "text", content: "Show me failed logins" }],
metadata: { author: { id: "user-42", name: "Dana" } },
};
await client.append(stamped);
reload()
마지막 어시스턴트 메시지를 다시 로드합니다.
import { client } from "./client";
await client.reload();
attach()
추적을 시작합니다. 아직 진행 중인 실행에 다시 참여하며, 서버 권위 모드에서는 저장된 스레드를 로드합니다. 멱등적입니다. 자세한 내용은 수명 주기를 참조하세요.
detach()
추적을 중지하고 연결을 끊습니다. 메시지, 실행 포인터와 실행 ID를 유지하므로 이후
attach()가 중단된 지점부터 계속합니다. 자세한 내용은
수명 주기를 참조하세요.
stop()
현재 응답 생성을 중지하고 진행 중인 요청을 중단합니다. 해당 턴의 보류 중인
클라이언트 도구 결과는 무시됩니다. 이후 해당 턴에 대한 addToolResult() 호출도
무시됩니다. 재개를 시작하지 않습니다.
import { client } from "./client";
client.stop();
clear()
모든 메시지를 삭제합니다.
import { client } from "./client";
client.clear();
setMessagesManually(messages: UIMessage[])
메시지 배열을 수동으로 설정합니다.
import { client } from "./client";
import type { UIMessage } from "@tanstack/ai-client";
const newMessages: UIMessage[] = [];
client.setMessagesManually([...newMessages]);
addToolResult(result)
클라이언트 측 도구 실행 결과를 추가합니다. stop() 후에는 중지된 턴의 결과를
무시합니다. 새 사용자 메시지가 새 턴을 시작하면 addToolResult()가 해당 턴에
적용됩니다.
import { client } from "./client";
await client.addToolResult({
toolCallId: "call_123",
tool: "toolName",
output: { result: "..." },
state: "output-available",
});
addToolApprovalResponse(response)
도구 승인 요청에 응답합니다.
import { client } from "./client";
await client.addToolApprovalResponse({
id: "approval_123",
approved: true,
});
속성
messages: UIMessage[]- 현재 메시지isLoading: boolean- 응답이 생성 중인지 여부error: Error | undefined- 현재 오류(있는 경우)
연결 어댑터
전체 전송 과정은 연결 어댑터를 참조합니다. React Native 및 Expo는 빠른 시작: React Native를 참조합니다.
fetchServerSentEvents(url, options?)
SSE 연결 어댑터를 생성합니다.
import { fetchServerSentEvents } from "@tanstack/ai-client";
const adapter = fetchServerSentEvents("/api/chat", {
headers: {
Authorization: "Bearer token",
},
});
fetchHttpStream(url, options?)
줄바꿈으로 구분된 JSON HTTP 스트림 연결 어댑터를 생성합니다. 서버에서
toHttpResponse()와 함께 사용합니다.
import { fetchHttpStream } from "@tanstack/ai-client";
const adapter = fetchHttpStream("/api/chat");
fetchHttpStream()에는 스트리밍 fetch, Response.body.getReader() 및
TextDecoder를 지원하는 런타임이 필요합니다. 런타임이 점진적 응답 본문을 노출할 수
없으면 UnsupportedResponseStreamError를 발생시키므로 React Native 또는 Expo에서는
XHR 어댑터를 사용합니다.
xhrHttpStream(url, options?)
XMLHttpRequest 기반 줄바꿈 구분 JSON 스트림 어댑터를 생성합니다. React Native 및
Expo 채팅 화면에서 권장되는 기본값입니다. 서버에서 toHttpResponse()와 함께
사용합니다.
import { xhrHttpStream } from "@tanstack/ai-client";
const adapter = xhrHttpStream("http://192.168.1.10:8787/chat/http", {
headers: { Authorization: "Bearer token" },
withCredentials: true,
});
xhrServerSentEvents(url, options?)
스트리밍 fetch보다 XHR 진행 이벤트가 더 안정적인 런타임을 위한
XMLHttpRequest 기반 SSE 어댑터를 생성합니다. 서버에서
toServerSentEventsResponse()와 함께 사용합니다.
import { xhrServerSentEvents } from "@tanstack/ai-client";
const adapter = xhrServerSentEvents("http://192.168.1.10:8787/chat/sse");
어댑터 옵션
Fetch 어댑터는 다음을 허용합니다.
headers?: Record<string, string> | Headerscredentials?: RequestCredentialssignal?: AbortSignalbody?: Record<string, any>fetchClient?: typeof globalThis.fetch
XHR 어댑터는 다음을 허용합니다.
headers?: Record<string, string> | HeaderswithCredentials?: booleansignal?: AbortSignalbody?: Record<string, any>xhrFactory?: () => XMLHttpRequest
body는 AG-UI forwardedProps 페이로드에 병합됩니다. 클라이언트의
클라이언트의 forwardedProps와 메시지별 sendMessage body(위치 인수 또는
sendOptions.body)가 정적 어댑터 body 값을 재정의합니다.
스트림 오류
UnsupportedResponseStreamError-Response.body,Response.body.getReader()또는TextDecoder가 없을 때 fetch 기반 어댑터가 발생시킵니다.StreamTruncatedError- SSE 또는 NDJSON 스트림이 종료되지 않은 후행 데이터와 함께 끝날 때 발생합니다. 일반적으로 서버, 프록시 또는 네트워크가 줄 중간에서 연결을 끊었기 때문입니다.
stream(connectFn)
사용자 지정 연결 어댑터를 생성합니다.
import { stream } from "@tanstack/ai-client";
const adapter = stream(async (messages, data, signal) => {
// `data` here carries the merged forwardedProps. The fetch-based
// adapters serialize it as the AG-UI `RunAgentInput.forwardedProps`
// field on the wire (with a backward-compat `data` mirror).
const response = await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ messages, forwardedProps: data }),
signal,
});
return processStream(response);
});
헬퍼 함수
clientTools(...tools)
선택 사항입니다. 일반 배열인 tools: [tool1, tool2]만으로도 래퍼나 as const 없이 도구 이름, 입력과 출력을 이미 좁힐 수 있습니다. clientTools()는 동일한 캡처를 명시적으로 수행하는 항등 헬퍼이며, 훅/옵션 호출 외부에서 공유 가능한 재사용 도구 튜플을 만들 때만 사용합니다.
import {
clientTools,
createChatClientOptions,
fetchServerSentEvents,
type UIMessage,
} from "@tanstack/ai-client";
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
const messages: UIMessage[] = [];
const myTool1 = toolDefinition({
name: "myTool1",
description: "First tool",
inputSchema: z.object({ query: z.string() }),
outputSchema: z.object({ result: z.string() }),
});
const myTool2 = toolDefinition({
name: "myTool2",
description: "Second tool",
inputSchema: z.object({ query: z.string() }),
outputSchema: z.object({ result: z.string() }),
});
// Create client implementations
const tool1Client = myTool1.client((input) => {
// Implementation
return { result: input.query };
});
const tool2Client = myTool2.client((input) => {
// Implementation
return { result: input.query };
});
// The explicit-capture form (equivalent to `[tool1Client, tool2Client]`).
const tools = clientTools(tool1Client, tool2Client);
// Now when you use these tools in chat options:
const chatOptions = createChatClientOptions({
connection: fetchServerSentEvents("/api/chat"),
tools, // Fully typed with literal tool names
});
// In your component:
messages.forEach((message) => {
message.parts.forEach((part) => {
if (part.type === "tool-call" && part.name === "myTool1") {
// ✅ TypeScript knows part.name is literally "myTool1"
// ✅ part.input is typed from myTool1's input schema
// ✅ part.output is typed from myTool1's output schema
}
});
});
createChatClientOptions(options)
적절한 타입 추론을 적용한 타입 지정 채팅 클라이언트 옵션을 생성하는 헬퍼 함수입니다.
import {
createChatClientOptions,
fetchServerSentEvents,
type InferChatMessages,
} from "@tanstack/ai-client";
import { tool1, tool2 } from "./tools";
const tools = [tool1, tool2];
const chatOptions = createChatClientOptions({
connection: fetchServerSentEvents("/api/chat"),
tools,
});
// Use InferChatMessages to extract message types
type ChatMessages = InferChatMessages<typeof chatOptions>;
createChatClientOptions는 타입이 지정된 클라이언트 런타임 컨텍스트도 유지합니다.
import {
createChatClientOptions,
fetchServerSentEvents,
} from "@tanstack/ai-client";
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
type ClientContext = {
activeProjectId: string;
};
const projectTool = toolDefinition({
name: "projectAction",
description: "Run a project action",
inputSchema: z.object({ action: z.string() }),
outputSchema: z.object({ ok: z.boolean() }),
});
const tool = projectTool.client<ClientContext>((input, ctx: { context: ClientContext }) => {
console.log(ctx.context.activeProjectId, input.action);
return { ok: true };
});
const chatOptions = createChatClientOptions({
connection: fetchServerSentEvents("/api/chat"),
tools: [tool],
context: {
activeProjectId: "project_123",
},
});
클라이언트 런타임 컨텍스트는 클라이언트 인스턴스에 로컬입니다. 직렬화 가능한 값을 클라이언트에서 서버로 명시적으로 전달하려면 forwardedProps를 사용한 다음, 해당 값을 검증하고 서버의 chat({ context })로 매핑합니다.
defineByok
헤드리스 BYOK 키링을 위한 팩토리입니다. @tanstack/ai-client/byok에서 가져옵니다. 인스턴스를 ChatClient, useChat 또는 생성 훅에 전달합니다. 전체 클라이언트 및 릴레이 안내는 Bring Your Own Key를 참조하세요.
import { defineByok, defaultByokStorage } from "@tanstack/ai-client/byok";
export const byok = defineByok({
storage: defaultByokStorage(),
});
팩토리 옵션
storage?-KeyringStorage구현입니다. 기본값은memoryStorage()입니다(세션에서만 유지되며 저장되지 않음).
메서드
update(provider, key)- provider slug([a-z][a-z0-9-]{0,63})에 대한 키를 영속화한 다음 스냅샷을 업데이트합니다. ID가 slug가 아니거나 키가 비어 있거나 저장에 실패하면 예외를 발생시킵니다.update(key)- 현재promptprovider에 대한 키를 영속화합니다.prompt가 null이면 예외를 발생시킵니다.clear(provider?)- 삭제를 영속화한 다음 하나의 키를 제거하거나,provider를 생략하면 모든 키를 제거합니다.unlock()- 잠금 해제 가능한 저장소(passkey)를 복호화합니다. 메모리 저장소에서는 아무 작업도 하지 않습니다.headers(provider)- 해당 slug의x-byok-*헤더를 반환합니다. slug가 확인되지 않으면 채팅 및 생성 클라이언트가 예외를 발생시키며 저장된 모든 키를 보내지 않습니다.prepare(provider?)- 하이드레이션을 기다린 다음 필요한 경우 잠금을 해제합니다.provider가 설정되어 있고 키가 비어 있으며 서버에 적용 범위가 없으면ByokBlockedError를 발생시키고prompt를 설정합니다.ready()- 생성자 하이드레이션(peek/load)이 완료되면 해결됩니다.setServerCoverage(flags)-true이면 브라우저에 키가 없어도 전송을 차단하지 않습니다(릴레이가 환경 변수에서 채울 수 있음).false이면 기본값인 차단으로 복원합니다. 레코드는 slug별 플래그를 병합합니다.request(provider, reason)-prompt를{ provider, reason }으로 설정합니다(missing|locked|invalid).getSnapshot()- 현재ByokSnapshot을 반환합니다.subscribe(listener)- 변경될 때마다listener를 호출합니다. 반환값은 구독 취소 함수입니다.keys()- 원시 키링의 복사본을 반환합니다. 이를 UI에 렌더링하지 마세요.
스냅샷
getSnapshot()(및 useByok 같은 framework reader)는 다음을 반환합니다:
type ByokSnapshot = {
status: Partial<Record<string, KeyStatus>>;
locked: boolean;
prompt: { provider: string; reason: "missing" | "locked" | "invalid" } | null;
storageError: string | null;
};
type KeyStatus =
| { state: "empty" }
| { state: "set"; masked: string }
| { state: "locked"; masked: string }
| { state: "validating"; masked: string }
| { state: "valid"; masked: string }
| { state: "invalid"; masked: string }
| { state: "error"; masked: string; message: string };
status는 희소합니다. 키, 잠금 또는 검증 결과가 있는 slug만 나타납니다. 항목이 없으면 키가 없는 것입니다. masked는 키의 마지막 네 문자(maskKey)입니다. "masked" in status로 읽으세요. { state: "empty" }에는 masked가 없습니다. peek/load가 실패하면 storageError가 설정됩니다. 스냅샷에는 원시 키가 절대 포함되지 않습니다.
저장소
defaultByokStorage(options?)- 보안 컨텍스트에서 WebAuthn을 사용할 수 있으면 패스키로 암호화된 IndexedDB를 사용합니다. 그렇지 않으면 경고와 함께memoryStorage()를 사용합니다. WebAuthn 지원은 PRF와 같지 않으므로 인증자가 PRF를 지원하지 않으면 최초 저장도 여전히 예외를 발생시킵니다.memoryStorage()-ByokClient인스턴스의 메모리에 저장합니다. 이 백엔드는 영속화하지 않습니다. 클라이언트가 삭제되거나 페이지가 다시 로드되면 키가 사라집니다.passkeyStorage(options?)- WebAuthn 패스키로 키링을 암호화합니다. 인증자가 PRF를 지원하지 않으면 최초 저장 시 예외를 발생시킵니다.KeyringStorage-{ id, label, persistent, unlockable?, peek?, load, save, clear }
이 라이브러리는 대화상자를 제공하지 않습니다. 자체 UI에서 byok.update(provider, value)를 호출합니다.
타입
UIMessage
interface UIMessage {
id: string;
role: "system" | "user" | "assistant";
parts: MessagePart[];
name?: string;
createdAt?: Date;
metadata?: Record<string, any>;
}
metadata는 선택 사항인 AG-UI 가방(Record<string, any>)입니다. TanStack은 tanstack 키를 기록하며, 다른 키는 최상위에 유지됩니다.
MessagePart
type MessagePart = TextPart | ThinkingPart | ToolCallPart | ToolResultPart;
TextPart
interface TextPart {
type: "text";
content: string;
}
ThinkingPart
interface ThinkingPart {
type: "thinking";
content: string;
}
사고 파트는 모델의 내부 추론 과정을 나타냅니다. 일반적으로 접을 수 있는 형식으로 표시되며 응답 텍스트가 나타나면 자동으로 접힙니다. 사고 파트는 UI 전용이며 후속 요청에서 모델로 다시 전송되지 않습니다.
참고: 사고 파트는 추론/사고를 지원하는 모델을 사용할 때만 사용할 수 있습니다(예: 사고가 활성화된 Anthropic Claude, 추론이 활성화된 OpenAI GPT-5).
ToolCallPart
interface ToolCallPart {
type: "tool-call";
id: string;
name: string;
arguments: string; // JSON string (may be incomplete during streaming)
input?: any; // Parsed tool input (typed from tool's inputSchema)
state: ToolCallState;
approval?: ApprovalRequest; // only on tools declared `needsApproval: true`
output?: any; // Tool execution output (typed from tool's outputSchema)
}
타입이 지정된 tools 배열을 전달하면(일반 배열도 작동하며 clientTools()는 선택 사항임) input 및 output 필드가 도구의 Zod 스키마를 기반으로 자동으로 타입 지정되고, name은 타입 좁히기를 지원하는 판별된 유니온이 됩니다. approval 필드는 needsApproval: true로 선언된 도구의 파트에만 있습니다. 액세스하기 전에 part.name으로 좁히거나('approval' in part로 보호할 수도 있음) 확인하세요.
ToolResultPart
interface ToolResultPart {
type: "tool-result";
id?: string;
name?: string;
toolCallId: string;
content: string | ContentPart[];
state: ToolResultState;
error?: string;
metadata?: Record<string, unknown>;
createdAt?: Date;
}
ToolCallState
type ToolCallState =
| "awaiting-input"
| "input-streaming"
| "input-complete"
| "approval-requested"
| "approval-responded"
| "complete";
ToolResultState
type ToolResultState =
| "streaming"
| "complete"
| "error";
스트림 처리
청크 전략을 사용해 스트림 처리를 구성합니다.
import {
ChatClient,
ImmediateStrategy,
fetchServerSentEvents,
} from "@tanstack/ai-client";
const client = new ChatClient({
connection: fetchServerSentEvents("/api/chat"),
streamProcessor: {
chunkStrategy: new ImmediateStrategy(), // Emit every chunk
},
});
다음 단계
- 시작하기 - 기본 사항을 알아봅니다.
- Bring Your Own Key - 브라우저에 키를 저장하고
x-byok-*헤더를 전송합니다. - 연결 어댑터 - 어댑터에 대해 알아봅니다.
- @tanstack/ai-react API - React 훅 래퍼