@assistant-ui/mcp-docs-server 0.1.30 → 0.1.31

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 (208) hide show
  1. package/.docs/organized/code-examples/waterfall.md +1 -1
  2. package/.docs/organized/code-examples/with-a2a.md +2 -2
  3. package/.docs/organized/code-examples/with-ag-ui.md +3 -3
  4. package/.docs/organized/code-examples/with-ai-sdk-v6.md +5 -5
  5. package/.docs/organized/code-examples/with-artifacts.md +5 -5
  6. package/.docs/organized/code-examples/with-assistant-transport.md +3 -3
  7. package/.docs/organized/code-examples/with-chain-of-thought.md +79 -50
  8. package/.docs/organized/code-examples/with-cloud-standalone.md +4 -4
  9. package/.docs/organized/code-examples/with-cloud.md +4 -4
  10. package/.docs/organized/code-examples/with-custom-thread-list.md +56 -11
  11. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +7 -7
  12. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +7 -7
  13. package/.docs/organized/code-examples/with-expo.md +16 -16
  14. package/.docs/organized/code-examples/with-external-store.md +2 -2
  15. package/.docs/organized/code-examples/with-ffmpeg.md +5 -5
  16. package/.docs/organized/code-examples/with-generative-ui.md +5 -5
  17. package/.docs/organized/code-examples/with-google-adk.md +4 -4
  18. package/.docs/organized/code-examples/with-heat-graph.md +1 -1
  19. package/.docs/organized/code-examples/with-interactables.md +5 -5
  20. package/.docs/organized/code-examples/with-langchain.md +3 -3
  21. package/.docs/organized/code-examples/with-langgraph.md +3 -3
  22. package/.docs/organized/code-examples/with-livekit.md +8 -8
  23. package/.docs/organized/code-examples/with-opencode.md +99 -54
  24. package/.docs/organized/code-examples/with-parent-id-grouping.md +4 -4
  25. package/.docs/organized/code-examples/with-react-hook-form.md +5 -5
  26. package/.docs/organized/code-examples/with-react-ink.md +1 -1
  27. package/.docs/organized/code-examples/with-react-router.md +8 -8
  28. package/.docs/organized/code-examples/with-store.md +1 -1
  29. package/.docs/organized/code-examples/with-tanstack.md +5 -5
  30. package/.docs/organized/code-examples/with-tap-runtime.md +2 -2
  31. package/.docs/raw/docs/(docs)/cli.mdx +2 -1
  32. package/.docs/raw/docs/(docs)/copilots/assistant-frame.mdx +1 -0
  33. package/.docs/raw/docs/(docs)/copilots/make-assistant-tool-ui.mdx +10 -3
  34. package/.docs/raw/docs/(docs)/copilots/make-assistant-tool.mdx +8 -3
  35. package/.docs/raw/docs/(docs)/copilots/make-assistant-visible.mdx +1 -0
  36. package/.docs/raw/docs/(docs)/copilots/model-context.mdx +1 -0
  37. package/.docs/raw/docs/(docs)/copilots/motivation.mdx +1 -0
  38. package/.docs/raw/docs/(docs)/copilots/use-assistant-instructions.mdx +1 -0
  39. package/.docs/raw/docs/(docs)/devtools.mdx +1 -0
  40. package/.docs/raw/docs/(docs)/index.mdx +1 -0
  41. package/.docs/raw/docs/(docs)/installation.mdx +1 -0
  42. package/.docs/raw/docs/(docs)/rtl.mdx +1 -0
  43. package/.docs/raw/docs/(reference)/api-reference/adapters/attachments.mdx +34 -0
  44. package/.docs/raw/docs/(reference)/api-reference/adapters/feedback-speech.mdx +41 -0
  45. package/.docs/raw/docs/(reference)/api-reference/adapters/index.mdx +26 -0
  46. package/.docs/raw/docs/(reference)/api-reference/adapters/persistence.mdx +34 -0
  47. package/.docs/raw/docs/(reference)/api-reference/adapters/runtime.mdx +31 -0
  48. package/.docs/raw/docs/(reference)/api-reference/context-providers/index.mdx +20 -0
  49. package/.docs/raw/docs/(reference)/api-reference/hooks/index.mdx +26 -0
  50. package/.docs/raw/docs/(reference)/api-reference/hooks/model-context.mdx +72 -0
  51. package/.docs/raw/docs/(reference)/api-reference/hooks/runtimes.mdx +41 -0
  52. package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +48 -0
  53. package/.docs/raw/docs/(reference)/api-reference/hooks/utilities.mdx +30 -0
  54. package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +23 -0
  55. package/.docs/raw/docs/(reference)/api-reference/overview.mdx +21 -0
  56. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +7 -0
  57. package/.docs/raw/docs/(reference)/api-reference/primitives/index.mdx +65 -0
  58. package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +50 -6
  59. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list.mdx +15 -0
  60. package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +36 -1
  61. package/.docs/raw/docs/(reference)/api-reference/runtimes/index.mdx +38 -0
  62. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-runtime.mdx +5 -0
  63. package/.docs/raw/docs/(reference)/migrations/v0-14.mdx +144 -6
  64. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +230 -2
  65. package/.docs/raw/docs/cloud/ai-sdk.mdx +221 -3
  66. package/.docs/raw/docs/cloud/langgraph.mdx +274 -2
  67. package/.docs/raw/docs/{(docs)/guides → guides}/attachments.mdx +41 -36
  68. package/.docs/raw/docs/guides/branching.mdx +76 -0
  69. package/.docs/raw/docs/guides/chain-of-thought.mdx +166 -0
  70. package/.docs/raw/docs/{(docs)/guides → guides}/context-api.mdx +50 -22
  71. package/.docs/raw/docs/{(docs)/guides → guides}/dictation.mdx +2 -0
  72. package/.docs/raw/docs/guides/editing.mdx +102 -0
  73. package/.docs/raw/docs/guides/index.mdx +103 -0
  74. package/.docs/raw/docs/{(docs)/guides → guides}/interactables.mdx +49 -0
  75. package/.docs/raw/docs/{(docs)/guides → guides}/latex.mdx +51 -8
  76. package/.docs/raw/docs/{(docs)/guides → guides}/mentions.mdx +61 -86
  77. package/.docs/raw/docs/{(docs)/guides → guides}/message-timing.mdx +8 -2
  78. package/.docs/raw/docs/{(docs)/guides → guides}/multi-agent.mdx +64 -4
  79. package/.docs/raw/docs/{(docs)/guides → guides}/quoting.mdx +10 -17
  80. package/.docs/raw/docs/{(docs)/guides → guides}/slash-commands.mdx +103 -37
  81. package/.docs/raw/docs/guides/speech.mdx +156 -0
  82. package/.docs/raw/docs/{(docs)/guides → guides}/suggestions.mdx +21 -83
  83. package/.docs/raw/docs/{(docs)/guides → guides}/tool-ui.mdx +108 -36
  84. package/.docs/raw/docs/{(docs)/guides → guides}/tools.mdx +131 -35
  85. package/.docs/raw/docs/{(docs)/guides → guides}/voice.mdx +39 -0
  86. package/.docs/raw/docs/ink/index.mdx +1 -3
  87. package/.docs/raw/docs/ink/migration.mdx +1 -3
  88. package/.docs/raw/docs/ink/primitives.mdx +37 -1
  89. package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +520 -0
  90. package/.docs/raw/docs/integrations/auth/better-auth.mdx +191 -0
  91. package/.docs/raw/docs/integrations/auth/clerk.mdx +172 -0
  92. package/.docs/raw/docs/integrations/auth/next-auth.mdx +196 -0
  93. package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +79 -0
  94. package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +188 -0
  95. package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +57 -0
  96. package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +201 -0
  97. package/.docs/raw/docs/integrations/gateways/index.mdx +157 -0
  98. package/.docs/raw/docs/integrations/index.mdx +173 -0
  99. package/.docs/raw/docs/integrations/observability/helicone.mdx +130 -0
  100. package/.docs/raw/docs/integrations/observability/langfuse.mdx +156 -0
  101. package/.docs/raw/docs/integrations/observability/langsmith.mdx +146 -0
  102. package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +712 -0
  103. package/.docs/raw/docs/integrations/tools/mcp.mdx +267 -0
  104. package/.docs/raw/docs/primitives/action-bar.mdx +1 -0
  105. package/.docs/raw/docs/primitives/assistant-modal.mdx +1 -0
  106. package/.docs/raw/docs/primitives/attachment.mdx +1 -0
  107. package/.docs/raw/docs/primitives/branch-picker.mdx +1 -0
  108. package/.docs/raw/docs/primitives/chain-of-thought.mdx +90 -85
  109. package/.docs/raw/docs/primitives/composer.mdx +2 -1
  110. package/.docs/raw/docs/primitives/error.mdx +1 -0
  111. package/.docs/raw/docs/primitives/index.mdx +2 -1
  112. package/.docs/raw/docs/primitives/message.mdx +68 -5
  113. package/.docs/raw/docs/primitives/selection-toolbar.mdx +1 -0
  114. package/.docs/raw/docs/primitives/suggestion.mdx +1 -0
  115. package/.docs/raw/docs/primitives/thread-list.mdx +39 -0
  116. package/.docs/raw/docs/primitives/thread.mdx +16 -13
  117. package/.docs/raw/docs/react-native/index.mdx +1 -3
  118. package/.docs/raw/docs/react-native/migration.mdx +1 -3
  119. package/.docs/raw/docs/runtimes/a2a/client-and-hooks.mdx +396 -0
  120. package/.docs/raw/docs/runtimes/a2a/overview.mdx +60 -0
  121. package/.docs/raw/docs/runtimes/a2a/quickstart.mdx +216 -0
  122. package/.docs/raw/docs/runtimes/ag-ui/overview.mdx +70 -0
  123. package/.docs/raw/docs/runtimes/ag-ui/quickstart.mdx +243 -0
  124. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +123 -0
  125. package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +52 -0
  126. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +71 -131
  127. package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +69 -63
  128. package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +330 -123
  129. package/.docs/raw/docs/runtimes/concepts/adapters.mdx +265 -0
  130. package/.docs/raw/docs/runtimes/concepts/architecture.mdx +125 -0
  131. package/.docs/raw/docs/runtimes/concepts/stability.mdx +67 -0
  132. package/.docs/raw/docs/runtimes/concepts/threads.mdx +428 -0
  133. package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +703 -0
  134. package/.docs/raw/docs/runtimes/custom/data-stream.mdx +323 -0
  135. package/.docs/raw/docs/runtimes/custom/external-store.mdx +253 -1236
  136. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +746 -0
  137. package/.docs/raw/docs/runtimes/custom/overview.mdx +71 -0
  138. package/.docs/raw/docs/runtimes/google-adk/api.mdx +256 -0
  139. package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +717 -0
  140. package/.docs/raw/docs/runtimes/google-adk/overview.mdx +69 -0
  141. package/.docs/raw/docs/runtimes/google-adk/quickstart.mdx +229 -0
  142. package/.docs/raw/docs/runtimes/langchain.mdx +533 -0
  143. package/.docs/raw/docs/runtimes/langgraph/generative-ui.mdx +305 -0
  144. package/.docs/raw/docs/runtimes/langgraph/interrupts.mdx +104 -0
  145. package/.docs/raw/docs/runtimes/langgraph/overview.mdx +84 -0
  146. package/.docs/raw/docs/runtimes/langgraph/quickstart.mdx +496 -0
  147. package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +127 -0
  148. package/.docs/raw/docs/runtimes/langgraph/threads.mdx +113 -0
  149. package/.docs/raw/docs/runtimes/langgraph/tutorial/introduction.mdx +3 -3
  150. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +0 -23
  151. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +1 -1
  152. package/.docs/raw/docs/runtimes/opencode/hooks.mdx +191 -0
  153. package/.docs/raw/docs/runtimes/opencode/overview.mdx +48 -0
  154. package/.docs/raw/docs/runtimes/opencode/quickstart.mdx +119 -0
  155. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +71 -203
  156. package/.docs/raw/docs/ui/accordion.mdx +1 -0
  157. package/.docs/raw/docs/ui/assistant-modal.mdx +1 -0
  158. package/.docs/raw/docs/ui/assistant-sidebar.mdx +1 -0
  159. package/.docs/raw/docs/ui/attachment.mdx +1 -0
  160. package/.docs/raw/docs/ui/badge.mdx +1 -0
  161. package/.docs/raw/docs/ui/composer-trigger-popover.mdx +1 -0
  162. package/.docs/raw/docs/ui/context-display.mdx +1 -0
  163. package/.docs/raw/docs/ui/diff-viewer.mdx +1 -0
  164. package/.docs/raw/docs/ui/directive-text.mdx +1 -0
  165. package/.docs/raw/docs/ui/file.mdx +1 -0
  166. package/.docs/raw/docs/ui/image.mdx +1 -0
  167. package/.docs/raw/docs/ui/markdown.mdx +2 -14
  168. package/.docs/raw/docs/ui/mermaid.mdx +1 -0
  169. package/.docs/raw/docs/ui/message-timing.mdx +3 -2
  170. package/.docs/raw/docs/ui/model-selector.mdx +1 -0
  171. package/.docs/raw/docs/ui/part-grouping.mdx +325 -313
  172. package/.docs/raw/docs/ui/quote.mdx +1 -0
  173. package/.docs/raw/docs/ui/reasoning.mdx +66 -33
  174. package/.docs/raw/docs/ui/scrollbar.mdx +1 -0
  175. package/.docs/raw/docs/ui/select.mdx +1 -0
  176. package/.docs/raw/docs/ui/sources.mdx +1 -0
  177. package/.docs/raw/docs/ui/streamdown.mdx +1 -0
  178. package/.docs/raw/docs/ui/syntax-highlighting.mdx +1 -0
  179. package/.docs/raw/docs/ui/tabs.mdx +1 -0
  180. package/.docs/raw/docs/ui/thread-list.mdx +17 -0
  181. package/.docs/raw/docs/ui/thread.mdx +56 -1
  182. package/.docs/raw/docs/ui/tool-fallback.mdx +1 -0
  183. package/.docs/raw/docs/ui/tool-group.mdx +39 -11
  184. package/.docs/raw/docs/ui/voice.mdx +1 -0
  185. package/.docs/raw/docs/utilities/heat-graph.mdx +1 -0
  186. package/.docs/raw/docs/utilities/react-o11y.mdx +278 -0
  187. package/.docs/raw/docs/utilities/tw-shimmer.mdx +1 -0
  188. package/package.json +3 -3
  189. package/src/tools/tests/path-traversal.test.ts +1 -1
  190. package/.docs/raw/docs/(docs)/guides/branching.mdx +0 -65
  191. package/.docs/raw/docs/(docs)/guides/chain-of-thought.mdx +0 -164
  192. package/.docs/raw/docs/(docs)/guides/editing.mdx +0 -66
  193. package/.docs/raw/docs/(docs)/guides/speech.mdx +0 -38
  194. package/.docs/raw/docs/runtimes/a2a/index.mdx +0 -298
  195. package/.docs/raw/docs/runtimes/assistant-transport.mdx +0 -1033
  196. package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +0 -314
  197. package/.docs/raw/docs/runtimes/custom/local.mdx +0 -1464
  198. package/.docs/raw/docs/runtimes/data-stream.mdx +0 -422
  199. package/.docs/raw/docs/runtimes/google-adk/index.mdx +0 -686
  200. package/.docs/raw/docs/runtimes/helicone.mdx +0 -61
  201. package/.docs/raw/docs/runtimes/langchain/comparison.mdx +0 -60
  202. package/.docs/raw/docs/runtimes/langchain/index.mdx +0 -210
  203. package/.docs/raw/docs/runtimes/langgraph/index.mdx +0 -699
  204. package/.docs/raw/docs/runtimes/langgraph/tutorial/index.mdx +0 -12
  205. package/.docs/raw/docs/runtimes/langserve.mdx +0 -116
  206. package/.docs/raw/docs/runtimes/mastra/full-stack-integration.mdx +0 -218
  207. package/.docs/raw/docs/runtimes/mastra/overview.mdx +0 -18
  208. package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +0 -217
@@ -1,1464 +0,0 @@
1
- ---
2
- title: LocalRuntime
3
- description: Quickest path to a working chat. Handles state while you handle the API.
4
- ---
5
-
6
-
7
- ## Overview
8
-
9
- `LocalRuntime` is the simplest way to connect your own custom backend to assistant-ui. It manages all chat state internally while providing a clean adapter interface to connect with any REST API, OpenAI, or custom language model.
10
-
11
- `LocalRuntime` provides:
12
-
13
- - **Built-in state management** for messages, threads, and conversation history
14
- - **Automatic features** like message editing, reloading, and branch switching
15
- - **Multi-thread support** through [Assistant Cloud](/docs/cloud) or your own database using `useRemoteThreadListRuntime`
16
- - **Simple adapter pattern** to connect any backend API
17
-
18
- While LocalRuntime manages state in-memory by default, it offers multiple persistence options through adapters - use the history adapter for single-thread persistence, Assistant Cloud for managed multi-thread support, or implement your own storage with `useRemoteThreadListRuntime`.
19
-
20
- ## When to Use
21
-
22
- Use `LocalRuntime` if you need:
23
-
24
- - **Quick setup with minimal configuration** - Get a fully functional chat interface with just a few lines of code
25
- - **Built-in state management** - No need to manage messages, threads, or conversation history yourself
26
- - **Automatic features** - Branch switching, message editing, and regeneration work out of the box
27
- - **API flexibility** - Connect to any REST endpoint, OpenAI, or custom model with a simple adapter
28
- - **Multi-thread support** - Full thread management with Assistant Cloud or custom database
29
- - **Thread persistence** - Via history adapter, Assistant Cloud, or custom thread list adapter
30
-
31
- ## Key Features
32
-
33
- <Cards>
34
- <Card
35
- title="Built-in State Management"
36
- description="Automatic handling of messages, threads, and conversation history"
37
- />
38
- <Card
39
- title="Multi-Thread Support"
40
- description="Full thread management capabilities with Assistant Cloud or custom database adapter"
41
- />
42
- <Card
43
- title="Adapter System"
44
- description="Extend with attachments, speech, feedback, persistence, and suggestions"
45
- />
46
- <Card
47
- title="Tool Calling"
48
- description="Support for function calling with human-in-the-loop approval"
49
- />
50
- </Cards>
51
-
52
- ## Getting Started
53
-
54
- <Steps>
55
- <Step>
56
- ### Create a Next.js project
57
-
58
- ```sh
59
- npx create-next-app@latest my-app
60
- cd my-app
61
- ```
62
-
63
- </Step>
64
- <Step>
65
-
66
- ### Install `@assistant-ui/react`
67
-
68
- <InstallCommand npm={["@assistant-ui/react"]} />
69
-
70
- </Step>
71
- <Step>
72
-
73
- ### Add `assistant-ui` Thread component
74
-
75
- ```sh npm2yarn
76
- npx assistant-ui@latest add thread
77
- ```
78
-
79
- </Step>
80
- <Step>
81
-
82
- ### Define a `MyRuntimeProvider` component
83
-
84
- Update the `MyModelAdapter` below to integrate with your own custom API.
85
- See `LocalRuntimeOptions` [API Reference](#localruntimeoptions) for available configuration options.
86
-
87
- ```tsx twoslash include MyRuntimeProvider title="app/MyRuntimeProvider.tsx"
88
- // @filename: /app/MyRuntimeProvider.tsx
89
-
90
- // ---cut---
91
- "use client";
92
-
93
- import type { ReactNode } from "react";
94
- import {
95
- AssistantRuntimeProvider,
96
- useLocalRuntime,
97
- type ChatModelAdapter,
98
- } from "@assistant-ui/react";
99
-
100
- const MyModelAdapter: ChatModelAdapter = {
101
- async run({ messages, abortSignal }) {
102
- // TODO replace with your own API
103
- const result = await fetch("<YOUR_API_ENDPOINT>", {
104
- method: "POST",
105
- headers: {
106
- "Content-Type": "application/json",
107
- },
108
- // forward the messages in the chat to the API
109
- body: JSON.stringify({
110
- messages,
111
- }),
112
- // if the user hits the "cancel" button or escape keyboard key, cancel the request
113
- signal: abortSignal,
114
- });
115
-
116
- const data = await result.json();
117
- return {
118
- content: [
119
- {
120
- type: "text",
121
- text: data.text,
122
- },
123
- ],
124
- };
125
- },
126
- };
127
-
128
- export function MyRuntimeProvider({
129
- children,
130
- }: Readonly<{
131
- children: ReactNode;
132
- }>) {
133
- const runtime = useLocalRuntime(MyModelAdapter);
134
-
135
- return (
136
- <AssistantRuntimeProvider runtime={runtime}>
137
- {children}
138
- </AssistantRuntimeProvider>
139
- );
140
- }
141
- ```
142
-
143
- </Step>
144
- <Step>
145
-
146
- ### Wrap your app in `MyRuntimeProvider`
147
-
148
- ```tsx {1,11,17} twoslash title="app/layout.tsx"
149
- // @include: MyRuntimeProvider
150
- // @filename: /app/layout.tsx
151
- // ---cut---
152
- import type { ReactNode } from "react";
153
- import { MyRuntimeProvider } from "@/app/MyRuntimeProvider";
154
-
155
- export default function RootLayout({
156
- children,
157
- }: Readonly<{
158
- children: ReactNode;
159
- }>) {
160
- return (
161
- <MyRuntimeProvider>
162
- <html lang="en">
163
- <body>{children}</body>
164
- </html>
165
- </MyRuntimeProvider>
166
- );
167
- }
168
- ```
169
-
170
- </Step>
171
- <Step>
172
-
173
- ### Use the Thread component
174
-
175
- ```tsx title="app/page.tsx"
176
- import { Thread } from 'components/assistant-ui/thread.tsx'
177
-
178
- export default function Page() {
179
- return <Thread />;
180
- }
181
- ```
182
-
183
- </Step>
184
- </Steps>
185
-
186
- ## Streaming Responses
187
-
188
- Implement streaming by declaring the `run` function as an `AsyncGenerator`.
189
-
190
- ```tsx twoslash {2, 11-13} title="app/MyRuntimeProvider.tsx"
191
- import {
192
- ChatModelAdapter,
193
- ThreadMessage,
194
- type ModelContext,
195
- } from "@assistant-ui/react";
196
- import { OpenAI } from "openai";
197
-
198
- const openai = new OpenAI();
199
- const backendApi = async ({
200
- messages,
201
- abortSignal,
202
- context,
203
- }: {
204
- messages: readonly ThreadMessage[];
205
- abortSignal: AbortSignal;
206
- context: ModelContext;
207
- }) => {
208
- return openai.chat.completions.create({
209
- model: "gpt-4o",
210
- messages: [{ role: "user", content: "Say this is a test" }],
211
- stream: true,
212
- });
213
- };
214
-
215
- // ---cut---
216
- const MyModelAdapter: ChatModelAdapter = {
217
- async *run({ messages, abortSignal, context }) {
218
- const stream = await backendApi({ messages, abortSignal, context });
219
-
220
- let text = "";
221
- for await (const part of stream) {
222
- text += part.choices[0]?.delta?.content || "";
223
-
224
- yield {
225
- content: [{ type: "text", text }],
226
- };
227
- }
228
- },
229
- };
230
- ```
231
-
232
- ### Streaming with Tool Calls
233
-
234
- Handle streaming responses that include function calls:
235
-
236
- ```tsx
237
- const MyModelAdapter: ChatModelAdapter = {
238
- async *run({ messages, abortSignal, context }) {
239
- const stream = await openai.chat.completions.create({
240
- model: "gpt-4o",
241
- messages: convertToOpenAIMessages(messages),
242
- tools: context.tools,
243
- stream: true,
244
- signal: abortSignal,
245
- });
246
-
247
- let content = "";
248
- const toolCalls: any[] = [];
249
-
250
- for await (const chunk of stream) {
251
- const delta = chunk.choices[0]?.delta;
252
-
253
- // Handle text content
254
- if (delta?.content) {
255
- content += delta.content;
256
- }
257
-
258
- // Handle tool calls
259
- if (delta?.tool_calls) {
260
- for (const toolCall of delta.tool_calls) {
261
- if (!toolCalls[toolCall.index]) {
262
- toolCalls[toolCall.index] = {
263
- id: toolCall.id,
264
- type: "function",
265
- function: { name: "", arguments: "" },
266
- };
267
- }
268
-
269
- if (toolCall.function?.name) {
270
- toolCalls[toolCall.index].function.name = toolCall.function.name;
271
- }
272
-
273
- if (toolCall.function?.arguments) {
274
- toolCalls[toolCall.index].function.arguments +=
275
- toolCall.function.arguments;
276
- }
277
- }
278
- }
279
-
280
- // Yield current state
281
- yield {
282
- content: [
283
- ...(content ? [{ type: "text" as const, text: content }] : []),
284
- ...toolCalls.map((tc) => ({
285
- type: "tool-call" as const,
286
- toolCallId: tc.id,
287
- toolName: tc.function.name,
288
- args: JSON.parse(tc.function.arguments || "{}"),
289
- })),
290
- ],
291
- };
292
- }
293
- },
294
- };
295
- ```
296
-
297
- ## Tool Calling
298
-
299
- `LocalRuntime` supports OpenAI-compatible function calling with automatic or human-in-the-loop execution.
300
-
301
- ### Basic Tool Definition
302
-
303
- Tools should be registered using the `Tools()` API with `useAui()`:
304
-
305
- ```tsx
306
- import { useAui, Tools, type Toolkit } from "@assistant-ui/react";
307
- import { z } from "zod";
308
-
309
- // Define your toolkit
310
- const myToolkit: Toolkit = {
311
- getWeather: {
312
- description: "Get the current weather in a location",
313
- parameters: z.object({
314
- location: z.string().describe("The city and state, e.g. San Francisco, CA"),
315
- unit: z.enum(["celsius", "fahrenheit"]).default("celsius"),
316
- }),
317
- execute: async ({ location, unit }) => {
318
- const weather = await fetchWeatherAPI(location, unit);
319
- return weather;
320
- },
321
- },
322
- };
323
-
324
- // Register tools in your runtime provider
325
- function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
326
- const runtime = useLocalRuntime(MyModelAdapter);
327
-
328
- // Register all tools
329
- const aui = useAui({
330
- tools: Tools({ toolkit: myToolkit }),
331
- });
332
-
333
- return (
334
- <AssistantRuntimeProvider aui={aui} runtime={runtime}>
335
- {children}
336
- </AssistantRuntimeProvider>
337
- );
338
- }
339
- ```
340
-
341
- The tools will be available to your adapter via the `context` parameter in the `run` function. See the [Tools guide](/docs/guides/tools) for more details on tool registration and advanced features.
342
-
343
- ### Human-in-the-Loop Approval
344
-
345
- Require user confirmation before executing certain tools:
346
-
347
- ```tsx
348
- const runtime = useLocalRuntime(MyModelAdapter, {
349
- unstable_humanToolNames: ["delete_file", "send_email"],
350
- });
351
- ```
352
-
353
- ### Tool Execution
354
-
355
- Tools are executed automatically by the runtime. The model adapter receives tool results in subsequent messages:
356
-
357
- ```tsx
358
- // Messages will include tool calls and results:
359
- [
360
- { role: "user", content: "What's the weather in SF?" },
361
- {
362
- role: "assistant",
363
- content: [
364
- {
365
- type: "tool-call",
366
- toolCallId: "call_123",
367
- toolName: "get_weather",
368
- args: { location: "San Francisco, CA" },
369
- },
370
- ],
371
- },
372
- {
373
- role: "tool",
374
- content: [
375
- {
376
- type: "tool-result",
377
- toolCallId: "call_123",
378
- result: { temperature: 72, condition: "sunny" },
379
- },
380
- ],
381
- },
382
- {
383
- role: "assistant",
384
- content: "The weather in San Francisco is sunny and 72°F.",
385
- },
386
- ];
387
- ```
388
-
389
- ## Multi-Thread Support
390
-
391
- `LocalRuntime` supports multiple conversation threads through two approaches:
392
-
393
- ### 1. Assistant Cloud Integration
394
-
395
- ```tsx
396
- import { useLocalRuntime } from "@assistant-ui/react";
397
- import { AssistantCloud } from "assistant-cloud";
398
-
399
- const cloud = new AssistantCloud({
400
- apiKey: process.env.ASSISTANT_CLOUD_API_KEY,
401
- });
402
-
403
- const runtime = useLocalRuntime(MyModelAdapter, {
404
- cloud, // Enables multi-thread support
405
- });
406
- ```
407
-
408
- With Assistant Cloud, you get:
409
-
410
- - Multiple conversation threads
411
- - Thread persistence across sessions
412
- - Thread management (create, switch, rename, archive, delete)
413
- - Automatic synchronization across devices
414
- - Built-in user authentication
415
-
416
- ### 2. Custom Database with useRemoteThreadListRuntime
417
-
418
- For custom thread storage, use `useRemoteThreadListRuntime` with your own adapter:
419
-
420
- ```tsx
421
- import {
422
- useRemoteThreadListRuntime,
423
- useAui,
424
- RuntimeAdapterProvider,
425
- AssistantRuntimeProvider,
426
- type RemoteThreadListAdapter,
427
- type ThreadHistoryAdapter,
428
- } from "@assistant-ui/react";
429
- import { createAssistantStream } from "assistant-stream";
430
- import { useMemo } from "react";
431
-
432
- // Implement your custom adapter with proper message persistence
433
- const myDatabaseAdapter: RemoteThreadListAdapter = {
434
- async list() {
435
- const threads = await db.threads.findAll();
436
- return {
437
- threads: threads.map((t) => ({
438
- status: t.archived ? "archived" : "regular",
439
- remoteId: t.id,
440
- title: t.title,
441
- })),
442
- };
443
- },
444
-
445
- async initialize(threadId) {
446
- const thread = await db.threads.create({ id: threadId });
447
- return { remoteId: thread.id };
448
- },
449
-
450
- async rename(remoteId, newTitle) {
451
- await db.threads.update(remoteId, { title: newTitle });
452
- },
453
-
454
- async archive(remoteId) {
455
- await db.threads.update(remoteId, { archived: true });
456
- },
457
-
458
- async unarchive(remoteId) {
459
- await db.threads.update(remoteId, { archived: false });
460
- },
461
-
462
- async delete(remoteId) {
463
- // Delete thread and its messages
464
- await db.messages.deleteByThreadId(remoteId);
465
- await db.threads.delete(remoteId);
466
- },
467
-
468
- async generateTitle(remoteId, unstable_messages) {
469
- // Generate title from messages using your AI
470
- const newTitle = await generateTitle(unstable_messages);
471
-
472
- // Persist the title in your DB
473
- await db.threads.update(remoteId, { title: newTitle });
474
-
475
- // IMPORTANT: Return an AssistantStream so the UI updates
476
- return createAssistantStream((controller) => {
477
- controller.appendText(newTitle);
478
- controller.close();
479
- });
480
- },
481
- };
482
-
483
- // Complete implementation with message persistence using Provider pattern
484
- export function MyRuntimeProvider({ children }) {
485
- const runtime = useRemoteThreadListRuntime({
486
- runtimeHook: () => {
487
- return useLocalRuntime(MyModelAdapter);
488
- },
489
- adapter: {
490
- ...myDatabaseAdapter,
491
-
492
- // The Provider component adds thread-specific adapters
493
- unstable_Provider: ({ children }) => {
494
- // This runs in the context of each thread
495
- const aui = useAui();
496
-
497
- // Create thread-specific history adapter
498
- const history = useMemo<ThreadHistoryAdapter>(
499
- () => ({
500
- async load() {
501
- const { remoteId } = aui.threadListItem().getState();
502
- if (!remoteId) return { messages: [] };
503
-
504
- const rows = await db.messages.findByThreadId(remoteId);
505
- return {
506
- messages: rows.map((row) => {
507
- const common = {
508
- id: row.id,
509
- createdAt: new Date(row.createdAt),
510
- };
511
- // `content` is stored as JSON — parse back into message parts
512
- const content = JSON.parse(row.content);
513
-
514
- if (row.role === "user") {
515
- return {
516
- parentId: row.parentId,
517
- message: {
518
- ...common,
519
- role: "user" as const,
520
- content,
521
- attachments: [],
522
- metadata: { custom: {} },
523
- },
524
- };
525
- }
526
- if (row.role === "assistant") {
527
- return {
528
- parentId: row.parentId,
529
- message: {
530
- ...common,
531
- role: "assistant" as const,
532
- content,
533
- status: { type: "complete", reason: "stop" } as const,
534
- metadata: {
535
- custom: {},
536
- unstable_state: null,
537
- unstable_annotations: [],
538
- unstable_data: [],
539
- steps: [],
540
- },
541
- },
542
- };
543
- }
544
- return {
545
- parentId: row.parentId,
546
- message: {
547
- ...common,
548
- role: "system" as const,
549
- content,
550
- metadata: { custom: {} },
551
- },
552
- };
553
- }),
554
- };
555
- },
556
-
557
- async append({ message, parentId }) {
558
- // Wait for initialization to get remoteId (safe to call multiple times)
559
- const { remoteId } = await aui.threadListItem().initialize();
560
-
561
- await db.messages.create({
562
- threadId: remoteId,
563
- parentId,
564
- id: message.id,
565
- role: message.role,
566
- content: JSON.stringify(message.content),
567
- createdAt: message.createdAt,
568
- });
569
- },
570
- }),
571
- [aui],
572
- );
573
-
574
- const adapters = useMemo(() => ({ history }), [history]);
575
-
576
- return (
577
- <RuntimeAdapterProvider adapters={adapters}>
578
- {children}
579
- </RuntimeAdapterProvider>
580
- );
581
- },
582
- },
583
- });
584
-
585
- return (
586
- <AssistantRuntimeProvider runtime={runtime}>
587
- {children}
588
- </AssistantRuntimeProvider>
589
- );
590
- }
591
- ```
592
-
593
- <Callout type="info" title="Returning a title from generateTitle">
594
- The `generateTitle` method must return an <code>AssistantStream</code>{" "}
595
- containing the title text. The easiest, type-safe way is to use{" "}
596
- <code>createAssistantStream</code> and call{" "}
597
- <code>controller.appendText(newTitle)</code> followed by{" "}
598
- <code>controller.close()</code>. Returning a raw <code>ReadableStream</code>{" "}
599
- won't update the thread list UI.
600
- </Callout>
601
-
602
- #### Understanding the Architecture
603
-
604
- <Callout type="info">
605
- **Key Insight**: The `unstable_Provider` component in your adapter runs in the
606
- context of each thread, giving you access to thread-specific information like
607
- `remoteId`. This is where you add the history adapter for message persistence.
608
- </Callout>
609
-
610
- The complete multi-thread implementation requires:
611
-
612
- 1. **RemoteThreadListAdapter** - Manages thread metadata (list, create, rename, archive, delete)
613
- 2. **unstable_Provider** - Component that provides thread-specific adapters (like history)
614
- 3. **ThreadHistoryAdapter** - Persists messages for each thread (load, append)
615
- 4. **runtimeHook** - Creates a basic `LocalRuntime` (adapters are added by Provider)
616
-
617
- Without the history adapter, threads would have no message persistence, making them effectively useless. The Provider pattern allows you to add thread-specific functionality while keeping the runtime creation simple.
618
-
619
- <Callout type="warn" title="Avoiding Race Conditions">
620
- When implementing a history adapter, `append()` may be called before the thread is fully initialized, causing the first message to be lost. Instead of checking `if (!remoteId)`, await initialization to ensure the `remoteId` is available:
621
-
622
- ```tsx
623
- import { useAui } from "@assistant-ui/react";
624
-
625
- // Inside your unstable_Provider component
626
- const aui = useAui();
627
-
628
- const history = useMemo<ThreadHistoryAdapter>(
629
- () => ({
630
- async append({ message, parentId }) {
631
- // Wait for initialization - safe to call multiple times
632
- const { remoteId } = await aui.threadListItem().initialize();
633
- await db.messages.create({ threadId: remoteId, parentId, ...message });
634
- },
635
- // ...
636
- }),
637
- [aui],
638
- );
639
- ```
640
-
641
- See `AssistantCloudThreadHistoryAdapter` in the source for a production reference.
642
- </Callout>
643
-
644
- #### Database Schema Example
645
-
646
- ```typescript
647
- // Example database schema for thread persistence
648
- interface ThreadRecord {
649
- id: string;
650
- title: string;
651
- archived: boolean;
652
- createdAt: Date;
653
- updatedAt: Date;
654
- }
655
-
656
- interface MessageRecord {
657
- id: string;
658
- threadId: string;
659
- parentId: string | null;
660
- role: "user" | "assistant" | "system";
661
- content: string; // JSON-encoded message content parts
662
- createdAt: Date;
663
- }
664
- ```
665
-
666
- Both approaches provide full multi-thread support. Choose Assistant Cloud for a managed solution or implement your own adapter for custom storage requirements.
667
-
668
- ## Adapters
669
-
670
- Extend `LocalRuntime` capabilities with adapters. The runtime automatically enables/disables UI features based on which adapters are provided.
671
-
672
- ### Attachment Adapter
673
-
674
- Enable file and image uploads:
675
-
676
- ```tsx
677
- const attachmentAdapter: AttachmentAdapter = {
678
- accept: "image/*,application/pdf",
679
- async add({ file }) {
680
- const formData = new FormData();
681
- formData.append("file", file);
682
-
683
- const response = await fetch("/api/upload", {
684
- method: "POST",
685
- body: formData,
686
- });
687
-
688
- const { id, url } = await response.json();
689
- return {
690
- id,
691
- type: file.type.startsWith("image/") ? "image" : "document",
692
- name: file.name,
693
- contentType: file.type,
694
- file,
695
- url,
696
- status: { type: "requires-action", reason: "composer-send" },
697
- };
698
- },
699
- async send(attachment) {
700
- return {
701
- ...attachment,
702
- status: { type: "complete" },
703
- content: [
704
- attachment.type === "image"
705
- ? { type: "image", image: attachment.url }
706
- : { type: "text", text: `[${attachment.name}](${attachment.url})` },
707
- ],
708
- };
709
- },
710
- async remove(attachment) {
711
- await fetch(`/api/upload/${attachment.id}`, {
712
- method: "DELETE",
713
- });
714
- },
715
- };
716
-
717
- const runtime = useLocalRuntime(MyModelAdapter, {
718
- adapters: { attachments: attachmentAdapter },
719
- });
720
-
721
- // For multiple file types, use CompositeAttachmentAdapter:
722
- const runtime = useLocalRuntime(MyModelAdapter, {
723
- adapters: {
724
- attachments: new CompositeAttachmentAdapter([
725
- new SimpleImageAttachmentAdapter(),
726
- new SimpleTextAttachmentAdapter(),
727
- customPDFAdapter,
728
- ]),
729
- },
730
- });
731
- ```
732
-
733
- ### Thread History Adapter
734
-
735
- Persist and resume conversations:
736
-
737
- ```tsx
738
- const historyAdapter: ThreadHistoryAdapter = {
739
- async load() {
740
- // Load messages from your storage.
741
- // The API must return `{ messages: { parentId, message }[] }`
742
- // where each `message` is a full ThreadMessage
743
- // (including `metadata.custom`, plus `attachments` on user messages
744
- // and `status` + the rest of `metadata` on assistant messages).
745
- const response = await fetch(`/api/thread/current`);
746
- return await response.json();
747
- },
748
-
749
- async append({ message, parentId }) {
750
- // Save new message to storage
751
- await fetch(`/api/thread/messages`, {
752
- method: "POST",
753
- headers: { "Content-Type": "application/json" },
754
- body: JSON.stringify({ message, parentId }),
755
- });
756
- },
757
-
758
- // Optional: Resume interrupted conversations
759
- async resume({ messages }) {
760
- const lastMessage = messages[messages.length - 1];
761
- if (lastMessage?.role === "user") {
762
- // Resume generating assistant response
763
- const response = await fetch("/api/chat/resume", {
764
- method: "POST",
765
- body: JSON.stringify({ messages }),
766
- });
767
- return response.body; // Return stream
768
- }
769
- },
770
- };
771
-
772
- const runtime = useLocalRuntime(MyModelAdapter, {
773
- adapters: { history: historyAdapter },
774
- });
775
- ```
776
-
777
- <Callout type="info">
778
- The history adapter handles persistence for the current thread's messages. For
779
- multi-thread support with custom storage, use either
780
- `useRemoteThreadListRuntime` with `LocalRuntime` or `ExternalStoreRuntime`
781
- with a thread list adapter.
782
- </Callout>
783
-
784
- ### Speech Synthesis Adapter
785
-
786
- Add text-to-speech capabilities:
787
-
788
- ```tsx
789
- const speechAdapter: SpeechSynthesisAdapter = {
790
- speak(text) {
791
- const utterance = new SpeechSynthesisUtterance(text);
792
- utterance.rate = 1.0;
793
- utterance.pitch = 1.0;
794
-
795
- const subscribers = new Set<() => void>();
796
- const result: SpeechSynthesisAdapter.Utterance = {
797
- status: { type: "running" },
798
- cancel: () => {
799
- speechSynthesis.cancel();
800
- result.status = { type: "ended", reason: "cancelled" };
801
- subscribers.forEach((cb) => cb());
802
- },
803
- subscribe: (callback) => {
804
- subscribers.add(callback);
805
- return () => subscribers.delete(callback);
806
- },
807
- };
808
-
809
- utterance.addEventListener("end", () => {
810
- result.status = { type: "ended", reason: "finished" };
811
- subscribers.forEach((cb) => cb());
812
- });
813
- utterance.addEventListener("error", (e) => {
814
- result.status = { type: "ended", reason: "error", error: e.error };
815
- subscribers.forEach((cb) => cb());
816
- });
817
-
818
- speechSynthesis.speak(utterance);
819
- return result;
820
- },
821
- };
822
-
823
- const runtime = useLocalRuntime(MyModelAdapter, {
824
- adapters: { speech: speechAdapter },
825
- });
826
- ```
827
-
828
- ### Feedback Adapter
829
-
830
- Collect user feedback on messages:
831
-
832
- ```tsx
833
- const feedbackAdapter: FeedbackAdapter = {
834
- async submit(feedback) {
835
- await fetch("/api/feedback", {
836
- method: "POST",
837
- headers: { "Content-Type": "application/json" },
838
- body: JSON.stringify({
839
- messageId: feedback.message.id,
840
- rating: feedback.type, // "positive" or "negative"
841
- }),
842
- });
843
- },
844
- };
845
-
846
- const runtime = useLocalRuntime(MyModelAdapter, {
847
- adapters: { feedback: feedbackAdapter },
848
- });
849
- ```
850
-
851
- ### Suggestion Adapter
852
-
853
- Provide follow-up suggestions:
854
-
855
- ```tsx
856
- const suggestionAdapter: SuggestionAdapter = {
857
- async *generate({ messages }) {
858
- // Analyze conversation context
859
- const lastMessage = messages[messages.length - 1];
860
-
861
- // Generate suggestions
862
- const suggestions = await generateSuggestions(lastMessage);
863
-
864
- yield suggestions.map((prompt) => ({
865
- prompt,
866
- }));
867
- },
868
- };
869
-
870
- const runtime = useLocalRuntime(MyModelAdapter, {
871
- adapters: { suggestion: suggestionAdapter },
872
- });
873
- ```
874
-
875
- ## Advanced Features
876
-
877
- ### Resuming a Run
878
-
879
- `resumeRun` reconnects to an in-progress or interrupted assistant run. This is essential for scenarios like:
880
-
881
- - **Page refresh** while the backend is still generating a response
882
- - **Network reconnection** after a temporary disconnect
883
- - **Tab backgrounding** where the browser suspended the WebSocket connection
884
- - **Thread switching** to a conversation that has an active backend stream
885
-
886
- #### How it works
887
-
888
- When you call `resumeRun`, the local runtime:
889
-
890
- 1. Creates a new assistant message in the thread (or continues the existing one)
891
- 2. Calls your provided `stream` function with `ChatModelRunOptions` (messages, abort signal, model context, etc.)
892
- 3. Iterates over each `ChatModelRunResult` yielded by the stream, updating the assistant message with new content, status, and metadata
893
- 4. Completes when the stream finishes or is cancelled
894
-
895
- Unlike `startRun` (which uses the ChatModelAdapter), `resumeRun` **requires** a `stream` parameter — you provide the async generator that produces the response.
896
-
897
- #### Basic example
898
-
899
- ```tsx
900
- import { useAui } from "@assistant-ui/react";
901
- import type { ChatModelRunResult } from "@assistant-ui/core";
902
-
903
- const aui = useAui();
904
-
905
- // Create a custom stream
906
- async function* createCustomStream(): AsyncGenerator<ChatModelRunResult> {
907
- let text = "Initial response";
908
- yield {
909
- content: [{ type: "text", text }],
910
- };
911
-
912
- // Simulate delay
913
- await new Promise((resolve) => setTimeout(resolve, 500));
914
-
915
- text = "Initial response. And here's more content...";
916
- yield {
917
- content: [{ type: "text", text }],
918
- };
919
- }
920
-
921
- // Resume a run with the custom stream
922
- aui.thread().resumeRun({
923
- parentId: "message-id", // ID of the message to respond to
924
- stream: createCustomStream,
925
- });
926
- ```
927
-
928
- #### Reconnecting to a backend stream
929
-
930
- A common pattern is checking whether the backend is still running, then reconnecting:
931
-
932
- ```tsx
933
- import { useAui } from "@assistant-ui/react";
934
- import { useEffect, useRef } from "react";
935
-
936
- function useStreamReconnect(threadId: string) {
937
- const aui = useAui();
938
- const hasCheckedRef = useRef(false);
939
-
940
- useEffect(() => {
941
- if (hasCheckedRef.current) return;
942
- hasCheckedRef.current = true;
943
-
944
- const checkAndResume = async () => {
945
- // Check if the backend still has an active stream
946
- const status = await fetch(`/api/status/${threadId}`).then((r) =>
947
- r.json(),
948
- );
949
-
950
- if (status.isRunning) {
951
- const parentId =
952
- aui.thread().getState().messages.at(-1)?.id ?? null;
953
- aui.thread().resumeRun({ parentId });
954
- }
955
- };
956
-
957
- checkAndResume();
958
- }, [aui, threadId]);
959
- }
960
- ```
961
-
962
- #### ChatModelRunResult
963
-
964
- Each value yielded by the stream is a `ChatModelRunResult`:
965
-
966
- ```tsx
967
- type ChatModelRunResult = {
968
- /** The message content parts (text, tool calls, etc.) */
969
- content: ThreadAssistantContentPart[];
970
- /** Optional status override */
971
- status?: MessageStatus;
972
- /** Optional metadata (state, annotations, custom fields) */
973
- metadata?: {
974
- custom?: Record<string, unknown>;
975
- steps?: unknown[];
976
- // ...
977
- };
978
- };
979
- ```
980
-
981
- The stream should yield the **full cumulative content** on each iteration (not deltas). Each yield replaces the previous content of the assistant message.
982
-
983
- ### Custom Thread Management
984
-
985
- Access thread actions for advanced control with `useAui`:
986
-
987
- ```tsx
988
- import { useAui } from "@assistant-ui/react";
989
-
990
- function MyComponent() {
991
- const aui = useAui();
992
-
993
- // Cancel current generation
994
- const handleCancel = () => {
995
- aui.thread().cancelRun();
996
- };
997
-
998
- // Switch to a different branch (message scope)
999
- // aui.message().switchToBranch({ position: "next" });
1000
- // aui.message().switchToBranch({ position: "previous" });
1001
-
1002
- // Reload a message (message scope)
1003
- // aui.message().reload();
1004
-
1005
- return (
1006
- // Your UI
1007
- );
1008
- }
1009
- ```
1010
-
1011
- ## Integration Examples
1012
-
1013
- ### OpenAI Integration
1014
-
1015
- ```tsx
1016
- import { OpenAI } from "openai";
1017
-
1018
- const openai = new OpenAI({
1019
- apiKey: process.env.OPENAI_API_KEY,
1020
- dangerouslyAllowBrowser: true, // Use server-side in production
1021
- });
1022
-
1023
- const OpenAIAdapter: ChatModelAdapter = {
1024
- async *run({ messages, abortSignal, context }) {
1025
- const stream = await openai.chat.completions.create({
1026
- model: "gpt-4o",
1027
- messages: messages.map((m) => ({
1028
- role: m.role,
1029
- content: m.content
1030
- .filter((c) => c.type === "text")
1031
- .map((c) => c.text)
1032
- .join("\n"),
1033
- })),
1034
- stream: true,
1035
- signal: abortSignal,
1036
- });
1037
-
1038
- let fullText = "";
1039
- for await (const chunk of stream) {
1040
- const content = chunk.choices[0]?.delta?.content;
1041
- if (content) {
1042
- fullText += content;
1043
- yield {
1044
- content: [{ type: "text", text: fullText }],
1045
- };
1046
- }
1047
- }
1048
- },
1049
- };
1050
- ```
1051
-
1052
- ### Custom REST API Integration
1053
-
1054
- ```tsx
1055
- const CustomAPIAdapter: ChatModelAdapter = {
1056
- async run({ messages, abortSignal, unstable_threadId }) {
1057
- const response = await fetch("/api/chat", {
1058
- method: "POST",
1059
- headers: { "Content-Type": "application/json" },
1060
- body: JSON.stringify({
1061
- messages: messages.map((m) => ({
1062
- role: m.role,
1063
- content: m.content,
1064
- })),
1065
- threadId: unstable_threadId, // Pass thread ID to your backend
1066
- }),
1067
- signal: abortSignal,
1068
- });
1069
-
1070
- if (!response.ok) {
1071
- throw new Error(`API error: ${response.statusText}`);
1072
- }
1073
-
1074
- const data = await response.json();
1075
- return {
1076
- content: [{ type: "text", text: data.message }],
1077
- };
1078
- },
1079
- };
1080
- ```
1081
-
1082
- ## Best Practices
1083
-
1084
- 1. **Error Handling** - Always handle API errors gracefully:
1085
-
1086
- ```tsx
1087
- async *run({ messages, abortSignal }) {
1088
- try {
1089
- const response = await fetchAPI(messages, abortSignal);
1090
- yield response;
1091
- } catch (error) {
1092
- if (error.name === 'AbortError') {
1093
- // User cancelled - this is normal
1094
- return;
1095
- }
1096
- // Re-throw other errors to display in UI
1097
- throw error;
1098
- }
1099
- }
1100
- ```
1101
-
1102
- 2. **Abort Signal** - Always pass the abort signal to fetch requests:
1103
-
1104
- ```tsx
1105
- fetch(url, { signal: abortSignal });
1106
- ```
1107
-
1108
- 3. **Memory Management** - For long conversations, consider implementing message limits:
1109
-
1110
- ```tsx
1111
- const recentMessages = messages.slice(-20); // Keep last 20 messages
1112
- ```
1113
-
1114
- 4. **Type Safety** - Use TypeScript for better development experience:
1115
- ```tsx
1116
- import type { ChatModelAdapter, ThreadMessage } from "@assistant-ui/react";
1117
- ```
1118
-
1119
- ## Comparison with `ExternalStoreRuntime`
1120
-
1121
- | Feature | `LocalRuntime` | `ExternalStoreRuntime` |
1122
- | --------------------- | -------------------------------------------- | ------------------------------------------------ |
1123
- | State Management | Built-in | You manage |
1124
- | Setup Complexity | Simple | More complex |
1125
- | Flexibility | Extensible via adapters | Full control |
1126
- | Message Editing | Automatic | Requires `onEdit` handler |
1127
- | Branch Switching | Automatic | Requires `setMessages` handler |
1128
- | Multi-Thread Support | Yes (with Assistant Cloud or custom adapter) | Yes (with thread list adapter) |
1129
- | Custom Thread Storage | Yes (with useRemoteThreadListRuntime) | Yes |
1130
- | Persistence | Via history adapter or Assistant Cloud | Your implementation |
1131
- | Best For | Quick prototypes, standard apps, cloud-based | Complex state requirements, custom storage needs |
1132
-
1133
- ## Troubleshooting
1134
-
1135
- ### Common Issues
1136
-
1137
- <Callout type="error">
1138
- **Messages not appearing**: Ensure your adapter returns the correct format:
1139
- ```tsx
1140
- return {
1141
- content: [{ type: "text", text: "response" }]
1142
- };
1143
- ```
1144
- </Callout>
1145
-
1146
- <Callout type="warning">
1147
- **Streaming not working**: Make sure to use `async *run` (note the asterisk):
1148
- ```tsx
1149
- async *run({ messages }) { // ✅ Correct
1150
- async run({ messages }) { // ❌ Wrong for streaming
1151
- ```
1152
- </Callout>
1153
-
1154
- ### Tool UI Flickers or Disappears During Streaming
1155
-
1156
- A common issue when implementing a streaming `ChatModelAdapter` is seeing a tool's UI appear for a moment and then disappear. This is caused by failing to accumulate the `tool_calls` correctly across multiple stream chunks. State must be stored **outside** the streaming loop to persist.
1157
-
1158
- **❌ Incorrect: Forgetting Previous Tool Calls**
1159
-
1160
- This implementation incorrectly re-creates the `content` array for every chunk. If a later chunk contains only text, tool calls from previous chunks are lost, causing the UI to disappear.
1161
-
1162
- ```tsx
1163
- // This implementation incorrectly re-creates the `content` array for every chunk.
1164
- // If a later chunk contains only text, tool calls from previous chunks are lost.
1165
- async *run({ messages, abortSignal, context }) {
1166
- const stream = await backendApi({ messages, abortSignal, context });
1167
- let text = "";
1168
-
1169
- for await (const chunk of stream) {
1170
- // ❌ DON'T: This overwrites toolCalls with only the current chunk's data
1171
- const toolCalls = chunk.tool_calls || [];
1172
- const content = [{ type: "text", text }];
1173
- for (const toolCall of toolCalls) {
1174
- content.push({
1175
- type: "tool-call",
1176
- toolName: toolCall.name,
1177
- toolCallId: toolCall.id,
1178
- args: toolCall.args,
1179
- });
1180
- }
1181
- yield { content }; // This yield might not contain the tool call anymore
1182
- }
1183
- }
1184
- ```
1185
-
1186
- **✅ Correct: Accumulating State**
1187
-
1188
- This implementation uses a `Map` outside the loop to remember all tool calls.
1189
-
1190
- ```tsx
1191
- // This implementation uses a Map outside the loop to remember all tool calls.
1192
- async *run({ messages, abortSignal, context }) {
1193
- const stream = await backendApi({ messages, abortSignal, context });
1194
- let text = "";
1195
- // ✅ DO: Declare state outside the loop
1196
- const toolCallsMap = new Map();
1197
-
1198
- for await (const chunk of stream) {
1199
- text += chunk.content || "";
1200
-
1201
- // ✅ DO: Add/update tool calls in the persistent map
1202
- for (const toolCall of chunk.tool_calls || []) {
1203
- toolCallsMap.set(toolCall.toolCallId, {
1204
- type: "tool-call",
1205
- toolName: toolCall.name,
1206
- toolCallId: toolCall.toolCallId,
1207
- args: toolCall.args,
1208
- });
1209
- }
1210
-
1211
- // ✅ DO: Build content from accumulated state
1212
- const content = [
1213
- ...(text ? [{ type: "text", text }] : []),
1214
- ...Array.from(toolCallsMap.values()),
1215
- ];
1216
-
1217
- yield { content }; // Yield the complete, correct state every time
1218
- }
1219
- }
1220
- ```
1221
-
1222
- ### Debug Tips
1223
-
1224
- 1. **Log adapter calls** to trace execution:
1225
-
1226
- ```tsx
1227
- async *run(options) {
1228
- console.log("Adapter called with:", options);
1229
- // ... rest of implementation
1230
- }
1231
- ```
1232
-
1233
- 2. **Check network requests** in browser DevTools
1234
-
1235
- 3. **Verify message format** matches ThreadMessage structure
1236
-
1237
- ## API Reference
1238
-
1239
- ### `ChatModelAdapter`
1240
-
1241
- The main interface for connecting your API to `LocalRuntime`.
1242
-
1243
- <ParametersTable
1244
- type="ChatModelAdapter"
1245
- parameters={[
1246
- {
1247
- name: "run",
1248
- type: "ChatModelRunOptions => ChatModelRunResult | AsyncGenerator<ChatModelRunResult>",
1249
- description:
1250
- "Function that sends messages to your API and returns the response",
1251
- required: true,
1252
- },
1253
- ]}
1254
- />
1255
-
1256
- ### `ChatModelRunOptions`
1257
-
1258
- Parameters passed to the `run` function.
1259
-
1260
- <ParametersTable
1261
- type="ChatModelRunOptions"
1262
- parameters={[
1263
- {
1264
- name: "messages",
1265
- type: "readonly ThreadMessage[]",
1266
- description: "The conversation history to send to your API",
1267
- required: true,
1268
- },
1269
- {
1270
- name: "runConfig",
1271
- type: "RunConfig",
1272
- description: "Run configuration with optional custom metadata. `RunConfig` is `{ readonly custom?: Record<string, unknown> }`.",
1273
- required: true,
1274
- },
1275
- {
1276
- name: "abortSignal",
1277
- type: "AbortSignal",
1278
- description: "Signal to cancel the request if user interrupts",
1279
- required: true,
1280
- },
1281
- {
1282
- name: "context",
1283
- type: "ModelContext",
1284
- description: "Additional context including configuration and tools",
1285
- required: true,
1286
- },
1287
- {
1288
- name: "unstable_assistantMessageId",
1289
- type: "string | undefined",
1290
- description: "The ID of the assistant message being generated. Useful for tracking or updating specific messages.",
1291
- },
1292
- {
1293
- name: "unstable_threadId",
1294
- type: "string | undefined",
1295
- description: "The current thread/conversation identifier. Useful for passing to your backend API.",
1296
- },
1297
- {
1298
- name: "unstable_parentId",
1299
- type: "string | null | undefined",
1300
- description: "The ID of the parent message this response is replying to. `null` if this is the first message in the thread.",
1301
- },
1302
- {
1303
- name: "unstable_getMessage",
1304
- type: "() => ThreadMessage",
1305
- description: "Returns the current assistant message being generated. Useful for accessing message state during streaming.",
1306
- },
1307
- {
1308
- name: "config",
1309
- type: "ModelContext",
1310
- description: "Deprecated. Renamed to `context`. Additional context including configuration and tools.",
1311
- },
1312
- ]}
1313
- />
1314
-
1315
- ### `LocalRuntimeOptions`
1316
-
1317
- Configuration options for the `LocalRuntime`.
1318
-
1319
- <ParametersTable
1320
- type="LocalRuntimeOptions"
1321
- parameters={[
1322
- {
1323
- name: "initialMessages",
1324
- type: "readonly ThreadMessageLike[]",
1325
- description: "Pre-populate the thread with messages",
1326
- },
1327
- {
1328
- name: "maxSteps",
1329
- type: "number",
1330
- description:
1331
- "Maximum number of sequential tool calls before requiring user input",
1332
- default: "2",
1333
- },
1334
- {
1335
- name: "cloud",
1336
- type: "AssistantCloud",
1337
- description:
1338
- "Enable Assistant Cloud integration for multi-thread support and persistence",
1339
- },
1340
- {
1341
- name: "adapters",
1342
- type: "LocalRuntimeAdapters",
1343
- description:
1344
- "Additional capabilities through adapters. Features are automatically enabled based on provided adapters",
1345
- children: [
1346
- {
1347
- type: "adapters",
1348
- parameters: [
1349
- {
1350
- name: "attachments",
1351
- type: "AttachmentAdapter",
1352
- description: "Enable file/image attachments",
1353
- },
1354
- {
1355
- name: "speech",
1356
- type: "SpeechSynthesisAdapter",
1357
- description: "Enable text-to-speech for messages",
1358
- },
1359
- {
1360
- name: "dictation",
1361
- type: "DictationAdapter",
1362
- description: "Enable speech-to-text dictation",
1363
- },
1364
- {
1365
- name: "feedback",
1366
- type: "FeedbackAdapter",
1367
- description: "Enable message feedback (thumbs up/down)",
1368
- },
1369
- {
1370
- name: "history",
1371
- type: "ThreadHistoryAdapter",
1372
- description: "Enable thread persistence and resumption",
1373
- },
1374
- {
1375
- name: "suggestion",
1376
- type: "SuggestionAdapter",
1377
- description: "Enable follow-up suggestions",
1378
- },
1379
- ],
1380
- },
1381
- ],
1382
- },
1383
- {
1384
- name: "unstable_humanToolNames",
1385
- type: "string[]",
1386
- description:
1387
- "Tool names that require human approval before execution (experimental API)",
1388
- },
1389
- ]}
1390
- />
1391
-
1392
- ### `RemoteThreadListAdapter`
1393
-
1394
- Interface for implementing custom thread list storage.
1395
-
1396
- <ParametersTable
1397
- type="RemoteThreadListAdapter"
1398
- parameters={[
1399
- {
1400
- name: "list",
1401
- type: "() => Promise<RemoteThreadListResponse>",
1402
- description: "Returns list of all threads (regular and archived)",
1403
- required: true,
1404
- },
1405
- {
1406
- name: "initialize",
1407
- type: "(threadId: string) => Promise<RemoteThreadInitializeResponse>",
1408
- description: "Creates a new thread with the given ID",
1409
- required: true,
1410
- },
1411
- {
1412
- name: "rename",
1413
- type: "(remoteId: string, newTitle: string) => Promise<void>",
1414
- description: "Updates the title of a thread",
1415
- required: true,
1416
- },
1417
- {
1418
- name: "archive",
1419
- type: "(remoteId: string) => Promise<void>",
1420
- description: "Archives a thread",
1421
- required: true,
1422
- },
1423
- {
1424
- name: "unarchive",
1425
- type: "(remoteId: string) => Promise<void>",
1426
- description: "Unarchives a thread",
1427
- required: true,
1428
- },
1429
- {
1430
- name: "delete",
1431
- type: "(remoteId: string) => Promise<void>",
1432
- description: "Deletes a thread permanently",
1433
- required: true,
1434
- },
1435
- {
1436
- name: "generateTitle",
1437
- type: "(remoteId: string, unstable_messages: readonly ThreadMessage[]) => Promise<AssistantStream>",
1438
- description: "Generates a title for the thread based on the conversation",
1439
- required: true,
1440
- },
1441
- {
1442
- name: "fetch",
1443
- type: "(threadId: string) => Promise<RemoteThreadMetadata>",
1444
- description: "Fetches metadata for a specific thread",
1445
- required: true,
1446
- },
1447
- {
1448
- name: "unstable_Provider",
1449
- type: "(...args: any[]) => unknown",
1450
- description: "Optional React provider component to wrap the runtime context",
1451
- },
1452
- ]}
1453
- />
1454
-
1455
- ### Related Runtime APIs
1456
-
1457
- - [AssistantRuntime API](/docs/api-reference/runtimes/assistant-runtime) - Core runtime interface and methods
1458
- - [ThreadRuntime API](/docs/api-reference/runtimes/thread-runtime) - Thread-specific operations and state management
1459
-
1460
- ## Related Resources
1461
-
1462
- - [Pick a Runtime Guide](/docs/runtimes/pick-a-runtime)
1463
- - [`ExternalStoreRuntime`](/docs/runtimes/custom/external-store)
1464
- - [Examples Repository](https://github.com/assistant-ui/assistant-ui/tree/main/examples)