@assistant-ui/mcp-docs-server 0.1.33 → 0.1.34

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 (81) hide show
  1. package/.docs/organized/code-examples/waterfall.md +5 -5
  2. package/.docs/organized/code-examples/with-a2a.md +5 -5
  3. package/.docs/organized/code-examples/with-ag-ui.md +9 -9
  4. package/.docs/organized/code-examples/with-ai-sdk-v6.md +7 -7
  5. package/.docs/organized/code-examples/with-artifacts.md +37 -31
  6. package/.docs/organized/code-examples/with-assistant-transport.md +8 -8
  7. package/.docs/organized/code-examples/with-browser-extension.md +5 -5
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +68 -47
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +7 -7
  10. package/.docs/organized/code-examples/with-cloud.md +7 -7
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +7 -7
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +8 -8
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +8 -8
  14. package/.docs/organized/code-examples/with-expo.md +33 -24
  15. package/.docs/organized/code-examples/with-external-store.md +5 -5
  16. package/.docs/organized/code-examples/with-ffmpeg.md +10 -10
  17. package/.docs/organized/code-examples/with-generative-ui.md +70 -64
  18. package/.docs/organized/code-examples/with-google-adk.md +6 -6
  19. package/.docs/organized/code-examples/with-heat-graph.md +5 -5
  20. package/.docs/organized/code-examples/with-image-generation.md +7 -7
  21. package/.docs/organized/code-examples/with-interactables.md +7 -7
  22. package/.docs/organized/code-examples/with-langchain.md +7 -7
  23. package/.docs/organized/code-examples/with-langgraph.md +30 -26
  24. package/.docs/organized/code-examples/with-livekit.md +8 -8
  25. package/.docs/organized/code-examples/with-mcp.md +8 -8
  26. package/.docs/organized/code-examples/with-opencode.md +6 -6
  27. package/.docs/organized/code-examples/with-react-hook-form.md +7 -7
  28. package/.docs/organized/code-examples/with-react-ink.md +295 -100
  29. package/.docs/organized/code-examples/with-react-router.md +11 -11
  30. package/.docs/organized/code-examples/with-resumable-stream.md +8 -8
  31. package/.docs/organized/code-examples/with-store.md +64 -64
  32. package/.docs/organized/code-examples/with-tanstack.md +8 -8
  33. package/.docs/organized/code-examples/with-tap-runtime.md +5 -5
  34. package/.docs/raw/docs/(docs)/architecture.mdx +52 -41
  35. package/.docs/raw/docs/(reference)/api-reference/external-store/message-conversion.mdx +1 -1
  36. package/.docs/raw/docs/(reference)/api-reference/external-store/runtime.mdx +3 -0
  37. package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +14 -3
  38. package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +94 -3
  39. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +5 -69
  40. package/.docs/raw/docs/ink/adapters.mdx +23 -1
  41. package/.docs/raw/docs/ink/hooks.mdx +20 -17
  42. package/.docs/raw/docs/migrations/toolkit-tools.mdx +14 -8
  43. package/.docs/raw/docs/react-native/hooks.mdx +25 -17
  44. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +41 -0
  45. package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +3 -3
  46. package/.docs/raw/docs/runtimes/concepts/architecture.mdx +46 -4
  47. package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +54 -12
  48. package/.docs/raw/docs/runtimes/custom/external-store.mdx +62 -4
  49. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +12 -0
  50. package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +9 -9
  51. package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +13 -0
  52. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +5 -5
  53. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +3 -3
  54. package/.docs/raw/docs/tools/backend.mdx +19 -11
  55. package/.docs/raw/docs/tools/defining-tools.mdx +177 -52
  56. package/.docs/raw/docs/tools/index.mdx +7 -12
  57. package/.docs/raw/docs/tools/mcp.mdx +83 -15
  58. package/.docs/raw/docs/tools/multi-agent.mdx +5 -5
  59. package/.docs/raw/docs/tools/tool-ui.mdx +27 -27
  60. package/.docs/raw/docs/tools/user-managed-mcp.mdx +4 -4
  61. package/.docs/raw/docs/ui/mermaid.mdx +16 -9
  62. package/.docs/raw/docs/ui/part-grouping.mdx +2 -2
  63. package/.docs/raw/docs/ui/tool-fallback.mdx +16 -0
  64. package/.docs/raw/docs/utilities/react-o11y.mdx +2 -2
  65. package/dist/constants.js.map +1 -1
  66. package/dist/index.js.map +1 -1
  67. package/dist/prepare-docs/code-examples.js.map +1 -1
  68. package/dist/prepare-docs/copy-raw.js.map +1 -1
  69. package/dist/prepare-docs/prepare.js.map +1 -1
  70. package/dist/stdio.js.map +1 -1
  71. package/dist/tools/docs.js.map +1 -1
  72. package/dist/tools/examples.js.map +1 -1
  73. package/dist/tools/tests/test-setup.js.map +1 -1
  74. package/dist/utils/mdx.js.map +1 -1
  75. package/dist/utils/paths.js.map +1 -1
  76. package/package.json +4 -4
  77. /package/.docs/raw/docs/{(docs)/copilots → copilots}/assistant-frame.mdx +0 -0
  78. /package/.docs/raw/docs/{(docs)/copilots → copilots}/make-assistant-visible.mdx +0 -0
  79. /package/.docs/raw/docs/{(docs)/copilots → copilots}/model-context.mdx +0 -0
  80. /package/.docs/raw/docs/{(docs)/copilots → copilots}/motivation.mdx +0 -0
  81. /package/.docs/raw/docs/{(docs)/copilots → copilots}/use-assistant-instructions.mdx +0 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Adapters
3
- description: Title generation and storage adapters for React Ink.
3
+ description: Attachment, title generation, and storage adapters for React Ink.
4
4
  ---
5
5
 
6
6
  Adapters customize runtime behavior. They can be passed as options to `useLocalRuntime` or `useRemoteThreadListRuntime`.
@@ -17,6 +17,28 @@ const adapter = createFileStorageAdapter({
17
17
  });
18
18
  ```
19
19
 
20
+ ## Attachment adapters
21
+
22
+ `SimpleTextAttachmentAdapter` and `SimpleImageAttachmentAdapter` work the same in the terminal as on web and React Native. Text contents are sent to the model wrapped in `<attachment name=...>` tags, and images are sent as base64 data URLs.
23
+
24
+ ```ts
25
+ import {
26
+ CompositeAttachmentAdapter,
27
+ SimpleImageAttachmentAdapter,
28
+ SimpleTextAttachmentAdapter,
29
+ useLocalRuntime,
30
+ } from "@assistant-ui/react-ink";
31
+
32
+ const runtime = useLocalRuntime(chatModelAdapter, {
33
+ adapters: {
34
+ attachments: new CompositeAttachmentAdapter([
35
+ new SimpleTextAttachmentAdapter(),
36
+ new SimpleImageAttachmentAdapter(),
37
+ ]),
38
+ },
39
+ });
40
+ ```
41
+
20
42
  ## TitleGenerationAdapter
21
43
 
22
44
  Produces a thread title from a thread's messages. Pass one as the `titleGenerator` option to `createFileStorageAdapter`, or call it from a custom `RemoteThreadListAdapter`.
@@ -155,37 +155,40 @@ const runtime = useRemoteThreadListRuntime({
155
155
 
156
156
  ### Tools
157
157
 
158
- Register tools with a toolkit. The tool definition is forwarded to the model, and when the model calls it, the `execute` function runs and the `render` component displays the result.
158
+ Author tools with `defineToolkit`, the same API as on the web ([Defining Tools](/docs/tools/defining-tools)). The tool definition is forwarded to the model; when the model calls it, the `execute` function runs and the `render` component displays the result.
159
+
160
+ <Callout type="info">
161
+ An Ink app runs in a single Node process, so there is no client/server
162
+ boundary to split and no build step. `defineToolkit` from
163
+ `@assistant-ui/react-ink` runs at runtime: import it as a value, with no
164
+ `"use generative"` directive and no `"use client"` inside `execute`.
165
+ </Callout>
159
166
 
160
167
  ```tsx title="weather-toolkit.tsx"
161
- import type { Toolkit } from "@assistant-ui/react-ink";
168
+ import { defineToolkit } from "@assistant-ui/react-ink";
162
169
  import { Text } from "ink";
170
+ import { z } from "zod";
163
171
 
164
- export const toolkit = {
172
+ export default defineToolkit({
165
173
  get_weather: {
166
- type: "frontend",
167
174
  description: "Get the current weather for a city",
168
- parameters: {
169
- type: "object",
170
- properties: {
171
- city: { type: "string" },
172
- },
173
- required: ["city"],
174
- },
175
+ parameters: z.object({ city: z.string() }),
175
176
  execute: async ({ city }) => {
176
177
  const res = await fetch(`https://api.weather.example/${city}`);
177
178
  return res.json();
178
179
  },
179
180
  render: ({ args, result }) => (
180
- <Text>{args.city}: {result?.temperature}°F</Text>
181
+ <Text>
182
+ {args.city}: {result?.temperature}°F
183
+ </Text>
181
184
  ),
182
185
  },
183
- } satisfies Toolkit;
186
+ });
184
187
  ```
185
188
 
186
189
  ```tsx title="ToolProvider.tsx"
187
190
  import { AuiProvider, Tools, useAui } from "@assistant-ui/react-ink";
188
- import { toolkit } from "./weather-toolkit";
191
+ import toolkit from "./weather-toolkit";
189
192
 
190
193
  function ToolProvider({ children }: { children: React.ReactNode }) {
191
194
  const aui = useAui({ tools: Tools({ toolkit }) });
@@ -246,7 +249,7 @@ const WeatherCardUI = makeAssistantDataUI({
246
249
  Wrap a render function component so that it always uses the latest version without re-creating a stable reference. Useful when passing a render prop inline and the function closes over changing state.
247
250
 
248
251
  ```tsx title="weather-toolkit.tsx"
249
- import { type Toolkit, useInlineRender } from "@assistant-ui/react-ink";
252
+ import { defineToolkit, useInlineRender } from "@assistant-ui/react-ink";
250
253
  import { Text } from "ink";
251
254
  import { useMemo } from "react";
252
255
 
@@ -257,12 +260,12 @@ export function useWeatherToolkit() {
257
260
 
258
261
  return useMemo(
259
262
  () =>
260
- ({
263
+ defineToolkit({
261
264
  get_weather: {
262
265
  type: "backend",
263
266
  render,
264
267
  },
265
- }) satisfies Toolkit,
268
+ }),
266
269
  [render],
267
270
  );
268
271
  }
@@ -103,25 +103,31 @@ export function App() {
103
103
 
104
104
  ## UI-Only Tool Renderers
105
105
 
106
- If you used `makeAssistantToolUI` or `useAssistantToolUI` for a backend, MCP, or LangGraph tool, the tool executes elsewhere, so there is no `execute` to author. That makes it a **render-only** entry, which belongs in a plain `satisfies Toolkit` object — not a `"use generative"` file (a generative tool must declare an `execute`). Author an explicit `type: "backend"` with just a `render`:
106
+ If you used `makeAssistantToolUI` or `useAssistantToolUI` for a backend, MCP, or
107
+ LangGraph tool, the tool executes elsewhere. Prefer a `"use generative"` toolkit
108
+ with `execute: externalTool()` so the compiler omits the server entry, while the
109
+ client keeps your renderer as `type: "backend"`:
107
110
 
108
111
  ```tsx
109
- // app/tool-ui.tsx
110
- "use client";
112
+ // app/toolkit.tsx
113
+ "use generative";
111
114
 
112
- import type { Toolkit } from "@assistant-ui/react";
115
+ import { defineToolkit, externalTool } from "@assistant-ui/react";
113
116
 
114
- export const toolkit = {
117
+ export default defineToolkit({
115
118
  web_search: {
116
- type: "backend",
119
+ execute: externalTool(),
117
120
  render: ({ args, result }) => (
118
121
  <SearchResults query={args.query} results={result?.results ?? []} />
119
122
  ),
120
123
  },
121
- } satisfies Toolkit;
124
+ });
122
125
  ```
123
126
 
124
- Register it like any toolkit: `useAui({ tools: Tools({ toolkit }) })`. Render-only entries upload no schema and run no browser code — they only attach UI for matching tool-call message parts.
127
+ Register it like any toolkit: `useAui({ tools: Tools({ toolkit }) })`.
128
+ Render-only entries upload no schema and run no browser code — they only attach
129
+ UI for matching tool-call message parts. For MCP server catalogs, spread
130
+ `defineMcpToolkit({ ... })` in the same generative toolkit.
125
131
 
126
132
  For a one-off renderer that should only affect a particular message surface, use `MessagePrimitive.Parts` inline tool render overrides instead of a global registration.
127
133
 
@@ -116,39 +116,47 @@ const runtime = useRemoteThreadListRuntime({
116
116
 
117
117
  ### Tools
118
118
 
119
- Register tools with a toolkit. The tool definition is forwarded to the model, and when the model calls it, the `execute` function runs and the `render` component displays the result.
119
+ Author tools in a `"use generative"` file with `defineToolkit`, the same API as on the web ([Defining Tools](/docs/tools/defining-tools)). The tool definition is forwarded to the model; when the model calls it, the `execute` function runs and the `render` component displays the result.
120
+
121
+ Add the [`@assistant-ui/metro`](https://www.npmjs.com/package/@assistant-ui/metro) plugin so Metro compiles the `"use generative"` directive:
122
+
123
+ ```js title="metro.config.js"
124
+ const { getDefaultConfig } = require("expo/metro-config");
125
+ const { withAui } = require("@assistant-ui/metro");
126
+
127
+ module.exports = withAui(getDefaultConfig(__dirname));
128
+ ```
120
129
 
121
130
  ```tsx title="weather-toolkit.tsx"
122
- import type { Toolkit } from "@assistant-ui/react-native";
131
+ "use generative";
132
+
133
+ import { defineToolkit } from "@assistant-ui/react-native";
123
134
  import { Text, View } from "react-native";
135
+ import { z } from "zod";
124
136
 
125
- export const toolkit = {
137
+ export default defineToolkit({
126
138
  get_weather: {
127
- type: "frontend",
128
139
  description: "Get the current weather for a city",
129
- parameters: {
130
- type: "object",
131
- properties: {
132
- city: { type: "string" },
133
- },
134
- required: ["city"],
135
- },
140
+ parameters: z.object({ city: z.string() }),
136
141
  execute: async ({ city }) => {
142
+ "use client";
137
143
  const res = await fetch(`https://api.weather.example/${city}`);
138
144
  return res.json();
139
145
  },
140
146
  render: ({ args, result }) => (
141
147
  <View>
142
- <Text>{args.city}: {result?.temperature}°F</Text>
148
+ <Text>
149
+ {args.city}: {result?.temperature}°F
150
+ </Text>
143
151
  </View>
144
152
  ),
145
153
  },
146
- } satisfies Toolkit;
154
+ });
147
155
  ```
148
156
 
149
157
  ```tsx title="ToolProvider.tsx"
150
158
  import { AuiProvider, Tools, useAui } from "@assistant-ui/react-native";
151
- import { toolkit } from "./weather-toolkit";
159
+ import toolkit from "./weather-toolkit";
152
160
 
153
161
  function ToolProvider({ children }: { children: React.ReactNode }) {
154
162
  const aui = useAui({ tools: Tools({ toolkit }) });
@@ -188,7 +196,7 @@ useAssistantInstructions("You are a helpful weather assistant.");
188
196
  Wrap a tool UI component so that inline state updates (from a parent component's render) are reflected without remounting. Use this when the render function closes over props that change over time.
189
197
 
190
198
  ```tsx title="my-tool-toolkit.tsx"
191
- import { type Toolkit, useInlineRender } from "@assistant-ui/react-native";
199
+ import { defineToolkit, useInlineRender } from "@assistant-ui/react-native";
192
200
  import { Text } from "react-native";
193
201
  import { useMemo } from "react";
194
202
 
@@ -199,12 +207,12 @@ export function useMyToolToolkit(someOuterProp: string) {
199
207
 
200
208
  return useMemo(
201
209
  () =>
202
- ({
210
+ defineToolkit({
203
211
  my_tool: {
204
212
  type: "backend",
205
213
  render: stableRender,
206
214
  },
207
- }) satisfies Toolkit,
215
+ }),
208
216
  [stableRender],
209
217
  );
210
218
  }
@@ -29,6 +29,47 @@ Reference for the runtime's API surface. Start with [quickstart](/docs/runtimes/
29
29
  | History | `adapters.history` | Per-thread message persistence. |
30
30
  | Thread list | `adapters.threadList` | Multi-thread switching (experimental, see below). |
31
31
 
32
+ ## Loading conversation history
33
+
34
+ If your backend exposes the persisted AG-UI messages of a conversation (for
35
+ example a `GET /agents/state` endpoint), use `fromAgUiMessages` to convert them
36
+ to assistant-ui messages and return them from the history adapter so the thread
37
+ is restored on page load:
38
+
39
+ ```tsx
40
+ import { fromAgUiMessages } from "@assistant-ui/react-ag-ui";
41
+ import { ExportedMessageRepository } from "@assistant-ui/react";
42
+
43
+ const runtime = useAgUiRuntime({
44
+ agent,
45
+ adapters: {
46
+ history: {
47
+ async load() {
48
+ const { messages } = await fetch("/agents/state").then((r) => r.json());
49
+ return ExportedMessageRepository.fromArray(fromAgUiMessages(messages));
50
+ },
51
+ async append({ message }) {
52
+ // persist the newly sent message on your backend
53
+ },
54
+ },
55
+ },
56
+ });
57
+ ```
58
+
59
+ Messages sent during the session are always forwarded to the agent through the
60
+ run input, independent of `append`. A no-op `append` is therefore only safe when
61
+ your backend already persists the conversation on its own; otherwise those
62
+ messages are gone on the next page load.
63
+
64
+ `fromAgUiMessages` accepts an optional second argument: pass
65
+ `{ showThinking: false }` to match a runtime configured with
66
+ `showThinking: false`, so imported reasoning messages are dropped at conversion
67
+ time, the same way a live run never stores them.
68
+
69
+ `fromAgUiMessages` reconstructs the text, reasoning, and tool calls of each
70
+ message. Non-text message content such as images and files is not restored, so a
71
+ backend that persists multimodal messages loads only their text on reload.
72
+
32
73
  ## Thread list (experimental)
33
74
 
34
75
  <Callout type="warn">
@@ -353,15 +353,15 @@ Render the gate with a toolkit entry. The `approval` field carries the gate stat
353
353
  import { useChat } from "@ai-sdk/react";
354
354
  import {
355
355
  AssistantRuntimeProvider,
356
+ defineToolkit,
356
357
  Tools,
357
- type Toolkit,
358
358
  useAui,
359
359
  } from "@assistant-ui/react";
360
360
  import { useAISDKRuntime } from "@assistant-ui/react-ai-sdk";
361
361
  import { lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
362
362
  import { Thread } from "@/components/assistant-ui/thread";
363
363
 
364
- const toolkit = {
364
+ const toolkit = defineToolkit({
365
365
  deploy: {
366
366
  type: "backend",
367
367
  render: ({ args, approval, respondToApproval, result }) => {
@@ -387,7 +387,7 @@ const toolkit = {
387
387
  return <p>Deployed {result.deployed}</p>;
388
388
  },
389
389
  },
390
- } satisfies Toolkit;
390
+ });
391
391
 
392
392
  export default function Page() {
393
393
  const chat = useChat({
@@ -7,8 +7,8 @@ assistant-ui exposes runtime integrations at three layers. Understanding which l
7
7
 
8
8
  ## The three layers
9
9
 
10
- ```mermaid
11
- graph TD
10
+ <Flow.Root
11
+ llm={`graph TD
12
12
  subgraph Framework["Framework adapters"]
13
13
  A1[react-ai-sdk]
14
14
  A2[react-langgraph]
@@ -34,8 +34,50 @@ graph TD
34
34
  A6 --> C2
35
35
  A7 --> C2
36
36
  P1 --> C1
37
- P2 --> C2
38
- ```
37
+ P2 --> C2`}
38
+ >
39
+ <Flow.Canvas
40
+ edges={[
41
+ { from: "datastream", to: "local", route: "down", midFrac: 0.4, laneOffset: -60 },
42
+ { from: "transport", to: "external", route: "down", midFrac: 0.65, toOffset: -14 },
43
+ { from: "adapters", to: "external", route: "down", midFrac: 0.4, toOffset: 14 },
44
+ ]}
45
+ >
46
+ <div className="grid max-w-2xl grid-cols-[1fr_1.6fr] gap-4">
47
+ <Flow.Group>
48
+ <Flow.GroupLabel>Protocol layers</Flow.GroupLabel>
49
+ <Flow.Column className="items-stretch gap-5">
50
+ <Flow.Row className="justify-start">
51
+ <Flow.Node flowId="datastream">DataStream</Flow.Node>
52
+ </Flow.Row>
53
+ <Flow.Row className="justify-end">
54
+ <Flow.Node flowId="transport">AssistantTransport</Flow.Node>
55
+ </Flow.Row>
56
+ </Flow.Column>
57
+ </Flow.Group>
58
+ <Flow.Group flowId="adapters">
59
+ <Flow.GroupLabel>Framework adapters</Flow.GroupLabel>
60
+ <Flow.Row className="flex-wrap justify-start">
61
+ <Flow.Node>react-ai-sdk</Flow.Node>
62
+ <Flow.Node>react-langgraph</Flow.Node>
63
+ <Flow.Node>react-langchain</Flow.Node>
64
+ <Flow.Node>react-google-adk</Flow.Node>
65
+ <Flow.Node>react-a2a</Flow.Node>
66
+ <Flow.Node>react-ag-ui</Flow.Node>
67
+ <Flow.Node>react-opencode</Flow.Node>
68
+ </Flow.Row>
69
+ </Flow.Group>
70
+ </div>
71
+ <div className="h-14" aria-hidden />
72
+ <Flow.Group className="mx-auto w-fit">
73
+ <Flow.GroupLabel>Core runtimes</Flow.GroupLabel>
74
+ <Flow.Row className="gap-10">
75
+ <Flow.Node flowId="local">LocalRuntime</Flow.Node>
76
+ <Flow.Node flowId="external">ExternalStoreRuntime</Flow.Node>
77
+ </Flow.Row>
78
+ </Flow.Group>
79
+ </Flow.Canvas>
80
+ </Flow.Root>
39
81
 
40
82
  Each upper layer is implemented in terms of a lower one. You can drop down a layer whenever you need more control, but most users start at the framework layer and never touch the others.
41
83
 
@@ -25,11 +25,17 @@ If you only need message streaming, [DataStream](/docs/runtimes/custom/data-stre
25
25
 
26
26
  ## Mental model
27
27
 
28
- ```mermaid
29
- graph LR
28
+ <Flow.Root
29
+ llm={`graph LR
30
30
  Frontend -->|Commands| Agent[Agent server]
31
- Agent -->|State snapshots| Frontend
32
- ```
31
+ Agent -->|State snapshots| Frontend`}
32
+ >
33
+ <Flow.Row>
34
+ <Flow.Node>Frontend</Flow.Node>
35
+ <Flow.Arrow label="Commands" reverseLabel="State snapshots" length={150} />
36
+ <Flow.Node>Agent server</Flow.Node>
37
+ </Flow.Row>
38
+ </Flow.Root>
33
39
 
34
40
  The frontend receives state snapshots and converts them to React components. The UI is a stateless view on top of the agent state.
35
41
 
@@ -37,21 +43,57 @@ The agent server receives commands from the frontend. When a user interacts with
37
43
 
38
44
  ### Command lifecycle
39
45
 
40
- ```mermaid
41
- graph LR
46
+ <Flow.Root
47
+ llm={`graph LR
42
48
  queued -->|sent to backend| in_transit
43
- in_transit -->|backend processes| applied
44
- ```
49
+ in_transit -->|backend processes| applied`}
50
+ >
51
+ <Flow.Row>
52
+ <Flow.Node>queued</Flow.Node>
53
+ <Flow.Arrow label="sent to backend" length={120} />
54
+ <Flow.Node>in_transit</Flow.Node>
55
+ <Flow.Arrow label="backend processes" length={132} />
56
+ <Flow.Node>applied</Flow.Node>
57
+ </Flow.Row>
58
+ </Flow.Root>
45
59
 
46
60
  The runtime alternates between **idle** (no active backend request) and **sending** (request in flight). When a new command is created while idle, it is sent immediately; otherwise it is queued until the current request completes.
47
61
 
48
- ```mermaid
49
- graph LR
62
+ <Flow.Root
63
+ llm={`graph LR
50
64
  idle -->|new command| sending
51
65
  sending -->|request completes| check{check queue}
52
66
  check -->|queue has commands| sending
53
- check -->|queue empty| idle
54
- ```
67
+ check -->|queue empty| idle`}
68
+ >
69
+ <Flow.Canvas
70
+ edges={[
71
+ {
72
+ from: "check",
73
+ to: "sending",
74
+ route: "loop-bottom",
75
+ label: "queue has commands",
76
+ laneOffset: 28,
77
+ },
78
+ {
79
+ from: "check",
80
+ to: "idle",
81
+ route: "loop-bottom",
82
+ label: "queue empty",
83
+ laneOffset: 60,
84
+ },
85
+ ]}
86
+ >
87
+ <Flow.Row>
88
+ <Flow.Node flowId="idle">idle</Flow.Node>
89
+ <Flow.Arrow label="new command" length={108} />
90
+ <Flow.Node flowId="sending">sending</Flow.Node>
91
+ <Flow.Arrow label="request completes" length={132} />
92
+ <Flow.Node flowId="check" variant="decision">check queue</Flow.Node>
93
+ </Flow.Row>
94
+ <div className="h-20" aria-hidden />
95
+ </Flow.Canvas>
96
+ </Flow.Root>
55
97
 
56
98
  To implement this you build two pieces:
57
99
 
@@ -17,14 +17,44 @@ If you do not have an existing store, use [`LocalRuntime`](/docs/runtimes/custom
17
17
 
18
18
  ## Architecture
19
19
 
20
- ```mermaid
21
- graph TD
20
+ <Flow.Root
21
+ llm={`graph TD
22
22
  A[Your state] -->|messages| B[ExternalStoreAdapter]
23
23
  B --> C[ExternalStoreRuntime]
24
24
  C --> D[assistant-ui components]
25
25
  D -->|user actions| B
26
- B -->|state updates| A
27
- ```
26
+ B -->|state updates| A`}
27
+ >
28
+ <Flow.Canvas
29
+ className="pr-44"
30
+ edges={[
31
+ {
32
+ from: "components",
33
+ to: "adapter",
34
+ route: "loop-right",
35
+ label: "user actions",
36
+ laneOffset: 40,
37
+ },
38
+ {
39
+ from: "adapter",
40
+ to: "state",
41
+ route: "loop-right",
42
+ label: "state updates",
43
+ laneOffset: 88,
44
+ },
45
+ ]}
46
+ >
47
+ <Flow.Column>
48
+ <Flow.Node flowId="state">Your state</Flow.Node>
49
+ <Flow.Arrow direction="down" label="messages" length={36} />
50
+ <Flow.Node flowId="adapter">ExternalStoreAdapter</Flow.Node>
51
+ <Flow.Arrow direction="down" length={36} />
52
+ <Flow.Node>ExternalStoreRuntime</Flow.Node>
53
+ <Flow.Arrow direction="down" length={36} />
54
+ <Flow.Node flowId="components">assistant-ui components</Flow.Node>
55
+ </Flow.Column>
56
+ </Flow.Canvas>
57
+ </Flow.Root>
28
58
 
29
59
  Key idea: you own the state, the adapter translates between your format and assistant-ui's. UI features are capability-based; if you provide `setMessages`, branching turns on; if you provide `onEdit`, editing turns on; etc.
30
60
 
@@ -178,6 +208,7 @@ Each handler enables a specific UI feature.
178
208
  | `onReload` | Regenerate button |
179
209
  | `onCancel` | Cancel button while generating |
180
210
  | `onAddToolResult` | Client-side tool result handoff |
211
+ | `queue` | Queueing messages sent while a run is in progress |
181
212
 
182
213
  ## Streaming responses
183
214
 
@@ -312,6 +343,33 @@ const runtime = useExternalStoreRuntime({
312
343
  });
313
344
  ```
314
345
 
346
+ ## Queueing messages during a run
347
+
348
+ By default, sending while the thread is running is disabled. Provide a `queue` adapter to buffer a message sent during a run and process it once the run settles. The pending message is exposed on `composer.queue` and renders through [`ComposerPrimitive.Queue`](/docs/api-reference/primitives/composer).
349
+
350
+ The `createMessageQueue` helper owns the FIFO ordering and the in-flight guard. Supply a driver that runs a message, pass its `adapter` to the runtime, and tell the queue when a run starts (`notifyBusy()`, so concurrent sends buffer) and ends (`notifyIdle()`).
351
+
352
+ ```tsx
353
+ import { useEffect, useRef, useState } from "react";
354
+ import { createMessageQueue, useExternalStoreRuntime } from "@assistant-ui/react";
355
+
356
+ const [queue] = useState(() => createMessageQueue({ run: onNew }));
357
+
358
+ const runtime = useExternalStoreRuntime({
359
+ messages,
360
+ isRunning,
361
+ onNew,
362
+ queue: queue.adapter,
363
+ });
364
+
365
+ const wasRunning = useRef(isRunning);
366
+ useEffect(() => {
367
+ if (!wasRunning.current && isRunning) queue.notifyBusy();
368
+ if (wasRunning.current && !isRunning) queue.notifyIdle();
369
+ wasRunning.current = isRunning;
370
+ }, [isRunning, queue]);
371
+ ```
372
+
315
373
  ## Multi-thread
316
374
 
317
375
  `ExternalStoreRuntime` uses `ExternalStoreThreadListAdapter` (synchronous, inline). See [threads](/docs/runtimes/concepts/threads#externalstorethreadlistadapter) for the contract and best practices on keeping `currentThreadId` in sync with your store.
@@ -521,6 +521,18 @@ function useStreamReconnect(threadId: string) {
521
521
  }
522
522
  ```
523
523
 
524
+ ## Queueing messages during a run
525
+
526
+ Set `unstable_enableMessageQueue` to keep the composer usable while a run is in progress. A message sent during a run is held in `composer.queue` and sent once the run settles; steering a queued message runs it next.
527
+
528
+ ```tsx
529
+ const runtime = useLocalRuntime(MyModelAdapter, {
530
+ unstable_enableMessageQueue: true,
531
+ });
532
+ ```
533
+
534
+ Render the pending messages with [`ComposerPrimitive.Queue`](/docs/api-reference/primitives/composer) and [`QueueItemPrimitive`](/docs/api-reference/primitives/queue-item).
535
+
524
536
  ## Adapters
525
537
 
526
538
  Attachments, speech, feedback, history, and suggestions are wired through the standard adapter contracts, see [adapters](/docs/runtimes/concepts/adapters):
@@ -389,8 +389,8 @@ Register the renderer with a backend toolkit entry inside `AssistantRuntimeProvi
389
389
  ```tsx
390
390
  import {
391
391
  AssistantRuntimeProvider,
392
+ defineToolkit,
392
393
  Tools,
393
- type Toolkit,
394
394
  useAui,
395
395
  } from "@assistant-ui/react";
396
396
  import {
@@ -399,12 +399,12 @@ import {
399
399
  } from "@assistant-ui/react-google-adk";
400
400
  import { Thread } from "@/components/assistant-ui/thread";
401
401
 
402
- const toolkit = {
402
+ const toolkit = defineToolkit({
403
403
  adk_request_input: {
404
404
  type: "backend",
405
405
  render: RequestInputToolUI,
406
406
  },
407
- } satisfies Toolkit;
407
+ });
408
408
 
409
409
  function App() {
410
410
  const runtime = useAdkRuntime({
@@ -426,8 +426,8 @@ function App() {
426
426
  ```tsx
427
427
  import {
428
428
  AssistantRuntimeProvider,
429
+ defineToolkit,
429
430
  Tools,
430
- type Toolkit,
431
431
  useAui,
432
432
  } from "@assistant-ui/react-native";
433
433
  import {
@@ -439,12 +439,12 @@ import { Thread } from "@/components/assistant-ui/thread";
439
439
 
440
440
  const API_URL = process.env.EXPO_PUBLIC_API_URL ?? "http://localhost:3000";
441
441
 
442
- const toolkit = {
442
+ const toolkit = defineToolkit({
443
443
  adk_request_input: {
444
444
  type: "backend",
445
445
  render: RequestInputToolUI,
446
446
  },
447
- } satisfies Toolkit;
447
+ });
448
448
 
449
449
  function App() {
450
450
  const runtime = useAdkRuntime({
@@ -468,8 +468,8 @@ function App() {
468
468
  ```tsx
469
469
  import {
470
470
  AssistantRuntimeProvider,
471
+ defineToolkit,
471
472
  Tools,
472
- type Toolkit,
473
473
  useAui,
474
474
  } from "@assistant-ui/react-ink";
475
475
  import {
@@ -479,12 +479,12 @@ import {
479
479
  import { Box } from "ink";
480
480
  import { Thread } from "./components/thread.js";
481
481
 
482
- const toolkit = {
482
+ const toolkit = defineToolkit({
483
483
  adk_request_input: {
484
484
  type: "backend",
485
485
  render: RequestInputToolUI,
486
486
  },
487
- } satisfies Toolkit;
487
+ });
488
488
 
489
489
  function App() {
490
490
  const runtime = useAdkRuntime({
@@ -106,6 +106,19 @@ LangGraph can emit structured UI components alongside assistant messages via `pu
106
106
 
107
107
  See [Generative UI](/docs/runtimes/langgraph/generative-ui) for full setup: enabling the `custom` stream channel, emitting UI messages, registering renderers, dynamic loading, and persisting UI state across thread switches.
108
108
 
109
+ ## Queueing messages during a run
110
+
111
+ Set `unstable_enableMessageQueue` to keep the composer usable while a run is streaming. A message sent during a run is held in `composer.queue` and sent once the run settles; steering a queued message runs it next.
112
+
113
+ ```tsx
114
+ const runtime = useLangGraphRuntime({
115
+ stream,
116
+ unstable_enableMessageQueue: true,
117
+ });
118
+ ```
119
+
120
+ Render the pending messages with [`ComposerPrimitive.Queue`](/docs/api-reference/primitives/composer) and [`QueueItemPrimitive`](/docs/api-reference/primitives/queue-item).
121
+
109
122
  ## Next
110
123
 
111
124
  <Cards>