Skip to content
API

@tanstack/ai-octane

Octane hooks for TanStack AI. They work like the React hooks. To build your first chat, start with the Quick Start. For a typed chat layout, see Octane Chat UI.

Installation

shell
npm i @tanstack/ai-octane octane

octane is a required peer. The package publishes uncompiled .tsrx source, and your Octane plugin compiles it.

useChat(options)

Holds the messages and the run state of one chat, and talks to your server route.

tsx
import { useState } from "octane";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-octane";

export function Chat() {
  const [input, setInput] = useState("");
  const { messages, sendMessage, isLoading } = useChat({
    connection: fetchServerSentEvents("/api/chat"),
    threadId: "support-chat",
  });

  return (
    <div>
      {messages.map((message) => (
        <div key={message.id}>
          <strong>{message.role}:</strong>
          {message.parts
            .filter((part) => part.type === "text")
            .map((part) => part.content)
            .join("")}
        </div>
      ))}
      <input
        value={input}
        onInput={(event) => setInput(event.currentTarget.value)}
      />
      <button
        disabled={isLoading}
        onClick={() => {
          void sendMessage(input);
          setInput("");
        }}
        type="button"
      >
        Send
      </button>
    </div>
  );
}

Options

You must pass threadId. You must also pass connection or fetcher, not both.

OptionWhat it does
connectionConnection adapter, for example fetchServerSentEvents("/api/chat"). See Transports.
fetcherA function that returns a Response or a stream. Use it with server functions.
toolsClient tools from .client(). They run when the model calls them. See Client Tools.
threadIdRequired. The id of this chat. Use a stable id for each conversation, for example the id from your route.
initialMessagesThe messages to show first.
forwardedPropsJSON sent to the server in the AG-UI forwardedProps field, for example { model: "gpt-6.1-sol" }.
persistencetrue to load the thread from the server, or a storage adapter. See Chat Persistence.
outputSchemaA Standard Schema. Adds typed partial and final to the return value. See Streaming structured output.
contextClient-only runtime context for client tools. It is not sent to the server.
liveSubscribe on mount and unsubscribe on unmount.
queueWhat sendMessage does while a run is busy. Default: queue the message. See Queueing.
interruptsDefinitions from defineInterrupt(). See Interrupts.
byok, byokProviderA keyring from defineByok(). See Bring Your Own Key.
devtoolsDisplay options for TanStack AI Devtools. The hook always tags framework: 'octane'.

The hook also takes the ChatClient callbacks (onResponse, onChunk, onFinish, onError, onInterruptStateChange, onCustomEvent) and the other client options. See ChatClient options.

When connection, fetcher, forwardedProps, tools, context, or queue change, the hook updates the current chat. A new threadId creates a new chat.

Returns

State values are plain Octane values. The hook renders again when they change.

FieldWhat it is
messagesThe UIMessage array of the chat.
sendMessage(content, options?)Sends a user message. content is a string or { content, id?, metadata? }. options takes whenBusy and body.
isLoadingtrue while a response streams.
errorThe last error, or undefined.
status"ready", "submitted", "streaming", or "error".
stop()Stops the current response.
reload()Runs the last user message again.
append(message)Adds a UIMessage or ModelMessage and starts a run.
addToolResult(result)Gives the result of a client tool by hand: { toolCallId, tool, output, state?, errorText? }.
setMessages(messages), clear()Replace or clear the messages.
queue, cancelQueued(id)Messages that wait while a run is busy. See Queueing.
runIdThe id of the run in flight, or null.
interruptsPending interrupts. Each item has resolveInterrupt(...). See Interrupts.
resolveInterrupts, cancelInterrupts, retryInterrupts, resumeInterruptsUnsafeAct on all pending interrupts at once.
interruptErrors, resumingErrors from the last resume, and true while a resume runs.
isHydratingtrue while the chat loads from persistence.
hasOlderMessages, loadOlderMessages()Page in older messages. See Chat Persistence.
isSubscribed, connectionStatus, sessionGeneratingState of the live subscription.
partial, finalStructured output. Only when you pass outputSchema.

Example: tool approval

A tool with needsApproval: true pauses the run. The pending call shows up in interrupts.

tsx
import { toolDefinition } from "@tanstack/ai";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-octane";
import { z } from "zod";

const deleteFileTool = toolDefinition({
  name: "delete_file",
  description: "Delete a file",
  needsApproval: true,
  inputSchema: z.object({ path: z.string() }),
});

export function ChatWithApproval() {
  const { interrupts } = useChat({
    connection: fetchServerSentEvents("/api/chat"),
    threadId: "support-chat",
    tools: [deleteFileTool],
  });

  return (
    <div>
      {interrupts.map((interrupt) =>
        interrupt.kind === "tool-approval" ? (
          <div key={interrupt.id}>
            <p>Approve: {interrupt.toolName}</p>
            <button onClick={() => interrupt.resolveInterrupt(true)} type="button">
              Approve
            </button>
            <button onClick={() => interrupt.resolveInterrupt(false)} type="button">
              Deny
            </button>
          </div>
        ) : null,
      )}
    </div>
  );
}

See Tool Approval for the server side.

createChatHook(options)

Binds chat options once at module scope and returns a useChat hook that uses them.

tsx
import { createChatHook, fetchServerSentEvents } from "@tanstack/ai-octane";

const { useChat: useSupportChat } = createChatHook({
  connection: fetchServerSentEvents("/api/chat"),
});

export function SupportScreen() {
  const chat = useSupportChat({ threadId: "support-1" });
  return <p>{chat.messages.length} messages</p>;
}

The bound hook takes these per-call overrides: threadId, initialMessages, live, and forwardedProps. Keep tools, interrupts, and outputSchema on the factory options.

@tanstack/ai-octane/ui has its own createChatHook that also returns typed UI components. See Octane Chat UI.

Generation hooks

Each hook runs one generation request and keeps its result. See Generation Hooks.

Hookgenerate() inputresult
useGenerateImageImageGenerateInputImageGenerationResult
useGenerateAudioAudioGenerateInputAudioGenerationResult
useGenerateSpeechSpeechGenerateInputTTSResult
useTranscriptionTranscriptionGenerateInputTranscriptionResult
useSummarizeSummarizeGenerateInputSummarizationResult
useGenerateVideoVideoGenerateInputVideoGenerateResult
useGenerationYour own input typeYour own result type
tsx
import { useGenerateImage, fetchServerSentEvents } from "@tanstack/ai-octane";

export function ImageButton() {
  const image = useGenerateImage({
    connection: fetchServerSentEvents("/api/generate/image"),
    threadId: "image-studio",
  });

  return (
    <button
      disabled={image.isLoading}
      onClick={() => void image.generate({ prompt: "A mountain at sunset" })}
      type="button"
    >
      Generate
    </button>
  );
}

Options: threadId (required), connection or fetcher, body, devtools, onResult, onError, onProgress, onChunk.

Returns: generate(input), result, isLoading, error, status, stop(), and reset().

useGenerateVideo also returns jobId and videoStatus, and takes onJobCreated and onStatusUpdate.

useRealtimeChat(options)

Runs a voice conversation with a realtime model. Pass getToken and adapter. See Realtime Chat.

tsx
import { useRealtimeChat } from "@tanstack/ai-octane";
import { openaiRealtime } from "@tanstack/ai-openai";

export function VoiceChat() {
  const chat = useRealtimeChat({
    getToken: () => fetch("/api/realtime-token").then((r) => r.json()),
    adapter: openaiRealtime(),
  });

  return chat.status === "idle" ? (
    <button onClick={() => void chat.connect()} type="button">Start</button>
  ) : (
    <button onClick={() => void chat.disconnect()} type="button">Stop</button>
  );
}

Returns status, error, mode, messages, connect(), disconnect(), startListening(), stopListening(), interrupt(), sendText(text), sendImage(data, mimeType), updateSession(config), plus audio levels and frequency data for visualizers.

useAudioRecorder(options?)

Records audio from the microphone. See Audio Recording.

Returns recording, isRecording, isSupported, start(), stop(), and cancel(). Pass onComplete to transform the recording. Then recording and stop() use the transformed type.

BYOK

This package does not export a BYOK hook. Read the keyring with byok.getSnapshot() and byok.subscribe(listener). BYOK helpers (defineByok, defaultByokStorage, and more) are on the @tanstack/ai-octane/byok subpath. See Bring Your Own Key.

useMcpAppBridge(options)

Returns a stable MCP App bridge for one thread. It always calls the latest chat.sendMessage and onLink. Options: threadId, callEndpoint, chat, fetchImpl, onLink. See MCP Apps.

This package has no mcp-apps subpath, so it does not ship the MCPAppResource component.

useRegisterWebMCPTools(tools, options?)

Exposes client tools to the browser WebMCP API while the component is mounted. See WebMCP Tools.

tsx
import { useRegisterWebMCPTools } from "@tanstack/ai-octane";
import { searchProducts } from "./tools";

const tools = [searchProducts];

export function ProductsPage() {
  useRegisterWebMCPTools(tools, {
    onError: (error) => console.error(error),
  });
  return null;
}

Options: toolOptions, context, and onError. context is required when a tool declares a required runtime context. The hook registers the tools again when tools or options change, so keep them stable.

usePageWebMCPTools(options?)

Reads the WebMCP tools that the page registered, as client tools. The array starts empty and updates when the page adds or removes a tool. Pass it to useChat as tools. See Page WebMCP Tools in Chat.

tsx
import { usePageWebMCPTools } from "@tanstack/ai-octane";

export function useSameOriginPageTools() {
  return usePageWebMCPTools({
    filter: (tool) => tool.origin === location.origin,
  });
}

Options: filter (return false to skip a tool) and onError.

Re-exports from @tanstack/ai-client

You can import these from @tanstack/ai-octane directly. See @tanstack/ai-client for details.

GroupExports
Connection adaptersfetchServerSentEvents, fetchHttpStream, fetchJson, xhrServerSentEvents, xhrHttpStream, webSocket, stream, rpcStream
Typed optionscreateChatClientOptions, clientTools
ClientsChatClient, GenerationClient, VideoGenerationClient, RealtimeClient, AudioRecorder, InterruptManager
WebMCP and MCP AppsregisterWebMCPTools, getWebMCPTools, subscribeWebMCPTools, createMcpAppBridge
ErrorsUnsupportedResponseStreamError, StreamTruncatedError, DurableStreamIncompleteError, StreamReconnectLimitError
Stream helpersStreamProcessor, chunk strategies, parsePartialJSON, message converters

The browser persistence adapters (localStoragePersistence and the others) are not re-exported here. Import them from @tanstack/ai-client.

Types

TypeWhat it is
UseChatOptions, UseChatReturnThe options and return value of useChat.
UIMessage<TTools>A chat message with typed parts.
InferChatMessages<T>The message type for a createChatClientOptions result.
SendMessageOptions, WhenBusy, QueueOption, QueuedMessageSend and queue types.
DeepPartial<T>The type of partial while structured output streams.
Use<Hook>Options, Use<Hook>ReturnOptions and return types for the generation, realtime, and recorder hooks. The WebMCP hooks export only an options type.

Pass a plain tools array to useChat or createChatClientOptions. Tool names, inputs, and outputs stay typed without as const. Import toolDefinition from @tanstack/ai.