@assistant-ui/mcp-docs-server 0.1.28 → 0.1.30
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/.docs/organized/code-examples/waterfall.md +18 -10
- package/.docs/organized/code-examples/with-a2a.md +12 -24
- package/.docs/organized/code-examples/with-ag-ui.md +14 -11
- package/.docs/organized/code-examples/with-ai-sdk-v6.md +12 -12
- package/.docs/organized/code-examples/with-artifacts.md +14 -12
- package/.docs/organized/code-examples/with-assistant-transport.md +13 -14
- package/.docs/organized/code-examples/with-chain-of-thought.md +11 -11
- package/.docs/organized/code-examples/with-cloud-standalone.md +17 -14
- package/.docs/organized/code-examples/with-cloud.md +12 -13
- package/.docs/organized/code-examples/with-custom-thread-list.md +12 -12
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +19 -14
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +15 -15
- package/.docs/organized/code-examples/with-expo.md +27 -23
- package/.docs/organized/code-examples/with-external-store.md +11 -11
- package/.docs/organized/code-examples/with-ffmpeg.md +19 -14
- package/.docs/organized/code-examples/with-generative-ui.md +11 -11
- package/.docs/organized/code-examples/with-google-adk.md +10 -10
- package/.docs/organized/code-examples/with-heat-graph.md +8 -8
- package/.docs/organized/code-examples/with-interactables.md +12 -27
- package/.docs/organized/code-examples/with-langchain.md +437 -0
- package/.docs/organized/code-examples/with-langgraph.md +20 -20
- package/.docs/organized/code-examples/with-livekit.md +59 -18
- package/.docs/organized/code-examples/with-opencode.md +2392 -0
- package/.docs/organized/code-examples/with-parent-id-grouping.md +12 -12
- package/.docs/organized/code-examples/with-react-hook-form.md +223 -151
- package/.docs/organized/code-examples/with-react-ink.md +3 -3
- package/.docs/organized/code-examples/with-react-router.md +15 -15
- package/.docs/organized/code-examples/with-store.md +11 -8
- package/.docs/organized/code-examples/with-tanstack.md +14 -14
- package/.docs/organized/code-examples/with-tap-runtime.md +13 -9
- package/.docs/raw/docs/(docs)/guides/mentions.mdx +248 -109
- package/.docs/raw/docs/(docs)/guides/slash-commands.mdx +112 -92
- package/.docs/raw/docs/(docs)/guides/voice.mdx +10 -3
- package/.docs/raw/docs/(docs)/rtl.mdx +79 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +149 -40
- package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +2 -0
- package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-runtime.mdx +4 -0
- package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +1 -1
- package/.docs/raw/docs/primitives/composer.mdx +94 -62
- package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +57 -0
- package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +49 -3
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +39 -1
- package/.docs/raw/docs/runtimes/custom/local.mdx +65 -18
- package/.docs/raw/docs/runtimes/google-adk/index.mdx +62 -0
- package/.docs/raw/docs/runtimes/langchain/comparison.mdx +60 -0
- package/.docs/raw/docs/runtimes/langchain/index.mdx +210 -0
- package/.docs/raw/docs/runtimes/langgraph/index.mdx +288 -60
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +12 -4
- package/.docs/raw/docs/ui/composer-trigger-popover.mdx +199 -0
- package/.docs/raw/docs/ui/directive-text.mdx +113 -0
- package/.docs/raw/docs/ui/reasoning.mdx +13 -9
- package/dist/utils/logger.js +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/package.json +4 -4
- package/src/utils/logger.ts +1 -1
- package/.docs/raw/docs/ui/mention.mdx +0 -168
|
@@ -216,12 +216,58 @@ When the hook mounts it calls `list()` on your adapter, hydrates existing thread
|
|
|
216
216
|
|
|
217
217
|
## Thread Lifecycle Cheatsheet
|
|
218
218
|
|
|
219
|
-
- `list()` hydrates threads on mount
|
|
219
|
+
- `list()` hydrates threads on mount. It runs once; call `runtime.threads.reload()` to re-fetch.
|
|
220
220
|
- Creating a new conversation calls `initialize()` once the user sends the first message.
|
|
221
221
|
- `archive`, `unarchive`, and `delete` are called optimistically; throw to revert the UI.
|
|
222
222
|
- `generateTitle()` powers the automatic title button and expects an `AssistantStream`.
|
|
223
223
|
- Provide a `runtimeHook` that always returns a fresh runtime instance per active thread.
|
|
224
224
|
|
|
225
|
+
## Reloading After Async Authentication
|
|
226
|
+
|
|
227
|
+
If your adapter depends on an authenticated user (e.g. an OIDC provider, `better-auth`, `next-auth`) and the auth state resolves asynchronously after `useRemoteThreadListRuntime` has mounted, the initial `list()` call may run before the user is available. Call `aui.threads().reload()` when auth completes to re-fetch with the authenticated request.
|
|
228
|
+
|
|
229
|
+
```tsx title="app/ThreadListProvider.tsx"
|
|
230
|
+
"use client";
|
|
231
|
+
|
|
232
|
+
import { useEffect } from "react";
|
|
233
|
+
import { useAuth } from "react-oidc-context";
|
|
234
|
+
import {
|
|
235
|
+
AssistantRuntimeProvider,
|
|
236
|
+
useAui,
|
|
237
|
+
useLocalRuntime,
|
|
238
|
+
useRemoteThreadListRuntime,
|
|
239
|
+
} from "@assistant-ui/react";
|
|
240
|
+
|
|
241
|
+
function ReloadOnAuth() {
|
|
242
|
+
const aui = useAui();
|
|
243
|
+
const { isLoading, user } = useAuth();
|
|
244
|
+
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
if (!isLoading && user) {
|
|
247
|
+
aui.threads().reload();
|
|
248
|
+
}
|
|
249
|
+
}, [isLoading, user?.id]);
|
|
250
|
+
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function ThreadListProvider({ children }) {
|
|
255
|
+
const runtime = useRemoteThreadListRuntime({
|
|
256
|
+
runtimeHook: () => useLocalRuntime(myModelAdapter),
|
|
257
|
+
adapter: useThreadListAdapter(),
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
262
|
+
<ReloadOnAuth />
|
|
263
|
+
{children}
|
|
264
|
+
</AssistantRuntimeProvider>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`reload()` replaces the cached load promise and discards any in-flight response from a superseded call, so it is safe to invoke multiple times (for example, on logout followed by a new login). `useAui` must be called inside `AssistantRuntimeProvider`, so the effect lives in a child component.
|
|
270
|
+
|
|
225
271
|
## Avoiding Race Conditions in History Adapters
|
|
226
272
|
|
|
227
273
|
<Callout type="warn">
|
|
@@ -238,12 +284,12 @@ const aui = useAui();
|
|
|
238
284
|
|
|
239
285
|
const history = useMemo<ThreadHistoryAdapter>(
|
|
240
286
|
() => ({
|
|
241
|
-
async append(message) {
|
|
287
|
+
async append({ message, parentId }) {
|
|
242
288
|
// Wait for initialization to complete and get the remoteId
|
|
243
289
|
const { remoteId } = await aui.threadListItem().initialize();
|
|
244
290
|
|
|
245
291
|
// Now safe to save the message using the remoteId
|
|
246
|
-
await saveMessageToDatabase(remoteId, message);
|
|
292
|
+
await saveMessageToDatabase(remoteId, parentId, message);
|
|
247
293
|
},
|
|
248
294
|
// ...
|
|
249
295
|
}),
|
|
@@ -424,6 +424,44 @@ const onEdit = async (message: AppendMessage) => {
|
|
|
424
424
|
};
|
|
425
425
|
```
|
|
426
426
|
|
|
427
|
+
### Branching Support
|
|
428
|
+
|
|
429
|
+
The `messages` array path assumes a linear conversation — each message's parent is the previous message. To support branching (e.g. regenerating responses creates alternative branches), use `ExportedMessageRepository.fromBranchableArray()` combined with `thread.import()`.
|
|
430
|
+
|
|
431
|
+
Each message must have an explicit `id` and `parentId`. Messages with the same `parentId` create branches:
|
|
432
|
+
|
|
433
|
+
```tsx
|
|
434
|
+
import {
|
|
435
|
+
ExportedMessageRepository,
|
|
436
|
+
useExternalStoreRuntime,
|
|
437
|
+
} from "@assistant-ui/react";
|
|
438
|
+
|
|
439
|
+
// Your messages from the backend, each with an id and parentId
|
|
440
|
+
const backendMessages = [
|
|
441
|
+
{ id: "user-1", role: "user", content: "Hello", parentId: null },
|
|
442
|
+
{ id: "asst-1", role: "assistant", content: "Hi!", parentId: "user-1" },
|
|
443
|
+
// A second response to the same user message = a branch
|
|
444
|
+
{ id: "asst-2", role: "assistant", content: "Hey!", parentId: "user-1" },
|
|
445
|
+
];
|
|
446
|
+
|
|
447
|
+
// Convert to ExportedMessageRepository
|
|
448
|
+
const repo = ExportedMessageRepository.fromBranchableArray(
|
|
449
|
+
backendMessages.map((m) => ({
|
|
450
|
+
message: { id: m.id, role: m.role, content: m.content },
|
|
451
|
+
parentId: m.parentId,
|
|
452
|
+
})),
|
|
453
|
+
{ headId: "asst-1" }, // which branch to display initially
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
// Import into the runtime
|
|
457
|
+
runtime.thread.import(repo);
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
<Callout type="warn">
|
|
461
|
+
Messages in the array must be ordered so that parents appear before their
|
|
462
|
+
children. Each message **must** have an `id` field set.
|
|
463
|
+
</Callout>
|
|
464
|
+
|
|
427
465
|
### Tool Calling
|
|
428
466
|
|
|
429
467
|
Support tool calls with proper result handling:
|
|
@@ -1409,7 +1447,7 @@ The main interface for connecting your state to assistant-ui.
|
|
|
1409
1447
|
name: "isRunning",
|
|
1410
1448
|
type: "boolean",
|
|
1411
1449
|
description:
|
|
1412
|
-
"Whether the assistant is currently generating a response. When true, shows optimistic assistant message",
|
|
1450
|
+
"Whether the assistant is currently generating a response. When true, shows an optimistic assistant message and flows directly to `thread.isRunning`, so the thread stays in a running state even after the last assistant message has completed (e.g. while suggestions or metadata chunks are still arriving). When omitted, `thread.isRunning` falls back to the last-message-status heuristic.",
|
|
1413
1451
|
default: "false",
|
|
1414
1452
|
},
|
|
1415
1453
|
{
|
|
@@ -501,26 +501,69 @@ export function MyRuntimeProvider({ children }) {
|
|
|
501
501
|
const { remoteId } = aui.threadListItem().getState();
|
|
502
502
|
if (!remoteId) return { messages: [] };
|
|
503
503
|
|
|
504
|
-
const
|
|
504
|
+
const rows = await db.messages.findByThreadId(remoteId);
|
|
505
505
|
return {
|
|
506
|
-
messages:
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
506
|
+
messages: rows.map((row) => {
|
|
507
|
+
const common = {
|
|
508
|
+
id: row.id,
|
|
509
|
+
createdAt: new Date(row.createdAt),
|
|
510
|
+
};
|
|
511
|
+
// `content` is stored as JSON — parse back into message parts
|
|
512
|
+
const content = JSON.parse(row.content);
|
|
513
|
+
|
|
514
|
+
if (row.role === "user") {
|
|
515
|
+
return {
|
|
516
|
+
parentId: row.parentId,
|
|
517
|
+
message: {
|
|
518
|
+
...common,
|
|
519
|
+
role: "user" as const,
|
|
520
|
+
content,
|
|
521
|
+
attachments: [],
|
|
522
|
+
metadata: { custom: {} },
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
if (row.role === "assistant") {
|
|
527
|
+
return {
|
|
528
|
+
parentId: row.parentId,
|
|
529
|
+
message: {
|
|
530
|
+
...common,
|
|
531
|
+
role: "assistant" as const,
|
|
532
|
+
content,
|
|
533
|
+
status: { type: "complete", reason: "stop" } as const,
|
|
534
|
+
metadata: {
|
|
535
|
+
custom: {},
|
|
536
|
+
unstable_state: null,
|
|
537
|
+
unstable_annotations: [],
|
|
538
|
+
unstable_data: [],
|
|
539
|
+
steps: [],
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
return {
|
|
545
|
+
parentId: row.parentId,
|
|
546
|
+
message: {
|
|
547
|
+
...common,
|
|
548
|
+
role: "system" as const,
|
|
549
|
+
content,
|
|
550
|
+
metadata: { custom: {} },
|
|
551
|
+
},
|
|
552
|
+
};
|
|
553
|
+
}),
|
|
512
554
|
};
|
|
513
555
|
},
|
|
514
556
|
|
|
515
|
-
async append(message) {
|
|
557
|
+
async append({ message, parentId }) {
|
|
516
558
|
// Wait for initialization to get remoteId (safe to call multiple times)
|
|
517
559
|
const { remoteId } = await aui.threadListItem().initialize();
|
|
518
560
|
|
|
519
561
|
await db.messages.create({
|
|
520
562
|
threadId: remoteId,
|
|
521
|
-
|
|
522
|
-
content: message.content,
|
|
563
|
+
parentId,
|
|
523
564
|
id: message.id,
|
|
565
|
+
role: message.role,
|
|
566
|
+
content: JSON.stringify(message.content),
|
|
524
567
|
createdAt: message.createdAt,
|
|
525
568
|
});
|
|
526
569
|
},
|
|
@@ -584,10 +627,10 @@ const aui = useAui();
|
|
|
584
627
|
|
|
585
628
|
const history = useMemo<ThreadHistoryAdapter>(
|
|
586
629
|
() => ({
|
|
587
|
-
async append(message) {
|
|
630
|
+
async append({ message, parentId }) {
|
|
588
631
|
// Wait for initialization - safe to call multiple times
|
|
589
632
|
const { remoteId } = await aui.threadListItem().initialize();
|
|
590
|
-
await db.messages.create({ threadId: remoteId, ...message });
|
|
633
|
+
await db.messages.create({ threadId: remoteId, parentId, ...message });
|
|
591
634
|
},
|
|
592
635
|
// ...
|
|
593
636
|
}),
|
|
@@ -613,8 +656,9 @@ interface ThreadRecord {
|
|
|
613
656
|
interface MessageRecord {
|
|
614
657
|
id: string;
|
|
615
658
|
threadId: string;
|
|
659
|
+
parentId: string | null;
|
|
616
660
|
role: "user" | "assistant" | "system";
|
|
617
|
-
content:
|
|
661
|
+
content: string; // JSON-encoded message content parts
|
|
618
662
|
createdAt: Date;
|
|
619
663
|
}
|
|
620
664
|
```
|
|
@@ -693,18 +737,21 @@ Persist and resume conversations:
|
|
|
693
737
|
```tsx
|
|
694
738
|
const historyAdapter: ThreadHistoryAdapter = {
|
|
695
739
|
async load() {
|
|
696
|
-
// Load messages from your storage
|
|
740
|
+
// Load messages from your storage.
|
|
741
|
+
// The API must return `{ messages: { parentId, message }[] }`
|
|
742
|
+
// where each `message` is a full ThreadMessage
|
|
743
|
+
// (including `metadata.custom`, plus `attachments` on user messages
|
|
744
|
+
// and `status` + the rest of `metadata` on assistant messages).
|
|
697
745
|
const response = await fetch(`/api/thread/current`);
|
|
698
|
-
|
|
699
|
-
return { messages };
|
|
746
|
+
return await response.json();
|
|
700
747
|
},
|
|
701
748
|
|
|
702
|
-
async append(message) {
|
|
749
|
+
async append({ message, parentId }) {
|
|
703
750
|
// Save new message to storage
|
|
704
751
|
await fetch(`/api/thread/messages`, {
|
|
705
752
|
method: "POST",
|
|
706
753
|
headers: { "Content-Type": "application/json" },
|
|
707
|
-
body: JSON.stringify({ message }),
|
|
754
|
+
body: JSON.stringify({ message, parentId }),
|
|
708
755
|
});
|
|
709
756
|
},
|
|
710
757
|
|
|
@@ -317,6 +317,64 @@ function AuthUI() {
|
|
|
317
317
|
|
|
318
318
|
`AdkAuthCredential` supports all ADK auth types: `apiKey`, `http`, `oauth2`, `openIdConnect`, `serviceAccount`.
|
|
319
319
|
|
|
320
|
+
### Input Requests
|
|
321
|
+
|
|
322
|
+
When an ADK Python 2.0+ Workflow's `RequestInput` node pauses execution to ask the user a question, ADK emits an `adk_request_input` function call marked as long-running. Respond with `useAdkSubmitInput` inside a tool UI — the helper wraps the answer as `{ result }` to match ADK's `unwrap_response` contract, so the Workflow node resumes with the unwrapped value:
|
|
323
|
+
|
|
324
|
+
```tsx
|
|
325
|
+
import { makeAssistantToolUI } from "@assistant-ui/react";
|
|
326
|
+
import { useAdkSubmitInput } from "@assistant-ui/react-google-adk";
|
|
327
|
+
|
|
328
|
+
type RequestInputArgs = {
|
|
329
|
+
interrupt_id?: string;
|
|
330
|
+
message?: string;
|
|
331
|
+
payload?: unknown;
|
|
332
|
+
response_schema?: unknown;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
export const RequestInputToolUI = makeAssistantToolUI<RequestInputArgs, unknown>({
|
|
336
|
+
toolName: "adk_request_input",
|
|
337
|
+
render: function RequestInputUI({ toolCallId, args, result }) {
|
|
338
|
+
const submitInput = useAdkSubmitInput();
|
|
339
|
+
|
|
340
|
+
if (result !== undefined) {
|
|
341
|
+
return <p>Answered: {String(result)}</p>;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<form
|
|
346
|
+
onSubmit={(e) => {
|
|
347
|
+
e.preventDefault();
|
|
348
|
+
const value = (
|
|
349
|
+
e.currentTarget.elements.namedItem("answer") as HTMLInputElement
|
|
350
|
+
).value;
|
|
351
|
+
submitInput(toolCallId, value);
|
|
352
|
+
}}
|
|
353
|
+
>
|
|
354
|
+
<p>{args.message ?? "Please provide input:"}</p>
|
|
355
|
+
<input name="answer" autoFocus />
|
|
356
|
+
<button type="submit">Submit</button>
|
|
357
|
+
</form>
|
|
358
|
+
);
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Register the tool UI inside `AssistantRuntimeProvider`:
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
367
|
+
<RequestInputToolUI />
|
|
368
|
+
<Thread />
|
|
369
|
+
</AssistantRuntimeProvider>
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
<Callout type="info">
|
|
373
|
+
`adk_request_input` is emitted only by ADK Python 2.0+ Workflow `RequestInput` nodes — ADK JS has no equivalent. Always respond via a tool UI with `useAdkSubmitInput`; HITL interrupts are automatically exempt from `autoCancelPendingToolCalls`, so typing a normal message in the composer will not overwrite the pending interrupt.
|
|
374
|
+
</Callout>
|
|
375
|
+
|
|
376
|
+
`useAdkSubmitInput` is sugar over the generic `addResult` — if you prefer, you can call `addResult({ result: value })` from inside the render function directly. The `{ result }` wrapper is required either way: the adapter JSON-stringifies the value before sending, and ADK's `unwrap_response` unwraps it on the backend before the Workflow node resumes.
|
|
377
|
+
|
|
320
378
|
### Artifacts
|
|
321
379
|
|
|
322
380
|
Track file artifacts created or modified by the agent:
|
|
@@ -384,6 +442,8 @@ function PendingToolsIndicator() {
|
|
|
384
442
|
}
|
|
385
443
|
```
|
|
386
444
|
|
|
445
|
+
This hook reports every tool call ADK marked via `long_running_tool_ids`, including HITL interrupts. To respond to a specific HITL type, see [Tool Confirmations](#tool-confirmations), [Auth Requests](#auth-requests), or [Input Requests](#input-requests).
|
|
446
|
+
|
|
387
447
|
### Per-Message Metadata
|
|
388
448
|
|
|
389
449
|
Access grounding, citation, and token usage metadata per message:
|
|
@@ -581,6 +641,7 @@ The resolved `checkpointId` is passed to your `stream` callback via `config.chec
|
|
|
581
641
|
| `useAdkSend()` | Send raw ADK messages |
|
|
582
642
|
| `useAdkConfirmTool()` | Confirm or deny a pending tool confirmation |
|
|
583
643
|
| `useAdkSubmitAuth()` | Submit auth credentials for a pending auth request |
|
|
644
|
+
| `useAdkSubmitInput()` | Submit the user's answer for a pending `adk_request_input` HITL interrupt |
|
|
584
645
|
| `useAdkToolConfirmations()` | Pending tool confirmation requests |
|
|
585
646
|
| `useAdkAuthRequests()` | Pending auth credential requests |
|
|
586
647
|
| `useAdkLongRunningToolIds()` | IDs of long-running tools awaiting input |
|
|
@@ -596,6 +657,7 @@ The resolved `checkpointId` is passed to your `stream` callback via `config.chec
|
|
|
596
657
|
| Tool calls & results | Supported |
|
|
597
658
|
| Tool confirmations (`useAdkConfirmTool`) | Supported |
|
|
598
659
|
| Auth credential flow (`useAdkSubmitAuth`) | Supported |
|
|
660
|
+
| Workflow input requests (`useAdkSubmitInput`, ADK Python 2.0+) | Supported |
|
|
599
661
|
| Multi-agent (author/branch tracking) | Supported |
|
|
600
662
|
| Agent transfer events | Supported |
|
|
601
663
|
| Escalation detection | Supported |
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Comparison with `react-langgraph`
|
|
3
|
+
description: How `@assistant-ui/react-langchain` differs from `@assistant-ui/react-langgraph`.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Both packages connect assistant-ui to LangGraph backends. They are **independent adapters for different upstream libraries** — one is not a successor to the other.
|
|
7
|
+
|
|
8
|
+
| Aspect | `@assistant-ui/react-langgraph` | `@assistant-ui/react-langchain` |
|
|
9
|
+
| ------------------------------ | ------------------------------------------------------------ | ----------------------------------------------------- |
|
|
10
|
+
| Wraps | `@langchain/langgraph-sdk` (raw SDK) | `@langchain/react` (`useStream` hook) |
|
|
11
|
+
| Age | Sept 2024 onward | April 2026 onward |
|
|
12
|
+
| Version | `0.13.x` | `0.0.x` |
|
|
13
|
+
| Lines of source | ~7,500 | ~600 |
|
|
14
|
+
| Built on | `useExternalStoreRuntime` | `useExternalStoreRuntime` |
|
|
15
|
+
| `create-assistant-ui` template | `-t langgraph` ships this package | No template yet |
|
|
16
|
+
|
|
17
|
+
## Feature coverage
|
|
18
|
+
|
|
19
|
+
| Feature | `react-langgraph` | `react-langchain` |
|
|
20
|
+
| --------------------------------------- | -------------------------------- | ----------------------------------- |
|
|
21
|
+
| Stream messages | ✅ `useLangGraphRuntime` | ✅ `useStreamRuntime` |
|
|
22
|
+
| Interrupt state | ✅ `useLangGraphInterruptState` | ✅ `useLangChainInterruptState` |
|
|
23
|
+
| Send raw state update / resume command | ✅ `useLangGraphSendCommand` | ✅ `useLangChainSubmit` |
|
|
24
|
+
| Read arbitrary custom state key | ❌ | ✅ `useLangChainState<T>(key)` |
|
|
25
|
+
| Per-message metadata (`messages-tuple`) | ✅ `useLangGraphMessageMetadata` | ❌ not exposed |
|
|
26
|
+
| Generative UI messages (LangSmith) | ✅ `useLangGraphUIMessages` | ❌ not exposed |
|
|
27
|
+
| Subgraph / namespaced stream events | ✅ via `eventHandlers` | ❌ not exposed |
|
|
28
|
+
| End-to-end cancellation primitive | ✅ `unstable_createLangGraphStream` | ❌ not exposed |
|
|
29
|
+
| Message accumulator utility | ✅ `LangGraphMessageAccumulator` | ❌ not exposed |
|
|
30
|
+
| Cloud thread persistence | ✅ `cloud` option | ✅ `cloud` option |
|
|
31
|
+
|
|
32
|
+
`react-langchain` is the newer, thinner wrapper — it delegates to the upstream `useStream` hook rather than re-implementing the stream plumbing. That is why its footprint is smaller and its surface area is narrower today. Features that exist in `react-langgraph` but not `react-langchain` are absent because they have not yet been ported, not because they are deprecated.
|
|
33
|
+
|
|
34
|
+
## Choosing between them
|
|
35
|
+
|
|
36
|
+
Use `@assistant-ui/react-langgraph` when:
|
|
37
|
+
|
|
38
|
+
- You are scaffolding via `npx create-assistant-ui -t langgraph`.
|
|
39
|
+
- You want the broader feature set today (subgraph events, UI messages, message metadata, cancellation).
|
|
40
|
+
- You prefer integrating with `@langchain/langgraph-sdk` directly.
|
|
41
|
+
|
|
42
|
+
Use `@assistant-ui/react-langchain` when:
|
|
43
|
+
|
|
44
|
+
- Your app already depends on `@langchain/react` and uses `useStream` elsewhere.
|
|
45
|
+
- You want to read custom state keys (`todos`, `files`, plans, ...) with `useLangChainState<T>(key)` without reconstructing them from tool-call streams.
|
|
46
|
+
- You prefer a thin wrapper that stays pinned to upstream behavior.
|
|
47
|
+
|
|
48
|
+
## Hook name mapping
|
|
49
|
+
|
|
50
|
+
If you are moving code between the two adapters, most hooks have a counterpart — but note the feature gaps above.
|
|
51
|
+
|
|
52
|
+
| `react-langgraph` | `react-langchain` | Notes |
|
|
53
|
+
| ---------------------------------- | ----------------------------------- | ----------------------------------------------------------- |
|
|
54
|
+
| `useLangGraphRuntime` | `useStreamRuntime` | Options extend upstream `UseStreamOptions`; no `stream` / `create` / `load` to write. |
|
|
55
|
+
| `useLangGraphInterruptState` | `useLangChainInterruptState` | Same return shape: `{ value?: unknown } \| undefined`. |
|
|
56
|
+
| `useLangGraphSendCommand` | `useLangChainSubmit` | `submit(values, { command })` replaces the dedicated hook. |
|
|
57
|
+
| `useLangGraphSend` | _(use `runtime.thread.append`)_ | No direct equivalent; send turns through the runtime. |
|
|
58
|
+
| `useLangGraphMessageMetadata` | _(not available)_ | Open an issue if you rely on this. |
|
|
59
|
+
| `useLangGraphUIMessages` | _(not available)_ | Open an issue if you rely on this. |
|
|
60
|
+
| _(none)_ | `useLangChainState<T>(key)` | New — reads any custom state key reactively. |
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Getting Started
|
|
3
|
+
description: Adapter for LangChain's `useStream` hook, exposed as an assistant-ui runtime.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`@assistant-ui/react-langchain` wraps [`useStream`](https://docs.langchain.com/oss/javascript/langgraph-sdk/react-stream) from `@langchain/react` and exposes it as an assistant-ui runtime. Use this package if you are already integrating your app with `@langchain/react` and want assistant-ui on top of the upstream hook.
|
|
7
|
+
|
|
8
|
+
<Callout type="info">
|
|
9
|
+
assistant-ui ships two adapters for LangGraph backends:
|
|
10
|
+
- **[`@assistant-ui/react-langgraph`](/docs/runtimes/langgraph)** integrates with `@langchain/langgraph-sdk` directly and exposes features like subgraph events, UI messages, message metadata, and end-to-end cancellation.
|
|
11
|
+
- **`@assistant-ui/react-langchain`** (this page) wraps `@langchain/react`'s `useStream`. It is lighter-weight and stays aligned with upstream, but currently does not expose every `react-langgraph` feature.
|
|
12
|
+
|
|
13
|
+
See [the comparison doc](/docs/runtimes/langchain/comparison) for a feature gap table.
|
|
14
|
+
</Callout>
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
You need a LangGraph Cloud API server. You can start a server locally via [LangGraph Studio](https://github.com/langchain-ai/langgraph-studio) or use [LangSmith](https://www.langchain.com/langsmith) for a hosted version.
|
|
19
|
+
|
|
20
|
+
The state of the graph you are using must have a `messages` key with a list of LangChain-alike messages (or pass a custom `messagesKey`).
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
<Steps>
|
|
25
|
+
<Step>
|
|
26
|
+
|
|
27
|
+
### Install dependencies
|
|
28
|
+
|
|
29
|
+
<InstallCommand npm={["@assistant-ui/react", "@assistant-ui/react-langchain", "@langchain/react", "@langchain/langgraph-sdk"]} />
|
|
30
|
+
|
|
31
|
+
</Step>
|
|
32
|
+
<Step>
|
|
33
|
+
|
|
34
|
+
### Define a `MyAssistant` component
|
|
35
|
+
|
|
36
|
+
```tsx title="@/components/MyAssistant.tsx"
|
|
37
|
+
"use client";
|
|
38
|
+
|
|
39
|
+
import { Thread } from "@/components/assistant-ui/thread";
|
|
40
|
+
import { AssistantRuntimeProvider } from "@assistant-ui/react";
|
|
41
|
+
import { useStreamRuntime } from "@assistant-ui/react-langchain";
|
|
42
|
+
|
|
43
|
+
export function MyAssistant() {
|
|
44
|
+
const runtime = useStreamRuntime({
|
|
45
|
+
assistantId: process.env["NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID"]!,
|
|
46
|
+
apiUrl: process.env["NEXT_PUBLIC_LANGGRAPH_API_URL"],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
51
|
+
<Thread />
|
|
52
|
+
</AssistantRuntimeProvider>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
</Step>
|
|
58
|
+
<Step>
|
|
59
|
+
|
|
60
|
+
### Use the `MyAssistant` component
|
|
61
|
+
|
|
62
|
+
```tsx title="@/app/page.tsx"
|
|
63
|
+
import { MyAssistant } from "@/components/MyAssistant";
|
|
64
|
+
|
|
65
|
+
export default function Home() {
|
|
66
|
+
return (
|
|
67
|
+
<main className="h-dvh">
|
|
68
|
+
<MyAssistant />
|
|
69
|
+
</main>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
</Step>
|
|
75
|
+
<Step>
|
|
76
|
+
|
|
77
|
+
### Set environment variables
|
|
78
|
+
|
|
79
|
+
Create a `.env.local` file in your project with the following variables:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
NEXT_PUBLIC_LANGGRAPH_API_URL=http://localhost:2024
|
|
83
|
+
NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID=your_graph_id
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
</Step>
|
|
87
|
+
<Step>
|
|
88
|
+
|
|
89
|
+
### Set up UI components
|
|
90
|
+
|
|
91
|
+
Follow the [UI Components](/docs/ui/thread) guide to set up the UI components.
|
|
92
|
+
|
|
93
|
+
</Step>
|
|
94
|
+
</Steps>
|
|
95
|
+
|
|
96
|
+
## `useStreamRuntime` options
|
|
97
|
+
|
|
98
|
+
`useStreamRuntime` accepts every option [`useStream`](https://docs.langchain.com/oss/javascript/langgraph-sdk/react-stream) from `@langchain/react` does, plus three assistant-ui-specific fields:
|
|
99
|
+
|
|
100
|
+
| Option | Type | Description |
|
|
101
|
+
| ------------- | ---------------------------------------------- | ------------------------------------------------------------------------ |
|
|
102
|
+
| `cloud` | `AssistantCloud` | Optional — persists threads via assistant-cloud. |
|
|
103
|
+
| `adapters` | `{ attachments?, speech?, feedback? }` | Optional — attachment, speech, and feedback adapters. |
|
|
104
|
+
| `messagesKey` | `string` | The state key that holds messages. Defaults to `"messages"`. |
|
|
105
|
+
|
|
106
|
+
## Reading custom state keys
|
|
107
|
+
|
|
108
|
+
LangGraph agents often expose structured state beyond messages (plans, todos, scratch files, generative-UI artifacts). Read them directly with `useLangChainState`. It mirrors `useStream().values[key]` upstream and updates when the stream emits new state.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
import { useLangChainState } from "@assistant-ui/react-langchain";
|
|
112
|
+
|
|
113
|
+
type Todo = { id: string; title: string; done: boolean };
|
|
114
|
+
|
|
115
|
+
function TodoList() {
|
|
116
|
+
const todos = useLangChainState<Todo[]>("todos", []);
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<ul>
|
|
120
|
+
{todos.map((t) => (
|
|
121
|
+
<li key={t.id}>
|
|
122
|
+
{t.done ? "✓" : "○"} {t.title}
|
|
123
|
+
</li>
|
|
124
|
+
))}
|
|
125
|
+
</ul>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Signatures:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
useLangChainState<T>(key: string): T | undefined;
|
|
134
|
+
useLangChainState<T>(key: string, defaultValue: T): T;
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
This hook is especially useful with the [`deepagents`](https://docs.langchain.com/oss/python/deepagents) middleware, whose `write_todos` step updates `state.todos` alongside the tool-call stream. Reading the state key directly avoids reconstructing the list from partial tool-call args.
|
|
138
|
+
|
|
139
|
+
<Callout type="info">
|
|
140
|
+
Added in v0.0.2 — see issue [#3862](https://github.com/assistant-ui/assistant-ui/issues/3862) for motivation.
|
|
141
|
+
</Callout>
|
|
142
|
+
|
|
143
|
+
## Interrupts
|
|
144
|
+
|
|
145
|
+
LangGraph interrupts pause the graph and wait for client input. `useLangChainInterruptState` exposes the current interrupt; `useLangChainSubmit` resumes the graph with a raw state update.
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
import {
|
|
149
|
+
useLangChainInterruptState,
|
|
150
|
+
useLangChainSubmit,
|
|
151
|
+
} from "@assistant-ui/react-langchain";
|
|
152
|
+
import { Command } from "@langchain/langgraph-sdk";
|
|
153
|
+
|
|
154
|
+
function InterruptPrompt() {
|
|
155
|
+
const interrupt = useLangChainInterruptState();
|
|
156
|
+
const submit = useLangChainSubmit();
|
|
157
|
+
|
|
158
|
+
if (!interrupt) return null;
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<div>
|
|
162
|
+
<pre>{JSON.stringify(interrupt.value, null, 2)}</pre>
|
|
163
|
+
<button
|
|
164
|
+
onClick={() =>
|
|
165
|
+
submit(null, { command: new Command({ resume: "approved" }) })
|
|
166
|
+
}
|
|
167
|
+
>
|
|
168
|
+
Approve
|
|
169
|
+
</button>
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Message conversion
|
|
176
|
+
|
|
177
|
+
`convertLangChainBaseMessage` transforms a LangChain `BaseMessage` into an assistant-ui message. Use it when building a custom `ExternalStoreAdapter` that needs to consume LangChain messages outside of `useStreamRuntime`.
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import { convertLangChainBaseMessage } from "@assistant-ui/react-langchain";
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Cloud persistence
|
|
184
|
+
|
|
185
|
+
Pass an `AssistantCloud` instance to persist threads across sessions. The runtime automatically wires thread list management and resumes state from the cloud.
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
import { AssistantCloud } from "assistant-cloud";
|
|
189
|
+
import { useStreamRuntime } from "@assistant-ui/react-langchain";
|
|
190
|
+
|
|
191
|
+
const cloud = new AssistantCloud({ baseUrl: "/api/cloud" });
|
|
192
|
+
|
|
193
|
+
const runtime = useStreamRuntime({
|
|
194
|
+
cloud,
|
|
195
|
+
assistantId: "agent",
|
|
196
|
+
apiUrl: "http://localhost:2024",
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Custom `messagesKey`
|
|
201
|
+
|
|
202
|
+
If your graph stores messages under a non-default key, pass `messagesKey` so the runtime submits tool results and human turns to the correct state slot:
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
const runtime = useStreamRuntime({
|
|
206
|
+
assistantId: "agent",
|
|
207
|
+
apiUrl: "http://localhost:2024",
|
|
208
|
+
messagesKey: "chat_messages",
|
|
209
|
+
});
|
|
210
|
+
```
|