@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.
- package/.docs/organized/code-examples/local-ollama.md +1135 -0
- package/.docs/organized/code-examples/search-agent-for-e-commerce.md +1721 -0
- package/.docs/organized/code-examples/with-ai-sdk.md +1081 -0
- package/.docs/organized/code-examples/with-cloud.md +1164 -0
- package/.docs/organized/code-examples/with-external-store.md +1064 -0
- package/.docs/organized/code-examples/with-ffmpeg.md +1305 -0
- package/.docs/organized/code-examples/with-langgraph.md +1819 -0
- package/.docs/organized/code-examples/with-openai-assistants.md +1175 -0
- package/.docs/organized/code-examples/with-react-hook-form.md +1727 -0
- package/.docs/organized/code-examples/with-vercel-ai-rsc.md +1157 -0
- package/.docs/raw/blog/2024-07-29-hello/index.mdx +65 -0
- package/.docs/raw/blog/2024-09-11/index.mdx +10 -0
- package/.docs/raw/blog/2024-12-15/index.mdx +10 -0
- package/.docs/raw/blog/2025-01-31-changelog/index.mdx +129 -0
- package/.docs/raw/docs/about-assistantui.mdx +44 -0
- package/.docs/raw/docs/api-reference/context-providers/AssistantRuntimeProvider.mdx +30 -0
- package/.docs/raw/docs/api-reference/context-providers/TextContentPartProvider.mdx +26 -0
- package/.docs/raw/docs/api-reference/integrations/react-hook-form.mdx +103 -0
- package/.docs/raw/docs/api-reference/integrations/vercel-ai-sdk.mdx +145 -0
- package/.docs/raw/docs/api-reference/overview.mdx +583 -0
- package/.docs/raw/docs/api-reference/primitives/ActionBar.mdx +264 -0
- package/.docs/raw/docs/api-reference/primitives/AssistantModal.mdx +129 -0
- package/.docs/raw/docs/api-reference/primitives/Attachment.mdx +96 -0
- package/.docs/raw/docs/api-reference/primitives/BranchPicker.mdx +87 -0
- package/.docs/raw/docs/api-reference/primitives/Composer.mdx +204 -0
- package/.docs/raw/docs/api-reference/primitives/ContentPart.mdx +173 -0
- package/.docs/raw/docs/api-reference/primitives/Error.mdx +70 -0
- package/.docs/raw/docs/api-reference/primitives/Message.mdx +181 -0
- package/.docs/raw/docs/api-reference/primitives/Thread.mdx +197 -0
- package/.docs/raw/docs/api-reference/primitives/composition.mdx +21 -0
- package/.docs/raw/docs/api-reference/runtimes/AssistantRuntime.mdx +33 -0
- package/.docs/raw/docs/api-reference/runtimes/AttachmentRuntime.mdx +46 -0
- package/.docs/raw/docs/api-reference/runtimes/ComposerRuntime.mdx +69 -0
- package/.docs/raw/docs/api-reference/runtimes/ContentPartRuntime.mdx +22 -0
- package/.docs/raw/docs/api-reference/runtimes/MessageRuntime.mdx +49 -0
- package/.docs/raw/docs/api-reference/runtimes/ThreadListItemRuntime.mdx +32 -0
- package/.docs/raw/docs/api-reference/runtimes/ThreadListRuntime.mdx +31 -0
- package/.docs/raw/docs/api-reference/runtimes/ThreadRuntime.mdx +48 -0
- package/.docs/raw/docs/architecture.mdx +92 -0
- package/.docs/raw/docs/cloud/authorization.mdx +152 -0
- package/.docs/raw/docs/cloud/overview.mdx +55 -0
- package/.docs/raw/docs/cloud/persistence/ai-sdk.mdx +54 -0
- package/.docs/raw/docs/cloud/persistence/langgraph.mdx +123 -0
- package/.docs/raw/docs/concepts/architecture.mdx +19 -0
- package/.docs/raw/docs/concepts/runtime-layer.mdx +163 -0
- package/.docs/raw/docs/concepts/why.mdx +9 -0
- package/.docs/raw/docs/copilots/make-assistant-readable.mdx +71 -0
- package/.docs/raw/docs/copilots/make-assistant-tool-ui.mdx +76 -0
- package/.docs/raw/docs/copilots/make-assistant-tool.mdx +117 -0
- package/.docs/raw/docs/copilots/model-context.mdx +135 -0
- package/.docs/raw/docs/copilots/motivation.mdx +191 -0
- package/.docs/raw/docs/copilots/use-assistant-instructions.mdx +62 -0
- package/.docs/raw/docs/getting-started.mdx +1133 -0
- package/.docs/raw/docs/guides/Attachments.mdx +640 -0
- package/.docs/raw/docs/guides/Branching.mdx +59 -0
- package/.docs/raw/docs/guides/Editing.mdx +56 -0
- package/.docs/raw/docs/guides/Speech.mdx +43 -0
- package/.docs/raw/docs/guides/ToolUI.mdx +663 -0
- package/.docs/raw/docs/guides/Tools.mdx +496 -0
- package/.docs/raw/docs/index.mdx +7 -0
- package/.docs/raw/docs/legacy/styled/AssistantModal.mdx +85 -0
- package/.docs/raw/docs/legacy/styled/Decomposition.mdx +633 -0
- package/.docs/raw/docs/legacy/styled/Markdown.mdx +86 -0
- package/.docs/raw/docs/legacy/styled/Scrollbar.mdx +71 -0
- package/.docs/raw/docs/legacy/styled/Thread.mdx +84 -0
- package/.docs/raw/docs/legacy/styled/ThreadWidth.mdx +21 -0
- package/.docs/raw/docs/mcp-docs-server.mdx +324 -0
- package/.docs/raw/docs/migrations/deprecation-policy.mdx +41 -0
- package/.docs/raw/docs/migrations/v0-7.mdx +188 -0
- package/.docs/raw/docs/migrations/v0-8.mdx +160 -0
- package/.docs/raw/docs/migrations/v0-9.mdx +75 -0
- package/.docs/raw/docs/react-compatibility.mdx +208 -0
- package/.docs/raw/docs/runtimes/ai-sdk/rsc.mdx +226 -0
- package/.docs/raw/docs/runtimes/ai-sdk/use-assistant-hook.mdx +195 -0
- package/.docs/raw/docs/runtimes/ai-sdk/use-chat-hook.mdx +138 -0
- package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +136 -0
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +1624 -0
- package/.docs/raw/docs/runtimes/custom/local.mdx +1185 -0
- package/.docs/raw/docs/runtimes/helicone.mdx +60 -0
- package/.docs/raw/docs/runtimes/langgraph/index.mdx +320 -0
- package/.docs/raw/docs/runtimes/langgraph/tutorial/index.mdx +11 -0
- package/.docs/raw/docs/runtimes/langgraph/tutorial/introduction.mdx +28 -0
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +120 -0
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +336 -0
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +385 -0
- package/.docs/raw/docs/runtimes/langserve.mdx +126 -0
- package/.docs/raw/docs/runtimes/mastra/full-stack-integration.mdx +218 -0
- package/.docs/raw/docs/runtimes/mastra/overview.mdx +17 -0
- package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +196 -0
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +222 -0
- package/.docs/raw/docs/ui/AssistantModal.mdx +46 -0
- package/.docs/raw/docs/ui/AssistantSidebar.mdx +42 -0
- package/.docs/raw/docs/ui/Attachment.mdx +82 -0
- package/.docs/raw/docs/ui/Markdown.mdx +72 -0
- package/.docs/raw/docs/ui/Mermaid.mdx +79 -0
- package/.docs/raw/docs/ui/Scrollbar.mdx +59 -0
- package/.docs/raw/docs/ui/SyntaxHighlighting.mdx +253 -0
- package/.docs/raw/docs/ui/Thread.mdx +47 -0
- package/.docs/raw/docs/ui/ThreadList.mdx +49 -0
- package/.docs/raw/docs/ui/ToolFallback.mdx +64 -0
- package/.docs/raw/docs/ui/primitives/Thread.mdx +197 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/dist/chunk-C7O7EFKU.js +38 -0
- package/dist/chunk-CZCDQ3YH.js +420 -0
- package/dist/index.js +1 -0
- package/dist/prepare-docs/prepare.js +199 -0
- package/dist/stdio.js +8 -0
- 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)
|