Use LangChain's useStream hook with a React chat UI through assistant-ui — a lighter LangGraph adapter that delegates streaming to @langchain/react.
@assistant-ui/react-langchain wraps useStream from @langchain/react and exposes it as an assistant-ui runtime. It targets the same backend as @assistant-ui/react-langgraph (LangGraph Cloud) but at a higher level, delegating stream plumbing to the upstream hook.
When to use it
Pick react-langchain over react-langgraph when:
- You are scaffolding via
npx create-assistant-ui -t langchain(the official template uses it). - Your app already depends on
@langchain/reactand usesuseStreamelsewhere. - You want to read custom state keys (
todos,files, plans) reactively withuseLangChainState<T>(key). - You prefer a thin wrapper that stays pinned to upstream behavior.
Pick react-langgraph instead when:
- You need a custom backend stream (your own async generator) rather than
useStream's transport, or you're maintaining an existingreact-langgraphapp.
The comparison below lists what each adapter supports. react-langchain is the newer, thinner wrapper pinned to upstream useStream.
Architecture
@assistant-ui/react-langchain is layered on ExternalStoreRuntime (see architecture). Graph state is the source of truth; the runtime renders messages from state.values.messages and submits user input back to the graph.
Shared adapters (attachments, speech, dictation, voice, feedback) work the same way described in adapters. Cloud thread persistence is built in.
thread.isLoading reflects useStream's isThreadLoading (initial history hydration), separate from isRunning (a run being in-flight).
Requirements
- A LangGraph Cloud API server (locally via LangGraph Studio or hosted via LangSmith).
- The graph state must include a
messageskey with LangChain-alike messages, or pass a custommessagesKey.
Quickstart
Install dependencies
npm install @assistant-ui/react @assistant-ui/react-langchain @langchain/react @langchain/langgraph-sdknpx expo install @assistant-ui/react-native @assistant-ui/react-langchain @langchain/react @langchain/langgraph-sdknpm install @assistant-ui/react-ink @assistant-ui/react-langchain @langchain/react @langchain/langgraph-sdk ink reactDefine the assistant component
"use client";
import { Thread } from "@/components/assistant-ui/elements/thread.aui";
import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useStreamRuntime } from "@assistant-ui/react-langchain";
export function MyAssistant() {
const runtime = useStreamRuntime({
assistantId: process.env["NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID"]!,
apiUrl: process.env["NEXT_PUBLIC_LANGGRAPH_API_URL"],
});
return (
<AssistantRuntimeProvider runtime={runtime}>
<Thread />
</AssistantRuntimeProvider>
);
}import { Thread } from "@/components/assistant-ui/elements/thread.aui";
import { AssistantRuntimeProvider } from "@assistant-ui/react-native";
import { useStreamRuntime } from "@assistant-ui/react-langchain";
export function MyAssistant() {
const runtime = useStreamRuntime({
assistantId: process.env.EXPO_PUBLIC_LANGGRAPH_ASSISTANT_ID!,
apiUrl: process.env.EXPO_PUBLIC_LANGGRAPH_API_URL,
});
return (
<AssistantRuntimeProvider runtime={runtime}>
<Thread />
</AssistantRuntimeProvider>
);
}import { Thread } from "./thread.js";
import { AssistantRuntimeProvider } from "@assistant-ui/react-ink";
import { useStreamRuntime } from "@assistant-ui/react-langchain";
export function MyAssistant() {
const runtime = useStreamRuntime({
assistantId: process.env.LANGGRAPH_ASSISTANT_ID!,
apiUrl: process.env.LANGGRAPH_API_URL,
});
return (
<AssistantRuntimeProvider runtime={runtime}>
<Thread />
</AssistantRuntimeProvider>
);
}Mount the component
import { MyAssistant } from "@/components/MyAssistant";
export default function Home() {
return (
<main className="h-dvh">
<MyAssistant />
</main>
);
}import { View } from "react-native";
import { MyAssistant } from "@/components/MyAssistant";
export default function Home() {
return (
<View style={{ flex: 1 }}>
<MyAssistant />
</View>
);
}import { Box } from "ink";
import { MyAssistant } from "./components/MyAssistant.js";
export function App() {
return (
<Box flexDirection="column">
<MyAssistant />
</Box>
);
}Set environment variables
NEXT_PUBLIC_LANGGRAPH_API_URL=http://localhost:2024
NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID=your_graph_idEXPO_PUBLIC_LANGGRAPH_API_URL=http://localhost:2024
EXPO_PUBLIC_LANGGRAPH_ASSISTANT_ID=your_graph_idLANGGRAPH_API_URL=http://localhost:2024
LANGGRAPH_ASSISTANT_ID=your_graph_iduseStreamRuntime options
useStreamRuntime accepts every option upstream useStream does, plus these assistant-ui-specific fields:
| Option | Type | Description |
|---|---|---|
cloud | AssistantCloud | Optional. Uses Assistant Cloud for the thread list and a copy of settled messages for the dashboard. |
create | () => Promise<{ externalId: string | undefined }> | Optional. Creates a thread in your backend. |
delete | (threadId: string) => Promise<void> | Optional. When Cloud owns the thread list, called before a thread is deleted and given its Cloud thread id; read that thread's external_id to delete the backend thread, as LangGraph shows. |
onThreadIdChange | (threadId: string | undefined) => void | Optional. Called when the active thread's canonical ID changes. |
unstable_threadListAdapter | RemoteThreadListAdapter | Optional. Replaces the Cloud thread list adapter. |
adapters | { attachments?, speech?, dictation?, voice?, feedback? } | Optional. Attachment, speech, dictation, voice, and feedback adapters. |
autoCancelPendingToolCalls | boolean | Optional. When the user sends a message while tool calls are still pending, submits tool messages that cancel them. Defaults to true. |
unstable_allowCancellation | boolean | Optional. Routes the Cancel button to useStream().stop(). On by default; pass false to disable the Cancel button. |
messagesKey | string | The state key that holds messages. Defaults to "messages". |
uiStateKey | string | The state key that holds generative UIMessages. Defaults to "ui". |
Forwarding per-run config
When a message is sent, its runConfig.custom (for example a selected mode or model) is forwarded on the underlying useStream().submit call as config.configurable. Automatic tool-result resumes and the interrupt helpers (useLangChainRespond, useLangChainRespondAll, and useLangChainSubmit(null, { command })) reuse that same recorded configurable unless the caller passes config. Raw useLangChainSubmit / useLangChainSend calls that start a new run do not inherit it. The recording is session-scoped and does not survive a reload. Read it in the graph from config["configurable"]; on LangGraph v1 the same values are also reachable through the Runtime context, which config.configurable is aliased to.
Reading custom state keys
LangGraph agents often expose structured state beyond messages (plans, todos, scratch files, generative-UI artifacts). Read them directly with useLangChainState. It mirrors useStream().values[key] upstream and updates when the stream emits new state.
import { useLangChainState } from "@assistant-ui/react-langchain";
type Todo = { id: string; title: string; done: boolean };
function TodoList() {
const todos = useLangChainState<Todo[]>("todos", []);
return (
<ul>
{todos.map((t) => (
<li key={t.id}>
{t.done ? "✓" : "○"} {t.title}
</li>
))}
</ul>
);
}import { useLangChainState } from "@assistant-ui/react-langchain";
import { Text, View } from "react-native";
type Todo = { id: string; title: string; done: boolean };
function TodoList() {
const todos = useLangChainState<Todo[]>("todos", []);
return (
<View>
{todos.map((t) => (
<Text key={t.id}>
{t.done ? "✓" : "○"} {t.title}
</Text>
))}
</View>
);
}import { useLangChainState } from "@assistant-ui/react-langchain";
import { Box, Text } from "ink";
type Todo = { id: string; title: string; done: boolean };
function TodoList() {
const todos = useLangChainState<Todo[]>("todos", []);
return (
<Box flexDirection="column">
{todos.map((t) => (
<Text key={t.id}>
{t.done ? "✓" : "○"} {t.title}
</Text>
))}
</Box>
);
}Signatures:
useLangChainState<T>(key: string): T | undefined;
useLangChainState<T>(key: string, defaultValue: T): T;This is useful with the deepagents middleware, whose write_todos step updates state.todos alongside the tool-call stream. Reading the state key directly avoids reconstructing the list from partial tool-call args.
Interrupts
LangGraph interrupts pause the graph and wait for client input. useLangChainInterruptState exposes the current interrupt; useLangChainRespond resumes it with a response payload, while useLangChainSubmit resumes the graph with a raw state update.
Prefer useLangChainRespond: it carries the response payload via useStream().respond and handles interrupt namespaces, so you don't have to construct a Command.
import {
useLangChainInterruptState,
useLangChainRespond,
} from "@assistant-ui/react-langchain";
function InterruptPrompt() {
const interrupt = useLangChainInterruptState();
const respond = useLangChainRespond();
if (!interrupt) return null;
return (
<div>
<pre>{JSON.stringify(interrupt.value, null, 2)}</pre>
<button onClick={() => respond({ approved: true })}>Approve</button>
</div>
);
}import {
useLangChainInterruptState,
useLangChainRespond,
} from "@assistant-ui/react-langchain";
import { Pressable, Text, View } from "react-native";
function InterruptPrompt() {
const interrupt = useLangChainInterruptState();
const respond = useLangChainRespond();
if (!interrupt) return null;
return (
<View>
<Text>{JSON.stringify(interrupt.value, null, 2)}</Text>
<Pressable onPress={() => respond({ approved: true })}>
<Text>Approve</Text>
</Pressable>
</View>
);
}import {
useLangChainInterruptState,
useLangChainRespond,
} from "@assistant-ui/react-langchain";
import { Box, Text, useInput } from "ink";
function InterruptPrompt() {
const interrupt = useLangChainInterruptState();
const respond = useLangChainRespond();
useInput((input) => {
if (!interrupt || input !== "a") return;
respond({ approved: true });
});
if (!interrupt) return null;
return (
<Box flexDirection="column">
<Text>{JSON.stringify(interrupt.value, null, 2)}</Text>
<Text>Press a to approve.</Text>
</Box>
);
}When several interrupts are pending at once (e.g. parallel approvals), use useLangChainRespondAll() to resume them all in one run. Sequential useLangChainRespond calls can't, since the first resume starts a run and strands the rest.
import {
useLangChainInterrupts,
useLangChainRespondAll,
} from "@assistant-ui/react-langchain";
const interrupts = useLangChainInterrupts();
const respondAll = useLangChainRespondAll();
// resume every pending interrupt with the same payload:
await respondAll(
Object.fromEntries(
interrupts.flatMap((i) => (i.id ? [[i.id, { approved: true }]] : [])),
),
);You can also resume with a raw Command via useLangChainSubmit:
import {
useLangChainInterruptState,
useLangChainSubmit,
} from "@assistant-ui/react-langchain";
function InterruptPrompt() {
const interrupt = useLangChainInterruptState();
const submit = useLangChainSubmit();
if (!interrupt) return null;
return (
<div>
<pre>{JSON.stringify(interrupt.value, null, 2)}</pre>
<button
onClick={() =>
submit(null, { command: { resume: "approved" } })
}
>
Approve
</button>
</div>
);
}import {
useLangChainInterruptState,
useLangChainSubmit,
} from "@assistant-ui/react-langchain";
import { Pressable, Text, View } from "react-native";
function InterruptPrompt() {
const interrupt = useLangChainInterruptState();
const submit = useLangChainSubmit();
if (!interrupt) return null;
return (
<View>
<Text>{JSON.stringify(interrupt.value, null, 2)}</Text>
<Pressable
onPress={() =>
submit(null, { command: { resume: "approved" } })
}
>
<Text>Approve</Text>
</Pressable>
</View>
);
}import {
useLangChainInterruptState,
useLangChainSubmit,
} from "@assistant-ui/react-langchain";
import { Box, Text, useInput } from "ink";
function InterruptPrompt() {
const interrupt = useLangChainInterruptState();
const submit = useLangChainSubmit();
useInput((input) => {
if (!interrupt || input !== "a") return;
submit(null, { command: { resume: "approved" } });
});
if (!interrupt) return null;
return (
<Box flexDirection="column">
<Text>{JSON.stringify(interrupt.value, null, 2)}</Text>
<Text>Press a to approve.</Text>
</Box>
);
}Tool calls
useLangChainToolCalls exposes the root tool calls useStream assembles from the tools channel. Each entry carries a name, args, and id. Use it to render pending or streamed tool calls and approval UIs. It defaults to an empty array, so you can .map without a guard.
import { useLangChainToolCalls } from "@assistant-ui/react-langchain";
function PendingToolCalls() {
const toolCalls = useLangChainToolCalls();
return (
<ul>
{toolCalls.map((tc) => (
<li key={tc.id}>
{tc.name}: <code>{JSON.stringify(tc.args)}</code>
</li>
))}
</ul>
);
}import { useLangChainToolCalls } from "@assistant-ui/react-langchain";
import { Text, View } from "react-native";
function PendingToolCalls() {
const toolCalls = useLangChainToolCalls();
return (
<View>
{toolCalls.map((tc) => (
<Text key={tc.id}>
{tc.name}: {JSON.stringify(tc.args)}
</Text>
))}
</View>
);
}import { useLangChainToolCalls } from "@assistant-ui/react-langchain";
import { Box, Text } from "ink";
function PendingToolCalls() {
const toolCalls = useLangChainToolCalls();
return (
<Box flexDirection="column">
{toolCalls.map((tc) => (
<Text key={tc.id}>
{tc.name}: {JSON.stringify(tc.args)}
</Text>
))}
</Box>
);
}Subagent and subgraph discovery
For multi-agent graphs, useStream tracks which subagents and subgraphs it has seen on the current run as cheap discovery maps. useLangChainSubagents and useLangChainSubgraphs surface those maps, keyed by namespace, with each value a SubagentDiscoverySnapshot / SubgraphDiscoverySnapshot. Both default to a stable empty map, so you can iterate without a guard.
These replace react-langgraph's subgraph eventHandlers: v1 surfaces discovery as maps you read reactively rather than per-event callbacks.
import {
useLangChainSubagents,
useLangChainSubgraphs,
} from "@assistant-ui/react-langchain";
function Discovered() {
const subagents = useLangChainSubagents();
const subgraphs = useLangChainSubgraphs();
return (
<ul>
{[...subagents.keys()].map((ns) => (
<li key={ns}>subagent: {ns}</li>
))}
{[...subgraphs.keys()].map((ns) => (
<li key={ns}>subgraph: {ns}</li>
))}
</ul>
);
}Subagent and subgraph views
Every task subagent transcript is attached to its spawning tool call as ToolCallMessagePart.messages, keyed by the task tool call id. It streams while the subagent runs and nests recursively up to sixteen levels deep when subagents spawn subagents. MessagePartPrimitive.Messages renders the transcript and s.thread.tasks lists it. See Multi-agent tools. Threads loaded from history carry these transcripts because the SDK seeds stream.subagents from checkpoint messages and seeds the scoped message projections from history.
The discovery maps above pair with v1's scoped selector hooks to build a custom subagent or subgraph view. useLangChainStream exposes the underlying useStream handle; feed it to useMessages(stream, target) / useToolCalls(stream, target) from @langchain/react, with a SubagentDiscoverySnapshot / SubgraphDiscoverySnapshot from useLangChainSubagents / useLangChainSubgraphs as the target. useLangChainStream returns undefined outside the runtime provider.
import {
useLangChainStream,
useLangChainSubagents,
} from "@assistant-ui/react-langchain";
import { useMessages, useToolCalls } from "@langchain/react";
function SubagentCard({ stream, subagent }) {
const messages = useMessages(stream, subagent);
const toolCalls = useToolCalls(stream, subagent);
return (
<div>
<strong>{subagent.namespace.join("/")}</strong>
<pre>{messages.map((m) => m.content).join("\n")}</pre>
<ul>
{toolCalls.map((tc) => (
<li key={tc.id}>{tc.name}</li>
))}
</ul>
</div>
);
}
function Subagents() {
const stream = useLangChainStream();
const subagents = useLangChainSubagents();
if (!stream) return null;
return [...subagents.values()].map((s) => (
<SubagentCard key={s.namespace.join("/")} stream={stream} subagent={s} />
));
}Per-message metadata
useMessageMetadata (from @langchain/react) reads per-message metadata (e.g. parentCheckpointId) for a given message id. Pair it with useLangChainStream, which returns undefined outside the runtime provider; guard the stream before calling the selector, since useMessageMetadata throws on an undefined stream.
import { useLangChainStream } from "@assistant-ui/react-langchain";
import { useMessageMetadata } from "@langchain/react";
function MessageInfoView({
stream,
messageId,
}: {
stream: NonNullable<ReturnType<typeof useLangChainStream>>;
messageId: string;
}) {
const { parentCheckpointId } = useMessageMetadata(stream, messageId) ?? {};
// …render per-message metadata…
}
function MessageInfo({ messageId }: { messageId: string }) {
const stream = useLangChainStream();
if (!stream) return null;
return <MessageInfoView stream={stream} messageId={messageId} />;
}Media
The v1 media hooks surface multimodal media streamed by the agent. Feed the useLangChainStream handle to them, as in the views and metadata sections above.
import { useLangChainStream } from "@assistant-ui/react-langchain";
import { useImages, useAudio } from "@langchain/react";
function MediaView({
stream,
}: {
stream: NonNullable<ReturnType<typeof useLangChainStream>>;
}) {
const images = useImages(stream);
// useAudio / useVideo / useFiles likewise
return <>{/* …render media… */}</>;
}
function Media() {
const stream = useLangChainStream();
if (!stream) return null;
return <MediaView stream={stream} />;
}Generative UI
Graphs can accumulate UI components in their state and attach each one to the assistant message that produced it. The runtime reads these from stream.values[uiStateKey] (default "ui") and, for every UIMessage whose parent id matches an assistant message, emits a data part on that message. The parent id is read from metadata.message_id (Python SDK) or metadata.id (JS SDK). Pass uiStateKey if your graph stores them elsewhere.
Register the components with makeAssistantDataUI, keyed by the UIMessage's name, and mount the returned element once inside the runtime provider:
import {
AssistantRuntimeProvider,
makeAssistantDataUI,
} from "@assistant-ui/react";
import { Thread } from "@/components/assistant-ui/elements/thread.aui";
import { useStreamRuntime } from "@assistant-ui/react-langchain";
const ChartUI = makeAssistantDataUI({
name: "chart",
render: ({ data }) => <Chart points={data.points} />,
});
function App() {
const runtime = useStreamRuntime({
assistantId: "agent",
apiUrl: "http://localhost:2024",
});
return (
<AssistantRuntimeProvider runtime={runtime}>
<ChartUI />
<Thread />
</AssistantRuntimeProvider>
);
}This covers two paths. The state-snapshot path renders whatever UI the graph has committed to its state. The live path renders UI streamed over the custom channel by push_ui_message / delete_ui_message before (or instead of) it lands in state, so components appear as the graph emits them. The runtime accumulates the live events and merges them with the state snapshot, with the snapshot authoritative once a UI lands in state. Both feed the same makeAssistantDataUI renderers.
The live path requires the graph's streamMode to include "custom" so the UI events reach the client.
Errors
useLangChainError exposes the last run or hydration error from upstream useStream().error. It is untyped (unknown), so narrow it at the use site before rendering.
import { useLangChainError } from "@assistant-ui/react-langchain";
function ErrorBanner() {
const error = useLangChainError();
if (!error) return null;
return <div role="alert">{error instanceof Error ? error.message : String(error)}</div>;
}Message conversion
convertLangChainBaseMessage transforms a LangChain BaseMessage into an assistant-ui message. Use it when building a custom ExternalStoreAdapter that consumes LangChain messages outside useStreamRuntime.
import { convertLangChainBaseMessage } from "@assistant-ui/react-langchain";Cloud persistence
Pass an AssistantCloud instance to persist the thread list across sessions. useStream({ threadId: externalId }) loads state from the LangGraph server, not from Assistant Cloud. The runtime copies settled messages to Assistant Cloud for the dashboard; set telemetry: { messages: false } on the cloud client to disable that copy.
// see "AssistantCloud" in /docs/runtimes/concepts/threads for cloud setup
const runtime = useStreamRuntime({
cloud,
assistantId: "agent",
apiUrl: "http://localhost:2024",
});Custom messagesKey
If your graph stores messages under a non-default key, pass messagesKey so the runtime submits tool results and human turns to the correct state slot:
const runtime = useStreamRuntime({
assistantId: "agent",
apiUrl: "http://localhost:2024",
messagesKey: "chat_messages",
});Comparison with react-langgraph
Both packages connect assistant-ui to LangGraph backends. They are independent adapters for different upstream libraries; one is not a successor to the other. The official create-assistant-ui template (-t langchain) ships react-langchain. react-langgraph remains the choice for a custom backend stream or an existing react-langgraph app.
| Aspect | react-langgraph | react-langchain |
|---|---|---|
| Wraps | @langchain/langgraph-sdk (raw SDK) | @langchain/react (useStream hook) |
| Age | Sept 2024 onward | April 2026 onward |
| Version | 0.14.x | 0.0.x |
| Built on | useExternalStoreRuntime | useExternalStoreRuntime |
create-assistant-ui template | No template | -t langchain |
Feature coverage
| Feature | react-langgraph | react-langchain |
|---|---|---|
| Stream messages | Yes (useLangGraphRuntime) | Yes (useStreamRuntime) |
| Interrupt state | Yes | Yes |
Thread history loading state (thread.isLoading) | Yes | Yes |
| Send raw state update / resume command | Yes | Yes (useLangChainSubmit; useLangChainRespond for payload resume) |
Forward per-run runConfig | Yes (whole object → config) | Yes (custom → config.configurable) |
| Read arbitrary custom state key | Yes (useLangGraphState returns the whole state) | Yes (useLangChainState<T>(key)) |
| Root tool-calls projection | Via message parts | Yes (useLangChainToolCalls) |
| File content parts in messages | Yes | Yes |
| Regenerate (checkpoint fork) | Yes (getCheckpointId) | Yes (auto-resolved) |
| Edit message (checkpoint fork) | Yes | Yes (auto-resolved) |
Per-message metadata (messages-tuple) | Yes | Yes (useMessageMetadata via useLangChainStream) |
| Multimodal media (audio/image/video/file) | Via parts | Yes (v1 media hooks via useLangChainStream) |
| Generative UI (state snapshot) | Yes | Yes (uiStateKey) |
Generative UI (live push_ui_message) | Yes | Yes (streamMode: ["custom"]) |
| Subgraph / namespaced stream events | Yes (via eventHandlers) | Replaced by useLangChainSubagents / useLangChainSubgraphs + views |
| Subagent discovery | Via eventHandlers | Yes (useLangChainSubagents) |
| Subgraph discovery | Via eventHandlers | Yes (useLangChainSubgraphs) |
| Subagent/subgraph message views | Via eventHandlers | Yes (via useLangChainStream + v1 selector hooks) |
| End-to-end cancellation primitive | Yes (unstable_createLangGraphStream) | n/a; cancellation via useStream().stop() (Cancel button on by default) |
| Message accumulator utility | Yes (LangGraphMessageAccumulator) | n/a; useStream owns accumulation |
| Message queue and steering | Yes (unstable_enableMessageQueue) | No |
| Streaming timing (per-message) | Yes | Yes |
| Cloud thread persistence | Yes | Yes |
Per-message streaming timing is attached automatically to message.metadata.timing (no setup), matching react-langgraph. It captures time-to-first-token, total stream time, chunk count, and tokens/sec, computed from message growth while the run is in flight.
react-langchain is the newer, thinner wrapper. It delegates to upstream useStream rather than re-implementing the stream plumbing.
Regenerate works without configuration. Clicking regenerate resolves the server checkpoint to fork from (the checkpoint each message records as it streams in, falling back to a stream.client.threads.getHistory(threadId) id match for older turns), then re-runs with submit(null, { forkFrom }). It works on checkpoint-enabled backends (for example LangGraph Cloud); when no checkpoint resolves, regenerate is a no-op. react-langgraph instead requires a user-supplied getCheckpointId callback. Editing a message forks from the same checkpoint and submits the edited human content instead of re-running the prior turn; editing the first message forks from the thread's initial checkpoint to restart it.
Hook name mapping
react-langgraph | react-langchain | Notes |
|---|---|---|
useLangGraphRuntime | useStreamRuntime | Options extend upstream UseStreamOptions; no stream / create / load to write. |
useLangGraphInterruptState | useLangChainInterruptState | Same return shape. |
useLangGraphSendCommand | useLangChainSendCommand | Submits a v1 command ({ resume?, goto?, update? }) via useLangChainSubmit(null, { command }). To carry a response payload, prefer useLangChainRespond. |
useLangGraphSend | useLangChainSend | Submits messages via useStream().submit({ [messagesKey]: messages }, options). |
useLangGraphMessageMetadata | (use useMessageMetadata(useLangChainStream(), id)) | Per-message metadata via the upstream selector hook. |
useLangGraphUIMessages | (UI renders as data parts; register with makeAssistantDataUI) | See Generative UI. |
useLangGraphState | useLangChainState<T>(key) | Reads one custom state key reactively; useLangGraphState returns the whole state object. |
| (none) | useLangChainToolCalls | Root tool calls assembled by useStream. |
| (none) | useLangChainError() | Reads the last run/hydration error reactively. |
| (none) | useLangChainRespondAll | Resume multiple interrupts at one checkpoint (stream.respondAll). |
| (none) | useLangChainInterrupts | Every interrupt pending at the checkpoint, each with id and value. |
(via eventHandlers) | useLangChainSubagents | Subagents discovered on the current run; replaces subgraph event handlers. |
(via eventHandlers) | useLangChainSubgraphs | Subgraphs discovered on the current run; replaces subgraph event handlers. |
| (none) | useLangChainStream | The raw useStream handle for v1's scoped useMessages / useToolCalls selector hooks. |