@langchain/react 0.3.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +48 -523
  2. package/dist/context.cjs +12 -30
  3. package/dist/context.cjs.map +1 -1
  4. package/dist/context.d.cts +22 -39
  5. package/dist/context.d.cts.map +1 -1
  6. package/dist/context.d.ts +22 -39
  7. package/dist/context.d.ts.map +1 -1
  8. package/dist/context.js +11 -29
  9. package/dist/context.js.map +1 -1
  10. package/dist/index.cjs +29 -30
  11. package/dist/index.d.cts +10 -7
  12. package/dist/index.d.cts.map +1 -1
  13. package/dist/index.d.ts +10 -7
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +10 -6
  16. package/dist/selectors.cjs +178 -0
  17. package/dist/selectors.cjs.map +1 -0
  18. package/dist/selectors.d.cts +183 -0
  19. package/dist/selectors.d.cts.map +1 -0
  20. package/dist/selectors.d.ts +183 -0
  21. package/dist/selectors.d.ts.map +1 -0
  22. package/dist/selectors.js +168 -0
  23. package/dist/selectors.js.map +1 -0
  24. package/dist/suspense-stream.cjs +34 -159
  25. package/dist/suspense-stream.cjs.map +1 -1
  26. package/dist/suspense-stream.d.cts +15 -71
  27. package/dist/suspense-stream.d.cts.map +1 -1
  28. package/dist/suspense-stream.d.ts +15 -71
  29. package/dist/suspense-stream.d.ts.map +1 -1
  30. package/dist/suspense-stream.js +35 -158
  31. package/dist/suspense-stream.js.map +1 -1
  32. package/dist/use-audio-player.cjs +679 -0
  33. package/dist/use-audio-player.cjs.map +1 -0
  34. package/dist/use-audio-player.d.cts +161 -0
  35. package/dist/use-audio-player.d.cts.map +1 -0
  36. package/dist/use-audio-player.d.ts +161 -0
  37. package/dist/use-audio-player.d.ts.map +1 -0
  38. package/dist/use-audio-player.js +679 -0
  39. package/dist/use-audio-player.js.map +1 -0
  40. package/dist/use-media-url.cjs +49 -0
  41. package/dist/use-media-url.cjs.map +1 -0
  42. package/dist/use-media-url.d.cts +28 -0
  43. package/dist/use-media-url.d.cts.map +1 -0
  44. package/dist/use-media-url.d.ts +28 -0
  45. package/dist/use-media-url.d.ts.map +1 -0
  46. package/dist/use-media-url.js +49 -0
  47. package/dist/use-media-url.js.map +1 -0
  48. package/dist/use-projection.cjs +41 -0
  49. package/dist/use-projection.cjs.map +1 -0
  50. package/dist/use-projection.d.cts +27 -0
  51. package/dist/use-projection.d.cts.map +1 -0
  52. package/dist/use-projection.d.ts +27 -0
  53. package/dist/use-projection.d.ts.map +1 -0
  54. package/dist/use-projection.js +41 -0
  55. package/dist/use-projection.js.map +1 -0
  56. package/dist/use-stream.cjs +185 -0
  57. package/dist/use-stream.cjs.map +1 -0
  58. package/dist/use-stream.d.cts +184 -0
  59. package/dist/use-stream.d.cts.map +1 -0
  60. package/dist/use-stream.d.ts +184 -0
  61. package/dist/use-stream.d.ts.map +1 -0
  62. package/dist/use-stream.js +183 -0
  63. package/dist/use-stream.js.map +1 -0
  64. package/dist/use-video-player.cjs +218 -0
  65. package/dist/use-video-player.cjs.map +1 -0
  66. package/dist/use-video-player.d.cts +65 -0
  67. package/dist/use-video-player.d.cts.map +1 -0
  68. package/dist/use-video-player.d.ts +65 -0
  69. package/dist/use-video-player.d.ts.map +1 -0
  70. package/dist/use-video-player.js +218 -0
  71. package/dist/use-video-player.js.map +1 -0
  72. package/package.json +9 -8
  73. package/dist/stream.cjs +0 -18
  74. package/dist/stream.cjs.map +0 -1
  75. package/dist/stream.custom.cjs +0 -209
  76. package/dist/stream.custom.cjs.map +0 -1
  77. package/dist/stream.custom.d.cts +0 -3
  78. package/dist/stream.custom.d.ts +0 -3
  79. package/dist/stream.custom.js +0 -209
  80. package/dist/stream.custom.js.map +0 -1
  81. package/dist/stream.d.cts +0 -174
  82. package/dist/stream.d.cts.map +0 -1
  83. package/dist/stream.d.ts +0 -174
  84. package/dist/stream.d.ts.map +0 -1
  85. package/dist/stream.js +0 -18
  86. package/dist/stream.js.map +0 -1
  87. package/dist/stream.lgp.cjs +0 -671
  88. package/dist/stream.lgp.cjs.map +0 -1
  89. package/dist/stream.lgp.js +0 -671
  90. package/dist/stream.lgp.js.map +0 -1
  91. package/dist/thread.cjs +0 -18
  92. package/dist/thread.cjs.map +0 -1
  93. package/dist/thread.js +0 -18
  94. package/dist/thread.js.map +0 -1
  95. package/dist/types.d.cts +0 -109
  96. package/dist/types.d.cts.map +0 -1
  97. package/dist/types.d.ts +0 -109
  98. package/dist/types.d.ts.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,18 @@
1
1
  # @langchain/react
2
2
 
3
- React SDK for building AI-powered applications with [Deep Agents](https://docs.langchain.com/oss/javascript/deepagents/overview), [LangChain](https://docs.langchain.com/oss/javascript/langchain/overview) and [LangGraph](https://docs.langchain.com/oss/javascript/langgraph/overview). It provides a `useStream` hook that manages streaming, state, branching, and interrupts out of the box.
3
+ React SDK for building AI-powered applications with [Deep Agents](https://docs.langchain.com/oss/javascript/deepagents/overview), [LangChain](https://docs.langchain.com/oss/javascript/langchain/overview) and [LangGraph](https://docs.langchain.com/oss/javascript/langgraph/overview).
4
+
5
+ `@langchain/react` v1 ships a v2-native `useStream` hook together with a small family of companion selector hooks. The root hook gives you always-on access to thread state, messages, tool calls, and interrupts; the selector hooks open ref-counted subscriptions for the things that aren't needed on every view (per-subagent messages, media streams, submission queue, message metadata, raw channels, …).
6
+
7
+ ## Highlights
8
+
9
+ - **v2-native streaming protocol.** Session-based transport with automatic re-attach on remount; no more `reconnectOnMount` / `joinStream` dance.
10
+ - **Selector-based subscriptions.** Namespaced data (subagents, subgraphs, media) streams only when a component actually mounts the matching selector hook, and releases on unmount.
11
+ - **Always-on root projections.** `values`, `messages`, `toolCalls`, and `interrupts` are live at the root with zero per-subscription cost.
12
+ - **Agent-brand type inference.** `useStream<typeof agent>()` unwraps state, tool calls, and subagent state maps from an agent brand.
13
+ - **Discriminated options.** The hosted Agent Server path and the custom-adapter path are two arms of a single typed union — mixing them is a compile-time error.
14
+ - **Multimodal media streams.** Built-in assembly for audio, images, video, and files.
15
+ - **Suspense integration.** `useSuspenseStream` hands the initial hydration phase to `<Suspense>` and non-streaming errors to Error Boundaries.
4
16
 
5
17
  ## Installation
6
18
 
@@ -8,7 +20,7 @@ React SDK for building AI-powered applications with [Deep Agents](https://docs.l
8
20
  npm install @langchain/react @langchain/core
9
21
  ```
10
22
 
11
- **Peer dependencies:** `react` (^18 || ^19), `@langchain/core` (^1.1.27)
23
+ **Peer dependencies:** `react` (^18 || ^19), `@langchain/core` (^1.1.27).
12
24
 
13
25
  ## Quick Start
14
26
 
@@ -24,389 +36,9 @@ function Chat() {
24
36
  return (
25
37
  <div>
26
38
  {messages.map((msg, i) => (
27
- <div key={msg.id ?? i}>{msg.content}</div>
28
- ))}
29
-
30
- <button
31
- disabled={isLoading}
32
- onClick={() =>
33
- void submit({
34
- messages: [{ type: "human", content: "Hello!" }],
35
- })
36
- }
37
- >
38
- Send
39
- </button>
40
- </div>
41
- );
42
- }
43
- ```
44
-
45
- ## `useStream` Options
46
-
47
- | Option | Type | Description |
48
- |---|---|---|
49
- | `assistantId` | `string` | **Required.** The assistant/graph ID to stream from. |
50
- | `apiUrl` | `string` | Base URL of the LangGraph API. |
51
- | `client` | `Client` | Pre-configured `Client` instance (alternative to `apiUrl`). |
52
- | `messagesKey` | `string` | State key containing messages. Defaults to `"messages"`. |
53
- | `initialValues` | `StateType` | Initial state values before any stream data arrives. |
54
- | `fetchStateHistory` | `boolean \| { limit: number }` | Fetch thread history on stream completion. Enables branching. |
55
- | `throttle` | `boolean \| number` | Throttle state updates for performance. |
56
- | `onFinish` | `(state, error?) => void` | Called when the stream completes. |
57
- | `onError` | `(error, state?) => void` | Called on stream errors. |
58
- | `onThreadId` | `(threadId) => void` | Called when a new thread is created. |
59
- | `onUpdateEvent` | `(event) => void` | Receive update events from the stream. |
60
- | `onCustomEvent` | `(event) => void` | Receive custom events from the stream. |
61
- | `onStop` | `() => void` | Called when the stream is stopped by the user. |
62
-
63
- ## Return Values
64
-
65
- | Property | Type | Description |
66
- |---|---|---|
67
- | `values` | `StateType` | Current graph state. |
68
- | `messages` | `Message[]` | Messages from the current state. |
69
- | `isLoading` | `boolean` | Whether a stream is currently active. |
70
- | `error` | `unknown` | The most recent error, if any. |
71
- | `interrupt` | `Interrupt \| undefined` | Current interrupt requiring user input. |
72
- | `branch` | `string` | Active branch identifier. |
73
- | `submit(values, options?)` | `function` | Submit new input to the graph. When called while a stream is active, the run is created on the server with `multitaskStrategy: "enqueue"` and queued automatically. |
74
- | `stop()` | `function` | Cancel the active stream. |
75
- | `setBranch(branch)` | `function` | Switch to a different conversation branch. |
76
- | `getMessagesMetadata(msg, index?)` | `function` | Get branching and checkpoint metadata for a message. |
77
- | `switchThread(id)` | `(id: string \| null) => void` | Switch to a different thread. Pass `null` to start a new thread on next submit. |
78
- | `queue.entries` | `ReadonlyArray<QueueEntry>` | Pending server-side runs. Each entry has `id` (server run ID), `values`, `options`, and `createdAt`. |
79
- | `queue.size` | `number` | Number of pending runs on the server. |
80
- | `queue.cancel(id)` | `(id: string) => Promise<boolean>` | Cancel a pending run on the server by its run ID. |
81
- | `queue.clear()` | `() => Promise<void>` | Cancel all pending runs on the server. |
82
-
83
- ## `useSuspenseStream`
84
-
85
- `useSuspenseStream` is a companion hook to `useStream` that integrates with React's [Suspense](https://react.dev/reference/react/Suspense) and [Error Boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) protocols. Instead of handling loading and error states inside your component, you declare them in parent boundaries:
86
-
87
- ```tsx
88
- import { Suspense } from "react";
89
- import { ErrorBoundary } from "react-error-boundary";
90
- import { useSuspenseStream } from "@langchain/react";
91
-
92
- function App() {
93
- return (
94
- <ErrorBoundary
95
- fallback={({ error, resetErrorBoundary }) => (
96
- <div>
97
- <p>{error.message}</p>
98
- <button onClick={resetErrorBoundary}>Retry</button>
99
- </div>
100
- )}
101
- >
102
- <Suspense fallback={<Spinner />}>
103
- <Chat />
104
- </Suspense>
105
- </ErrorBoundary>
106
- );
107
- }
108
-
109
- function Chat() {
110
- // No isLoading/error checks needed — Suspense and ErrorBoundary handle them.
111
- const { messages, submit, isStreaming } = useSuspenseStream({
112
- assistantId: "agent",
113
- apiUrl: "http://localhost:2024",
114
- });
115
-
116
- return (
117
- <div>
118
- {messages.map((msg, i) => (
119
- <div key={msg.id ?? i}>{msg.content}</div>
120
- ))}
121
- {isStreaming && <TypingIndicator />}
122
-
123
- <button
124
- onClick={() =>
125
- void submit({
126
- messages: [{ type: "human", content: "Hello!" }],
127
- })
128
- }
129
- >
130
- Send
131
- </button>
132
- </div>
133
- );
134
- }
135
- ```
136
-
137
- ### How it works
138
-
139
- - **Suspends** while the initial thread history is loading (e.g. when a `threadId` is provided and the thread data is being fetched). The nearest `<Suspense>` boundary renders its fallback during this time.
140
- - **Throws errors** to the nearest Error Boundary when the stream encounters an error outside of active streaming.
141
- - **Does not suspend during streaming.** Streaming is incremental — messages arrive progressively and the UI must update in real time. The `isStreaming` flag indicates whether tokens are currently arriving.
142
-
143
- ### Options
144
-
145
- `useSuspenseStream` accepts the same options as `useStream` (LangGraph Platform mode), plus:
146
-
147
- | Option | Type | Description |
148
- |---|---|---|
149
- | `suspenseCache` | `SuspenseCache` | Optional cache instance for Suspense history prefetching. Useful in tests to avoid cross-test cache sharing. |
150
-
151
- ### Return Values
152
-
153
- The return type is identical to `useStream` except:
154
-
155
- | Removed | Reason |
156
- |---|---|
157
- | `isLoading` | Replaced by `isStreaming`; initial loading is handled by Suspense. |
158
- | `error` | Thrown to the nearest Error Boundary instead. |
159
- | `isThreadLoading` | Handled by Suspense (the component suspends until the thread is ready). |
160
-
161
- | Added | Type | Description |
162
- |---|---|---|
163
- | `isStreaming` | `boolean` | `true` while the stream is receiving data. The component is never suspended during streaming. |
164
-
165
- All other properties (`messages`, `submit`, `stop`, `interrupt`, `branch`, `switchThread`, `queue`, etc.) are unchanged.
166
-
167
- ### Thread-switching with Suspense
168
-
169
- `useSuspenseStream` works naturally with thread switching. When the `threadId` changes, the component suspends while the new thread's history loads, and `<Suspense>` shows a smooth skeleton/fallback transition:
170
-
171
- ```tsx
172
- function App() {
173
- const [threadId, setThreadId] = useState<string | null>(null);
174
-
175
- return (
176
- <div className="flex">
177
- <ThreadSidebar onSelect={setThreadId} />
178
-
179
- <Suspense fallback={<ThreadSkeleton />}>
180
- <ChatPanel threadId={threadId} />
181
- </Suspense>
182
- </div>
183
- );
184
- }
185
-
186
- function ChatPanel({ threadId }: { threadId: string | null }) {
187
- const { messages, submit, isStreaming } = useSuspenseStream({
188
- assistantId: "agent",
189
- apiUrl: "http://localhost:2024",
190
- threadId,
191
- });
192
-
193
- return <MessageList messages={messages} />;
194
- }
195
- ```
196
-
197
- ### Error recovery
198
-
199
- When an error is thrown to an Error Boundary, call `invalidateSuspenseCache()` in the boundary's reset handler so the retry triggers a fresh data fetch:
200
-
201
- ```tsx
202
- import { invalidateSuspenseCache } from "@langchain/react";
203
-
204
- <ErrorBoundary
205
- onReset={() => invalidateSuspenseCache()}
206
- fallbackRender={({ error, resetErrorBoundary }) => (
207
- <div>
208
- <p>{error.message}</p>
209
- <button onClick={resetErrorBoundary}>Retry</button>
210
- </div>
211
- )}
212
- >
213
- <Suspense fallback={<Spinner />}>
214
- <Chat />
215
- </Suspense>
216
- </ErrorBoundary>
217
- ```
218
-
219
- For test isolation, you can create and pass a dedicated cache instance:
220
-
221
- ```tsx
222
- import { createSuspenseCache, useSuspenseStream } from "@langchain/react";
223
-
224
- const suspenseCache = createSuspenseCache();
225
-
226
- function Chat() {
227
- const stream = useSuspenseStream({
228
- assistantId: "agent",
229
- apiUrl: "http://localhost:2024",
230
- suspenseCache,
231
- });
232
- // ...
233
- }
234
- ```
235
-
236
- ## Type Safety
237
-
238
- ### With `createAgent`
239
-
240
- When using `createAgent`, pass `typeof agent` to automatically infer tool call types:
241
-
242
- ```tsx
243
- import type { agent } from "./agent";
244
-
245
- function Chat() {
246
- const stream = useStream<typeof agent>({
247
- assistantId: "agent",
248
- apiUrl: "http://localhost:2024",
249
- });
250
-
251
- // stream.messages, tool calls, etc. are fully typed
252
- }
253
- ```
254
-
255
- ### With `StateGraph`
256
-
257
- For custom graphs, provide your state type directly:
258
-
259
- ```tsx
260
- import type { BaseMessage } from "langchain";
261
-
262
- interface MyState {
263
- messages: BaseMessage[];
264
- context?: string;
265
- }
266
-
267
- function Chat() {
268
- const { messages, submit } = useStream<MyState>({
269
- assistantId: "my-graph",
270
- apiUrl: "http://localhost:2024",
271
- });
272
- }
273
- ```
274
-
275
- ### Typed Interrupts
276
-
277
- Pass interrupt types via the second generic parameter:
278
-
279
- ```tsx
280
- const { interrupt, submit } = useStream<
281
- MyState,
282
- { InterruptType: { question: string } }
283
- >({
284
- assistantId: "my-graph",
285
- apiUrl: "http://localhost:2024",
286
- });
287
-
288
- if (interrupt) {
289
- // interrupt.value is typed as { question: string }
290
- }
291
- ```
292
-
293
- ## Handling Interrupts
294
-
295
- Interrupts let you pause graph execution and wait for user input:
296
-
297
- ```tsx
298
- function Chat() {
299
- const { messages, interrupt, submit } = useStream<
300
- { messages: BaseMessage[] },
301
- { InterruptType: { question: string } }
302
- >({
303
- assistantId: "agent",
304
- apiUrl: "http://localhost:2024",
305
- });
306
-
307
- return (
308
- <div>
309
- {messages.map((msg, i) => (
310
- <div key={msg.id ?? i}>{msg.content}</div>
311
- ))}
312
-
313
- {interrupt && (
314
- <div>
315
- <p>{interrupt.value.question}</p>
316
- <button
317
- onClick={() =>
318
- void submit(null, { command: { resume: "Approved" } })
319
- }
320
- >
321
- Approve
322
- </button>
323
- </div>
324
- )}
325
-
326
- <button
327
- onClick={() =>
328
- void submit({
329
- messages: [{ type: "human", content: "Hello" }],
330
- })
331
- }
332
- >
333
- Send
334
- </button>
335
- </div>
336
- );
337
- }
338
- ```
339
-
340
- ## Branching
341
-
342
- Enable conversation branching by setting `fetchStateHistory: true`:
343
-
344
- ```tsx
345
- function Chat() {
346
- const { messages, submit, getMessagesMetadata, setBranch } = useStream({
347
- assistantId: "agent",
348
- apiUrl: "http://localhost:2024",
349
- fetchStateHistory: true,
350
- });
351
-
352
- return (
353
- <div>
354
- {messages.map((msg, i) => {
355
- const metadata = getMessagesMetadata(msg, i);
356
- const branchOptions = metadata?.branchOptions;
357
- const branch = metadata?.branch;
358
-
359
- return (
360
- <div key={msg.id ?? i}>
361
- <p>{msg.content}</p>
362
- {branchOptions && branch && (
363
- <div>
364
- <button onClick={() => {
365
- const prev = branchOptions[branchOptions.indexOf(branch) - 1];
366
- if (prev) setBranch(prev);
367
- }}>
368
- Previous
369
- </button>
370
- <button onClick={() => {
371
- const next = branchOptions[branchOptions.indexOf(branch) + 1];
372
- if (next) setBranch(next);
373
- }}>
374
- Next
375
- </button>
376
- </div>
377
- )}
378
- </div>
379
- );
380
- })}
381
- </div>
382
- );
383
- }
384
- ```
385
-
386
- ## Server-Side Queuing
387
-
388
- When `submit()` is called while a stream is already active, the SDK automatically creates the run on the server with `multitaskStrategy: "enqueue"`. The pending runs are tracked in `queue` and processed in order as each finishes:
389
-
390
- ```tsx
391
- function Chat() {
392
- const { messages, submit, isLoading, queue, switchThread } = useStream({
393
- assistantId: "agent",
394
- apiUrl: "http://localhost:2024",
395
- });
396
-
397
- return (
398
- <div>
399
- {messages.map((msg, i) => (
400
- <div key={msg.id ?? i}>{msg.content}</div>
39
+ <div key={msg.id ?? i}>{String(msg.content)}</div>
401
40
  ))}
402
41
 
403
- {queue.size > 0 && (
404
- <div>
405
- <p>{queue.size} message(s) queued</p>
406
- <button onClick={() => void queue.clear()}>Clear Queue</button>
407
- </div>
408
- )}
409
-
410
42
  <button
411
43
  disabled={isLoading}
412
44
  onClick={() =>
@@ -417,169 +49,62 @@ function Chat() {
417
49
  >
418
50
  Send
419
51
  </button>
420
- <button onClick={() => switchThread(null)}>New Thread</button>
421
52
  </div>
422
53
  );
423
54
  }
424
55
  ```
425
56
 
426
- Switching threads via `switchThread()` cancels all pending runs and clears the queue.
57
+ ## Mental model
427
58
 
428
- ## Custom Transport
59
+ `@langchain/react` v1 splits the surface into two layers:
429
60
 
430
- Instead of connecting to a LangGraph API, you can provide your own streaming transport. Pass a `transport` object instead of `assistantId` to use a custom backend:
61
+ 1. **Root hook (`useStream`).** Owns the thread lifecycle, the transport, and a handful of always-on projections (`values`, `messages`, `toolCalls`, `interrupts`, `error`, `isLoading`, discovery maps). Mount it once per thread.
62
+ 2. **Companion selector hooks.** Each one opens a ref-counted subscription when the first component mounts it and releases it when the last consumer unmounts. Use them for anything scoped to a namespace, a subagent / subgraph, a specific message, a specific extension channel, or a media stream.
431
63
 
432
64
  ```tsx
433
- import { useStream, FetchStreamTransport } from "@langchain/react";
434
- import type { BaseMessage } from "langchain";
65
+ import {
66
+ useStream,
67
+ useMessages,
68
+ useToolCalls,
69
+ useSubmissionQueue,
70
+ } from "@langchain/react";
435
71
 
436
72
  function Chat() {
437
- const {
438
- messages,
439
- submit,
440
- isLoading,
441
- branch,
442
- setBranch,
443
- getMessagesMetadata,
444
- } = useStream<{ messages: BaseMessage[] }>({
445
- transport: new FetchStreamTransport({
446
- url: "https://my-api.example.com/stream",
447
- }),
448
- threadId: null,
449
- onThreadId: (id) => console.log("Thread created:", id),
450
- });
451
-
452
- return (
453
- <div>
454
- {messages.map((msg, i) => {
455
- const metadata = getMessagesMetadata(msg, i);
456
- return (
457
- <div key={msg.id ?? i}>
458
- <p>{msg.content}</p>
459
- {metadata?.streamMetadata && (
460
- <span>Node: {metadata.streamMetadata.langgraph_node}</span>
461
- )}
462
- </div>
463
- );
464
- })}
73
+ const stream = useStream({ assistantId: "agent", apiUrl: "/api" });
465
74
 
466
- <p>Current branch: {branch}</p>
75
+ // Root: free reads, no new subscription.
76
+ const messages = useMessages(stream); // same as stream.messages
467
77
 
468
- <button
469
- disabled={isLoading}
470
- onClick={() =>
471
- void submit({
472
- messages: [{ type: "human", content: "Hello!" }],
473
- })
474
- }
475
- >
476
- Send
477
- </button>
478
- </div>
479
- );
78
+ // Scoped: opens a namespaced subscription on mount.
79
+ const queue = useSubmissionQueue(stream);
480
80
  }
481
81
  ```
482
82
 
483
- The custom transport interface returns the same properties as the standard `useStream` hook, including `getMessagesMetadata`, `branch`, `setBranch`, `switchThread`, and all message/interrupt/subagent helpers. When using a custom transport, `getMessagesMetadata` returns stream metadata sent alongside messages during streaming; `branch` and `setBranch` provide local branch state management. `onFinish` is also supported and receives a synthetic `ThreadState` built from the final locally streamed values; the run metadata argument is `undefined`.
83
+ ## Documentation
484
84
 
485
- ## Sharing State with `StreamProvider`
85
+ Detailed guides live in [`./docs`](./docs/). Start with the two files most apps need first:
486
86
 
487
- When multiple components in a tree need access to the same stream (a message list, a header with loading status, an input bar), use `StreamProvider` and `useStreamContext` to avoid prop drilling:
87
+ - **[`useStream`](./docs/use-stream.md)** options, return shape, `submit()`, `stop()`, `respond()`, `hydrationPromise`.
88
+ - **[Companion selector hooks](./docs/selectors.md)** — `useValues`, `useMessages`, `useToolCalls`, `useMessageMetadata`, `useChannel`, `useExtension`, and friends.
488
89
 
489
- ```tsx
490
- import { StreamProvider, useStreamContext } from "@langchain/react";
90
+ Feature-specific guides:
491
91
 
492
- function App() {
493
- return (
494
- <StreamProvider assistantId="agent" apiUrl="http://localhost:2024">
495
- <ChatHeader />
496
- <MessageList />
497
- <MessageInput />
498
- </StreamProvider>
499
- );
500
- }
92
+ - **[Transports](./docs/transports.md)** — SSE, WebSocket, `HttpAgentServerAdapter`, custom `AgentServerAdapter`.
93
+ - **[Custom transports](./docs/custom-transport.md)** — implementing `AgentServerAdapter` against your own backend, with a worked walkthrough of [`examples/ui-react-transport`](../../examples/ui-react-transport).
94
+ - **[Interrupts & headless tools](./docs/interrupts.md)** — pausing runs, `respond()`, `tools` + `onTool`.
95
+ - **[Fork / edit from a checkpoint](./docs/fork-from-checkpoint.md)** — `useMessageMetadata` + `submit({ forkFrom })`.
96
+ - **[Submission queue](./docs/submission-queue.md)** — `multitaskStrategy: "enqueue"` + `useSubmissionQueue`.
97
+ - **[Subagents & subgraphs](./docs/subagents.md)** — discovery maps, scoped selector subscriptions.
98
+ - **[Multimodal media](./docs/multimodal.md)** — `useAudio` / `useImages` / `useVideo` / `useFiles`, `useMediaURL`, players.
99
+ - **[`useSuspenseStream`](./docs/suspense.md)** — Suspense + Error Boundary integration.
100
+ - **[`StreamProvider` / `useStreamContext`](./docs/context.md)** — share one stream across a subtree.
101
+ - **[Type safety](./docs/type-safety.md)** — agent-brand inference, prop-drilling, type helpers.
501
102
 
502
- function ChatHeader() {
503
- const { isLoading, error } = useStreamContext();
504
- return (
505
- <header>
506
- <h1>Chat</h1>
507
- {isLoading && <span>Thinking...</span>}
508
- {error != null && <span>Error occurred</span>}
509
- </header>
510
- );
511
- }
103
+ ## Migrating from v0 to v1
512
104
 
513
- function MessageList() {
514
- const { messages, getMessagesMetadata } = useStreamContext();
515
- return (
516
- <div>
517
- {messages.map((msg, i) => (
518
- <div key={msg.id ?? i}>{msg.content}</div>
519
- ))}
520
- </div>
521
- );
522
- }
523
-
524
- function MessageInput() {
525
- const { submit, isLoading } = useStreamContext();
526
- return (
527
- <button
528
- disabled={isLoading}
529
- onClick={() =>
530
- void submit({
531
- messages: [{ type: "human", content: "Hello!" }],
532
- })
533
- }
534
- >
535
- Send
536
- </button>
537
- );
538
- }
539
- ```
540
-
541
- ### Type Safety with `StreamProvider`
542
-
543
- Pass agent or state types to both `StreamProvider` and `useStreamContext`:
544
-
545
- ```tsx
546
- import type { agent } from "./agent";
547
-
548
- function App() {
549
- return (
550
- <StreamProvider<typeof agent>
551
- assistantId="agent"
552
- apiUrl="http://localhost:2024"
553
- >
554
- <Chat />
555
- </StreamProvider>
556
- );
557
- }
558
-
559
- function Chat() {
560
- const { toolCalls } = useStreamContext<typeof agent>();
561
- // toolCalls are fully typed from the agent's tools
562
- }
563
- ```
564
-
565
- ### Multiple Agents
105
+ The `useStream` import name is unchanged, but the return shape, option bag, and protocol semantics all shifted. Most chat apps migrate in well under an hour — the full migration guide with line-by-line diffs lives in [`./docs/v1-migration.md`](./docs/v1-migration.md).
566
106
 
567
- Nest providers for multi-agent scenarios each subtree gets its own isolated stream:
568
-
569
- ```tsx
570
- function MultiAgentApp() {
571
- return (
572
- <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
573
- <StreamProvider assistantId="researcher" apiUrl="http://localhost:2024">
574
- <ResearchPanel />
575
- </StreamProvider>
576
- <StreamProvider assistantId="writer" apiUrl="http://localhost:2024">
577
- <WriterPanel />
578
- </StreamProvider>
579
- </div>
580
- );
581
- }
582
- ```
107
+ Legacy type aliases (`UseStream`, `UseSuspenseStream`, `UseStreamOptions`, `UseStreamTransport`, `QueueEntry`, `GetToolCallsType`, `SubagentStream`, …) and the legacy `FetchStreamTransport` class are **no longer re-exported** from `@langchain/react`. Apps still on the legacy surface can import directly from `@langchain/langgraph-sdk/ui` during their migration.
583
108
 
584
109
  ## Playground
585
110