LangChain Reference home pageLangChain ReferenceLangChain Reference
  • GitHub
  • Main Docs
Deep Agents
LangChain
LangGraph
Integrations
LangSmith
LangGraph
  • Web
  • Channels
  • Pregel
  • Prebuilt
  • Remote
  • Overview
  • Getting started
  • useStream
  • Selectors
  • Interrupts & headless tools
  • Subagents & subgraphs
  • Fork & edit from a checkpoint
  • Submission queue
  • Multimodal media
  • Transports
  • Suspense
  • StreamProvider & context
  • Type safety
  • Migrating to v1
LangGraph SDK
  • Ui
  • Client
  • Auth
  • React
  • Logging
  • React Ui
  • Utils
  • Server
  • Stream
LangGraph Checkpoint
LangGraph Checkpoint MongoDB
LangGraph Checkpoint Postgres
  • Store
LangGraph Checkpoint Redis
  • Shallow
  • Store
LangGraph Checkpoint SQLite
LangGraph Checkpoint Validation
  • Cli
LangGraph API
LangGraph CLI
LangGraph CUA
  • Utils
LangGraph Supervisor
LangGraph Swarm
⌘I

LangChain Assistant

Ask a question to get started

Enter to send•Shift+Enter new line

Menu

LangGraph
WebChannelsPregelPrebuiltRemote
OverviewGetting starteduseStreamSelectorsInterrupts & headless toolsSubagents & subgraphsFork & edit from a checkpointSubmission queueMultimodal mediaTransportsSuspenseStreamProvider & contextType safetyMigrating to v1
LangGraph SDK
UiClientAuthReactLoggingReact UiUtilsServerStream
LangGraph Checkpoint
LangGraph Checkpoint MongoDB
LangGraph Checkpoint Postgres
Store
LangGraph Checkpoint Redis
ShallowStore
LangGraph Checkpoint SQLite
LangGraph Checkpoint Validation
Cli
LangGraph API
LangGraph CLI
LangGraph CUA
Utils
LangGraph Supervisor
LangGraph Swarm
Language
Theme
JavaScript@langchain/reactMigrating to v1

Migrating to v1

This guide walks application authors through the jump from the pre-v1 useStream hook (previously shipped as @langchain/langgraph-sdk/react and later re-exported as @langchain/react) to the useStream hook built for new event-based streaming, which ships with @langchain/react v1.

Short version: the useStream import name does not change, but the return shape, option bag, and protocol semantics do. Most chat apps migrate in well under an hour by following the checklists below. Apps that lean heavily on history / branch / fetchStateHistory or on a custom UseStreamTransport have more work to do.

1. Why the breaking change?

The legacy useStream was built against the legacy streaming protocol and accreted a large surface of opt-in callbacks (onUpdateEvent, onCustomEvent, onMetadataEvent, …) plus derived state (history, branch, getMessagesMetadata, joinStream) that had to be recomputed on every render.

The v1 package hook uses new event-based streaming. In practice that means:

  • Selector-based subscriptions. Namespaced data (subagent messages, subgraph tool calls, media) is opened only when a component actually mounts a selector hook, and released on unmount.
  • Always-on root projections. values / messages / toolCalls / interrupts are always available at the root with zero extra wire cost.
  • First-class re-attach. Remounting a hook on an in-flight thread attaches to the live subscription instead of replaying from scratch.
  • Discriminated option bag. The Agent Server path and the custom-adapter path are now two arms of a discriminated union.
  • Type inference from agent brands. typeof agent flows through to values, toolCalls[].args, and subagent-state maps.

2. TL;DR migration checklist

  • Upgrade @langchain/react to ^1.0.0 and @langchain/langgraph-sdk to the matching new event-based streaming runtime.
  • Imports stay the same — import { useStream } from "@langchain/react" now resolves to the hook built for new event-based streaming. useStreamExperimental is not exported from this package.
  • Remove these option-bag fields (gone; see §3): onError, onFinish, onUpdateEvent, onCustomEvent, onMetadataEvent, onLangChainEvent, onDebugEvent, onCheckpointEvent, onTaskEvent, onToolEvent, onStop, fetchStateHistory, reconnectOnMount, throttle, thread, filterSubagentMessages, subagentToolNames.
  • Replace transport: new FetchStreamTransport(...) with transport: new HttpAgentServerAdapter(...) (see §9).
  • Remove these return-shape fields (moved or dropped; see §4): branch, setBranch, history, experimental_branchTree, getMessagesMetadata, toolProgress, joinStream, switchThread, queue, activeSubagents, getSubagent, getSubagentsByType, getSubagentsByMessage.
  • Replace getMessagesMetadata(msg)?.firstSeenState?.parent_checkpoint with useMessageMetadata(stream, msg.id)?.parentCheckpointId (§6).
  • Replace stream.queue with useSubmissionQueue(stream) (§6).
  • Replace stream.switchThread(id) with passing a new threadId prop and letting the hook reload on change (§4).
  • Inside subagent-aware UIs, read per-subagent data with selector hooks (useMessages(stream, subagent) etc.) (§7).
  • Replace submit(..., { onDisconnect, streamResumable }) with stream.stop() (cancel) or stream.disconnect() (join/rejoin) (§5.3).
  • Re-run tsc. The option bag and return type are now discriminated and strongly typed.

3. Option-bag migration

3.1 Still supported — same meaning

assistantId, client, apiUrl, apiKey, callerOptions, defaultHeaders, threadId, onThreadId, initialValues, messagesKey, onCreated, tools, onTool all keep working.

3.2 New options

Option Notes
transport "sse" / "websocket" selects the built-in wire transport; an AgentServerAdapter instance flips the hook into the custom branch.
fetch Agent Server branch only. Forwarded to the built-in SSE transport.
webSocketFactory Agent Server branch only. Forwarded to the built-in WebSocket transport.
onCompleted Fires with { runId?, reason } when active streaming ends; runId may be absent for re-attached in-flight runs.

3.3 Removed — with replacements

Legacy option v1 replacement
onError (hook-level) Read stream.error directly, or pass a per-submit onError via submit(input, { onError }).
onFinish Use onCompleted, or derive render state from isLoading / useValues(stream).
onUpdateEvent, onCustomEvent, onMetadataEvent, onLangChainEvent, onDebugEvent, onCheckpointEvent, onTaskEvent, onToolEvent Drop. Read raw events via selector hooks (useChannel, useExtension) when you genuinely need them.
onStop Drop. Use stream.stop() to cancel or stream.disconnect() to leave the agent running server-side (§5.3).
fetchStateHistory Drop. Fork/edit flows use useMessageMetadata + submit({}, { forkFrom }) (§5).
reconnectOnMount Drop. Re-attach is automatic.
throttle Drop. The hook batches state updates natively.
thread Drop. External thread managers should drive threadId / initialValues.
filterSubagentMessages Drop. Subagent messages live on per-subagent selector hooks (§7).
subagentToolNames Drop. Subagent classification is driven by new event-based streaming lifecycle events.
// Before
useStream({ onFinish: (state) => analytics.track("turn_finished", state) });

// After
useStream({
  assistantId,
  onCompleted: ({ runId, reason }) => analytics.track("turn_finished", { runId, reason }),
});

4. Return-shape migration

4.1 Still there — same meaning

values, messages, toolCalls, interrupts / interrupt, isLoading, error, threadId, client, assistantId, submit, stop, respond, disconnect. Note submit argument types are wider (§5); stop(options?) cancels server-side by default, disconnect() is join/rejoin client-only (§5.3).

4.2 Still there — different meaning

Field What changed
subagents Now a ReadonlyMap<string, SubagentDiscoverySnapshot>. Snapshot carries id / name / namespace / status only — read content via selector hooks (§7).
isThreadLoading Reflects the initial thread-load lifecycle rather than fetchStateHistory.

4.3 Removed — with replacements

Legacy field v1 replacement
branch, setBranch, experimental_branchTree useMessageMetadata(stream, msg.id) + submit(input, { forkFrom }).
history, fetchStateHistory Fetch explicitly with client.threads.getHistory(threadId) if you need it; most apps do not.
getMessagesMetadata(msg, i) useMessageMetadata(stream, msg.id) returns { parentCheckpointId } (§6).
toolProgress Each AssembledToolCall carries its own status — read via useToolCalls(stream).
joinStream(runId, ...) Remounting the hook with the right threadId rejoins automatically.
switchThread(newThreadId) Drive threadId as a prop. The hook reloads on change.
queue useSubmissionQueue(stream) companion hook (§6).
activeSubagents, getSubagent, getSubagentsByType, getSubagentsByMessage Iterate stream.subagents (a Map) and filter inline.

4.4 New fields

subgraphs (ReadonlyMap<string, SubgraphDiscoverySnapshot>) and subgraphsByNode (ReadonlyMap<string, SubgraphDiscoverySnapshot[]>).

4.5 Worked example — minimal diff

// Before
const { messages, isLoading, error, submit, branch, setBranch, getMessagesMetadata } = useStream({
  assistantId: "agent",
  apiUrl: "http://localhost:2024",
  onError: (err) => console.error(err),
  fetchStateHistory: true,
});

// After
const stream = useStream({
  assistantId: "agent",
  apiUrl: "http://localhost:2024",
});
const { messages, isLoading, error, submit } = stream;

useEffect(() => {
  if (error) console.error(error);
}, [error]);

const { parentCheckpointId } = useMessageMetadata(stream, messages.at(-1)?.id) ?? {};

5. submit() signature changes

5.1 Input widening

submit() now accepts either a wire-format message payload or an array of BaseMessage class instances:

await submit({ messages: [{ role: "user", content: "hi" }] });
await submit({ messages: [new HumanMessage("hi")] });
await submit({ messages: new HumanMessage("hi") });

This is driven by the WidenUpdateMessages<T> helper.

5.2 Option changes

Legacy SubmitOptions field v1 StreamSubmitOptions equivalent
context Fold into config.configurable.
checkpoint: { checkpoint_id } forkFrom: "cp_123" (direct checkpoint id string).
command: { resume } Use stream.respond() instead.
interruptBefore, interruptAfter Drop — not supported with new event-based streaming.
multitaskStrategy Unchanged. "rollback" (default), "reject", "enqueue" honoured client-side; "interrupt" falls back to "rollback".
onCompletion Use the hook-level onCompleted option.
onDisconnect, feedbackKeys, streamMode, runId, optimisticValues, streamSubgraphs, streamResumable, checkpointDuring Drop from submit. Disconnect/cancel policy now lives on stop() / disconnect() (§5.3).
(new submit option) onError Per-submit fire-and-forget error callback.
(new) threadId Per-submit thread override.
// Before
await submit(
  { messages: [new HumanMessage("retry")] },
  { checkpoint: { checkpoint_id: "cp_123" }, multitaskStrategy: "rollback" },
);

// After
await submit(
  { messages: [new HumanMessage("retry")] },
  { forkFrom: "cp_123", multitaskStrategy: "rollback" },
);

5.3 Stop / disconnect

Legacy stop() only aborted the client transport, and per-submit onDisconnect decided whether the agent kept running. v1 makes the split explicit on the stream handle:

Legacy pattern v1 replacement
Stop button in a normal chat (cancel the agent) await stream.stop() — default { cancel: true } calls client.runs.cancel, then disconnects.
Join/rejoin — leave the agent running await stream.disconnect() or await stream.stop({ cancel: false })
submit(..., { onDisconnect: "cancel" }) Call stream.stop() when the user cancels.
submit(..., { onDisconnect: "continue", streamResumable: true }) Call stream.disconnect() when navigating away; reattach by remounting with the same threadId.

6. Companion selector hooks — the new mental model

Legacy useStream returned everything in one object. v1 keeps the always-on data on the root return and pushes the rest into companion selector hooks that ref-count their server subscriptions.

Hook Replaces
useValues(stream) stream.values
useMessages(stream) stream.messages
useToolCalls(stream) stream.toolCalls
useMessageMetadata(stream, msgId) stream.getMessagesMetadata(msg, i)
useSubmissionQueue(stream) stream.queue
useExtension(stream, name) Per-event callbacks
useChannel(stream, channels) Raw event callbacks
useAudio / useImages / useVideo / useFiles —
// Before: everything on the root
const { messages, toolCalls, getMessagesMetadata, queue } = useStream({ assistantId });

// After: always-on stays on the root; rest moves to selectors
const stream = useStream({ assistantId });
const messages = useMessages(stream); // or just stream.messages
const metadata = useMessageMetadata(stream, messages.at(-1)?.id);
const { entries, size, cancel, clear } = useSubmissionQueue(stream);

7. Subagents & subgraphs

Subagents and subgraphs are now discovered eagerly but streamed lazily. The discovery maps (stream.subagents, stream.subgraphs, stream.subgraphsByNode) carry identity fields only — no messages / toolCalls / values. Read those via selector hooks, passing the discovery snapshot as target:

// Before
{
  [...stream.subagents.values()].map((s) => (
    <SubagentCard key={s.id} messages={s.messages} toolCalls={s.toolCalls} />
  ));
}

// After
{
  [...stream.subagents.values()].map((s) => (
    <SubagentCard key={s.id} stream={stream} subagent={s} />
  ));
}

function SubagentCard({ stream, subagent }) {
  const messages = useMessages(stream, subagent);
  const toolCalls = useToolCalls(stream, subagent);
  const values = useValues<ResearcherState>(stream, subagent);
}

8. Headless tools (tools + onTool)

The legacy tools / onTool options are preserved one-for-one. No migration is needed if you were already using this API. The helper exports (flushPendingHeadlessToolInterrupts, findHeadlessTool, handleHeadlessToolInterrupt, …) are still available from @langchain/react.

9. Custom transports: UseStreamTransport → AgentServerAdapter

The legacy UseStreamTransport interface and FetchStreamTransport class are replaced by AgentServerAdapter and the convenience HttpAgentServerAdapter (SSE + WS with injectable fetch / webSocketFactory / defaultHeaders).

// Before
import { FetchStreamTransport, useStream } from "@langchain/react";
const transport = new FetchStreamTransport({ apiUrl: "/api/chat" });
const stream = useStream({ transport });

// After
import { HttpAgentServerAdapter, useStream } from "@langchain/react";
const transport = new HttpAgentServerAdapter({
  apiUrl: "/api/chat",
  threadId: "thread-123", // required: the adapter is bound to a thread
  defaultHeaders: { Authorization: `Bearer ${token}` },
});
const stream = useStream({ transport });

Passing both assistantId + apiUrl and a transport: AgentServerAdapter is now a compile-time error. See Transports for the full interface.

10. StreamProvider / useStreamContext

The provider and consumer are unchanged at the call site. Because the underlying useStream changed, the context value changes accordingly — update any destructuring per §4. StreamProviderProps<T> (Agent Server branch) and StreamProviderCustomProps<T> (custom-adapter branch) mirror the two arms of the options union.

11. useSuspenseStream

useSuspenseStream is now a slim port aligned with the v1 package API, built on top of useStream and the controller's hydrationPromise.

Legacy surface v1 replacement
SuspenseCache, createSuspenseCache, invalidateSuspenseCache Gone. The hook uses a module-level cache keyed on (apiUrl, assistantId, threadId).
suspenseCache option Gone. No caller-side setup required.
fetchStateHistory: { limit } prefetch Gone. The hook hydrates via threads.getState() and suspends until that settles.
branch / setBranch / history / getMessagesMetadata on the return Gone, same as plain useStream. Use the companion hooks (§6).

UseSuspenseStreamReturn<T> is UseStreamReturn<T> minus isLoading / isThreadLoading / hydrationPromise, plus isStreaming: boolean. Non-streaming errors are thrown to the nearest Error Boundary.

12. Type helpers

Helper Use
UseStreamReturn<T> (alias: UseStreamResult<T>) The fully-resolved return type of useStream<T>. Prop-drill as { stream: UseStreamReturn<typeof agent> }.
AnyStream Type-erased handle (UseStreamReturn<any, any, any>).
InferStateType<T> Unwraps a compiled graph / agent brand / agent tool array into its state shape.
InferToolCalls<T> Derives a discriminated union of tool-call shapes.
InferSubagentStates<T> { name: State, … } map derived from a DeepAgent brand.
WidenUpdateMessages<T> Widens messages in a partial state update.
StreamSubmitOptions<State, Configurable> Options shape accepted by submit().
AgentServerAdapter, HttpAgentServerAdapter, HttpAgentServerAdapterOptions Custom-transport interface + convenience class.
UseStreamOptions, AgentServerOptions, CustomAdapterOptions Discriminated options union; rarely needed at call sites.

12.1 Legacy type aliases (removed)

Breaking change: the legacy type aliases re-exported from @langchain/react are no longer available from this package: UseStream, UseSuspenseStream, UseStreamCustom, UseStreamCustomOptions, UseStreamTransport, UseStreamThread, GetToolCallsType, QueueEntry, QueueInterface, SubagentStream, FetchStreamTransport, and others.

Legacy name v1 replacement
UseStream<State, Bag> UseStreamReturn<State> (or UseStreamReturn<typeof agent>).
UseStreamOptions<State, Bag> UseStreamOptions<State> (or just let the hook infer).
UseStreamTransport AgentServerAdapter (§9).
FetchStreamTransport HttpAgentServerAdapter (§9).
GetToolCallsType<State> InferToolCalls<typeof agent>.
UseSuspenseStream<…> UseSuspenseStreamReturn<T> (§11).
QueueEntry, QueueInterface SubmissionQueueEntry, UseSubmissionQueueReturn (§6).
SubagentStream, SubagentStreamInterface SubagentDiscoverySnapshot + useMessages(stream, subagent) (§7).

Apps that cannot migrate all call sites in one pass can keep using the legacy types by importing them directly from @langchain/langgraph-sdk/ui during the transition.

12.2 MessageMetadata collision

Breaking change: v1 exports a new MessageMetadata from @langchain/langgraph-sdk/stream with shape { parentCheckpointId }, different from the legacy one ({ messageId, firstSeenState, branch, branchOptions }). Legacy call sites must import the legacy type from @langchain/langgraph-sdk/ui.

13. Known gaps & server-side prerequisites

Some v1 features are accepted but not yet executed end-to-end on all servers:

Feature Status today
submit(input, { forkFrom }) Type-accepted; forwarded on run.start.
multitaskStrategy: "enqueue" Fully honoured client-side; drains queued entries sequentially.
multitaskStrategy: "reject" Fully honoured client-side — submit() throws when a run is already in flight.
multitaskStrategy: "rollback" (default) Fully honoured client-side — in-flight run aborted, new submission dispatched.
multitaskStrategy: "interrupt" Type-accepted. Falls back to "rollback" until server-side semantics land.
useMessageMetadata().parentCheckpointId Populated from the parent_checkpoint field on values events.

14. FAQ

We still need a raw event stream for analytics. What replaces onLangChainEvent / onDebugEvent / onCustomEvent? Use useChannel(stream, channels) for a bounded buffer of raw events scoped to a namespace, or useExtension(stream, name) for a specific extension. For app-wide telemetry, tee events through a custom AgentServerAdapter (§9).

My backend only emits values events (no messages channel). Will streaming still work? Yes — stream.messages merges messages-channel deltas and values.messages snapshots. Backends that only emit values render full turns at once instead of token-by-token.

We pinned @langchain/langgraph-sdk in app code. Do we need to bump it? Yes. @langchain/react v1 depends on the new event-based streaming runtime in @langchain/langgraph-sdk.

How do I migrate a useStream call that was deeply generic (useStream<State, Bag>)? v1 takes three generics: useStream<T, InterruptType, ConfigurableType> where T is either a plain state shape or an agent brand. The legacy Bag options are gone; if you passed InterruptType via Bag, lift it to the second generic slot.

// Before
useStream<MyState, { InterruptType: MyInterrupt }>({ ... });
// After
useStream<MyState, MyInterrupt>({ ... });