본문으로 건너뛰기

@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-UI RunAgentInput.forwardedProps 필드로 서버에 전달합니다.
  • body? - 더 이상 권장되지 않습니다. 대신 forwardedProps를 사용합니다. 계속 작동하지만, 전송 시 값이 forwardedProps에 병합되고 이전 버전과의 호환성을 위해 레거시 data 필드에도 반영됩니다.
  • byok? - defineByok에서 가져오는 선택적 BYOK 키링입니다. 전송할 때마다 클라이언트가 확인된 provider를 준비하고 x-byok-* 요청 헤더를 추가합니다. 키는 본문에 절대 포함되지 않습니다.
  • byokProvider? - 이 채팅의 provider slug를 반환하는 선택적 함수입니다. slug를 반환하면 해당 키만 준비하여 전송합니다. 그렇지 않으면 forwardedProps, body 및 호출별 sendMessage body에서 병합된 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> | Headers
  • credentials?: RequestCredentials
  • signal?: AbortSignal
  • body?: Record<string, any>
  • fetchClient?: typeof globalThis.fetch

XHR 어댑터는 다음을 허용합니다.

  • headers?: Record<string, string> | Headers
  • withCredentials?: boolean
  • signal?: AbortSignal
  • body?: 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) - 현재 prompt provider에 대한 키를 영속화합니다. 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
},
});

다음 단계​