LangChain Reference home pageLangChain ReferenceLangChain Reference
  • GitHub
  • Main Docs
Deep Agents
LangChain
LangGraph
Integrations
LangSmith
LangGraph
  • Web
  • Channels
  • Pregel
  • Prebuilt
  • Remote
  • Overview
  • Getting started
  • injectStream
  • Selectors
  • Interrupts & headless tools
  • Subagents & subgraphs
  • Fork & edit from a checkpoint
  • Submission queue
  • Multimodal media
  • Transports
  • Dependency injection
  • 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 startedinjectStreamSelectorsInterrupts & headless toolsSubagents & subgraphsFork & edit from a checkpointSubmission queueMultimodal mediaTransportsDependency injectionType 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/angularMigrating to v1

Migrating to v1

Migrating to @langchain/angular v1

This 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.


Table of contents

  1. Why the breaking change?
  2. TL;DR migration checklist
  3. Option-bag migration
  4. Return-shape migration
  5. submit() signature changes
  6. Companion selector injectors — the new mental model
  7. Subagents & subgraphs
  8. Headless tools (tools + onTool)
  9. Custom transports: UseStreamTransport → AgentServerAdapter
  10. provideStream / injectStream()
  11. Hydration and loading state
  12. Type helpers: UseStream<T> → UseStreamReturn<T> & friends
  13. Known gaps & server-side prerequisites
  14. FAQ

1. Why the breaking change?

The 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:

  • Selector-based subscriptions. Namespaced data (subagent messages, subgraph tool calls, media) is opened only when a component actually mounts a selector injector, and released on DestroyRef. No more fan-out cost for views that aren't on screen.
  • Always-on root projections. values / messages / toolCalls / interrupts are always available at the root with zero wire cost beyond the protocol stream itself.
  • First-class re-attach. Reconstructing the injector on an in-flight thread attaches to the live subscription instead of replaying from scratch; isLoading behaves consistently across route navigations.
  • Discriminated option bag. The LGP path and the custom-adapter path are now two arms of a discriminated union, so passing both assistantId and agentServerAdapter is a compile-time error.
  • Type inference from agent brands. 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.


2. TL;DR migration checklist

For the typical app this is the whole migration. Deeper changes are flagged in the later sections.

  • Upgrade @langchain/angular to ^1.0.0 and @langchain/langgraph-sdk to the matching new event-based streaming runtime.
  • Imports stay the same — import { injectStream } from "@langchain/angular" now resolves to the injector built for new event-based streaming. useStreamExperimental is not exported from this package.
  • Remove these option-bag fields (they are 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(...) — the legacy FetchStreamTransport class is no longer exported (see §9).
  • Remove these return-shape fields (they moved or were dropped; see §4): branch, setBranch, history, experimental_branchTree, getMessagesMetadata, toolProgress, joinStream, switchThread, queue, activeSubagents, getSubagent, getSubagentsByType, getSubagentsByMessage.
  • Replace getMessagesMetadata(msg)?.firstSeenState?.parent_checkpoint with injectMessageMetadata(stream, msg.id)?.parentCheckpointId (see §6).
  • Replace stream.queue with injectSubmissionQueue(stream) (see §6).
  • Replace stream.switchThread(id) with passing a new threadId prop and letting the hook reload on change (see §4).
  • Inside subagent-aware UIs, read per-subagent data with the new selector hooks (injectMessages(stream, subagent) etc.) rather than reading subagent.messages / subagent.toolCalls off the discovery snapshot (see §7).
  • Suspense apps: injectStream now returns the v1 shape (matching injectStream minus isLoading / isThreadLoading / hydrationPromise, plus isStreaming). Remove any suspenseCache, createSuspenseCache, or fetchStateHistory props (see §11).
  • Replace submit(..., { onDisconnect, streamResumable }) with stream.stop() (cancel, default) or stream.disconnect() (join/rejoin) on the stream handle (see §5.3).
  • Re-run 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.

3. Option-bag migration

3.1 Still supported — same meaning

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.

3.2 New option

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.

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 }) — 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 }),
});

4. Return-shape migration

4.1 Still there — same meaning

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).

4.2 Still there — different meaning

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.

4.3 Removed — with replacements

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.

4.4 New fields

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.

4.5 Worked example — minimal diff

// 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) ?? {};

5. submit() signature changes

5.1 Input widening

submit() 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.

5.2 Option changes

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" },
);

5.3 Stop / disconnect

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.


6. Companion selector hooks — the new mental model

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);

6.1 Fork from message (the old 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>
  );
}

6.2 Enqueue-and-cancel (the old 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>}
    </>
  );
}

7. Subagents & subgraphs

7.1 Discovery

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.
}

7.2 Per-subagent content

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.

7.3 Removed helpers

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);
}

8. Headless tools (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.


9. Custom transports: UseStreamTransport → AgentServerAdapter

The 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).

9.1 Most apps: drop in 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 });

9.2 Custom implementations

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.

9.3 Discriminated options

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.


10. provideStream / injectStream

The 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.


11. Hydration and loading state

@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.


13. Type helpers

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.

12.1 Legacy type aliases (removed)

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.

12.2 MessageMetadata collision

Breaking 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";

14. Known gaps & server-side prerequisites

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.


14. FAQ

Q. 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 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.

Q. 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 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.

Q. We pinned @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.

Q. How do I migrate a 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>({ ... });

Q. Where did the legacy 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).
  • The legacy type aliases (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.

Q. Does 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.