Packages
React
Client transports and hooks from @anvia/react.
Import from @anvia/react.
Types
type EventStreamFormat = "jsonl" | "sse";
type TransportOptions = {
signal?: AbortSignal;
headers?: HeadersInit;
};
type UIMessageRole = "system" | "user" | "assistant" | "tool";
type UIError = {
name?: string;
message: string;
};
type UIMessage = {
id: string;
role: UIMessageRole;
parts: UIMessagePart[];
metadata?: unknown;
};
type UIAttachment = {
id: string;
type: "image" | "document" | "file";
name?: string;
mediaType?: string;
url?: string;
data?: string;
text?: string;
detail?: "auto" | "low" | "high";
metadata?: unknown;
};
type CreateUIAttachment = Omit<UIAttachment, "id"> & {
id?: string;
};
type UIMessagePart =
| { id: string; type: "text"; text: string }
| { id: string; type: "reasoning"; text: string; reasoningId?: string }
| {
id: string;
type: "tool";
toolName: string;
toolCallId: string;
callId?: string;
state: "input-streaming" | "input-available" | "output-available" | "error";
input?: unknown;
output?: unknown;
error?: UIError;
}
| { id: string; type: "data"; name: string; data: unknown }
| { id: string; type: "attachment"; attachment: UIAttachment }
| { id: string; type: "error"; error: UIError };
type UIStreamRequest = {
messages: Message[];
stream: true;
metadata?: unknown;
};
type UIStreamEvent =
| { type: "message_start"; message: UIMessage }
| { type: "text_delta"; messageId: string; partId: string; delta: string }
| { type: "reasoning_delta"; messageId: string; partId: string; delta: string }
| {
type: "tool_update";
messageId: string;
partId: string;
part: Extract<UIMessagePart, { type: "tool" }>;
}
| { type: "message_end"; messageId: string; usage?: unknown; metadata?: unknown }
| { type: "error"; error: UIError };
type SendMessageInput =
| string
| UIMessage
| {
id?: string;
text?: string;
attachments?: CreateUIAttachment[];
metadata?: UIMessage["metadata"];
};
type ChatSuggestion = {
id: string;
prompt: string;
label?: string;
metadata?: UIMessage["metadata"];
};
type CreateChatRequestArgs = {
messages: UIMessage[];
uiMessages: UIMessage[];
coreMessages: Message[];
resume?: ChatResumeCursor;
};
type UseChatStatus = "idle" | "streaming" | "error";
type ChatResumeCursor = {
streamId: string;
after: number;
};
type ChatResumeStorage = "sessionStorage" | "localStorage" | Storage;
type ChatResumeOptions = {
key: string;
storage?: ChatResumeStorage;
auto?: boolean;
};
type ChatResumeState = {
version: 1;
streamId: string;
lastEventId: number;
messages: UIMessage[];
};
type ResumableStreamEnvelope<TEvent> =
| { type: "stream_start"; streamId: string; eventId: 0 }
| { type: "stream_event"; streamId: string; eventId: number; event: TEvent }
| {
type: "stream_end";
streamId: string;
eventId: number;
status: "running" | "completed" | "error" | "missing";
};
type ToolApprovalStatus = "pending" | "approved" | "rejected" | "timed_out";
type ToolApproval = {
id: string;
runId?: string;
agentId?: string;
sessionId?: string;
toolName: string;
callId?: string;
internalCallId?: string;
args?: string;
status: ToolApprovalStatus;
requestedAt?: string;
resolvedAt?: string;
reason?: string;
};
type ToolQuestionStatus = "pending" | "answered";
type ToolQuestionChoice = {
label: string;
value: string;
};
type ToolQuestionPrompt = {
id: string;
question: string;
choices: ToolQuestionChoice[];
};
type ToolQuestionAnswer = {
questionId: string;
answer: string;
choice?: string;
custom?: boolean;
};
type ToolQuestion = {
id: string;
runId?: string;
agentId?: string;
sessionId?: string;
toolName: string;
callId?: string;
internalCallId?: string;
args?: string;
questions: ToolQuestionPrompt[];
status: ToolQuestionStatus;
requestedAt?: string;
answeredAt?: string;
answers?: ToolQuestionAnswer[];
};
type ToolApprovalDecisionInput = {
approvalId: string;
approved: boolean;
reason?: string;
approval?: ToolApproval;
};
type ToolQuestionAnswerInput = {
questionId: string;
answers: ToolQuestionAnswer[];
question?: ToolQuestion;
};
type HumanInputOptions<TEvent = unknown> = {
endpoint?: string | URL;
fetch?: typeof fetch;
eventToApproval?: (event: TEvent) => ToolApproval | undefined;
eventToQuestion?: (event: TEvent) => ToolQuestion | undefined;
decideApproval?: (decision: ToolApprovalDecisionInput) => Promise<ToolApproval | undefined>;
answerQuestion?: (answer: ToolQuestionAnswerInput) => Promise<ToolQuestion | undefined>;
};
type HumanInputState = {
approvals: { all: ToolApproval[]; pending: ToolApproval[] };
questions: { all: ToolQuestion[]; pending: ToolQuestion[] };
};
type SetMessages = (
messages: UIMessage[] | ((messages: UIMessage[]) => UIMessage[]),
) => void;
defaultEventToApproval
function defaultEventToApproval<TEvent>(event: TEvent): ToolApproval | undefined;
Purpose: map Studio tool_approval_request and tool_approval_result stream events into tracked ToolApproval records for useChat human-input state.
defaultEventToQuestion
function defaultEventToQuestion<TEvent>(event: TEvent): ToolQuestion | undefined;
Purpose: map Studio tool_question_request and tool_question_result stream events into tracked ToolQuestion records for useChat human-input state.
defaultDecideApproval
function defaultDecideApproval(
input: ToolApprovalDecisionInput,
options: { endpoint?: string | URL; fetch?: typeof fetch },
): Promise<ToolApproval | undefined>;
Purpose: submit a tool approval decision to the default human-input endpoint path, approvals/:approvalId/decision, and return an updated approval when the response contains JSON.
defaultAnswerQuestion
function defaultAnswerQuestion(
input: ToolQuestionAnswerInput,
options: { endpoint?: string | URL; fetch?: typeof fetch },
): Promise<ToolQuestion | undefined>;
Purpose: submit tool-question answers to the default human-input endpoint path, questions/:questionId/answer, and return an updated question when the response contains JSON.
EventTransport
type EventTransport<TRequest, TEvent> = {
send(request: TRequest, options?: TransportOptions): AsyncIterable<TEvent>;
};
Purpose: common boundary for JSONL, SSE, WebSocket, local, and custom transports.
readJsonlStream
function readJsonlStream<TEvent>(stream: ReadableStream<Uint8Array>): AsyncIterable<TEvent>;
Purpose: parse newline-delimited JSON from a web stream.
readSseStream
function readSseStream<TEvent>(stream: ReadableStream<Uint8Array>): AsyncIterable<TEvent>;
Purpose: parse Server-Sent Events whose data: payload is JSON.
fetchEventStream
type FetchEventStreamOptions = RequestInit & {
format?: "jsonl" | "sse";
fetch?: typeof fetch;
};
function fetchEventStream<TEvent>(
input: string | URL | Request,
options?: FetchEventStreamOptions,
): AsyncIterable<TEvent>;
Purpose: fetch and parse a streaming response as an async iterable.
createFetchTransport
type CreateFetchTransportOptions<TRequest, TEvent> = {
endpoint: string | URL | ((request: TRequest) => string | URL);
method?: string;
format?: "jsonl" | "sse";
fetch?: typeof fetch;
headers?: HeadersInit | ((request: TRequest) => HeadersInit | Promise<HeadersInit>);
body?: (request: TRequest) => BodyInit | null | undefined | Promise<BodyInit | null | undefined>;
init?: Omit<RequestInit, "body" | "headers" | "method" | "signal">;
mapEvent?: (event: unknown) => TEvent;
};
function createFetchTransport<TRequest, TEvent>(
options: CreateFetchTransportOptions<TRequest, TEvent>,
): EventTransport<TRequest, TEvent>;
Purpose: create a POST JSON transport by default while allowing custom headers, bodies, endpoints, init options, and event mapping. GET and HEAD transports do not add an implicit JSON body or content-type; pass body explicitly if a custom transport needs one.
createChatTransport
function createChatTransport<TRequest, TEvent>(
options: CreateFetchTransportOptions<TRequest, TEvent>,
): EventTransport<TRequest, TEvent>;
Purpose: named chat transport helper built on the fetch transport.
initialMessagesFromMemory
function initialMessagesFromMemory(messages: Message[]): UIMessage[];
Purpose: convert stored Anvia memory messages into the UIMessage[] shape accepted by
useChat({ initialMessages }).
The helper adapts core message data for React state. It does not depend on a specific database schema.
useChat
type UseChatOptions<TRequest = UIStreamRequest, TEvent = UIStreamEvent> = {
transport?: EventTransport<TRequest, TEvent>;
endpoint?: string | URL;
format?: "jsonl" | "sse";
initialMessages?: UIMessage[];
resume?: ChatResumeOptions;
createRequest?: (args: CreateChatRequestArgs) => TRequest;
eventToUIEvent?: (event: TEvent) => UIStreamEvent | undefined;
eventToDelta?: (event: TEvent) => string | undefined;
eventToFinal?: (event: TEvent) => string | undefined;
humanInput?: HumanInputOptions<TEvent>;
suggestions?: ChatSuggestion[];
onEvent?: (event: TEvent) => void;
onError?: (error: unknown) => void;
};
type UseChatResult<TEvent = UIStreamEvent> = {
messages: UIMessage[];
events: TEvent[];
suggestions?: ChatSuggestion[];
setMessages: SetMessages;
sendMessage(input: SendMessageInput): Promise<void>;
send(input?: string): Promise<void>;
regenerate(): Promise<void>;
stop(): void;
reset(messages?: UIMessage[]): void;
status: UseChatStatus;
error: unknown;
text: string;
streamId?: string;
isResuming: boolean;
resume(): Promise<void>;
humanInput: HumanInputState;
decidingApprovals: Set<string>;
answeringQuestions: Set<string>;
approveTool(approvalId: string, reason?: string): Promise<void>;
rejectTool(approvalId: string, reason?: string): Promise<void>;
answerToolQuestion(questionId: string, answers: ToolQuestionAnswer[]): Promise<void>;
};
function useChat<TRequest = UIStreamRequest, TEvent = UIStreamEvent>(options?: {
transport?: EventTransport<TRequest, TEvent>;
endpoint?: string | URL;
format?: "jsonl" | "sse";
initialMessages?: UIMessage[];
resume?: ChatResumeOptions;
createRequest?: (args: CreateChatRequestArgs) => TRequest;
eventToUIEvent?: (event: TEvent) => UIStreamEvent | undefined;
eventToDelta?: (event: TEvent) => string | undefined;
eventToFinal?: (event: TEvent) => string | undefined;
humanInput?: HumanInputOptions<TEvent>;
suggestions?: ChatSuggestion[];
onEvent?: (event: TEvent) => void;
onError?: (error: unknown) => void;
}): UseChatResult<TEvent>;
Purpose: React chat state machine that sends UIStreamRequest by default and accumulates response events into UIMessage[].
Passing endpoint creates a default JSONL fetch transport. Passing transport makes the hook independent of HTTP. sendMessage(...) appends a user message, keeps the full UI message history in state, and sends the converted core message history. Custom request factories receive the UI array as messages for compatibility and the converted array as coreMessages. The hook applies raw CompletionStreamEvent, raw AgentStreamEvent, or UIStreamEvent records as the assistant response arrives. Tool-call deltas produce an input-streaming tool part automatically; append fragments are concatenated and replacement snapshots overwrite the provisional input.
Passing humanInput tracks streamed tool approval and question events in humanInput.approvals and humanInput.questions. The action helpers submit approval decisions and question answers through custom handlers or the default /approvals/:id/decision and /questions/:id/answer endpoint paths.
Passing resume: { key } stores the active streamId, last event cursor, and current messages in
sessionStorage by default. On mount, useChat sends the same endpoint a POST body with
resume: { streamId, after }, unwraps ResumableStreamEnvelope records, replays missed events, and
continues tailing live events until stream_end. Custom createRequest callbacks receive
args.resume and should forward it when using resumable server routes.
useSmoothStreamText
type StreamSmoothingLifecycle = {
isStreaming: boolean;
resetKey: string | number;
flushImmediately?: boolean;
};
type UseSmoothStreamTextOptions = StreamSmoothingLifecycle;
type UseSmoothStreamTextResult = {
text: string;
isAnimating: boolean;
isDraining: boolean;
flush(): void;
};
function useSmoothStreamText(
content: string,
options: UseSmoothStreamTextOptions,
): UseSmoothStreamTextResult;
Purpose: buffer and pace append-only display text with elapsed-time requestAnimationFrame
updates. It does not consume transport events, update UIMessage[], or replace useChat.
Keep the hook mounted when isStreaming changes to false; it enters a bounded completion drain
instead of snapping to the target. Change resetKey when loading a different conversation so its
existing content seeds immediately. flushImmediately and flush() reveal the complete target,
including for terminal errors, hidden tabs, and non-animation environments. Reveals respect
grapheme boundaries and the internal backlog is bounded.
useSmoothStreamItems
type SmoothStreamItemAdapter<T> = {
getKey(item: T): string;
getText(item: T): string | undefined;
withText(item: T, text: string): T;
};
type UseSmoothStreamItemsOptions<T> = StreamSmoothingLifecycle & {
adapter: SmoothStreamItemAdapter<T>;
};
type UseSmoothStreamItemsResult<T> = {
items: readonly T[];
isAnimating: boolean;
isDraining: boolean;
liveItemKey: string | null;
flush(): void;
};
function useSmoothStreamItems<T>(
items: readonly T[],
options: UseSmoothStreamItemsOptions<T>,
): UseSmoothStreamItemsResult<T>;
Purpose: apply the same smoothing state machine to ordered heterogeneous items. Return text only
for items that should be paced; return undefined for opaque items such as tools. Opaque items are
held behind preceding buffered text, while updates to an already visible opaque item are applied
immediately. liveItemKey identifies the current growing item for a tail-only visual treatment.
useCompletion
type UseCompletionStatus = "idle" | "streaming" | "error";
type UseCompletionRequestArgs = {
messages: UIMessage[];
uiMessages: UIMessage[];
coreMessages: Message[];
};
type UseCompletionOptions<TRequest = UIStreamRequest, TEvent = UIStreamEvent> = {
transport?: EventTransport<TRequest, TEvent>;
endpoint?: string | URL;
format?: "jsonl" | "sse";
initialMessages?: UIMessage[];
initialCompletion?: string;
createRequest?: (args: UseCompletionRequestArgs) => TRequest;
eventToUIEvent?: (event: TEvent) => UIStreamEvent | undefined;
eventToDelta?: (event: TEvent) => string | undefined;
eventToFinal?: (event: TEvent) => string | undefined;
onEvent?: (event: TEvent) => void;
onError?: (error: unknown) => void;
};
type UseCompletionResult<TEvent = UIStreamEvent> = {
messages: UIMessage[];
completion: string;
input: string;
setInput(input: string): void;
complete(prompt?: string): Promise<void>;
stop(): void;
reset(messagesOrCompletion?: UIMessage[] | string): void;
status: UseCompletionStatus;
error: unknown;
events: TEvent[];
};
function useCompletion<TRequest = UIStreamRequest, TEvent = UIStreamEvent>(
options?: UseCompletionOptions<TRequest, TEvent>,
): UseCompletionResult<TEvent>;
Purpose: React hook for text completion streaming that also exposes the underlying assistant messages.
By default, complete(prompt) appends one user UIMessage to the current messages, converts the full UI history to core messages, sends { messages, stream: true }, and consumes raw CompletionStreamEvent, raw AgentStreamEvent, or UIStreamEvent records. Custom request factories receive the UI array as messages for compatibility and the converted array as coreMessages. completion is a convenience string derived from the latest assistant text parts.
createDirectTransport
function createDirectTransport<TRequest, TEvent>(
handler: (request: TRequest) => AsyncIterable<TEvent>,
): EventTransport<TRequest, TEvent>;
Purpose: in-process transport that calls a handler function directly, bypassing HTTP. Works with both useChat and useCompletion. Useful for SSR, testing, and single-process applications.
EventStreamHttpError
class EventStreamHttpError extends Error {
readonly response: Response;
readonly body: string;
}
Purpose: thrown by fetchEventStream(...) when the HTTP response is not ok.
For workflow guidance, see Client Transports.
