@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.
Files changed (56) hide show
  1. package/.docs/organized/code-examples/waterfall.md +18 -10
  2. package/.docs/organized/code-examples/with-a2a.md +12 -24
  3. package/.docs/organized/code-examples/with-ag-ui.md +14 -11
  4. package/.docs/organized/code-examples/with-ai-sdk-v6.md +12 -12
  5. package/.docs/organized/code-examples/with-artifacts.md +14 -12
  6. package/.docs/organized/code-examples/with-assistant-transport.md +13 -14
  7. package/.docs/organized/code-examples/with-chain-of-thought.md +11 -11
  8. package/.docs/organized/code-examples/with-cloud-standalone.md +17 -14
  9. package/.docs/organized/code-examples/with-cloud.md +12 -13
  10. package/.docs/organized/code-examples/with-custom-thread-list.md +12 -12
  11. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +19 -14
  12. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +15 -15
  13. package/.docs/organized/code-examples/with-expo.md +27 -23
  14. package/.docs/organized/code-examples/with-external-store.md +11 -11
  15. package/.docs/organized/code-examples/with-ffmpeg.md +19 -14
  16. package/.docs/organized/code-examples/with-generative-ui.md +11 -11
  17. package/.docs/organized/code-examples/with-google-adk.md +10 -10
  18. package/.docs/organized/code-examples/with-heat-graph.md +8 -8
  19. package/.docs/organized/code-examples/with-interactables.md +12 -27
  20. package/.docs/organized/code-examples/with-langchain.md +437 -0
  21. package/.docs/organized/code-examples/with-langgraph.md +20 -20
  22. package/.docs/organized/code-examples/with-livekit.md +59 -18
  23. package/.docs/organized/code-examples/with-opencode.md +2392 -0
  24. package/.docs/organized/code-examples/with-parent-id-grouping.md +12 -12
  25. package/.docs/organized/code-examples/with-react-hook-form.md +223 -151
  26. package/.docs/organized/code-examples/with-react-ink.md +3 -3
  27. package/.docs/organized/code-examples/with-react-router.md +15 -15
  28. package/.docs/organized/code-examples/with-store.md +11 -8
  29. package/.docs/organized/code-examples/with-tanstack.md +14 -14
  30. package/.docs/organized/code-examples/with-tap-runtime.md +13 -9
  31. package/.docs/raw/docs/(docs)/guides/mentions.mdx +248 -109
  32. package/.docs/raw/docs/(docs)/guides/slash-commands.mdx +112 -92
  33. package/.docs/raw/docs/(docs)/guides/voice.mdx +10 -3
  34. package/.docs/raw/docs/(docs)/rtl.mdx +79 -0
  35. package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +149 -40
  36. package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +2 -0
  37. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-runtime.mdx +4 -0
  38. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +1 -1
  39. package/.docs/raw/docs/primitives/composer.mdx +94 -62
  40. package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +57 -0
  41. package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +49 -3
  42. package/.docs/raw/docs/runtimes/custom/external-store.mdx +39 -1
  43. package/.docs/raw/docs/runtimes/custom/local.mdx +65 -18
  44. package/.docs/raw/docs/runtimes/google-adk/index.mdx +62 -0
  45. package/.docs/raw/docs/runtimes/langchain/comparison.mdx +60 -0
  46. package/.docs/raw/docs/runtimes/langchain/index.mdx +210 -0
  47. package/.docs/raw/docs/runtimes/langgraph/index.mdx +288 -60
  48. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +12 -4
  49. package/.docs/raw/docs/ui/composer-trigger-popover.mdx +199 -0
  50. package/.docs/raw/docs/ui/directive-text.mdx +113 -0
  51. package/.docs/raw/docs/ui/reasoning.mdx +13 -9
  52. package/dist/utils/logger.js +1 -1
  53. package/dist/utils/logger.js.map +1 -1
  54. package/package.json +4 -4
  55. package/src/utils/logger.ts +1 -1
  56. package/.docs/raw/docs/ui/mention.mdx +0 -168
@@ -3,6 +3,10 @@ title: Getting Started
3
3
  description: Connect to LangGraph Cloud API for agent workflows with streaming.
4
4
  ---
5
5
 
6
+ <Callout type="info">
7
+ If you are already using `@langchain/react`'s `useStream` hook, the alternative [`@assistant-ui/react-langchain`](/docs/runtimes/langchain) adapter may fit better. `@assistant-ui/react-langgraph` (this page) integrates with `@langchain/langgraph-sdk` directly and has the broader feature set — subgraph events, UI messages, message metadata, end-to-end cancellation. See the [comparison](/docs/runtimes/langchain/comparison).
8
+ </Callout>
9
+
6
10
  ## Requirements
7
11
 
8
12
  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.
@@ -60,6 +64,8 @@ NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID=your_graph_id
60
64
  ```tsx twoslash title="@/app/api/[...path]/route.ts"
61
65
  import { NextRequest, NextResponse } from "next/server";
62
66
 
67
+ export const runtime = "edge";
68
+
63
69
  function getCorsHeaders() {
64
70
  return {
65
71
  "Access-Control-Allow-Origin": "*",
@@ -84,6 +90,7 @@ async function handleRequest(req: NextRequest, method: string) {
84
90
  headers: {
85
91
  "x-api-key": process.env["LANGCHAIN_API_KEY"] || "",
86
92
  },
93
+ signal: req.signal,
87
94
  };
88
95
 
89
96
  if (["POST", "PUT", "PATCH"].includes(method)) {
@@ -142,45 +149,15 @@ export const OPTIONS = () =>
142
149
  // @filename: /lib/chatApi.ts
143
150
 
144
151
  // ---cut---
145
- import { Client, type ThreadState } from "@langchain/langgraph-sdk";
146
- import { LangChainMessage, LangGraphCommand } from "@assistant-ui/react-langgraph";
147
-
148
- const createClient = () => {
149
- const apiUrl = process.env["NEXT_PUBLIC_LANGGRAPH_API_URL"] || "/api";
150
- return new Client({
151
- apiUrl,
152
- });
153
- };
154
-
155
- export const createThread = async () => {
156
- const client = createClient();
157
- return client.threads.create();
158
- };
159
-
160
- export const getThreadState = async (
161
- threadId: string,
162
- ): Promise<ThreadState<{ messages: LangChainMessage[] }>> => {
163
- const client = createClient();
164
- return client.threads.getState(threadId);
165
- };
166
-
167
- export const sendMessage = async (params: {
168
- threadId: string;
169
- messages?: LangChainMessage[];
170
- command?: LangGraphCommand;
171
- }) => {
172
- const client = createClient();
173
- return client.runs.stream(
174
- params.threadId,
175
- process.env["NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID"]!,
176
- {
177
- input: params.messages?.length
178
- ? { messages: params.messages }
179
- : null,
180
- command: params.command,
181
- streamMode: ["messages", "updates"],
182
- },
183
- );
152
+ import { Client } from "@langchain/langgraph-sdk";
153
+
154
+ export const createClient = () => {
155
+ const apiUrl =
156
+ process.env["NEXT_PUBLIC_LANGGRAPH_API_URL"] ||
157
+ (typeof window !== "undefined"
158
+ ? new URL("/api", window.location.href).href
159
+ : "/api");
160
+ return new Client({ apiUrl });
184
161
  };
185
162
  ```
186
163
 
@@ -196,32 +173,41 @@ export const sendMessage = async (params: {
196
173
  // ---cut---
197
174
  "use client";
198
175
 
176
+ import { useMemo } from "react";
199
177
  import { Thread } from "@/components/assistant-ui/thread";
200
178
  import { AssistantRuntimeProvider } from "@assistant-ui/react";
201
- import { useLangGraphRuntime } from "@assistant-ui/react-langgraph";
179
+ import {
180
+ unstable_createLangGraphStream,
181
+ useLangGraphRuntime,
182
+ type LangChainMessage,
183
+ } from "@assistant-ui/react-langgraph";
202
184
 
203
- import { createThread, getThreadState, sendMessage } from "@/lib/chatApi";
185
+ import { createClient } from "@/lib/chatApi";
204
186
 
205
- export function MyAssistant() {
206
- const runtime = useLangGraphRuntime({
207
- stream: async function* (messages, { initialize, command }) {
208
- const { externalId } = await initialize();
209
- if (!externalId) throw new Error("Thread not found");
187
+ const ASSISTANT_ID = process.env["NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID"]!;
210
188
 
211
- const generator = await sendMessage({
212
- threadId: externalId,
213
- messages,
214
- command,
215
- });
189
+ export function MyAssistant() {
190
+ const client = useMemo(() => createClient(), []);
191
+ const stream = useMemo(
192
+ () =>
193
+ unstable_createLangGraphStream({
194
+ client,
195
+ assistantId: ASSISTANT_ID,
196
+ }),
197
+ [client],
198
+ );
216
199
 
217
- yield* generator;
218
- },
200
+ const runtime = useLangGraphRuntime({
201
+ unstable_allowCancellation: true,
202
+ stream,
219
203
  create: async () => {
220
- const { thread_id } = await createThread();
204
+ const { thread_id } = await client.threads.create();
221
205
  return { externalId: thread_id };
222
206
  },
223
207
  load: async (externalId) => {
224
- const state = await getThreadState(externalId);
208
+ const state = await client.threads.getState<{
209
+ messages: LangChainMessage[];
210
+ }>(externalId);
225
211
  return {
226
212
  messages: state.values.messages,
227
213
  interrupts: state.tasks[0]?.interrupts,
@@ -320,18 +306,38 @@ const runtime = useLangGraphRuntime({
320
306
  stream: async (messages, { initialize, ...config }) => { /* ... */ },
321
307
  eventHandlers: {
322
308
  onMessageChunk: (chunk, metadata) => {
323
- // Fired for each chunk in messages-tuple mode
324
- // metadata contains langgraph_step, langgraph_node, ls_model_name, etc.
309
+ // Fired for each chunk in messages-tuple mode.
310
+ // `metadata` contains langgraph_step, langgraph_node, ls_model_name, etc.
311
+ // For pipe-namespaced events emitted by subgraphs (e.g. `messages|tools:call_abc`),
312
+ // `metadata.namespace` holds the suffix ("tools:call_abc"). Use it to attribute
313
+ // a chunk to a specific subgraph.
325
314
  },
326
315
  onValues: (values) => {
327
- // Fired when a "values" event is received
316
+ // Fired when a top-level `values` event is received.
317
+ // Subgraph `values` events are routed to `onSubgraphValues` instead.
328
318
  },
329
319
  onUpdates: (updates) => {
330
- // Fired when an "updates" event is received
320
+ // Fired when a top-level `updates` event is received.
321
+ // Subgraph `updates` events are routed to `onSubgraphUpdates` instead.
322
+ },
323
+ onSubgraphValues: (namespace, values) => {
324
+ // Fired when a subgraph `values|<namespace>` event is received
325
+ // (e.g. `namespace === "tools:call_abc"`). Use this to observe
326
+ // subgraph-internal state without mixing it into `onValues`.
327
+ },
328
+ onSubgraphUpdates: (namespace, updates) => {
329
+ // Fired when a subgraph `updates|<namespace>` event is received.
331
330
  },
332
331
  onMetadata: (metadata) => { /* thread metadata */ },
333
332
  onInfo: (info) => { /* informational messages */ },
334
- onError: (error) => { /* stream errors */ },
333
+ onError: (error) => {
334
+ // Fired for both top-level and subgraph errors.
335
+ },
336
+ onSubgraphError: (namespace, error) => {
337
+ // Additionally fired for subgraph errors with the namespace.
338
+ // Use to attribute a subgraph failure to its source without marking
339
+ // the parent message as incomplete (that only happens for top-level errors).
340
+ },
335
341
  onCustomEvent: (type, data) => { /* custom events */ },
336
342
  },
337
343
  });
@@ -396,6 +402,51 @@ const runtime = useLangGraphRuntime({
396
402
 
397
403
  See the [Cloud Persistence guide](/docs/cloud/langgraph) for detailed setup instructions.
398
404
 
405
+ ### Custom Thread List
406
+
407
+ To surface pre-existing LangGraph `thread_id`s in the thread picker without running assistant-cloud, pass a `RemoteThreadListAdapter` via `unstable_threadListAdapter`. A common implementation backs `list()` with `client.threads.search()` and `initialize()` with `client.threads.create()`.
408
+
409
+ ```typescript
410
+ import type { RemoteThreadListAdapter } from "@assistant-ui/react";
411
+ import { Client } from "@langchain/langgraph-sdk";
412
+
413
+ const client = new Client({ apiUrl: process.env.NEXT_PUBLIC_LANGGRAPH_API_URL });
414
+
415
+ const threadListAdapter: RemoteThreadListAdapter = {
416
+ async list() {
417
+ const threads = await client.threads.search({ limit: 50 });
418
+ return {
419
+ threads: threads.map((t) => ({
420
+ status: "regular",
421
+ remoteId: t.thread_id,
422
+ externalId: t.thread_id,
423
+ title: (t.metadata as { title?: string } | undefined)?.title,
424
+ })),
425
+ };
426
+ },
427
+ async initialize() {
428
+ const t = await client.threads.create();
429
+ return { remoteId: t.thread_id, externalId: t.thread_id };
430
+ },
431
+ async delete(remoteId) {
432
+ await client.threads.delete(remoteId);
433
+ },
434
+ // rename, archive, unarchive, fetch, generateTitle — see link below
435
+ };
436
+
437
+ const runtime = useLangGraphRuntime({
438
+ stream: async function* (messages, { initialize }) { /* ... */ },
439
+ load: async (externalId) => { /* ... */ },
440
+ unstable_threadListAdapter: threadListAdapter,
441
+ });
442
+ ```
443
+
444
+ Setting `remoteId === externalId` keeps the ids assistant-ui stores aligned with the LangGraph thread ids your `load` and `stream` callbacks receive. See the [Custom Thread List guide](/docs/runtimes/custom/custom-thread-list) for the full adapter contract.
445
+
446
+ <Callout type="info">
447
+ When `unstable_threadListAdapter` is provided, the `cloud`, `create`, and `delete` options are ignored — the adapter owns the full thread-list lifecycle.
448
+ </Callout>
449
+
399
450
  ## Message Editing & Regeneration
400
451
 
401
452
  LangGraph uses server-side checkpoints for state management. To support message editing (branching) and regeneration, you need to provide a `getCheckpointId` callback that resolves the appropriate checkpoint for server-side forking.
@@ -469,3 +520,180 @@ LangGraph supports interrupting the execution flow to request user input or hand
469
520
  3. The runtime will automatically restore the interrupt state when switching threads
470
521
 
471
522
  This feature is particularly useful for applications that require user approval flows, multi-step forms, or any other interactive elements that might span multiple thread switches.
523
+
524
+ ## Generative UI (`ui_message`)
525
+
526
+ LangGraph's [Generative UI](https://docs.langchain.com/langsmith/generative-ui-react) lets your graph emit structured UI components alongside assistant messages via `push_ui_message` (Python) or `typedUi().push()` (TypeScript). The assistant-ui LangGraph adapter translates these into [`DataMessagePart`s](/docs/guides/tool-ui) on the associated assistant message, which you render with the existing `makeAssistantDataUI` API.
527
+
528
+ ### Enable the `custom` stream mode
529
+
530
+ UI messages are emitted through LangGraph's `custom` stream channel. Make sure your `sendMessage` helper includes `"custom"` in `streamMode`:
531
+
532
+ ```ts
533
+ streamMode: ["messages", "updates", "custom"]
534
+ ```
535
+
536
+ Alternatively, if your graph accumulates UI messages in state under the `ui` key (the default for `typedUi`), `"values"` also works — the adapter reads both paths.
537
+
538
+ ### Custom state key
539
+
540
+ If your graph uses a non-default `stateKey` with `typedUi(config, { stateKey: "my_ui" })` on the server, pass the matching `uiStateKey` option to `useLangGraphRuntime` on the client:
541
+
542
+ ```ts
543
+ const runtime = useLangGraphRuntime({
544
+ stream: async function* (messages, { initialize }) { /* ... */ },
545
+ uiStateKey: "my_ui",
546
+ });
547
+ ```
548
+
549
+ This only affects the `values` stream path — the `custom` channel carries each UI event individually and doesn't rely on the state key.
550
+
551
+ ### Emit a UI message from your graph
552
+
553
+ ```python title="Python"
554
+ from langgraph.graph.ui import push_ui_message
555
+ from langchain_core.messages import AIMessage
556
+
557
+ async def chart_node(state, config):
558
+ message = AIMessage(id="msg-1", content="Here's your chart.")
559
+ push_ui_message(
560
+ "chart",
561
+ {"series": [1, 2, 3], "title": "Sales"},
562
+ message=message, # Links the UI to this AI message
563
+ )
564
+ return {"messages": [message]}
565
+ ```
566
+
567
+ ```ts title="TypeScript"
568
+ import { typedUi } from "@langchain/langgraph-sdk/react-ui/server";
569
+ import type { ComponentRegistry } from "./components";
570
+
571
+ export async function chartNode(state, config) {
572
+ const ui = typedUi<ComponentRegistry>(config);
573
+ const message = { id: "msg-1", type: "ai", content: "Here's your chart." };
574
+ ui.push(
575
+ { name: "chart", props: { series: [1, 2, 3], title: "Sales" } },
576
+ { message },
577
+ );
578
+ return { messages: [message] };
579
+ }
580
+ ```
581
+
582
+ Passing `message` (Python) or `{ message }` (TypeScript) is what links the UI component to a specific assistant message — the adapter reads `metadata.message_id` to attach the generated `DataMessagePart` to the correct message in the thread.
583
+
584
+ ### Register a renderer on the client
585
+
586
+ ```tsx title="@/components/ChartUI.tsx"
587
+ import { makeAssistantDataUI } from "@assistant-ui/react";
588
+
589
+ type ChartProps = {
590
+ series: number[];
591
+ title: string;
592
+ };
593
+
594
+ export const ChartUI = makeAssistantDataUI<ChartProps>({
595
+ name: "chart",
596
+ render: ({ data }) => (
597
+ <div>
598
+ <h3>{data.title}</h3>
599
+ <Chart series={data.series} />
600
+ </div>
601
+ ),
602
+ });
603
+ ```
604
+
605
+ Mount the component once somewhere inside the `AssistantRuntimeProvider` tree. It renders nothing itself — it only registers the renderer:
606
+
607
+ ```tsx title="@/components/MyAssistant.tsx"
608
+ <AssistantRuntimeProvider runtime={runtime}>
609
+ <ChartUI />
610
+ <Thread />
611
+ </AssistantRuntimeProvider>
612
+ ```
613
+
614
+ When a matching UI message arrives, the adapter appends a `{ type: "data", name: "chart", data: { series, title } }` part to the parent assistant message and the registered component renders inline.
615
+
616
+ ### Register renderers via `uiComponents`
617
+
618
+ Instead of mounting separate `makeAssistantDataUI` components, you can register renderers directly on the runtime hook via the `uiComponents` option:
619
+
620
+ ```tsx title="@/components/MyAssistant.tsx"
621
+ const runtime = useLangGraphRuntime({
622
+ stream: async function* (messages, { initialize }) { /* ... */ },
623
+ uiComponents: {
624
+ renderers: {
625
+ chart: ({ data }) => <Chart series={data.series} title={data.title} />,
626
+ table: ({ data }) => <DataTable rows={data.rows} />,
627
+ },
628
+ },
629
+ });
630
+ ```
631
+
632
+ Static `renderers` are matched by `ui_message` name. If no match is found, the part renders nothing unless a `fallback` is provided.
633
+
634
+ ### Dynamic loading with `fallback`
635
+
636
+ LangSmith's [Generative UI](https://docs.langchain.com/langsmith/generative-ui-react) supports colocating UI code with your graph and loading it at runtime via `LoadExternalComponent`. The `fallback` option handles any `ui_message` name that has no static renderer:
637
+
638
+ ```tsx title="@/components/MyAssistant.tsx"
639
+ import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui";
640
+
641
+ const runtime = useLangGraphRuntime({
642
+ stream: async function* (messages, { initialize }) { /* ... */ },
643
+ uiComponents: {
644
+ fallback: ({ name, data }) => (
645
+ <LoadExternalComponent name={name} props={data} />
646
+ ),
647
+ renderers: {
648
+ chart: ({ data }) => <Chart {...data} />,
649
+ },
650
+ },
651
+ });
652
+ ```
653
+
654
+ With this setup:
655
+ - A `ui_message` with `name: "chart"` renders the static `Chart` component
656
+ - Any other name (e.g. `"dashboard"`, `"form"`) is handled by `fallback`, which fetches the component from LangSmith at runtime
657
+
658
+ The `fallback` component receives the same props as any data renderer: `name`, `data`, and part state metadata. This lets you pass the component name and props straight through to `LoadExternalComponent`.
659
+
660
+ ### Semantics
661
+
662
+ The adapter mirrors the reducer in `@langchain/langgraph-sdk/react-ui` exactly:
663
+
664
+ - UI messages are keyed by their own `id`. Pushing the same id again **replaces** the existing entry
665
+ - Passing `metadata: { merge: true }` shallow-merges `props` onto the previous entry
666
+ - Emitting `{ type: "remove-ui", id }` (via `delete_ui_message` / `ui.delete(id)`) removes the entry
667
+ - UI messages without `metadata.message_id` are held in the runtime but not injected into any message; use `useLangGraphUIMessages()` to access the raw list if needed
668
+
669
+ ### Restore persisted UI messages on thread switch
670
+
671
+ If your graph persists UI messages in state via `typedUi`, return them from the `load` callback so they're restored when the user switches threads or refreshes the page:
672
+
673
+ ```tsx
674
+ const runtime = useLangGraphRuntime({
675
+ stream: async function* (messages, { initialize }) { /* ... */ },
676
+ load: async (externalId) => {
677
+ const state = await getThreadState(externalId);
678
+ return {
679
+ messages: state.values.messages,
680
+ uiMessages: state.values.ui,
681
+ interrupts: state.tasks[0]?.interrupts,
682
+ };
683
+ },
684
+ });
685
+ ```
686
+
687
+ Without this, each reload starts with an empty UI list even though the messages themselves are loaded.
688
+
689
+ ### Escape hatch: `useLangGraphUIMessages`
690
+
691
+ ```tsx
692
+ import { useLangGraphUIMessages } from "@assistant-ui/react-langgraph";
693
+
694
+ function Sidebar() {
695
+ const uiMessages = useLangGraphUIMessages();
696
+ // Filter, group, or render UI messages outside the thread
697
+ return <>{uiMessages.map(/* ... */)}</>;
698
+ }
699
+ ```
@@ -11,7 +11,8 @@ Choosing the right runtime is crucial for your assistant-ui implementation. This
11
11
  graph TD
12
12
  A[What's your starting point?] --> B{Existing Framework?}
13
13
  B -->|Vercel AI SDK| C[Use AI SDK Integration]
14
- B -->|LangGraph| D[Use LangGraph Runtime]
14
+ B -->|LangGraph via langgraph-sdk| D1[Use react-langgraph]
15
+ B -->|LangChain via @langchain/react useStream| D2[Use react-langchain]
15
16
  B -->|LangServe| E[Use LangServe Runtime]
16
17
  B -->|Mastra| F[Use Mastra Runtime]
17
18
  B -->|AG-UI Protocol| J[Use AG-UI Runtime]
@@ -55,9 +56,14 @@ For popular frameworks, we provide ready-to-use integrations built on top of our
55
56
  />
56
57
  <Card
57
58
  title="LangGraph"
58
- description="For complex agent workflows with LangChain's graph framework"
59
+ description="Integrates with `@langchain/langgraph-sdk` directly. Broader feature set: subgraph events, UI messages, message metadata, cancellation."
59
60
  href="/docs/runtimes/langgraph"
60
61
  />
62
+ <Card
63
+ title="LangChain useStream"
64
+ description="Wraps `useStream` from `@langchain/react`. Lighter-weight, stays aligned with upstream. Fewer features today."
65
+ href="/docs/runtimes/langchain"
66
+ />
61
67
  <Card
62
68
  title="LangServe"
63
69
  description="For LangChain applications deployed with LangServe"
@@ -87,13 +93,14 @@ For popular frameworks, we provide ready-to-use integrations built on top of our
87
93
  The pre-built integrations (AI SDK, LangGraph, etc.) are **not separate runtime types**. They're convenient wrappers built on top of our core runtimes:
88
94
 
89
95
  - **AI SDK Integration** → Built on `LocalRuntime` with streaming adapter
90
- - **LangGraph Runtime** → Built on `LocalRuntime` with graph execution adapter
96
+ - **LangGraph Runtime** → Built on `ExternalStoreRuntime`, integrates with `@langchain/langgraph-sdk`
97
+ - **LangChain useStream Runtime** → Built on `ExternalStoreRuntime`, wraps `useStream` from `@langchain/react`
91
98
  - **LangServe Runtime** → Built on `LocalRuntime` with LangServe client adapter
92
99
  - **Mastra Runtime** → Built on `LocalRuntime` with workflow adapter
93
100
  - **AG-UI Runtime** → Built on `LocalRuntime` with AG-UI protocol adapter
94
101
  - **A2A Runtime** → Built on `LocalRuntime` with Agent-to-Agent protocol adapter
95
102
 
96
- This means you get all the benefits of `LocalRuntime` (automatic state management, built-in features) with zero configuration for your specific framework.
103
+ This means pre-built integrations give you assistant-ui's features — state management, streaming, UI primitives — with zero configuration for your specific framework, regardless of whether the adapter happens to build on `LocalRuntime` or `ExternalStoreRuntime` internally. The list above tells you which core runtime each adapter uses.
97
104
 
98
105
  ### When to Use Pre-Built vs Core Runtimes
99
106
 
@@ -228,6 +235,7 @@ Explore our implementation examples:
228
235
  - [`LocalRuntime` Guide](/docs/runtimes/custom/local)
229
236
  - [`ExternalStoreRuntime` Guide](/docs/runtimes/custom/external-store)
230
237
  - [LangGraph Integration](/docs/runtimes/langgraph)
238
+ - [LangChain useStream Integration](/docs/runtimes/langchain)
231
239
  3. **Start with an example** from our [examples repository](https://github.com/assistant-ui/assistant-ui/tree/main/examples)
232
240
  4. **Add features progressively** using adapters
233
241
  5. **Consider Assistant Cloud** for production persistence
@@ -0,0 +1,199 @@
1
+ ---
2
+ title: Composer Trigger Popover
3
+ description: Reusable picker UI for @ mentions, / slash commands, and any other character-triggered popover.
4
+ ---
5
+
6
+ import { ComposerTriggerPopoverSample } from "@/components/docs/samples/composer-trigger-popover";
7
+
8
+ <ComposerTriggerPopoverSample />
9
+
10
+ ## Getting Started
11
+
12
+ <Steps>
13
+ <Step>
14
+
15
+ ### Add `composer-trigger-popover`
16
+
17
+ <InstallCommand shadcn={["composer-trigger-popover"]} />
18
+
19
+ This adds `/components/assistant-ui/composer-trigger-popover.tsx` — a generic picker UI (Categories + Items + Back) driven by an adapter and one of two behavior props: `directive` (insert a chip) or `action` (run a callback).
20
+
21
+ </Step>
22
+ <Step>
23
+
24
+ ### Wrap the composer
25
+
26
+ Place `ComposerPrimitive.Unstable_TriggerPopoverRoot` around your composer. Any number of `ComposerTriggerPopover` declarations can live inside — each with its own trigger character, adapter, and behavior prop.
27
+
28
+ ```tsx title="components/assistant-ui/thread.tsx"
29
+ import { ComposerPrimitive } from "@assistant-ui/react";
30
+ import { ComposerTriggerPopover } from "@/components/assistant-ui/composer-trigger-popover";
31
+
32
+ const Composer = () => (
33
+ <ComposerPrimitive.Unstable_TriggerPopoverRoot>
34
+ <ComposerPrimitive.Root>
35
+ <ComposerPrimitive.Input placeholder="Type @ to mention, / for commands..." />
36
+ <ComposerPrimitive.Send />
37
+
38
+ {/* triggers declared here */}
39
+ </ComposerPrimitive.Root>
40
+ </ComposerPrimitive.Unstable_TriggerPopoverRoot>
41
+ );
42
+ ```
43
+
44
+ </Step>
45
+ </Steps>
46
+
47
+ ## @ Mention
48
+
49
+ Pair the popover with `unstable_useMentionAdapter` — the hook returns a spreadable `{ adapter, directive }` bundle so selecting an item writes a `:tool[Label]{name=id}` directive into the composer text.
50
+
51
+ ```tsx
52
+ import { unstable_useMentionAdapter } from "@assistant-ui/react";
53
+ import { WrenchIcon } from "lucide-react";
54
+
55
+ const mention = unstable_useMentionAdapter();
56
+
57
+ <ComposerTriggerPopover
58
+ char="@"
59
+ {...mention}
60
+ fallbackIcon={WrenchIcon}
61
+ />;
62
+ ```
63
+
64
+ Override formatter or add an `onInserted` callback via hook options: `unstable_useMentionAdapter({ formatter, onInserted })`.
65
+
66
+ `unstable_useMentionAdapter` also accepts `items` (flat custom list), `categories` (multi-category drill-down), and `includeModelContextTools` for fine-grained control. See the [Mentions guide](/docs/guides/mentions#built-in-mention-adapter).
67
+
68
+ Render selected mentions as chips in user messages with [`DirectiveText`](/docs/ui/directive-text). For inline chips **inside** the composer, use [`LexicalComposerInput`](/docs/guides/mentions#textarea-vs-lexical).
69
+
70
+ ## / Slash Command
71
+
72
+ Use [`unstable_useSlashCommandAdapter`](/docs/guides/slash-commands) to bundle commands (data + `execute`) into `{ adapter, action }` — then plug both into `ComposerTriggerPopover`. By default a directive chip is left in the composer as an audit trail; pass `removeOnExecute` to strip the `/command` text entirely. `iconMap` maps `metadata.icon` strings on items and categories to Lucide icons.
73
+
74
+ ```tsx
75
+ import {
76
+ unstable_useSlashCommandAdapter,
77
+ type Unstable_SlashCommand,
78
+ } from "@assistant-ui/react";
79
+ import { FileTextIcon, GlobeIcon, LanguagesIcon, SlashIcon } from "lucide-react";
80
+
81
+ const SLASH_COMMANDS: readonly Unstable_SlashCommand[] = [
82
+ {
83
+ id: "summarize",
84
+ description: "Summarize the conversation",
85
+ icon: "FileText",
86
+ execute: () => {/* ... */},
87
+ },
88
+ {
89
+ id: "translate",
90
+ description: "Translate to another language",
91
+ icon: "Languages",
92
+ execute: () => {/* ... */},
93
+ },
94
+ {
95
+ id: "search",
96
+ description: "Search the web",
97
+ icon: "Globe",
98
+ execute: () => {/* ... */},
99
+ },
100
+ ];
101
+
102
+ function SlashComposer() {
103
+ const slash = unstable_useSlashCommandAdapter({ commands: SLASH_COMMANDS });
104
+
105
+ return (
106
+ <ComposerTriggerPopover
107
+ char="/"
108
+ {...slash}
109
+ iconMap={{
110
+ FileText: FileTextIcon,
111
+ Languages: LanguagesIcon,
112
+ Globe: GlobeIcon,
113
+ }}
114
+ fallbackIcon={SlashIcon}
115
+ />
116
+ );
117
+ }
118
+ ```
119
+
120
+ ## Combining Triggers
121
+
122
+ Multiple popovers coexist under one `TriggerPopoverRoot`. Each reads state from its own declaration, so `@` and `/` never collide.
123
+
124
+ ```tsx
125
+ const commandHandlers: Record<string, () => void> = {
126
+ summarize: () => {/* ... */},
127
+ translate: () => {/* ... */},
128
+ };
129
+
130
+ <ComposerPrimitive.Unstable_TriggerPopoverRoot>
131
+ <ComposerPrimitive.Root>
132
+ <ComposerPrimitive.Input placeholder="Type @ to mention, / for commands..." />
133
+
134
+ <ComposerTriggerPopover
135
+ char="@"
136
+ adapter={mentionAdapter}
137
+ directive={{ formatter: unstable_defaultDirectiveFormatter }}
138
+ fallbackIcon={WrenchIcon}
139
+ />
140
+ <ComposerTriggerPopover
141
+ char="/"
142
+ adapter={slashAdapter}
143
+ action={{
144
+ formatter: unstable_defaultDirectiveFormatter,
145
+ onExecute: (item) => commandHandlers[item.id]?.(),
146
+ }}
147
+ iconMap={slashIcons}
148
+ fallbackIcon={SlashIcon}
149
+ />
150
+ </ComposerPrimitive.Root>
151
+ </ComposerPrimitive.Unstable_TriggerPopoverRoot>
152
+ ```
153
+
154
+ ## Keyboard Navigation
155
+
156
+ | Key | Action |
157
+ | --- | --- |
158
+ | <Kbd>ArrowDown</Kbd> | Highlight next item |
159
+ | <Kbd>ArrowUp</Kbd> | Highlight previous item |
160
+ | <Kbd>Enter</Kbd> | Select highlighted item / drill into category |
161
+ | <Kbd>Escape</Kbd> | Close popover |
162
+ | <Kbd>Backspace</Kbd> | Go back to categories (when query is empty) |
163
+
164
+ ## API Reference
165
+
166
+ | Prop | Type | Default | Description |
167
+ | --- | --- | --- | --- |
168
+ | `char` | `string` | — | Trigger character, e.g. `"@"` or `"/"` (required; unique within the root) |
169
+ | `adapter` | `Unstable_TriggerAdapter` | — | Provides categories, items, and search (required) |
170
+ | `directive` | `{ formatter, onInserted?, chip? }` | — | Enables directive-insert behavior. Mutually exclusive with `action`. |
171
+ | `action` | `{ formatter, onExecute, removeOnExecute?, chip? }` | — | Enables action behavior. Mutually exclusive with `directive`. |
172
+ | `iconMap` | `Record<string, IconComponent>` | — | Maps `item.metadata.icon` / `category.metadata.icon` strings to icons |
173
+ | `fallbackIcon` | `IconComponent` | `SparklesIcon` | Icon used when no `iconMap` entry matches |
174
+ | `backLabel` | `string` | `"Back"` | Back button label |
175
+ | `emptyCategoriesLabel` | `string` | `"No items available"` | Shown when no categories are available |
176
+ | `emptyItemsLabel` | `string` | `"No matching items"` | Shown when no items match |
177
+
178
+ All other props (`className`, etc.) forward to the underlying popover `div`.
179
+
180
+ ### `directive` object
181
+
182
+ | Field | Type | Description |
183
+ | --- | --- | --- |
184
+ | `formatter` | `Unstable_DirectiveFormatter` | Serializes the selected item into the directive text written to the composer |
185
+ | `onInserted` | `(item) => void` | Optional callback fired after the directive has been inserted |
186
+
187
+ ### `action` object
188
+
189
+ | Field | Type | Description |
190
+ | --- | --- | --- |
191
+ | `formatter` | `Unstable_DirectiveFormatter` | Serializes the selected item into the chip left behind (unused when `removeOnExecute`) |
192
+ | `onExecute` | `(item) => void` | Callback fired when an item is selected |
193
+ | `removeOnExecute` | `boolean` | When `true`, strips the trigger text instead of leaving a chip. Default `false`. |
194
+
195
+ ## Related
196
+
197
+ - [Directive Text](/docs/ui/directive-text) — renderer for mention chips in user messages
198
+ - [Mentions guide](/docs/guides/mentions) — `@`-mention architecture and formatter details
199
+ - [Slash Commands guide](/docs/guides/slash-commands) — `/`-command architecture