@langchain/angular v1This guide walks application authors through the jump from the pre-v1
injectStream API in @langchain/angular v0 to the injectStream
injector built for new event-based streaming, which ships with
@langchain/angular v1.
Short version: the injectStream 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 and are covered in
dedicated sections.
submit() signature changestools + onTool)UseStreamTransport → AgentServerAdapterprovideStream / injectStream()UseStream<T> → UseStreamReturn<T> & friendsThe legacy injectStream was built against the legacy streaming protocol and
accreted a large surface of opt-in callbacks (onUpdateEvent,
onCustomEvent, onMetadataEvent, onLangChainEvent, onDebugEvent,
onCheckpointEvent, onTaskEvent, onToolEvent, onStop, …) plus
derived state (history, branch, experimental_branchTree,
getMessagesMetadata, joinStream) that had to be recomputed on every
change-detection cycle.
The v1 package injector uses new event-based streaming. In practice that means:
DestroyRef. No more
fan-out cost for views that aren't on screen.values / messages / toolCalls /
interrupts are always available at the root with zero wire cost
beyond the protocol stream itself.isLoading behaves consistently across route
navigations.assistantId and agentServerAdapter is a compile-time error.typeof agent flows through to
values, toolCalls[].args, and subagent-state maps without any
<MyState, MyBag> boilerplate.The net effect is a smaller, faster, more predictable API that still covers every scenario the legacy hook supported.
For the typical app this is the whole migration. Deeper changes are flagged in the later sections.
@langchain/angular to ^1.0.0 and
@langchain/langgraph-sdk to the matching new event-based streaming
runtime.import { injectStream } from "@langchain/angular"
now resolves to the injector built for new event-based streaming.
useStreamExperimental is not
exported from this package.onError, onFinish, onUpdateEvent, onCustomEvent,
onMetadataEvent, onLangChainEvent, onDebugEvent,
onCheckpointEvent, onTaskEvent, onToolEvent, onStop,
fetchStateHistory, reconnectOnMount, throttle, thread,
filterSubagentMessages, subagentToolNames.transport: new FetchStreamTransport(...) with
transport: new HttpAgentServerAdapter(...) — the legacy
FetchStreamTransport class is no longer exported (see §9).branch, setBranch, history, experimental_branchTree,
getMessagesMetadata, toolProgress, joinStream,
switchThread, queue, activeSubagents, getSubagent,
getSubagentsByType, getSubagentsByMessage.getMessagesMetadata(msg)?.firstSeenState?.parent_checkpoint
with injectMessageMetadata(stream, msg.id)?.parentCheckpointId (see
§6).stream.queue with injectSubmissionQueue(stream) (see
§6).stream.switchThread(id) with passing a new threadId
prop and letting the hook reload on change (see §4).injectMessages(stream, subagent) etc.) rather than
reading subagent.messages / subagent.toolCalls off the
discovery snapshot (see §7).injectStream now returns the v1 shape
(matching injectStream minus isLoading / isThreadLoading /
hydrationPromise, plus isStreaming). Remove any
suspenseCache, createSuspenseCache, or fetchStateHistory
props (see §11).submit(..., { onDisconnect, streamResumable }) with
stream.stop() (cancel, default) or stream.disconnect()
(join/rejoin) on the stream handle (see §5.3).tsc. The option bag and return type are now
discriminated and strongly typed; most remaining issues will
surface as type errors that map to one of the sections below.These keep working without changes:
| Option | Notes |
|---|---|
assistantId |
Required for the LGP path; optional (defaults to "_") for custom adapters. |
client |
LGP branch only. |
apiUrl, apiKey, callerOptions, defaultHeaders |
LGP branch only. Passed to the auto-constructed Client. |
threadId, onThreadId |
Unchanged. Pass null to detach; passing a new string reloads the thread and resubscribes. |
initialValues |
Unchanged. |
messagesKey |
Unchanged — defaults to "messages". |
onCreated |
Fires with { runId }. Read the current thread from stream.threadId when needed. |
tools, onTool |
Unchanged semantics; see §8. |
| Option | Notes |
|---|---|
transport |
Two meanings: "sse" / "websocket" selects the built-in wire transport (LGP branch, default "sse"); an AgentServerAdapter instance flips the hook into the custom-adapter branch. |
fetch |
LGP branch only. Forwarded to the built-in SSE transport. |
webSocketFactory |
LGP branch only. Forwarded to the built-in WebSocket transport. |
onCompleted |
Convenience callback for run-execution side effects. Fires with { runId?, reason } when active streaming ends; runId may be absent for re-attached in-flight runs. |
| Legacy option | v1 replacement |
|---|---|
onError (hook-level) |
Read stream.error directly, or pass a per-submit onError via submit(input, { onError }) — the v1 per-submit callback is fire-and-forget and scoped to the one submission it was passed to. |
onFinish |
Use onCompleted for imperative side effects, or derive render state from isLoading / injectValues(stream). |
onUpdateEvent, onCustomEvent, onMetadataEvent, onLangChainEvent, onDebugEvent, onCheckpointEvent, onTaskEvent, onToolEvent |
Drop. New event-based streaming delivers these as structured store updates; read them via selector hooks (useChannel, useExtension) when you genuinely need raw events. |
onStop |
Drop. Use stream.stop() to cancel the active run (default) or stream.disconnect() to leave the agent running server-side. See §5.3. |
fetchStateHistory |
Drop. Fork/edit flows use injectMessageMetadata + submit({}, { forkFrom }) instead (§5). |
reconnectOnMount |
Drop. Re-attach is automatic: remounting the hook with the same threadId attaches to the in-flight run. |
throttle |
Drop. The hook batches state updates natively; call sites that need render throttling can memoize at the selector site. |
thread |
Drop. External thread managers should drive the hook by controlling threadId and initialValues. |
filterSubagentMessages |
Drop. Subagent messages are already absent from stream.messages; they live on per-subagent selector hooks (§7). |
subagentToolNames |
Drop. Subagent classification is driven by new event-based streaming lifecycle events, not by a client-side tool-name list. |
Any previously silent callback-based side effects should migrate into effects that watch the relevant projection:
// Before
injectStream({ onFinish: (state) => analytics.track("turn_finished", state) });
// After
injectStream({
assistantId,
onCompleted: ({ runId, reason }) => analytics.track("turn_finished", { runId, reason }),
});
| Field | Notes |
|---|---|
values |
Now typed as the resolved StateType, non-nullable at the root (falls back to initialValues ?? {}). |
messages |
BaseMessage[] class instances from langchain. |
toolCalls |
AssembledToolCall[] — renamed shape, see §4.3. |
interrupts, interrupt |
Unchanged. interrupt is the most recent root interrupt. |
isLoading |
True while a run is in flight or initial hydration hasn't completed. |
error |
Unchanged. |
threadId |
Unchanged. |
client |
LGP Client when the built-in transport is in use. |
assistantId |
Resolved value including the "_" fallback used by custom adapters. |
submit, stop, respond, disconnect |
submit argument types are wider (§5). stop(options?) cancels server-side by default; disconnect() is join/rejoin client-only (§5.3). |
| Field | What changed |
|---|---|
subagents |
Now a ReadonlyMap<string, SubagentDiscoverySnapshot>. The snapshot only carries id / name / namespace / status — no messages / toolCalls / values. Read those via selector hooks (§7). |
isThreadLoading |
Still exposed but now reflects the initial thread-load lifecycle rather than fetchStateHistory. |
| Legacy field | v1 replacement |
|---|---|
branch, setBranch, experimental_branchTree |
Branching is expressed as fork-from-checkpoint: call injectMessageMetadata(stream, msg.id) to read the message's parent checkpoint and submit(input, { forkFrom }) to fork. |
history, fetchStateHistory |
Dropped from the hook. Fetch history explicitly with client.threads.getHistory(threadId) if you need it; most apps do not. |
getMessagesMetadata(msg, i) |
injectMessageMetadata(stream, msg.id) returns { parentCheckpointId } (see §6). |
toolProgress |
Dropped. Tool progress is now observable via injectToolCalls(stream) — each AssembledToolCall carries its own status. |
joinStream(runId, ...) |
Dropped. Remounting the hook with the right threadId rejoins automatically. |
switchThread(newThreadId) |
Drive threadId as a prop. The hook reloads on change: setThreadId("new-id"). |
queue |
injectSubmissionQueue(stream) companion hook (see §6). |
activeSubagents, getSubagent, getSubagentsByType, getSubagentsByMessage |
Iterate stream.subagents (a Map) and filter inline; every discovery snapshot carries name, status, parentId, namespace, and the tool-call id that spawned it. |
| Field | Purpose |
|---|---|
subgraphs |
ReadonlyMap<string, SubgraphDiscoverySnapshot> — subgraphs discovered on the thread (distinct from subagents). |
subgraphsByNode |
ReadonlyMap<string, SubgraphDiscoverySnapshot[]> — same data keyed by graph node. Arrays to preserve parallel fan-out order. |
// Before
const { messages, isLoading, error, submit, branch, setBranch, getMessagesMetadata } = injectStream(
{
assistantId: "agent",
apiUrl: "http://localhost:2024",
onError: (err) => console.error(err),
fetchStateHistory: true,
},
);
// After
const stream = injectStream({
assistantId: "agent",
apiUrl: "http://localhost:2024",
});
const { messages, isLoading, error, submit } = stream;
useEffect(() => {
if (error) console.error(error);
}, [error]);
// Branching → read the parent checkpoint off the message you want to fork from
const { parentCheckpointId } = injectMessageMetadata(stream, messages.at(-1)?.id) ?? {};
submit() signature changessubmit() now accepts either a wire-format message payload or an
array of BaseMessage class instances:
// All three forms are valid:
await submit({ messages: [{ role: "user", content: "hi" }] });
await submit({ messages: [new HumanMessage("hi")] });
await submit({ messages: new HumanMessage("hi") });
This is driven by the new WidenUpdateMessages<T> helper (exported
publicly for prop-drilling scenarios; see §12). You do not need to
reach for it unless you're typing an intermediate variable.
Legacy SubmitOptions field |
v1 StreamSubmitOptions equivalent |
|---|---|
config.configurable |
config.configurable (unchanged) |
context |
Drop — fold into config.configurable. |
checkpoint: { checkpoint_id } |
forkFrom: "cp_123" (direct checkpoint id string). The earlier non-functional forkFrom: { checkpointId } object form was removed. |
command: { resume } |
Use stream.respond() instead. |
interruptBefore, interruptAfter |
Drop — not supported with new event-based streaming. |
metadata |
Unchanged. |
multitaskStrategy |
Unchanged. "rollback" (default), "reject", and "enqueue" are honoured client-side today; "interrupt" falls back to "rollback" pending server support (see §13). |
onCompletion |
Use the hook-level onCompleted option for run-completion side effects. |
onDisconnect, feedbackKeys, streamMode, runId, optimisticValues, streamSubgraphs, streamResumable, checkpointDuring |
Drop from submit. Disconnect/cancel policy now lives on stop() / disconnect() instead of per-submit options (§5.3). optimisticValues has no client-side analogue — reconcile via values after the run settles. |
(new submit option) onError |
Per-submit fire-and-forget error callback. There is no hook-level onError option; transport-level stream.error updates still happen in parallel. |
(new) threadId |
Per-submit thread override — rebinds the controller to the given thread before dispatching, then keeps it bound until the hook's threadId prop changes again. |
// Before
await submit(
{ messages: [new HumanMessage("retry")] },
{
checkpoint: { checkpoint_id: "cp_123" },
multitaskStrategy: "rollback",
optimisticValues: (prev) => ({ ...prev, pending: true }),
},
);
// After
await submit(
{ messages: [new HumanMessage("retry")] },
{ forkFrom: "cp_123", multitaskStrategy: "rollback" },
);
Legacy stop() only aborted the client transport. Per-submit
onDisconnect: "continue" | "cancel" (often paired with
streamResumable: true) decided whether the agent kept running when
that transport dropped.
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 the client |
| Join/rejoin — leave the agent running | await stream.disconnect() or await stream.stop({ cancel: false }) |
submit(..., { onDisconnect: "cancel" }) |
Drop from submit — call stream.stop() when the user cancels |
submit(..., { onDisconnect: "continue", streamResumable: true }) |
Drop from submit — call stream.disconnect() when navigating away; reattach by remounting with the same threadId |
// Before — disconnect policy lived on submit
await submit(input, { onDisconnect: "continue", streamResumable: true });
// stream.stop() only aborted the client; the server kept running only
// when onDisconnect was "continue"
// After — explicit stop vs disconnect
await submit(input);
await stream.stop(); // chat cancel (server + client)
await stream.disconnect(); // join/rejoin (client only)
runs.cancel is issued only once onCreated has provided a runId.
Stopping before the run is accepted still disconnects the client
immediately.
Legacy injectStream 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. Mount them
where you render, unmount = automatic cleanup.
All of these are exported from @langchain/angular:
| Hook | Replaces | Notes |
|---|---|---|
injectValues(stream) |
stream.values |
Root form is a free read; scoped form (injectValues(stream, target)) opens a namespaced subscription. Explicit generic: injectValues<State>(stream, sub). |
injectMessages(stream) |
stream.messages |
Same pattern. Scoped view yields subagent / subgraph messages without fan-out. |
injectToolCalls(stream) |
stream.toolCalls |
Typed tool-call union is inferred from typeof agent or an explicit tools array. |
injectMessageMetadata(stream, msgId) |
stream.getMessagesMetadata(msg, i) |
Returns { parentCheckpointId } \| undefined. Drives fork-from-checkpoint. |
injectSubmissionQueue(stream) |
stream.queue |
Returns { entries, size, cancel(id), clear() }. Backed by multitaskStrategy: "enqueue". |
useExtension(stream, name) |
Per-event callbacks | Read a named protocol extension (custom channel). |
useChannel(stream, channels) |
Raw event callbacks | Low-level escape hatch. |
useAudio, useImages, useVideo, useFiles |
— | New, multimodal streaming. |
useMediaURL, useAudioPlayer, useVideoPlayer |
— | Helpers built on top of the media hooks. |
// Before: everything on the root
const { messages, toolCalls, getMessagesMetadata, queue } = injectStream({
assistantId,
});
// After: always-on stays on the root; rest moves to selectors
const stream = injectStream({ assistantId });
const messages = injectMessages(stream); // or just stream.messages
const toolCalls = injectToolCalls(stream); // or just stream.toolCalls
const metadata = injectMessageMetadata(stream, messages.at(-1)?.id);
const { entries, size, cancel, clear } = injectSubmissionQueue(stream);
branch flow)function EditButton({ stream, message }: { stream: UseStreamReturn; message: BaseMessage }) {
const metadata = injectMessageMetadata(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>
);
}
queue flow)function Composer({ stream }: { stream: UseStreamReturn }) {
const { entries, cancel, clear } = injectSubmissionQueue(stream);
return (
<>
<button
onClick={() =>
stream.submit({ messages: [new HumanMessage("go")] }, { multitaskStrategy: "enqueue" })
}
>
Queue turn
</button>
<ol>
{entries.map((e) => (
<li key={e.id}>
pending… <button onClick={() => cancel(e.id)}>cancel</button>
</li>
))}
</ol>
{entries.length > 0 && <button onClick={clear}>Clear queue</button>}
</>
);
}
Subagents and subgraphs are now discovered eagerly but streamed
lazily. The discovery maps (stream.subagents,
stream.subgraphs, stream.subgraphsByNode) are kept in sync with
zero extra wire cost; each snapshot exposes identity fields only:
interface SubagentDiscoverySnapshot {
readonly id: string; // tool-call id that spawned it
readonly name: string; // "researcher", "writer", …
readonly namespace: readonly string[];
readonly parentId: string | null;
readonly depth: number;
readonly status: "pending" | "running" | "complete" | "error";
// — no messages / toolCalls / values. Use selector hooks below.
}
Replace every subagent.messages / subagent.toolCalls / subagent.values
read with the matching selector, passing the discovery snapshot:
// 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 = injectMessages(stream, subagent);
const toolCalls = injectToolCalls(stream, subagent);
const values = injectValues<ResearcherState>(stream, subagent);
// …
}
The first time any component mounts injectMessages(stream, subagent) a
messages-channel subscription is opened, scoped to
subagent.namespace. When the last consumer unmounts, the subscription
is released automatically. This is the single biggest wire-cost win of
the new design — views that don't render a subagent's messages never
pay for them.
activeSubagents, getSubagent(id), getSubagentsByType(name), and
getSubagentsByMessage(msg) are gone. Derive the equivalents inline:
const active = [...stream.subagents.values()].filter((s) => s.status === "running");
const researcher = [...stream.subagents.values()].find((s) => s.name === "researcher");
const byType = new Map<string, SubagentDiscoverySnapshot[]>();
for (const s of stream.subagents.values()) {
const bucket = byType.get(s.name) ?? [];
bucket.push(s);
byType.set(s.name, bucket);
}
tools + onTool)The legacy tools / onTool options are preserved one-for-one. The
root hook listens for interrupt payloads that target a registered tool,
invokes the handler, and auto-resumes the run with the handler's return
value — exactly the pre-v1 behaviour. A mount-safe dedupe guard prevents
double invocation when the same interrupt is observed twice.
const stream = injectStream({
assistantId: "deep-agent",
tools: [getCurrentLocation, confirmAction],
onTool: (event) => {
if (event.type === "error") logger.error(event.error);
},
});
No migration is needed if you were already using this API. The helper
exports (flushPendingHeadlessToolInterrupts, findHeadlessTool,
handleHeadlessToolInterrupt, …) are still available from
@langchain/angular for advanced flows that compose their own interrupt
handling.
UseStreamTransport → AgentServerAdapterThe legacy custom-transport surface looked like:
interface UseStreamTransport<S, Bag> {
stream(payload: UseStreamTransportPayload<S, Bag>): Promise<
AsyncGenerator<{ id?: string; event: string; data: unknown }>
>;
}
// The convenience class that implemented it:
class FetchStreamTransport implements UseStreamTransport { ... }
v1 replaces this with AgentServerAdapter, a richer interface that
owns the entire transport — both commands and the event stream —
and matches the new event-based streaming protocol's request shape. There is a convenience
class HttpAgentServerAdapter that covers the common case (SSE + WS
with injectable fetch / webSocketFactory / defaultHeaders).
HttpAgentServerAdapter// Before
import { FetchStreamTransport, injectStream } from "@langchain/angular";
const transport = new FetchStreamTransport({ apiUrl: "/api/chat" });
const stream = injectStream({ transport });
// After
import { HttpAgentServerAdapter, injectStream } from "@langchain/angular";
const transport = new HttpAgentServerAdapter({
apiUrl: "/api/chat",
threadId: "thread-123", // required: the adapter is bound to a thread
defaultHeaders: { Authorization: `Bearer ${token}` },
// Optional: fetch override or webSocketFactory for WebSocket transports
fetch: myAuthedFetch,
});
const stream = injectStream({ transport });
If you hand-rolled a UseStreamTransport, migrate to
AgentServerAdapter:
interface AgentServerAdapter {
readonly threadId: string;
open(): Promise<void>;
send(command: Command): Promise<CommandResponse | ErrorResponse | void>;
events(): AsyncIterable<Message>;
openEventStream?(params: SubscribeParams): EventStreamHandle;
close(): Promise<void>;
// Optional — implement if your server supports them:
getState?(): Promise<{
values: unknown;
checkpoint?: { checkpoint_id?: string } | null;
} | null>;
getHistory?(options?: { limit?: number }): Promise<
Array<{
values: unknown;
checkpoint?: { checkpoint_id?: string } | null;
}>
>;
}
The adapter is used exactly as-is; the LGP Client is not
constructed when a custom adapter is supplied, so bundles that only
use a custom adapter tree-shake the entire sse/websocket transport
stack.
Passing both assistantId + apiUrl and a transport: AgentServerAdapter is now a compile-time error:
injectStream({
assistantId: "agent",
apiUrl: "http://localhost:2024",
transport: myAdapter, // ❌ `apiUrl` is `never` on the custom-adapter branch
});
Pick one branch per injectStream instance.
provideStream / injectStreamThe provider and consumer are unchanged at the call site — the
provider wraps whatever injectStream returns and publishes it over React
context:
<provideStream assistantId="agent" apiUrl="http://localhost:2024">
<Chat />
</provideStream>;
function Chat() {
const { messages, submit } = injectStream();
// …
}
Because the underlying injectStream changed, the context value changes
accordingly — any destructuring in consumers must be updated per §4.
Generic usage (injectStream<typeof agent>()) propagates the
typeof agent inference through to values / toolCalls /
subagents automatically.
provideStreamProps<T> (LGP branch) and provideStreamCustomProps<T>
(custom-adapter branch) mirror the two arms of the injectStream options
union; pass transport: adapter to route through a custom
AgentServerAdapter.
@langchain/angular does not ship a separate suspense hook. Use
injectStream directly and gate your templates on
isLoading, isThreadLoading, or hydrationPromise:
import { Component, inject } from "@angular/core";
import { injectStream } from "@langchain/angular";
@Component({
template: `
@if (stream.isThreadLoading()) {
<app-spinner />
} @else {
<app-message-list />
}
`,
})
export class Chat {
readonly stream = injectStream({
assistantId: "agent",
apiUrl: "http://localhost:2024",
threadId,
});
}
hydrationPromise settles when the active thread's initial hydrate
completes — useful for deferring child routes until the first snapshot
lands.
The v1 package exports a small set of type helpers you reach for when prop-drilling a stream handle:
| Helper | Use |
|---|---|
UseStreamReturn<T> |
The fully-resolved return type of injectStream<T>. Prop-drill as { stream: UseStreamReturn<typeof agent> }. |
AnyStream |
Type-erased handle (UseStreamReturn<any, any, any>) for components that only forward the stream to selector hooks. |
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 from tools array, agent brand, or an explicit shape. |
InferSubagentStates<T> |
{ name: State, … } map derived from a DeepAgent brand. |
WidenUpdateMessages<T> |
Widens messages in a partial state update so both wire-format and BaseMessage instances typecheck in submit(). |
StreamSubmitOptions<State, Configurable> |
Options shape accepted by submit(). |
AgentServerAdapter |
Interface for custom transports (see §9). |
HttpAgentServerAdapter, HttpAgentServerAdapterOptions |
Convenience adapter (see §9). |
UseStreamOptions, AgentServerOptions, CustomAdapterOptions |
Discriminated options union; rarely needed at call sites. |
SelectorTarget, SubagentDiscoverySnapshot, SubgraphDiscoverySnapshot |
For components that render per-subagent/subgraph views. |
AssembledToolCall, ToolCallStatus |
For rendering tool-call UI. |
MessageMetadata, MessageMetadataMap, UseSubmissionQueueReturn, SubmissionQueueEntry, SubmissionQueueSnapshot |
Companion-hook return shapes. |
Breaking change: the legacy type aliases that used to be
re-exported from @langchain/angular are no longer available from
this package. The following names are gone:
UseStream, UseSuspenseStream, UseStreamCustom, UseStreamOptions,
UseStreamCustomOptions, UseStreamTransport, UseStreamThread,
GetToolCallsType, QueueEntry, QueueInterface,
SubagentStream, SubagentStreamInterface,
BaseSubagentState, SubagentStateMap, DefaultSubagentStates,
InferAgentToolCalls, InferDeepAgentSubagents,
InferSubagentByName, InferSubagentNames, InferSubagentState,
SubAgentLike, CompiledSubAgentLike, DeepAgentTypeConfigLike,
AgentTypeConfigLike, IsAgentLike, IsDeepAgentLike,
ExtractAgentConfig, ExtractDeepAgentConfig,
ExtractSubAgentMiddleware, SubagentToolCall, SubagentStatus,
ClassSubagentStreamInterface, FetchStreamTransport.
Migrate each call site to the v1 name:
| 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> (v1 package API shape — see §11). |
QueueEntry, QueueInterface |
SubmissionQueueEntry, UseSubmissionQueueReturn (§6). |
SubagentStream, SubagentStreamInterface |
SubagentDiscoverySnapshot + injectMessages(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 — but mixing
legacy and v1 types on the same injectStream result will not
typecheck.
MessageMetadata collisionBreaking change: @langchain/angular v1 exports a new
MessageMetadata from @langchain/langgraph-sdk/stream with a
different shape ({ parentCheckpointId }) than the legacy one
({ messageId, firstSeenState, branch, branchOptions }). Legacy call
sites must import the legacy type directly:
// Before
import type { MessageMetadata } from "@langchain/angular";
// After (only if you still need the legacy shape)
import type { MessageMetadata } from "@langchain/langgraph-sdk/ui";
Some v1 type-level features are accepted but not yet executed end- to-end — they are wired through the client and will activate once the matching server-side protocol work ships. This matters if you are on self-hosted LangGraph or a pinned server version.
| Feature | Status today |
|---|---|
submit(input, { forkFrom }) |
Type-accepted; forwarded on run.start. |
multitaskStrategy: "enqueue" |
Fully honoured client-side. The controller records the submission in queueStore, exposes it via injectSubmissionQueue(stream), and drains queued entries sequentially after each run settles. Server-native queueing lands with A0.3. |
multitaskStrategy: "reject" |
Fully honoured client-side — submit() throws when a run is already in flight. |
multitaskStrategy: "rollback" (default) |
Fully honoured client-side — the in-flight run is aborted and the new submission dispatches immediately. |
multitaskStrategy: "interrupt" |
Type-accepted. Falls back to "rollback" behaviour until server-side interrupt semantics land (A0.3). |
injectMessageMetadata().parentCheckpointId |
Populated from the parent_checkpoint field on values events. |
Until those land, the client-side scaffolding is a no-op for the relevant field rather than a crash — code written against the v1 surface will start to function the moment the server catches up.
onLangChainEvent / onDebugEvent / onCustomEvent?Use useChannel(stream, channels) for a bounded buffer of raw events
scoped to a namespace, or subscribe to a specific extension with
useExtension(stream, name). For app-wide telemetry, pipe the raw
stream through a custom AgentServerAdapter (§9) and tee events to
your analytics sink there.
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 will
render full turns at once instead of token-by-token. This is a backend
concern; the React layer faithfully renders whatever the server sends.
@langchain/langgraph-sdk in app code. Do we need to bump it?Yes. @langchain/angular v1 depends on the new event-based streaming runtime in
@langchain/langgraph-sdk. Bumping the SDK is mandatory, but the
public v1 type helpers (InferStateType, AgentServerAdapter, …)
are re-exported from @langchain/angular so app imports rarely need to
reach into the SDK directly.
injectStream call that was deeply generic (injectStream<State, Bag>)?v1 takes three generics: injectStream<T, InterruptType, ConfigurableType>
where T is either a plain state shape or an agent brand
(typeof agent). The legacy Bag options (UpdateType,
CustomEventType, MetaType) are gone — widening is automatic for
messages, and update / custom / meta shapes are no longer tracked by
type. If you passed InterruptType via Bag, lift it to the second
generic slot.
// Before
injectStream<MyState, { InterruptType: MyInterrupt }>({ ... });
// After
injectStream<MyState, MyInterrupt>({ ... });
injectStream / FetchStreamTransport surface go?The pre-v1 injectStream implementation and legacy type re-exports have
been removed from @langchain/angular v1. In particular:
import { FetchStreamTransport } from "@langchain/angular" no longer
resolves — use HttpAgentServerAdapter from the same package (§9).UseStream, UseStreamOptions,
UseStreamTransport, QueueEntry, SubagentStream, …) are no
longer re-exported; use the v1 names from §12.@langchain/angular does not ship useSuspenseStream. Use
injectStream and gate on isThreadLoading / hydrationPromise
(§11).Apps that still need the legacy types mid-migration can import them
directly from @langchain/langgraph-sdk/ui. No pre-v1 runtime code
remains in the @langchain/angular bundle.
multitaskStrategy: "enqueue" work today?Yes, end-to-end on the client. Submissions issued with { multitaskStrategy: "enqueue" } while another run is in flight are
recorded in the controller's queueStore, exposed via
injectSubmissionQueue(stream) (with cancel(id) / clear()
affordances), and drained sequentially once the active run settles.
Switching threads clears the queue.