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

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 (102) hide show
  1. package/.docs/organized/code-examples/waterfall.md +7 -7
  2. package/.docs/organized/code-examples/with-a2a.md +8 -8
  3. package/.docs/organized/code-examples/with-ag-ui.md +12 -12
  4. package/.docs/organized/code-examples/with-ai-sdk-v6.md +10 -10
  5. package/.docs/organized/code-examples/with-artifacts.md +40 -34
  6. package/.docs/organized/code-examples/with-assistant-transport.md +11 -11
  7. package/.docs/organized/code-examples/with-browser-extension.md +9 -9
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +72 -51
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +10 -10
  10. package/.docs/organized/code-examples/with-cloud.md +10 -10
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +10 -10
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +12 -12
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +12 -12
  14. package/.docs/organized/code-examples/with-expo.md +66 -31
  15. package/.docs/organized/code-examples/with-external-store.md +8 -8
  16. package/.docs/organized/code-examples/with-ffmpeg.md +13 -13
  17. package/.docs/organized/code-examples/with-generative-ui.md +98 -368
  18. package/.docs/organized/code-examples/with-google-adk.md +9 -9
  19. package/.docs/organized/code-examples/with-heat-graph.md +7 -7
  20. package/.docs/organized/code-examples/with-image-generation.md +10 -10
  21. package/.docs/organized/code-examples/with-interactables.md +10 -10
  22. package/.docs/organized/code-examples/with-langchain.md +10 -10
  23. package/.docs/organized/code-examples/with-langgraph.md +33 -29
  24. package/.docs/organized/code-examples/with-livekit.md +12 -12
  25. package/.docs/organized/code-examples/with-mcp.md +11 -11
  26. package/.docs/organized/code-examples/with-opencode.md +109 -583
  27. package/.docs/organized/code-examples/with-pi.md +2044 -0
  28. package/.docs/organized/code-examples/with-react-hook-form.md +11 -11
  29. package/.docs/organized/code-examples/with-react-ink-web.md +691 -0
  30. package/.docs/organized/code-examples/with-react-ink.md +309 -102
  31. package/.docs/organized/code-examples/with-react-router.md +14 -14
  32. package/.docs/organized/code-examples/with-resumable-stream.md +11 -11
  33. package/.docs/organized/code-examples/with-store.md +70 -66
  34. package/.docs/organized/code-examples/with-tanstack.md +25 -11
  35. package/.docs/organized/code-examples/with-tap-runtime.md +8 -8
  36. package/.docs/organized/code-examples/with-virtualized-thread.md +656 -0
  37. package/.docs/raw/docs/(docs)/architecture.mdx +65 -53
  38. package/.docs/raw/docs/(reference)/api-reference/external-store/message-conversion.mdx +1 -1
  39. package/.docs/raw/docs/(reference)/api-reference/external-store/runtime.mdx +3 -0
  40. package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +44 -1
  41. package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +14 -3
  42. package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -1
  43. package/.docs/raw/docs/(reference)/api-reference/tools/rendering.mdx +2 -23
  44. package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +22 -0
  45. package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +93 -9
  46. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +21 -86
  47. package/.docs/raw/docs/guides/chain-of-thought.mdx +1 -1
  48. package/.docs/raw/docs/guides/index.mdx +3 -0
  49. package/.docs/raw/docs/guides/input-history.mdx +55 -0
  50. package/.docs/raw/docs/guides/virtualization.mdx +63 -0
  51. package/.docs/raw/docs/ink/adapters.mdx +23 -1
  52. package/.docs/raw/docs/ink/hooks.mdx +22 -19
  53. package/.docs/raw/docs/migrations/toolkit-tools.mdx +14 -8
  54. package/.docs/raw/docs/react-native/hooks.mdx +26 -18
  55. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +39 -0
  56. package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +3 -3
  57. package/.docs/raw/docs/runtimes/concepts/architecture.mdx +46 -4
  58. package/.docs/raw/docs/runtimes/concepts/stability.mdx +1 -1
  59. package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +54 -12
  60. package/.docs/raw/docs/runtimes/custom/data-stream.mdx +1 -1
  61. package/.docs/raw/docs/runtimes/custom/external-store.mdx +62 -4
  62. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +120 -4
  63. package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +9 -9
  64. package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +13 -0
  65. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +5 -5
  66. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +3 -3
  67. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +6 -6
  68. package/.docs/raw/docs/tools/backend.mdx +19 -11
  69. package/.docs/raw/docs/tools/defining-tools.mdx +177 -52
  70. package/.docs/raw/docs/tools/index.mdx +7 -12
  71. package/.docs/raw/docs/tools/mcp.mdx +83 -15
  72. package/.docs/raw/docs/tools/multi-agent.mdx +5 -5
  73. package/.docs/raw/docs/tools/tool-ui.mdx +64 -28
  74. package/.docs/raw/docs/tools/user-managed-mcp.mdx +4 -4
  75. package/.docs/raw/docs/ui/dot-matrix.mdx +133 -0
  76. package/.docs/raw/docs/ui/mermaid.mdx +16 -9
  77. package/.docs/raw/docs/ui/model-selector.mdx +219 -52
  78. package/.docs/raw/docs/ui/number-roll.mdx +154 -0
  79. package/.docs/raw/docs/ui/part-grouping.mdx +2 -2
  80. package/.docs/raw/docs/ui/reasoning.mdx +3 -3
  81. package/.docs/raw/docs/ui/streamdown.mdx +2 -0
  82. package/.docs/raw/docs/ui/syntax-highlighting.mdx +1 -1
  83. package/.docs/raw/docs/ui/thread.mdx +52 -0
  84. package/.docs/raw/docs/ui/tool-fallback.mdx +16 -0
  85. package/.docs/raw/docs/utilities/react-o11y.mdx +2 -2
  86. package/dist/constants.js.map +1 -1
  87. package/dist/index.js.map +1 -1
  88. package/dist/prepare-docs/code-examples.js.map +1 -1
  89. package/dist/prepare-docs/copy-raw.js.map +1 -1
  90. package/dist/prepare-docs/prepare.js.map +1 -1
  91. package/dist/stdio.js.map +1 -1
  92. package/dist/tools/docs.js.map +1 -1
  93. package/dist/tools/examples.js.map +1 -1
  94. package/dist/tools/tests/test-setup.js.map +1 -1
  95. package/dist/utils/mdx.js.map +1 -1
  96. package/dist/utils/paths.js.map +1 -1
  97. package/package.json +4 -4
  98. /package/.docs/raw/docs/{(docs)/copilots → copilots}/assistant-frame.mdx +0 -0
  99. /package/.docs/raw/docs/{(docs)/copilots → copilots}/make-assistant-visible.mdx +0 -0
  100. /package/.docs/raw/docs/{(docs)/copilots → copilots}/model-context.mdx +0 -0
  101. /package/.docs/raw/docs/{(docs)/copilots → copilots}/motivation.mdx +0 -0
  102. /package/.docs/raw/docs/{(docs)/copilots → copilots}/use-assistant-instructions.mdx +0 -0
@@ -0,0 +1,55 @@
1
+ ---
2
+ title: Input History
3
+ description: Terminal-style ArrowUp/ArrowDown recall of previously sent messages in the assistant-ui React composer.
4
+ platforms: ["react"]
5
+ ---
6
+
7
+ Input history lets users press ArrowUp in an empty composer to recall previously sent messages, newest first, like a shell prompt. ArrowDown steps back toward the newest entry and finally restores the draft that was being typed when browsing started.
8
+
9
+ <Callout type="warn">
10
+ This API is marked unstable and may change without notice.
11
+ </Callout>
12
+
13
+ ## Usage
14
+
15
+ Spread the hook's bundle onto `ComposerPrimitive.Input`:
16
+
17
+ ```tsx
18
+ import {
19
+ ComposerPrimitive,
20
+ unstable_useComposerInputHistory,
21
+ } from "@assistant-ui/react";
22
+
23
+ const Composer = () => {
24
+ const history = unstable_useComposerInputHistory();
25
+
26
+ return (
27
+ <ComposerPrimitive.Root>
28
+ <ComposerPrimitive.Input {...history} />
29
+ <ComposerPrimitive.Send />
30
+ </ComposerPrimitive.Root>
31
+ );
32
+ };
33
+ ```
34
+
35
+ The history ring is derived live from the current thread's user messages (trimmed, with adjacent duplicates collapsed), so it needs no persistence or configuration.
36
+
37
+ ## Behavior
38
+
39
+ - ArrowUp starts recall only when the composer is empty (whitespace-only drafts count as empty and are restored on the way back down).
40
+ - While a recalled multi-line message is shown, arrows move the caret line by line; recall only steps when the caret is on the first line (ArrowUp) or last line (ArrowDown).
41
+ - An open mention or slash-command popover keeps owning the arrow keys; the hook yields whenever a trigger popover is active.
42
+ - IME composition, modifier keys, text selections, and events a preceding handler already `preventDefault`ed are left untouched.
43
+ - Switching threads or sending a message resets the browse position.
44
+ - The hook is inert on edit composers.
45
+
46
+ To interleave your own ArrowUp handling ahead of history (for example, stepping through queued messages), compose your handler before the hook's and call `preventDefault` when you consume the key:
47
+
48
+ ```tsx
49
+ <ComposerPrimitive.Input
50
+ onKeyDown={(e) => {
51
+ myHandler(e);
52
+ history.onKeyDown(e);
53
+ }}
54
+ />
55
+ ```
@@ -0,0 +1,63 @@
1
+ ---
2
+ title: Thread Virtualization
3
+ description: Render very long threads with @tanstack/react-virtual and ThreadPrimitive.MessageByIndex.
4
+ platforms: ["react"]
5
+ ---
6
+
7
+ Virtualization mounts only the messages near the viewport and represents the rest as empty space, so a thread with thousands of messages scrolls like one with twenty. assistant-ui does not ship a virtualized thread component; this guide shows the supported composition, extracted from a production consumer and available as a runnable example: [`examples/with-virtualized-thread`](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-virtualized-thread).
8
+
9
+ ## Do you need this?
10
+
11
+ Probably not. The default kit already renders message bodies with `content-visibility: auto` and `contain-intrinsic-size`, which skips paint work for off-screen messages, and `ThreadPrimitive.Viewport` handles auto-scroll. That covers typical threads. Reach for virtualization when React mount and update cost itself becomes the bottleneck: threads with hundreds to thousands of messages, or very heavy per-message content, where typing latency degrades because every message stays mounted.
12
+
13
+ ## Rendering messages by index
14
+
15
+ `ThreadPrimitive.MessageByIndex` renders a single message at a given index and memoizes on the index plus per-field `components` identity. Pass a module-level components object so the memo holds and a streaming delta only re-renders the message it belongs to:
16
+
17
+ ```tsx
18
+ const MESSAGE_COMPONENTS = { UserMessage, AssistantMessage };
19
+
20
+ <ThreadPrimitive.MessageByIndex index={index} components={MESSAGE_COMPONENTS} />;
21
+ ```
22
+
23
+ ## Grouping into turns
24
+
25
+ Virtualizing per user turn (a user message plus the responses that follow it) gives the virtualizer stable, meaningfully sized items. Subscribe to a single signature string so the selector returns a primitive and the turn array only rebuilds when membership actually changes:
26
+
27
+ ```tsx
28
+ const signature = useAuiState((s) =>
29
+ s.thread.messages.map((m, i) => `${i}:${m.role}:${m.id}`).join("\n"),
30
+ );
31
+ const turns = useMemo(() => buildTurns(signature), [signature]);
32
+ ```
33
+
34
+ ## Padding spacers, not absolute positioning
35
+
36
+ Render the virtual items in normal document flow inside a spacer div whose `paddingTop`/`paddingBottom` represent the unmounted regions. Items remain regular flow children, so message CSS (including `position: sticky` patterns and the kit styling) keeps working, and `virtualizer.measureElement` records real heights as items mount:
37
+
38
+ ```tsx
39
+ const items = virtualizer.getVirtualItems();
40
+ const paddingTop = items[0]?.start ?? 0;
41
+ const paddingBottom = Math.max(
42
+ 0,
43
+ virtualizer.getTotalSize() - (items.at(-1)?.end ?? 0),
44
+ );
45
+ ```
46
+
47
+ ## Owning the scroll element
48
+
49
+ The composition owns its scroll container instead of using `ThreadPrimitive.Viewport`: the built-in auto-scroll assumes every message is mounted, and its resize-driven re-pin can fight the virtualizer's measurement adjustments. Three pieces replace it:
50
+
51
+ 1. **Auto-follow.** A ResizeObserver on the content wrapper re-pins the scroller to the bottom while a sticky flag is armed. The flag disarms when the user scrolls up (detected as `scrollTop` decreasing while `scrollHeight` and `clientHeight` are stable, the same heuristic the built-in viewport uses, plus wheel-up and touchmove) and re-arms when the user returns to the bottom.
52
+ 2. **Measurement guard.** While pinned at the bottom, the virtualizer's own scroll adjustments on item re-measurement are suppressed via a custom `scrollToFn`; without this the two scroll writers fight and the view rubber-bands during streaming.
53
+ 3. **Run-start jump.** A `useLayoutEffect` observes `s.thread.isRunning` flipping to true and jumps to the bottom before paint, so a just-sent message never flashes below the fold. The `thread.runStart` event is deprecated; deriving the transition from state is the supported path.
54
+
55
+ The production consumer this is extracted from disables streaming auto-follow entirely as a product choice; the example keeps following because it matches `ThreadPrimitive.Viewport`'s default behavior. Both are valid, and the disarm guard makes either safe.
56
+
57
+ ## Try it
58
+
59
+ ```sh
60
+ npx assistant-ui@latest create my-app
61
+ ```
62
+
63
+ Then copy `app/VirtualizedThread.tsx`, `app/MyRuntimeProvider.tsx`, and `app/seed-messages.ts` from [the example](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-virtualized-thread), or clone the repo and run `pnpm --filter with-virtualized-thread dev`.
@@ -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`.
@@ -62,7 +62,7 @@ useAuiEvent("thread.runStart", (payload) => {
62
62
 
63
63
  ### useNotification
64
64
 
65
- Ring the terminal bell and emit an OSC desktop notification when the assistant finishes a run, stops with an error, or pauses for human approval.
65
+ Ring the terminal bell and emit an OSC desktop notification when the assistant finishes a run, stops with an error, or pauses for user input.
66
66
 
67
67
  ```tsx
68
68
  import { useNotification } from "@assistant-ui/react-ink";
@@ -130,7 +130,7 @@ const runtime = useLocalRuntime(chatModel, {
130
130
  | `adapters.feedback` | `FeedbackAdapter` | Adapter for message feedback (thumbs up/down) |
131
131
  | `adapters.suggestion` | `SuggestionAdapter` | Adapter for suggested prompts |
132
132
  | `cloud` | `AssistantCloud` | Cloud instance for thread persistence via `@assistant-ui/cloud` |
133
- | `unstable_humanToolNames` | `string[]` | Tool names that trigger a run interruption to wait for human/external approval |
133
+ | `unstable_humanToolNames` | `string[]` | Tool names that pause the run until a result is added via `addResult` |
134
134
 
135
135
  ### useRemoteThreadListRuntime
136
136
 
@@ -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
 
@@ -80,7 +80,7 @@ const runtime = useLocalRuntime(chatModel, {
80
80
  | `maxSteps` | `number` | Maximum tool call steps per run |
81
81
  | `cloud` | `AssistantCloud` | Optional cloud instance for persistence |
82
82
  | `adapters` | `object` | Optional adapter overrides (see below) |
83
- | `unstable_humanToolNames` | `string[]` | Tool names that pause the run for human approval |
83
+ | `unstable_humanToolNames` | `string[]` | Tool names that pause the run until a result is added via `addResult` |
84
84
 
85
85
  The `adapters` option accepts the following fields (all optional):
86
86
 
@@ -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,45 @@ 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 message. Multimodal user input (`image`, `audio`, `video`, and `document` parts, as well as legacy `binary` parts) is restored as attachments on the user message, so a backend that persists multimodal messages shows them again on reload and re-sends them on the next run. Legacy `binary` parts that only reference a file id are not restored.
70
+
32
71
  ## Thread list (experimental)
33
72
 
34
73
  <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
 
@@ -30,7 +30,7 @@ A non-exhaustive list of `unstable_` exports surfaced in the runtime docs.
30
30
  | API | Package | Notes |
31
31
  | --- | --- | --- |
32
32
  | `unstable_createMessageConverter` | `@assistant-ui/react` | Message-format converter used by AssistantTransport and DataStream. |
33
- | `unstable_humanToolNames` | `@assistant-ui/react` | Tool names that pause for human approval. Only available on LocalRuntime; not supported in DataStream. |
33
+ | `unstable_humanToolNames` | `@assistant-ui/react` | Tool names that pause the run until a result is added via `addResult`. Only available on LocalRuntime; not supported in DataStream. |
34
34
  | `unstable_threadListAdapter` | `@assistant-ui/react-langgraph` | LangGraph thread-list adapter slot on `useLangGraphRuntime`. |
35
35
  | `unstable_createLangGraphStream` | `@assistant-ui/react-langgraph` | End-to-end cancellation primitive. |
36
36
  | `unstable_Provider` | Various adapters | Thread-scoped provider on `RemoteThreadListAdapter`. Must render children synchronously. |
@@ -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
 
@@ -206,7 +206,7 @@ const runtime = useDataStreamRuntime({
206
206
  ## Tool integration
207
207
 
208
208
  <Callout type="warn">
209
- Human-in-the-loop tools (`unstable_humanToolNames`, `human()` interrupts) are not supported in the data stream runtime. Use [`LocalRuntime`](/docs/runtimes/custom/local-runtime) directly if you need approval flows.
209
+ Human-in-the-loop tools (`unstable_humanToolNames`, `human()` interrupts) are not supported in the data stream runtime. Use [`LocalRuntime`](/docs/runtimes/custom/local-runtime#human-in-the-loop-tools) directly if you need them.
210
210
  </Callout>
211
211
 
212
212
  ### Frontend tools