@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.
- package/README.md +48 -523
- package/dist/context.cjs +12 -30
- package/dist/context.cjs.map +1 -1
- package/dist/context.d.cts +22 -39
- package/dist/context.d.cts.map +1 -1
- package/dist/context.d.ts +22 -39
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +11 -29
- package/dist/context.js.map +1 -1
- package/dist/index.cjs +29 -30
- package/dist/index.d.cts +10 -7
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +10 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -6
- package/dist/selectors.cjs +178 -0
- package/dist/selectors.cjs.map +1 -0
- package/dist/selectors.d.cts +183 -0
- package/dist/selectors.d.cts.map +1 -0
- package/dist/selectors.d.ts +183 -0
- package/dist/selectors.d.ts.map +1 -0
- package/dist/selectors.js +168 -0
- package/dist/selectors.js.map +1 -0
- package/dist/suspense-stream.cjs +34 -159
- package/dist/suspense-stream.cjs.map +1 -1
- package/dist/suspense-stream.d.cts +15 -71
- package/dist/suspense-stream.d.cts.map +1 -1
- package/dist/suspense-stream.d.ts +15 -71
- package/dist/suspense-stream.d.ts.map +1 -1
- package/dist/suspense-stream.js +35 -158
- package/dist/suspense-stream.js.map +1 -1
- package/dist/use-audio-player.cjs +679 -0
- package/dist/use-audio-player.cjs.map +1 -0
- package/dist/use-audio-player.d.cts +161 -0
- package/dist/use-audio-player.d.cts.map +1 -0
- package/dist/use-audio-player.d.ts +161 -0
- package/dist/use-audio-player.d.ts.map +1 -0
- package/dist/use-audio-player.js +679 -0
- package/dist/use-audio-player.js.map +1 -0
- package/dist/use-media-url.cjs +49 -0
- package/dist/use-media-url.cjs.map +1 -0
- package/dist/use-media-url.d.cts +28 -0
- package/dist/use-media-url.d.cts.map +1 -0
- package/dist/use-media-url.d.ts +28 -0
- package/dist/use-media-url.d.ts.map +1 -0
- package/dist/use-media-url.js +49 -0
- package/dist/use-media-url.js.map +1 -0
- package/dist/use-projection.cjs +41 -0
- package/dist/use-projection.cjs.map +1 -0
- package/dist/use-projection.d.cts +27 -0
- package/dist/use-projection.d.cts.map +1 -0
- package/dist/use-projection.d.ts +27 -0
- package/dist/use-projection.d.ts.map +1 -0
- package/dist/use-projection.js +41 -0
- package/dist/use-projection.js.map +1 -0
- package/dist/use-stream.cjs +185 -0
- package/dist/use-stream.cjs.map +1 -0
- package/dist/use-stream.d.cts +184 -0
- package/dist/use-stream.d.cts.map +1 -0
- package/dist/use-stream.d.ts +184 -0
- package/dist/use-stream.d.ts.map +1 -0
- package/dist/use-stream.js +183 -0
- package/dist/use-stream.js.map +1 -0
- package/dist/use-video-player.cjs +218 -0
- package/dist/use-video-player.cjs.map +1 -0
- package/dist/use-video-player.d.cts +65 -0
- package/dist/use-video-player.d.cts.map +1 -0
- package/dist/use-video-player.d.ts +65 -0
- package/dist/use-video-player.d.ts.map +1 -0
- package/dist/use-video-player.js +218 -0
- package/dist/use-video-player.js.map +1 -0
- package/package.json +9 -8
- package/dist/stream.cjs +0 -18
- package/dist/stream.cjs.map +0 -1
- package/dist/stream.custom.cjs +0 -209
- package/dist/stream.custom.cjs.map +0 -1
- package/dist/stream.custom.d.cts +0 -3
- package/dist/stream.custom.d.ts +0 -3
- package/dist/stream.custom.js +0 -209
- package/dist/stream.custom.js.map +0 -1
- package/dist/stream.d.cts +0 -174
- package/dist/stream.d.cts.map +0 -1
- package/dist/stream.d.ts +0 -174
- package/dist/stream.d.ts.map +0 -1
- package/dist/stream.js +0 -18
- package/dist/stream.js.map +0 -1
- package/dist/stream.lgp.cjs +0 -671
- package/dist/stream.lgp.cjs.map +0 -1
- package/dist/stream.lgp.js +0 -671
- package/dist/stream.lgp.js.map +0 -1
- package/dist/thread.cjs +0 -18
- package/dist/thread.cjs.map +0 -1
- package/dist/thread.js +0 -18
- package/dist/thread.js.map +0 -1
- package/dist/types.d.cts +0 -109
- package/dist/types.d.cts.map +0 -1
- package/dist/types.d.ts +0 -109
- 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).
|
|
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
|
-
|
|
57
|
+
## Mental model
|
|
427
58
|
|
|
428
|
-
|
|
59
|
+
`@langchain/react` v1 splits the surface into two layers:
|
|
429
60
|
|
|
430
|
-
|
|
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 {
|
|
434
|
-
|
|
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
|
-
|
|
75
|
+
// Root: free reads, no new subscription.
|
|
76
|
+
const messages = useMessages(stream); // same as stream.messages
|
|
467
77
|
|
|
468
|
-
|
|
469
|
-
|
|
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
|
-
|
|
83
|
+
## Documentation
|
|
484
84
|
|
|
485
|
-
|
|
85
|
+
Detailed guides live in [`./docs`](./docs/). Start with the two files most apps need first:
|
|
486
86
|
|
|
487
|
-
|
|
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
|
-
|
|
490
|
-
import { StreamProvider, useStreamContext } from "@langchain/react";
|
|
90
|
+
Feature-specific guides:
|
|
491
91
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|