@assistant-ui/mcp-docs-server 0.1.1

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 (109) hide show
  1. package/.docs/organized/code-examples/local-ollama.md +1135 -0
  2. package/.docs/organized/code-examples/search-agent-for-e-commerce.md +1721 -0
  3. package/.docs/organized/code-examples/with-ai-sdk.md +1081 -0
  4. package/.docs/organized/code-examples/with-cloud.md +1164 -0
  5. package/.docs/organized/code-examples/with-external-store.md +1064 -0
  6. package/.docs/organized/code-examples/with-ffmpeg.md +1305 -0
  7. package/.docs/organized/code-examples/with-langgraph.md +1819 -0
  8. package/.docs/organized/code-examples/with-openai-assistants.md +1175 -0
  9. package/.docs/organized/code-examples/with-react-hook-form.md +1727 -0
  10. package/.docs/organized/code-examples/with-vercel-ai-rsc.md +1157 -0
  11. package/.docs/raw/blog/2024-07-29-hello/index.mdx +65 -0
  12. package/.docs/raw/blog/2024-09-11/index.mdx +10 -0
  13. package/.docs/raw/blog/2024-12-15/index.mdx +10 -0
  14. package/.docs/raw/blog/2025-01-31-changelog/index.mdx +129 -0
  15. package/.docs/raw/docs/about-assistantui.mdx +44 -0
  16. package/.docs/raw/docs/api-reference/context-providers/AssistantRuntimeProvider.mdx +30 -0
  17. package/.docs/raw/docs/api-reference/context-providers/TextContentPartProvider.mdx +26 -0
  18. package/.docs/raw/docs/api-reference/integrations/react-hook-form.mdx +103 -0
  19. package/.docs/raw/docs/api-reference/integrations/vercel-ai-sdk.mdx +145 -0
  20. package/.docs/raw/docs/api-reference/overview.mdx +583 -0
  21. package/.docs/raw/docs/api-reference/primitives/ActionBar.mdx +264 -0
  22. package/.docs/raw/docs/api-reference/primitives/AssistantModal.mdx +129 -0
  23. package/.docs/raw/docs/api-reference/primitives/Attachment.mdx +96 -0
  24. package/.docs/raw/docs/api-reference/primitives/BranchPicker.mdx +87 -0
  25. package/.docs/raw/docs/api-reference/primitives/Composer.mdx +204 -0
  26. package/.docs/raw/docs/api-reference/primitives/ContentPart.mdx +173 -0
  27. package/.docs/raw/docs/api-reference/primitives/Error.mdx +70 -0
  28. package/.docs/raw/docs/api-reference/primitives/Message.mdx +181 -0
  29. package/.docs/raw/docs/api-reference/primitives/Thread.mdx +197 -0
  30. package/.docs/raw/docs/api-reference/primitives/composition.mdx +21 -0
  31. package/.docs/raw/docs/api-reference/runtimes/AssistantRuntime.mdx +33 -0
  32. package/.docs/raw/docs/api-reference/runtimes/AttachmentRuntime.mdx +46 -0
  33. package/.docs/raw/docs/api-reference/runtimes/ComposerRuntime.mdx +69 -0
  34. package/.docs/raw/docs/api-reference/runtimes/ContentPartRuntime.mdx +22 -0
  35. package/.docs/raw/docs/api-reference/runtimes/MessageRuntime.mdx +49 -0
  36. package/.docs/raw/docs/api-reference/runtimes/ThreadListItemRuntime.mdx +32 -0
  37. package/.docs/raw/docs/api-reference/runtimes/ThreadListRuntime.mdx +31 -0
  38. package/.docs/raw/docs/api-reference/runtimes/ThreadRuntime.mdx +48 -0
  39. package/.docs/raw/docs/architecture.mdx +92 -0
  40. package/.docs/raw/docs/cloud/authorization.mdx +152 -0
  41. package/.docs/raw/docs/cloud/overview.mdx +55 -0
  42. package/.docs/raw/docs/cloud/persistence/ai-sdk.mdx +54 -0
  43. package/.docs/raw/docs/cloud/persistence/langgraph.mdx +123 -0
  44. package/.docs/raw/docs/concepts/architecture.mdx +19 -0
  45. package/.docs/raw/docs/concepts/runtime-layer.mdx +163 -0
  46. package/.docs/raw/docs/concepts/why.mdx +9 -0
  47. package/.docs/raw/docs/copilots/make-assistant-readable.mdx +71 -0
  48. package/.docs/raw/docs/copilots/make-assistant-tool-ui.mdx +76 -0
  49. package/.docs/raw/docs/copilots/make-assistant-tool.mdx +117 -0
  50. package/.docs/raw/docs/copilots/model-context.mdx +135 -0
  51. package/.docs/raw/docs/copilots/motivation.mdx +191 -0
  52. package/.docs/raw/docs/copilots/use-assistant-instructions.mdx +62 -0
  53. package/.docs/raw/docs/getting-started.mdx +1133 -0
  54. package/.docs/raw/docs/guides/Attachments.mdx +640 -0
  55. package/.docs/raw/docs/guides/Branching.mdx +59 -0
  56. package/.docs/raw/docs/guides/Editing.mdx +56 -0
  57. package/.docs/raw/docs/guides/Speech.mdx +43 -0
  58. package/.docs/raw/docs/guides/ToolUI.mdx +663 -0
  59. package/.docs/raw/docs/guides/Tools.mdx +496 -0
  60. package/.docs/raw/docs/index.mdx +7 -0
  61. package/.docs/raw/docs/legacy/styled/AssistantModal.mdx +85 -0
  62. package/.docs/raw/docs/legacy/styled/Decomposition.mdx +633 -0
  63. package/.docs/raw/docs/legacy/styled/Markdown.mdx +86 -0
  64. package/.docs/raw/docs/legacy/styled/Scrollbar.mdx +71 -0
  65. package/.docs/raw/docs/legacy/styled/Thread.mdx +84 -0
  66. package/.docs/raw/docs/legacy/styled/ThreadWidth.mdx +21 -0
  67. package/.docs/raw/docs/mcp-docs-server.mdx +324 -0
  68. package/.docs/raw/docs/migrations/deprecation-policy.mdx +41 -0
  69. package/.docs/raw/docs/migrations/v0-7.mdx +188 -0
  70. package/.docs/raw/docs/migrations/v0-8.mdx +160 -0
  71. package/.docs/raw/docs/migrations/v0-9.mdx +75 -0
  72. package/.docs/raw/docs/react-compatibility.mdx +208 -0
  73. package/.docs/raw/docs/runtimes/ai-sdk/rsc.mdx +226 -0
  74. package/.docs/raw/docs/runtimes/ai-sdk/use-assistant-hook.mdx +195 -0
  75. package/.docs/raw/docs/runtimes/ai-sdk/use-chat-hook.mdx +138 -0
  76. package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +136 -0
  77. package/.docs/raw/docs/runtimes/custom/external-store.mdx +1624 -0
  78. package/.docs/raw/docs/runtimes/custom/local.mdx +1185 -0
  79. package/.docs/raw/docs/runtimes/helicone.mdx +60 -0
  80. package/.docs/raw/docs/runtimes/langgraph/index.mdx +320 -0
  81. package/.docs/raw/docs/runtimes/langgraph/tutorial/index.mdx +11 -0
  82. package/.docs/raw/docs/runtimes/langgraph/tutorial/introduction.mdx +28 -0
  83. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +120 -0
  84. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +336 -0
  85. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +385 -0
  86. package/.docs/raw/docs/runtimes/langserve.mdx +126 -0
  87. package/.docs/raw/docs/runtimes/mastra/full-stack-integration.mdx +218 -0
  88. package/.docs/raw/docs/runtimes/mastra/overview.mdx +17 -0
  89. package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +196 -0
  90. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +222 -0
  91. package/.docs/raw/docs/ui/AssistantModal.mdx +46 -0
  92. package/.docs/raw/docs/ui/AssistantSidebar.mdx +42 -0
  93. package/.docs/raw/docs/ui/Attachment.mdx +82 -0
  94. package/.docs/raw/docs/ui/Markdown.mdx +72 -0
  95. package/.docs/raw/docs/ui/Mermaid.mdx +79 -0
  96. package/.docs/raw/docs/ui/Scrollbar.mdx +59 -0
  97. package/.docs/raw/docs/ui/SyntaxHighlighting.mdx +253 -0
  98. package/.docs/raw/docs/ui/Thread.mdx +47 -0
  99. package/.docs/raw/docs/ui/ThreadList.mdx +49 -0
  100. package/.docs/raw/docs/ui/ToolFallback.mdx +64 -0
  101. package/.docs/raw/docs/ui/primitives/Thread.mdx +197 -0
  102. package/LICENSE +21 -0
  103. package/README.md +128 -0
  104. package/dist/chunk-C7O7EFKU.js +38 -0
  105. package/dist/chunk-CZCDQ3YH.js +420 -0
  106. package/dist/index.js +1 -0
  107. package/dist/prepare-docs/prepare.js +199 -0
  108. package/dist/stdio.js +8 -0
  109. package/package.json +43 -0
@@ -0,0 +1,1185 @@
1
+ ---
2
+ title: LocalRuntime
3
+ ---
4
+
5
+ import { Callout } from "fumadocs-ui/components/callout";
6
+ import { Steps, Step } from "fumadocs-ui/components/steps";
7
+ import { Card, Cards } from "fumadocs-ui/components/card";
8
+ import { ParametersTable } from "@/components/docs";
9
+
10
+ ## Overview
11
+
12
+ `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.
13
+
14
+ `LocalRuntime` provides:
15
+
16
+ - **Built-in state management** for messages, threads, and conversation history
17
+ - **Automatic features** like message editing, reloading, and branch switching
18
+ - **Multi-thread support** through [Assistant Cloud](/docs/cloud/overview) or your own database using `useRemoteThreadListRuntime`
19
+ - **Simple adapter pattern** to connect any backend API
20
+
21
+ 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`.
22
+
23
+ ## When to Use
24
+
25
+ Use `LocalRuntime` if you need:
26
+
27
+ - **Quick setup with minimal configuration** - Get a fully functional chat interface with just a few lines of code
28
+ - **Built-in state management** - No need to manage messages, threads, or conversation history yourself
29
+ - **Automatic features** - Branch switching, message editing, and regeneration work out of the box
30
+ - **API flexibility** - Connect to any REST endpoint, OpenAI, or custom model with a simple adapter
31
+ - **Multi-thread support** - Full thread management with Assistant Cloud or custom database
32
+ - **Thread persistence** - Via history adapter, Assistant Cloud, or custom thread list adapter
33
+
34
+ ## Key Features
35
+
36
+ <Cards>
37
+ <Card
38
+ title="Built-in State Management"
39
+ description="Automatic handling of messages, threads, and conversation history"
40
+ />
41
+ <Card
42
+ title="Multi-Thread Support"
43
+ description="Full thread management capabilities with Assistant Cloud or custom database adapter"
44
+ />
45
+ <Card
46
+ title="Adapter System"
47
+ description="Extend with attachments, speech, feedback, persistence, and suggestions"
48
+ />
49
+ <Card
50
+ title="Tool Calling"
51
+ description="Support for function calling with human-in-the-loop approval"
52
+ />
53
+ </Cards>
54
+
55
+ ## Getting Started
56
+
57
+ <Steps>
58
+ <Step>
59
+ ### Create a Next.js project
60
+
61
+ ```sh
62
+ npx create-next-app@latest my-app
63
+ cd my-app
64
+ ```
65
+
66
+ </Step>
67
+ <Step>
68
+
69
+ ### Install `@assistant-ui/react`
70
+
71
+ ```sh npm2yarn
72
+ npm install @assistant-ui/react
73
+ ```
74
+
75
+ </Step>
76
+ <Step>
77
+
78
+ ### Define a `MyRuntimeProvider` component
79
+
80
+ Update the `MyModelAdapter` below to integrate with your own custom API.
81
+ See `LocalRuntimeOptions` [API Reference](#localruntimeoptions) for available configuration options.
82
+
83
+ ```tsx twoslash include MyRuntimeProvider title="app/MyRuntimeProvider.tsx"
84
+ // @filename: /app/MyRuntimeProvider.tsx
85
+
86
+ // ---cut---
87
+ "use client";
88
+
89
+ import type { ReactNode } from "react";
90
+ import {
91
+ AssistantRuntimeProvider,
92
+ useLocalRuntime,
93
+ type ChatModelAdapter,
94
+ } from "@assistant-ui/react";
95
+
96
+ const MyModelAdapter: ChatModelAdapter = {
97
+ async run({ messages, abortSignal }) {
98
+ // TODO replace with your own API
99
+ const result = await fetch("<YOUR_API_ENDPOINT>", {
100
+ method: "POST",
101
+ headers: {
102
+ "Content-Type": "application/json",
103
+ },
104
+ // forward the messages in the chat to the API
105
+ body: JSON.stringify({
106
+ messages,
107
+ }),
108
+ // if the user hits the "cancel" button or escape keyboard key, cancel the request
109
+ signal: abortSignal,
110
+ });
111
+
112
+ const data = await result.json();
113
+ return {
114
+ content: [
115
+ {
116
+ type: "text",
117
+ text: data.text,
118
+ },
119
+ ],
120
+ };
121
+ },
122
+ };
123
+
124
+ export function MyRuntimeProvider({
125
+ children,
126
+ }: Readonly<{
127
+ children: ReactNode;
128
+ }>) {
129
+ const runtime = useLocalRuntime(MyModelAdapter);
130
+
131
+ return (
132
+ <AssistantRuntimeProvider runtime={runtime}>
133
+ {children}
134
+ </AssistantRuntimeProvider>
135
+ );
136
+ }
137
+ ```
138
+
139
+ </Step>
140
+ <Step>
141
+
142
+ ### Wrap your app in `MyRuntimeProvider`
143
+
144
+ ```tsx {1,11,17} twoslash title="app/layout.tsx"
145
+ // @include: MyRuntimeProvider
146
+ // @filename: /app/layout.tsx
147
+ // ---cut---
148
+ import type { ReactNode } from "react";
149
+ import { MyRuntimeProvider } from "@/app/MyRuntimeProvider";
150
+
151
+ export default function RootLayout({
152
+ children,
153
+ }: Readonly<{
154
+ children: ReactNode;
155
+ }>) {
156
+ return (
157
+ <MyRuntimeProvider>
158
+ <html lang="en">
159
+ <body>{children}</body>
160
+ </html>
161
+ </MyRuntimeProvider>
162
+ );
163
+ }
164
+ ```
165
+
166
+ </Step>
167
+ <Step>
168
+
169
+ ### Use the Thread component
170
+
171
+ ```tsx title="app/page.tsx"
172
+ import { Thread } from "@assistant-ui/react";
173
+
174
+ export default function Page() {
175
+ return <Thread />;
176
+ }
177
+ ```
178
+
179
+ </Step>
180
+ </Steps>
181
+
182
+ ## Streaming Responses
183
+
184
+ Implement streaming by declaring the `run` function as an `AsyncGenerator`.
185
+
186
+ ```tsx twoslash {2, 11-13} title="app/MyRuntimeProvider.tsx"
187
+ import {
188
+ ChatModelAdapter,
189
+ ThreadMessage,
190
+ type ModelContext,
191
+ } from "@assistant-ui/react";
192
+ import { OpenAI } from "openai";
193
+
194
+ const openai = new OpenAI();
195
+ const backendApi = async ({
196
+ messages,
197
+ abortSignal,
198
+ context,
199
+ }: {
200
+ messages: readonly ThreadMessage[];
201
+ abortSignal: AbortSignal;
202
+ context: ModelContext;
203
+ }) => {
204
+ return openai.chat.completions.create({
205
+ model: "gpt-4o",
206
+ messages: [{ role: "user", content: "Say this is a test" }],
207
+ stream: true,
208
+ });
209
+ };
210
+
211
+ // ---cut---
212
+ const MyModelAdapter: ChatModelAdapter = {
213
+ async *run({ messages, abortSignal, context }) {
214
+ const stream = await backendApi({ messages, abortSignal, context });
215
+
216
+ let text = "";
217
+ for await (const part of stream) {
218
+ text += part.choices[0]?.delta?.content || "";
219
+
220
+ yield {
221
+ content: [{ type: "text", text }],
222
+ };
223
+ }
224
+ },
225
+ };
226
+ ```
227
+
228
+ ### Streaming with Tool Calls
229
+
230
+ Handle streaming responses that include function calls:
231
+
232
+ ```tsx
233
+ const MyModelAdapter: ChatModelAdapter = {
234
+ async *run({ messages, abortSignal, context }) {
235
+ const stream = await openai.chat.completions.create({
236
+ model: "gpt-4o",
237
+ messages: convertToOpenAIMessages(messages),
238
+ tools: context.tools,
239
+ stream: true,
240
+ signal: abortSignal,
241
+ });
242
+
243
+ let content = "";
244
+ const toolCalls: any[] = [];
245
+
246
+ for await (const chunk of stream) {
247
+ const delta = chunk.choices[0]?.delta;
248
+
249
+ // Handle text content
250
+ if (delta?.content) {
251
+ content += delta.content;
252
+ }
253
+
254
+ // Handle tool calls
255
+ if (delta?.tool_calls) {
256
+ for (const toolCall of delta.tool_calls) {
257
+ if (!toolCalls[toolCall.index]) {
258
+ toolCalls[toolCall.index] = {
259
+ id: toolCall.id,
260
+ type: "function",
261
+ function: { name: "", arguments: "" },
262
+ };
263
+ }
264
+
265
+ if (toolCall.function?.name) {
266
+ toolCalls[toolCall.index].function.name = toolCall.function.name;
267
+ }
268
+
269
+ if (toolCall.function?.arguments) {
270
+ toolCalls[toolCall.index].function.arguments +=
271
+ toolCall.function.arguments;
272
+ }
273
+ }
274
+ }
275
+
276
+ // Yield current state
277
+ yield {
278
+ content: [
279
+ ...(content ? [{ type: "text" as const, text: content }] : []),
280
+ ...toolCalls.map((tc) => ({
281
+ type: "tool-call" as const,
282
+ toolCallId: tc.id,
283
+ toolName: tc.function.name,
284
+ args: JSON.parse(tc.function.arguments || "{}"),
285
+ })),
286
+ ],
287
+ };
288
+ }
289
+ },
290
+ };
291
+ ```
292
+
293
+ ## Tool Calling
294
+
295
+ `LocalRuntime` supports OpenAI-compatible function calling with automatic or human-in-the-loop execution.
296
+
297
+ ### Basic Tool Definition
298
+
299
+ ```tsx
300
+ const tools = [
301
+ {
302
+ type: "function",
303
+ function: {
304
+ name: "get_weather",
305
+ description: "Get the current weather in a location",
306
+ parameters: {
307
+ type: "object",
308
+ properties: {
309
+ location: {
310
+ type: "string",
311
+ description: "The city and state, e.g. San Francisco, CA",
312
+ },
313
+ unit: {
314
+ type: "string",
315
+ enum: ["celsius", "fahrenheit"],
316
+ },
317
+ },
318
+ required: ["location"],
319
+ },
320
+ },
321
+ },
322
+ ];
323
+
324
+ const runtime = useLocalRuntime(MyModelAdapter, {
325
+ // Tools are passed via context
326
+ context: { tools },
327
+ });
328
+ ```
329
+
330
+ ### Human-in-the-Loop Approval
331
+
332
+ Require user confirmation before executing certain tools:
333
+
334
+ ```tsx
335
+ const runtime = useLocalRuntime(MyModelAdapter, {
336
+ unstable_humanToolNames: ["delete_file", "send_email"],
337
+ });
338
+ ```
339
+
340
+ ### Tool Execution
341
+
342
+ Tools are executed automatically by the runtime. The model adapter receives tool results in subsequent messages:
343
+
344
+ ```tsx
345
+ // Messages will include tool calls and results:
346
+ [
347
+ { role: "user", content: "What's the weather in SF?" },
348
+ {
349
+ role: "assistant",
350
+ content: [
351
+ {
352
+ type: "tool-call",
353
+ toolCallId: "call_123",
354
+ toolName: "get_weather",
355
+ args: { location: "San Francisco, CA" },
356
+ },
357
+ ],
358
+ },
359
+ {
360
+ role: "tool",
361
+ content: [
362
+ {
363
+ type: "tool-result",
364
+ toolCallId: "call_123",
365
+ result: { temperature: 72, condition: "sunny" },
366
+ },
367
+ ],
368
+ },
369
+ {
370
+ role: "assistant",
371
+ content: "The weather in San Francisco is sunny and 72°F.",
372
+ },
373
+ ];
374
+ ```
375
+
376
+ ## Multi-Thread Support
377
+
378
+ `LocalRuntime` supports multiple conversation threads through two approaches:
379
+
380
+ ### 1. Assistant Cloud Integration
381
+
382
+ ```tsx
383
+ import { useLocalRuntime } from "@assistant-ui/react";
384
+ import { AssistantCloud } from "@assistant-ui/cloud";
385
+
386
+ const cloud = new AssistantCloud({
387
+ apiKey: process.env.ASSISTANT_CLOUD_API_KEY,
388
+ });
389
+
390
+ const runtime = useLocalRuntime(MyModelAdapter, {
391
+ cloud, // Enables multi-thread support
392
+ });
393
+ ```
394
+
395
+ With Assistant Cloud, you get:
396
+
397
+ - Multiple conversation threads
398
+ - Thread persistence across sessions
399
+ - Thread management (create, switch, rename, archive, delete)
400
+ - Automatic synchronization across devices
401
+ - Built-in user authentication
402
+
403
+ ### 2. Custom Database with useRemoteThreadListRuntime
404
+
405
+ For custom thread storage, use `useRemoteThreadListRuntime` with your own adapter:
406
+
407
+ ```tsx
408
+ import {
409
+ useLocalThreadRuntime,
410
+ unstable_useRemoteThreadListRuntime as useRemoteThreadListRuntime,
411
+ useThreadListItem,
412
+ RuntimeAdapterProvider,
413
+ AssistantRuntimeProvider,
414
+ type RemoteThreadListAdapter,
415
+ type ThreadHistoryAdapter,
416
+ } from "@assistant-ui/react";
417
+
418
+ // Implement your custom adapter with proper message persistence
419
+ const myDatabaseAdapter: RemoteThreadListAdapter = {
420
+ async list() {
421
+ const threads = await db.threads.findAll();
422
+ return {
423
+ threads: threads.map((t) => ({
424
+ status: t.archived ? "archived" : "regular",
425
+ remoteId: t.id,
426
+ title: t.title,
427
+ })),
428
+ };
429
+ },
430
+
431
+ async initialize(threadId) {
432
+ const thread = await db.threads.create({ id: threadId });
433
+ return { remoteId: thread.id };
434
+ },
435
+
436
+ async rename(remoteId, newTitle) {
437
+ await db.threads.update(remoteId, { title: newTitle });
438
+ },
439
+
440
+ async archive(remoteId) {
441
+ await db.threads.update(remoteId, { archived: true });
442
+ },
443
+
444
+ async unarchive(remoteId) {
445
+ await db.threads.update(remoteId, { archived: false });
446
+ },
447
+
448
+ async delete(remoteId) {
449
+ // Delete thread and its messages
450
+ await db.messages.deleteByThreadId(remoteId);
451
+ await db.threads.delete(remoteId);
452
+ },
453
+
454
+ async generateTitle(remoteId, messages) {
455
+ // Generate title from messages using your AI
456
+ const title = await generateTitle(messages);
457
+ await db.threads.update(remoteId, { title });
458
+ return new ReadableStream(); // Return empty stream
459
+ },
460
+ };
461
+
462
+ // Complete implementation with message persistence using Provider pattern
463
+ export function MyRuntimeProvider({ children }) {
464
+ const runtime = useRemoteThreadListRuntime({
465
+ runtimeHook: () => {
466
+ return useLocalThreadRuntime(MyModelAdapter);
467
+ },
468
+ adapter: {
469
+ ...myDatabaseAdapter,
470
+
471
+ // The Provider component adds thread-specific adapters
472
+ unstable_Provider: ({ children }) => {
473
+ // This runs in the context of each thread
474
+ const threadListItem = useThreadListItem();
475
+ const remoteId = threadListItem.remoteId;
476
+
477
+ // Create thread-specific history adapter
478
+ const history = useMemo<ThreadHistoryAdapter>(
479
+ () => ({
480
+ async load() {
481
+ if (!remoteId) return { messages: [] };
482
+
483
+ const messages = await db.messages.findByThreadId(remoteId);
484
+ return {
485
+ messages: messages.map((m) => ({
486
+ role: m.role,
487
+ content: m.content,
488
+ id: m.id,
489
+ createdAt: new Date(m.createdAt),
490
+ })),
491
+ };
492
+ },
493
+
494
+ async append(message) {
495
+ if (!remoteId) {
496
+ console.warn("Cannot save message - thread not initialized");
497
+ return;
498
+ }
499
+
500
+ await db.messages.create({
501
+ threadId: remoteId,
502
+ role: message.role,
503
+ content: message.content,
504
+ id: message.id,
505
+ createdAt: message.createdAt,
506
+ });
507
+ },
508
+ }),
509
+ [remoteId],
510
+ );
511
+
512
+ const adapters = useMemo(() => ({ history }), [history]);
513
+
514
+ return (
515
+ <RuntimeAdapterProvider adapters={adapters}>
516
+ {children}
517
+ </RuntimeAdapterProvider>
518
+ );
519
+ },
520
+ },
521
+ });
522
+
523
+ return (
524
+ <AssistantRuntimeProvider runtime={runtime}>
525
+ {children}
526
+ </AssistantRuntimeProvider>
527
+ );
528
+ }
529
+ ```
530
+
531
+ #### Understanding the Architecture
532
+
533
+ <Callout type="info">
534
+ **Key Insight**: The `unstable_Provider` component in your adapter runs in the
535
+ context of each thread, giving you access to thread-specific information like
536
+ `remoteId`. This is where you add the history adapter for message persistence.
537
+ </Callout>
538
+
539
+ The complete multi-thread implementation requires:
540
+
541
+ 1. **RemoteThreadListAdapter** - Manages thread metadata (list, create, rename, archive, delete)
542
+ 2. **unstable_Provider** - Component that provides thread-specific adapters (like history)
543
+ 3. **ThreadHistoryAdapter** - Persists messages for each thread (load, append)
544
+ 4. **runtimeHook** - Creates a basic `LocalRuntime` (adapters are added by Provider)
545
+
546
+ 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.
547
+
548
+ #### Database Schema Example
549
+
550
+ ```typescript
551
+ // Example database schema for thread persistence
552
+ interface ThreadRecord {
553
+ id: string;
554
+ title: string;
555
+ archived: boolean;
556
+ createdAt: Date;
557
+ updatedAt: Date;
558
+ }
559
+
560
+ interface MessageRecord {
561
+ id: string;
562
+ threadId: string;
563
+ role: "user" | "assistant" | "system";
564
+ content: any; // Store as JSON
565
+ createdAt: Date;
566
+ }
567
+ ```
568
+
569
+ Both approaches provide full multi-thread support. Choose Assistant Cloud for a managed solution or implement your own adapter for custom storage requirements.
570
+
571
+ ## Adapters
572
+
573
+ Extend `LocalRuntime` capabilities with adapters. The runtime automatically enables/disables UI features based on which adapters are provided.
574
+
575
+ ### Attachment Adapter
576
+
577
+ Enable file and image uploads:
578
+
579
+ ```tsx
580
+ const attachmentAdapter: AttachmentAdapter = {
581
+ accept: "image/*,application/pdf",
582
+ async add(file) {
583
+ const formData = new FormData();
584
+ formData.append("file", file);
585
+
586
+ const response = await fetch("/api/upload", {
587
+ method: "POST",
588
+ body: formData,
589
+ });
590
+
591
+ const { id, url } = await response.json();
592
+ return {
593
+ id,
594
+ type: file.type.startsWith("image/") ? "image" : "document",
595
+ name: file.name,
596
+ url,
597
+ };
598
+ },
599
+ async remove(attachment) {
600
+ await fetch(`/api/upload/${attachment.id}`, {
601
+ method: "DELETE",
602
+ });
603
+ },
604
+ };
605
+
606
+ const runtime = useLocalRuntime(MyModelAdapter, {
607
+ adapters: { attachments: attachmentAdapter },
608
+ });
609
+
610
+ // For multiple file types, use CompositeAttachmentAdapter:
611
+ const runtime = useLocalRuntime(MyModelAdapter, {
612
+ adapters: {
613
+ attachments: new CompositeAttachmentAdapter([
614
+ new SimpleImageAttachmentAdapter(),
615
+ new SimpleTextAttachmentAdapter(),
616
+ customPDFAdapter,
617
+ ]),
618
+ },
619
+ });
620
+ ```
621
+
622
+ ### Thread History Adapter
623
+
624
+ Persist and resume conversations:
625
+
626
+ ```tsx
627
+ const historyAdapter: ThreadHistoryAdapter = {
628
+ async load() {
629
+ // Load messages from your storage
630
+ const response = await fetch(`/api/thread/current`);
631
+ const { messages } = await response.json();
632
+ return { messages };
633
+ },
634
+
635
+ async append(message) {
636
+ // Save new message to storage
637
+ await fetch(`/api/thread/messages`, {
638
+ method: "POST",
639
+ headers: { "Content-Type": "application/json" },
640
+ body: JSON.stringify({ message }),
641
+ });
642
+ },
643
+
644
+ // Optional: Resume interrupted conversations
645
+ async resume({ messages }) {
646
+ const lastMessage = messages[messages.length - 1];
647
+ if (lastMessage?.role === "user") {
648
+ // Resume generating assistant response
649
+ const response = await fetch("/api/chat/resume", {
650
+ method: "POST",
651
+ body: JSON.stringify({ messages }),
652
+ });
653
+ return response.body; // Return stream
654
+ }
655
+ },
656
+ };
657
+
658
+ const runtime = useLocalRuntime(MyModelAdapter, {
659
+ adapters: { history: historyAdapter },
660
+ });
661
+ ```
662
+
663
+ <Callout type="info">
664
+ The history adapter handles persistence for the current thread's messages. For
665
+ multi-thread support with custom storage, use either
666
+ `useRemoteThreadListRuntime` with `LocalRuntime` or `ExternalStoreRuntime`
667
+ with a thread list adapter.
668
+ </Callout>
669
+
670
+ ### Speech Synthesis Adapter
671
+
672
+ Add text-to-speech capabilities:
673
+
674
+ ```tsx
675
+ const speechAdapter: SpeechSynthesisAdapter = {
676
+ speak(text) {
677
+ const utterance = new SpeechSynthesisUtterance(text);
678
+ utterance.rate = 1.0;
679
+ utterance.pitch = 1.0;
680
+ speechSynthesis.speak(utterance);
681
+ },
682
+
683
+ stop() {
684
+ speechSynthesis.cancel();
685
+ },
686
+ };
687
+
688
+ const runtime = useLocalRuntime(MyModelAdapter, {
689
+ adapters: { speech: speechAdapter },
690
+ });
691
+ ```
692
+
693
+ ### Feedback Adapter
694
+
695
+ Collect user feedback on messages:
696
+
697
+ ```tsx
698
+ const feedbackAdapter: FeedbackAdapter = {
699
+ async submit(feedback) {
700
+ await fetch("/api/feedback", {
701
+ method: "POST",
702
+ headers: { "Content-Type": "application/json" },
703
+ body: JSON.stringify({
704
+ messageId: feedback.messageId,
705
+ rating: feedback.type, // "positive" or "negative"
706
+ }),
707
+ });
708
+ },
709
+ };
710
+
711
+ const runtime = useLocalRuntime(MyModelAdapter, {
712
+ adapters: { feedback: feedbackAdapter },
713
+ });
714
+ ```
715
+
716
+ ### Suggestion Adapter
717
+
718
+ Provide follow-up suggestions:
719
+
720
+ ```tsx
721
+ const suggestionAdapter: SuggestionAdapter = {
722
+ async *get({ messages }) {
723
+ // Analyze conversation context
724
+ const lastMessage = messages[messages.length - 1];
725
+
726
+ // Generate suggestions
727
+ const suggestions = await generateSuggestions(lastMessage);
728
+
729
+ yield suggestions.map((text) => ({
730
+ id: crypto.randomUUID(),
731
+ text,
732
+ }));
733
+ },
734
+ };
735
+
736
+ const runtime = useLocalRuntime(MyModelAdapter, {
737
+ adapters: { suggestion: suggestionAdapter },
738
+ });
739
+ ```
740
+
741
+ ## Advanced Features
742
+
743
+ ### Resuming a Run
744
+
745
+ <Callout type="warning">
746
+ The `unstable_resumeRun` method is experimental and may change in future
747
+ releases.
748
+ </Callout>
749
+
750
+ Resume a conversation with a custom stream:
751
+
752
+ ```tsx
753
+ import { useThreadRuntime, type ChatModelRunResult } from "@assistant-ui/react";
754
+
755
+ // Get the thread runtime
756
+ const thread = useThreadRuntime();
757
+
758
+ // Create a custom stream
759
+ async function* createCustomStream(): AsyncGenerator<ChatModelRunResult> {
760
+ let text = "Initial response";
761
+ yield {
762
+ content: [{ type: "text", text }],
763
+ };
764
+
765
+ // Simulate delay
766
+ await new Promise((resolve) => setTimeout(resolve, 500));
767
+
768
+ text = "Initial response. And here's more content...";
769
+ yield {
770
+ content: [{ type: "text", text }],
771
+ };
772
+ }
773
+
774
+ // Resume a run with the custom stream
775
+ thread.unstable_resumeRun({
776
+ parentId: "message-id", // ID of the message to respond to
777
+ stream: createCustomStream(), // The stream to use for resuming
778
+ });
779
+ ```
780
+
781
+ ### Custom Thread Management
782
+
783
+ Access thread runtime for advanced control with `useThreadRuntime`:
784
+
785
+ ```tsx
786
+ import { useThreadRuntime } from "@assistant-ui/react";
787
+
788
+ function MyComponent() {
789
+ const thread = useThreadRuntime();
790
+
791
+ // Cancel current generation
792
+ const handleCancel = () => {
793
+ thread.cancelRun();
794
+ };
795
+
796
+ // Switch to a different branch
797
+ const handleSwitchBranch = (messageId: string, branchIndex: number) => {
798
+ thread.switchToBranch(messageId, branchIndex);
799
+ };
800
+
801
+ // Reload a message
802
+ const handleReload = (messageId: string) => {
803
+ thread.reload(messageId);
804
+ };
805
+
806
+ return (
807
+ // Your UI
808
+ );
809
+ }
810
+ ```
811
+
812
+ ### Custom Runtime Implementation
813
+
814
+ `useLocalThreadRuntime` provides the core single-thread runtime for building custom implementations:
815
+
816
+ ```tsx
817
+ import {
818
+ useLocalThreadRuntime,
819
+ unstable_useRemoteThreadListRuntime as useRemoteThreadListRuntime,
820
+ AssistantRuntimeProvider,
821
+ } from "@assistant-ui/react";
822
+
823
+ // Build your own multi-thread runtime
824
+ function MyCustomRuntimeProvider({ children }) {
825
+ const runtime = useRemoteThreadListRuntime({
826
+ runtimeHook: () => useLocalThreadRuntime(MyModelAdapter, options),
827
+ adapter: myCustomThreadListAdapter,
828
+ });
829
+
830
+ return (
831
+ <AssistantRuntimeProvider runtime={runtime}>
832
+ {children}
833
+ </AssistantRuntimeProvider>
834
+ );
835
+ }
836
+ ```
837
+
838
+ <Callout type="info">
839
+ `useLocalRuntime` internally uses `useLocalThreadRuntime` +
840
+ `useRemoteThreadListRuntime` for multi-thread support.
841
+ </Callout>
842
+
843
+ <Callout type="warning">
844
+ **`useThreadRuntime` vs `useLocalThreadRuntime`:**
845
+ - `useThreadRuntime` - Access the current thread's runtime from within components
846
+ - `useLocalThreadRuntime` - Create a new single-thread runtime instance
847
+ </Callout>
848
+
849
+ ## Integration Examples
850
+
851
+ ### OpenAI Integration
852
+
853
+ ```tsx
854
+ import { OpenAI } from "openai";
855
+
856
+ const openai = new OpenAI({
857
+ apiKey: process.env.OPENAI_API_KEY,
858
+ dangerouslyAllowBrowser: true, // Use server-side in production
859
+ });
860
+
861
+ const OpenAIAdapter: ChatModelAdapter = {
862
+ async *run({ messages, abortSignal, context }) {
863
+ const stream = await openai.chat.completions.create({
864
+ model: "gpt-4o",
865
+ messages: messages.map((m) => ({
866
+ role: m.role,
867
+ content: m.content
868
+ .filter((c) => c.type === "text")
869
+ .map((c) => c.text)
870
+ .join("\n"),
871
+ })),
872
+ stream: true,
873
+ signal: abortSignal,
874
+ });
875
+
876
+ for await (const chunk of stream) {
877
+ const content = chunk.choices[0]?.delta?.content;
878
+ if (content) {
879
+ yield {
880
+ content: [{ type: "text", text: content }],
881
+ };
882
+ }
883
+ }
884
+ },
885
+ };
886
+ ```
887
+
888
+ ### Custom REST API Integration
889
+
890
+ ```tsx
891
+ const CustomAPIAdapter: ChatModelAdapter = {
892
+ async run({ messages, abortSignal }) {
893
+ const response = await fetch("/api/chat", {
894
+ method: "POST",
895
+ headers: { "Content-Type": "application/json" },
896
+ body: JSON.stringify({
897
+ messages: messages.map((m) => ({
898
+ role: m.role,
899
+ content: m.content,
900
+ })),
901
+ }),
902
+ signal: abortSignal,
903
+ });
904
+
905
+ if (!response.ok) {
906
+ throw new Error(`API error: ${response.statusText}`);
907
+ }
908
+
909
+ const data = await response.json();
910
+ return {
911
+ content: [{ type: "text", text: data.message }],
912
+ };
913
+ },
914
+ };
915
+ ```
916
+
917
+ ## Best Practices
918
+
919
+ 1. **Error Handling** - Always handle API errors gracefully:
920
+
921
+ ```tsx
922
+ async *run({ messages, abortSignal }) {
923
+ try {
924
+ const response = await fetchAPI(messages, abortSignal);
925
+ yield response;
926
+ } catch (error) {
927
+ if (error.name === 'AbortError') {
928
+ // User cancelled - this is normal
929
+ return;
930
+ }
931
+ // Re-throw other errors to display in UI
932
+ throw error;
933
+ }
934
+ }
935
+ ```
936
+
937
+ 2. **Abort Signal** - Always pass the abort signal to fetch requests:
938
+
939
+ ```tsx
940
+ fetch(url, { signal: abortSignal });
941
+ ```
942
+
943
+ 3. **Memory Management** - For long conversations, consider implementing message limits:
944
+
945
+ ```tsx
946
+ const recentMessages = messages.slice(-20); // Keep last 20 messages
947
+ ```
948
+
949
+ 4. **Type Safety** - Use TypeScript for better development experience:
950
+ ```tsx
951
+ import type { ChatModelAdapter, ThreadMessage } from "@assistant-ui/react";
952
+ ```
953
+
954
+ ## Comparison with `ExternalStoreRuntime`
955
+
956
+ | Feature | `LocalRuntime` | `ExternalStoreRuntime` |
957
+ | --------------------- | -------------------------------------------- | ------------------------------------------------ |
958
+ | State Management | Built-in | You manage |
959
+ | Setup Complexity | Simple | More complex |
960
+ | Flexibility | Extensible via adapters | Full control |
961
+ | Message Editing | Automatic | Requires `onEdit` handler |
962
+ | Branch Switching | Automatic | Requires `setMessages` handler |
963
+ | Multi-Thread Support | Yes (with Assistant Cloud or custom adapter) | Yes (with thread list adapter) |
964
+ | Custom Thread Storage | Yes (with useRemoteThreadListRuntime) | Yes |
965
+ | Persistence | Via history adapter or Assistant Cloud | Your implementation |
966
+ | Best For | Quick prototypes, standard apps, cloud-based | Complex state requirements, custom storage needs |
967
+
968
+ ## Troubleshooting
969
+
970
+ ### Common Issues
971
+
972
+ <Callout type="error">
973
+ **Messages not appearing**: Ensure your adapter returns the correct format:
974
+ ```tsx
975
+ return {
976
+ content: [{ type: "text", text: "response" }]
977
+ };
978
+ ```
979
+ </Callout>
980
+
981
+ <Callout type="warning">
982
+ **Streaming not working**: Make sure to use `async *run` (note the asterisk):
983
+ ```tsx
984
+ async *run({ messages }) { // ✅ Correct
985
+ async run({ messages }) { // ❌ Wrong for streaming
986
+ ```
987
+ </Callout>
988
+
989
+ ### Debug Tips
990
+
991
+ 1. **Log adapter calls** to trace execution:
992
+
993
+ ```tsx
994
+ async *run(options) {
995
+ console.log("Adapter called with:", options);
996
+ // ... rest of implementation
997
+ }
998
+ ```
999
+
1000
+ 2. **Check network requests** in browser DevTools
1001
+
1002
+ 3. **Verify message format** matches ThreadMessage structure
1003
+
1004
+ ## API Reference
1005
+
1006
+ ### `ChatModelAdapter`
1007
+
1008
+ The main interface for connecting your API to `LocalRuntime`.
1009
+
1010
+ <ParametersTable
1011
+ type="ChatModelAdapter"
1012
+ parameters={[
1013
+ {
1014
+ name: "run",
1015
+ type: "ChatModelRunOptions => ChatModelRunResult | AsyncGenerator<ChatModelRunResult>",
1016
+ description:
1017
+ "Function that sends messages to your API and returns the response",
1018
+ required: true,
1019
+ },
1020
+ ]}
1021
+ />
1022
+
1023
+ ### `ChatModelRunOptions`
1024
+
1025
+ Parameters passed to the `run` function.
1026
+
1027
+ <ParametersTable
1028
+ type="ChatModelRunOptions"
1029
+ parameters={[
1030
+ {
1031
+ name: "messages",
1032
+ type: "readonly ThreadMessage[]",
1033
+ description: "The conversation history to send to your API",
1034
+ required: true,
1035
+ },
1036
+ {
1037
+ name: "abortSignal",
1038
+ type: "AbortSignal",
1039
+ description: "Signal to cancel the request if user interrupts",
1040
+ required: true,
1041
+ },
1042
+ {
1043
+ name: "context",
1044
+ type: "ModelContext",
1045
+ description: "Additional context including configuration and tools",
1046
+ required: true,
1047
+ },
1048
+ ]}
1049
+ />
1050
+
1051
+ ### `LocalRuntimeOptions`
1052
+
1053
+ Configuration options for the `LocalRuntime`.
1054
+
1055
+ <ParametersTable
1056
+ type="LocalRuntimeOptions"
1057
+ parameters={[
1058
+ {
1059
+ name: "initialMessages",
1060
+ type: "readonly ThreadMessage[]",
1061
+ description: "Pre-populate the thread with messages",
1062
+ },
1063
+ {
1064
+ name: "maxSteps",
1065
+ type: "number",
1066
+ description:
1067
+ "Maximum number of sequential tool calls before requiring user input",
1068
+ default: "5",
1069
+ },
1070
+ {
1071
+ name: "cloud",
1072
+ type: "AssistantCloud",
1073
+ description:
1074
+ "Enable Assistant Cloud integration for multi-thread support and persistence",
1075
+ },
1076
+ {
1077
+ name: "adapters",
1078
+ type: "LocalRuntimeAdapters",
1079
+ description:
1080
+ "Additional capabilities through adapters. Features are automatically enabled based on provided adapters",
1081
+ children: [
1082
+ {
1083
+ type: "adapters",
1084
+ parameters: [
1085
+ {
1086
+ name: "attachments",
1087
+ type: "AttachmentAdapter",
1088
+ description: "Enable file/image attachments",
1089
+ },
1090
+ {
1091
+ name: "speech",
1092
+ type: "SpeechSynthesisAdapter",
1093
+ description: "Enable text-to-speech for messages",
1094
+ },
1095
+ {
1096
+ name: "feedback",
1097
+ type: "FeedbackAdapter",
1098
+ description: "Enable message feedback (thumbs up/down)",
1099
+ },
1100
+ {
1101
+ name: "history",
1102
+ type: "ThreadHistoryAdapter",
1103
+ description: "Enable thread persistence and resumption",
1104
+ },
1105
+ {
1106
+ name: "suggestions",
1107
+ type: "SuggestionAdapter",
1108
+ description: "Enable follow-up suggestions",
1109
+ },
1110
+ ],
1111
+ },
1112
+ ],
1113
+ },
1114
+ {
1115
+ name: "unstable_humanToolNames",
1116
+ type: "string[]",
1117
+ description:
1118
+ "Tool names that require human approval before execution (experimental API)",
1119
+ },
1120
+ ]}
1121
+ />
1122
+
1123
+ ### `RemoteThreadListAdapter`
1124
+
1125
+ Interface for implementing custom thread list storage.
1126
+
1127
+ <ParametersTable
1128
+ type="RemoteThreadListAdapter"
1129
+ parameters={[
1130
+ {
1131
+ name: "list",
1132
+ type: "() => Promise<RemoteThreadListResponse>",
1133
+ description: "Returns list of all threads (regular and archived)",
1134
+ required: true,
1135
+ },
1136
+ {
1137
+ name: "initialize",
1138
+ type: "(threadId: string) => Promise<RemoteThreadInitializeResponse>",
1139
+ description: "Creates a new thread with the given ID",
1140
+ required: true,
1141
+ },
1142
+ {
1143
+ name: "rename",
1144
+ type: "(remoteId: string, newTitle: string) => Promise<void>",
1145
+ description: "Updates the title of a thread",
1146
+ required: true,
1147
+ },
1148
+ {
1149
+ name: "archive",
1150
+ type: "(remoteId: string) => Promise<void>",
1151
+ description: "Archives a thread",
1152
+ required: true,
1153
+ },
1154
+ {
1155
+ name: "unarchive",
1156
+ type: "(remoteId: string) => Promise<void>",
1157
+ description: "Unarchives a thread",
1158
+ required: true,
1159
+ },
1160
+ {
1161
+ name: "delete",
1162
+ type: "(remoteId: string) => Promise<void>",
1163
+ description: "Deletes a thread permanently",
1164
+ required: true,
1165
+ },
1166
+ {
1167
+ name: "generateTitle",
1168
+ type: "(remoteId: string, messages: readonly ThreadMessage[]) => Promise<AssistantStream>",
1169
+ description: "Generates a title for the thread based on the conversation",
1170
+ required: true,
1171
+ },
1172
+ ]}
1173
+ />
1174
+
1175
+ ### Related Runtime APIs
1176
+
1177
+ - [AssistantRuntime API](/docs/api-reference/runtimes/AssistantRuntime) - Core runtime interface and methods
1178
+ - [ThreadRuntime API](/docs/api-reference/runtimes/ThreadRuntime) - Thread-specific operations and state management
1179
+
1180
+ ## Related Resources
1181
+
1182
+ - [Runtime Layer Concepts](/docs/concepts/runtime-layer)
1183
+ - [Pick a Runtime Guide](/docs/runtimes/pick-a-runtime)
1184
+ - [`ExternalStoreRuntime`](/docs/runtimes/custom/external-store)
1185
+ - [Examples Repository](https://github.com/assistant-ui/assistant-ui/tree/main/examples)