@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.
- package/.docs/organized/code-examples/waterfall.md +5 -5
- package/.docs/organized/code-examples/with-a2a.md +5 -5
- package/.docs/organized/code-examples/with-ag-ui.md +9 -9
- package/.docs/organized/code-examples/with-ai-sdk-v6.md +7 -7
- package/.docs/organized/code-examples/with-artifacts.md +37 -31
- package/.docs/organized/code-examples/with-assistant-transport.md +8 -8
- package/.docs/organized/code-examples/with-browser-extension.md +5 -5
- package/.docs/organized/code-examples/with-chain-of-thought.md +68 -47
- package/.docs/organized/code-examples/with-cloud-standalone.md +7 -7
- package/.docs/organized/code-examples/with-cloud.md +7 -7
- package/.docs/organized/code-examples/with-custom-thread-list.md +7 -7
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +8 -8
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +8 -8
- package/.docs/organized/code-examples/with-expo.md +33 -24
- package/.docs/organized/code-examples/with-external-store.md +5 -5
- package/.docs/organized/code-examples/with-ffmpeg.md +10 -10
- package/.docs/organized/code-examples/with-generative-ui.md +70 -64
- package/.docs/organized/code-examples/with-google-adk.md +6 -6
- package/.docs/organized/code-examples/with-heat-graph.md +5 -5
- package/.docs/organized/code-examples/with-image-generation.md +7 -7
- package/.docs/organized/code-examples/with-interactables.md +7 -7
- package/.docs/organized/code-examples/with-langchain.md +7 -7
- package/.docs/organized/code-examples/with-langgraph.md +30 -26
- package/.docs/organized/code-examples/with-livekit.md +8 -8
- package/.docs/organized/code-examples/with-mcp.md +8 -8
- package/.docs/organized/code-examples/with-opencode.md +6 -6
- package/.docs/organized/code-examples/with-react-hook-form.md +7 -7
- package/.docs/organized/code-examples/with-react-ink.md +295 -100
- package/.docs/organized/code-examples/with-react-router.md +11 -11
- package/.docs/organized/code-examples/with-resumable-stream.md +8 -8
- package/.docs/organized/code-examples/with-store.md +64 -64
- package/.docs/organized/code-examples/with-tanstack.md +8 -8
- package/.docs/organized/code-examples/with-tap-runtime.md +5 -5
- package/.docs/raw/docs/(docs)/architecture.mdx +52 -41
- package/.docs/raw/docs/(reference)/api-reference/external-store/message-conversion.mdx +1 -1
- package/.docs/raw/docs/(reference)/api-reference/external-store/runtime.mdx +3 -0
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +14 -3
- package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +94 -3
- package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +5 -69
- package/.docs/raw/docs/ink/adapters.mdx +23 -1
- package/.docs/raw/docs/ink/hooks.mdx +20 -17
- package/.docs/raw/docs/migrations/toolkit-tools.mdx +14 -8
- package/.docs/raw/docs/react-native/hooks.mdx +25 -17
- package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +41 -0
- package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +3 -3
- package/.docs/raw/docs/runtimes/concepts/architecture.mdx +46 -4
- package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +54 -12
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +62 -4
- package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +12 -0
- package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +9 -9
- package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +13 -0
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +5 -5
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +3 -3
- package/.docs/raw/docs/tools/backend.mdx +19 -11
- package/.docs/raw/docs/tools/defining-tools.mdx +177 -52
- package/.docs/raw/docs/tools/index.mdx +7 -12
- package/.docs/raw/docs/tools/mcp.mdx +83 -15
- package/.docs/raw/docs/tools/multi-agent.mdx +5 -5
- package/.docs/raw/docs/tools/tool-ui.mdx +27 -27
- package/.docs/raw/docs/tools/user-managed-mcp.mdx +4 -4
- package/.docs/raw/docs/ui/mermaid.mdx +16 -9
- package/.docs/raw/docs/ui/part-grouping.mdx +2 -2
- package/.docs/raw/docs/ui/tool-fallback.mdx +16 -0
- package/.docs/raw/docs/utilities/react-o11y.mdx +2 -2
- package/dist/constants.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/prepare-docs/code-examples.js.map +1 -1
- package/dist/prepare-docs/copy-raw.js.map +1 -1
- package/dist/prepare-docs/prepare.js.map +1 -1
- package/dist/stdio.js.map +1 -1
- package/dist/tools/docs.js.map +1 -1
- package/dist/tools/examples.js.map +1 -1
- package/dist/tools/tests/test-setup.js.map +1 -1
- package/dist/utils/mdx.js.map +1 -1
- package/dist/utils/paths.js.map +1 -1
- package/package.json +4 -4
- /package/.docs/raw/docs/{(docs)/copilots → copilots}/assistant-frame.mdx +0 -0
- /package/.docs/raw/docs/{(docs)/copilots → copilots}/make-assistant-visible.mdx +0 -0
- /package/.docs/raw/docs/{(docs)/copilots → copilots}/model-context.mdx +0 -0
- /package/.docs/raw/docs/{(docs)/copilots → copilots}/motivation.mdx +0 -0
- /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:
|
|
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
|
-
|
|
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
|
|
168
|
+
import { defineToolkit } from "@assistant-ui/react-ink";
|
|
162
169
|
import { Text } from "ink";
|
|
170
|
+
import { z } from "zod";
|
|
163
171
|
|
|
164
|
-
export
|
|
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>
|
|
181
|
+
<Text>
|
|
182
|
+
{args.city}: {result?.temperature}°F
|
|
183
|
+
</Text>
|
|
181
184
|
),
|
|
182
185
|
},
|
|
183
|
-
}
|
|
186
|
+
});
|
|
184
187
|
```
|
|
185
188
|
|
|
186
189
|
```tsx title="ToolProvider.tsx"
|
|
187
190
|
import { AuiProvider, Tools, useAui } from "@assistant-ui/react-ink";
|
|
188
|
-
import
|
|
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 {
|
|
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
|
-
})
|
|
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
|
|
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/
|
|
110
|
-
"use
|
|
112
|
+
// app/toolkit.tsx
|
|
113
|
+
"use generative";
|
|
111
114
|
|
|
112
|
-
import
|
|
115
|
+
import { defineToolkit, externalTool } from "@assistant-ui/react";
|
|
113
116
|
|
|
114
|
-
export
|
|
117
|
+
export default defineToolkit({
|
|
115
118
|
web_search: {
|
|
116
|
-
|
|
119
|
+
execute: externalTool(),
|
|
117
120
|
render: ({ args, result }) => (
|
|
118
121
|
<SearchResults query={args.query} results={result?.results ?? []} />
|
|
119
122
|
),
|
|
120
123
|
},
|
|
121
|
-
}
|
|
124
|
+
});
|
|
122
125
|
```
|
|
123
126
|
|
|
124
|
-
Register it like any toolkit: `useAui({ tools: Tools({ toolkit }) })`.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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>
|
|
148
|
+
<Text>
|
|
149
|
+
{args.city}: {result?.temperature}°F
|
|
150
|
+
</Text>
|
|
143
151
|
</View>
|
|
144
152
|
),
|
|
145
153
|
},
|
|
146
|
-
}
|
|
154
|
+
});
|
|
147
155
|
```
|
|
148
156
|
|
|
149
157
|
```tsx title="ToolProvider.tsx"
|
|
150
158
|
import { AuiProvider, Tools, useAui } from "@assistant-ui/react-native";
|
|
151
|
-
import
|
|
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 {
|
|
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
|
-
})
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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>
|