The root useStream hook exposes always-on projections (values, messages, toolCalls, interrupts, error, isLoading, discovery maps). Anything else — scoped subagent state, message metadata, the submission queue, raw channels, media — is available through the companion selector hooks.
Each selector hook opens a ref-counted subscription when the first component mounts it and releases it when the last consumer unmounts. Root calls (no target) are free — they read the already-mounted root projection directly.
All scoped selectors accept a target argument. Valid targets are:
undefined (or omitted) — the root namespace. Free read.SubagentDiscoverySnapshot — as exposed via stream.subagents.values().SubgraphDiscoverySnapshot — as exposed via stream.subgraphs / stream.subgraphsByNode.{ namespace: string[] } (or a raw string[]) — an explicit namespace, useful for custom routing.Subscriptions open on mount and close when the last consumer for a given (channel, namespace) tuple unmounts. Components that don't render a subagent's content never pay for its wire traffic.
| Hook | Returns | Use for |
|---|---|---|
useValues(stream, target?) |
StateType (root) / T \| undefined (scoped) |
Arbitrary state / scoped snapshot. |
useMessages(stream, target?) |
BaseMessage[] |
Message stream, root or scoped. |
useToolCalls(stream, target?) |
AssembledToolCall[] |
Tool-call stream, with per-call status. |
useMessageMetadata(stream, msgId) |
{ parentCheckpointId } \| undefined |
Powers fork / edit flows. See Fork & edit. |
useSubmissionQueue(stream) |
{ entries, size, cancel, clear } |
Reactive client-side submission queue. See Queue. |
useExtension(stream, name, target?) |
T \| undefined |
Read a named custom:<name> extension. |
useChannel(stream, channels, target?, options?) |
Event[] |
Low-level raw-events escape hatch. |
useAudio / useImages / useVideo / useFiles |
AudioMedia[] / ImageMedia[] / VideoMedia[] / FileMedia[] |
Assembled multimodal streams. See Multimodal. |
useMediaUrl(handle) |
string \| undefined |
Turns a media handle into an <img/audio/video src>. |
useAudioPlayer(handle, options?) / useVideoPlayer(handle, options?) |
Player handles | Opinionated playback helpers on top of the media hooks. |
import {
useStream,
useMessages,
useToolCalls,
useValues,
type AnyStream,
type SubagentDiscoverySnapshot,
} from "@langchain/svelte";
function Chat() {
const stream = useStream({ assistantId: "agent", apiUrl: "/api" });
// Root projections — identical to `stream.messages` / `stream.values`.
// These calls are free: no new subscription is opened.
const rootMessages = useMessages(stream);
const rootValues = useValues(stream);
return (
<>
<ThreadView messages={rootMessages} />
{[...stream.subagents.values()].map((s) => (
<SubagentCard key={s.id} stream={stream} subagent={s} />
))}
</>
);
}
function SubagentCard({
stream,
subagent,
}: {
stream: AnyStream;
subagent: SubagentDiscoverySnapshot;
}) {
// Scoped: opens a namespaced subscription for this subagent only.
const messages = useMessages(stream, subagent);
const toolCalls = useToolCalls(stream, subagent);
const values = useValues<ResearcherState>(stream, subagent);
return (
<section>
<header>
{subagent.name} — {subagent.status}
</header>
{messages.map((m) => (
<Bubble key={m.id} msg={m} />
))}
</section>
);
}
useMessageMetadataReturns { parentCheckpointId } (and undefined while loading). Use it to drive fork / edit UIs:
import { useMessageMetadata } from "@langchain/svelte";
function EditButton({ stream, message }) {
const metadata = useMessageMetadata(stream, message.id);
return (
<button
disabled={!metadata?.parentCheckpointId}
onClick={() => {
const forkFrom = metadata?.parentCheckpointId;
if (!forkFrom) return;
void stream.submit({ messages: [new HumanMessage("...revised prompt...")] }, { forkFrom });
}}
>
Edit from here
</button>
);
}
See Fork & edit from a checkpoint for the full flow.
useChannelEscape hatch to the raw protocol event stream. Subscribe to one or more channels and get the buffered events as an array:
const events = useChannel(stream, ["values", "updates"]);
Pass target (subagent / subgraph / { namespace }) to scope. Useful for bespoke reducers that can't be expressed through useValues / useMessages.
useExtensionRead a single custom extension (wire-level custom:<name> channel) as a reactive snapshot:
const telemetry = useExtension<Telemetry>(stream, "telemetry");
Runnable example: The
a2uiapp in the streaming cookbook drives a generative UI withuseExtension(stream, "a2ui"); thestreamingpackage'scustom-transformer:*scripts show the server side.
useProjection — building your own selectoruseProjection is the low-level primitive every built-in selector is composed from. Reach for it only when you need a custom projection that the built-in hooks don't express. It acquires a ref-counted projection from the stream's channel registry and subscribes to its store via useSyncExternalStore:
import { useProjection } from "@langchain/svelte";
import { STREAM_CONTROLLER } from "@langchain/svelte"; // low-level access key
import { valuesProjection } from "@langchain/langgraph-sdk/stream";
function useScoredValues<T>(stream: AnyStream, namespace: readonly string[]) {
const registry = stream[STREAM_CONTROLLER].registry;
return useProjection<T | undefined>(
registry,
() => valuesProjection<T>(namespace, "messages"),
`values|${namespace.join(">")}`,
undefined,
);
}
The first render (before the effect runs) returns initialValue. Subsequent renders read from the acquired store. When the last consumer of a given key unmounts, the underlying server subscription is released automatically. Most apps never need this — prefer useValues / useMessages / useToolCalls / useChannel.
Subscribe to a scoped values stream — the most recent state
Subscribe to a scoped messages stream.
Subscribe to a scoped tools (tool-call) stream. Same target and
Read metadata recorded for a specific message id — today exposes
Subscribe to a custom:<name> stream extension — most-recent
Svelte binding over ChannelRegistry.acquire. Mirrors the