React

Client transports and hooks from @anvia/react.

Import from @anvia/react.

Types

type EventStreamFormat = "jsonl" | "sse";

type TransportOptions = {
  signal?: AbortSignal;
  headers?: HeadersInit;
};

type ChatRole = "system" | "user" | "assistant" | "tool";

type ChatMessage = {
  id: string;
  role: ChatRole;
  content: string;
  metadata?: unknown;
};

type DefaultChatRequest = {
  message: string;
  history: ChatMessage[];
  stream: true;
};

type UseChatStatus = "idle" | "streaming" | "error";

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[] };
};

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";
  headers?: HeadersInit | ((request: TRequest) => HeadersInit | Promise<HeadersInit>);
  body?: (request: TRequest) => BodyInit | null | undefined | Promise<BodyInit | null | undefined>;
  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, and event mapping.

createChatTransport

function createChatTransport<TRequest, TEvent>(
  options: CreateFetchTransportOptions<TRequest, TEvent>,
): EventTransport<TRequest, TEvent>;

Purpose: named chat transport helper built on the fetch transport.

useChat

type UseChatOptions<TRequest, TEvent, TMessage extends ChatMessage = ChatMessage> = {
  transport?: EventTransport<TRequest, TEvent>;
  endpoint?: string | URL;
  format?: "jsonl" | "sse";
  initialMessages?: TMessage[];
  createRequest?: (input: string, messages: TMessage[]) => TRequest;
  eventToDelta?: (event: TEvent) => string | undefined;
  eventToFinal?: (event: TEvent) => string | undefined;
  humanInput?: HumanInputOptions<TEvent>;
  onEvent?: (event: TEvent) => void;
  onError?: (error: unknown) => void;
};

type UseChatResult<TEvent, TMessage extends ChatMessage = ChatMessage> = {
  messages: TMessage[];
  events: TEvent[];
  input: string;
  setInput(input: string): void;
  send(input?: string): Promise<void>;
  stop(): void;
  reset(messages?: TMessage[]): void;
  status: UseChatStatus;
  error: unknown;
  text: string;
  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, TEvent>(options?: {
  transport?: EventTransport<TRequest, TEvent>;
  endpoint?: string | URL;
  format?: "jsonl" | "sse";
  initialMessages?: ChatMessage[];
  createRequest?: (input: string, messages: ChatMessage[]) => TRequest;
  eventToDelta?: (event: TEvent) => string | undefined;
  eventToFinal?: (event: TEvent) => string | undefined;
  humanInput?: HumanInputOptions<TEvent>;
  onEvent?: (event: TEvent) => void;
  onError?: (error: unknown) => void;
}): UseChatResult<TEvent>;

Purpose: React chat state machine that consumes events from any EventTransport.

Passing endpoint creates a default JSONL fetch transport. Passing transport makes the hook independent of HTTP. humanInput tracks Studio-compatible tool approval and ask_question events, or custom mapped events, and exposes submit actions for custom UI. To use the default submit actions, pass humanInput.endpoint; approval decisions post to {endpoint}/approvals/{approvalId}/decision, and question answers post to {endpoint}/questions/{questionId}/answer.

useCompletion

type UseCompletionRequest = {
  prompt: string;
  stream: true;
};

type UseCompletionStatus = "idle" | "streaming" | "error";

type UseCompletionOptions<TEvent = unknown> = {
  transport?: EventTransport<UseCompletionRequest, TEvent>;
  endpoint?: string | URL;
  format?: "jsonl" | "sse";
  initialCompletion?: string;
  eventToDelta?: (event: TEvent) => string | undefined;
  eventToFinal?: (event: TEvent) => string | undefined;
  onEvent?: (event: TEvent) => void;
  onError?: (error: unknown) => void;
};

type UseCompletionResult = {
  completion: string;
  input: string;
  setInput(input: string): void;
  complete(prompt?: string): Promise<void>;
  stop(): void;
  reset(completion?: string): void;
  status: UseCompletionStatus;
  error: unknown;
};

function useCompletion<TEvent = unknown>(
  options?: UseCompletionOptions<TEvent>,
): UseCompletionResult;

Purpose: React hook for single-prompt text completion streaming. Simpler alternative to useChat when you only need prompt-in, text-out without message history.

Default eventToDelta matches { type: "text_delta", delta: string } events. Default eventToFinal matches { type: "final", response: { choice: Array<{ type: "text", text: string }> } } events (compatible with CompletionStreamEvent from @anvia/core).

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.