@assistant-ui/mcp-docs-server 0.1.9 → 0.1.11
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/with-ai-sdk-v5.md +26 -26
- package/.docs/organized/code-examples/with-assistant-transport.md +29 -29
- package/.docs/organized/code-examples/with-cloud.md +21 -21
- package/.docs/organized/code-examples/with-external-store.md +18 -18
- package/.docs/organized/code-examples/with-ffmpeg.md +22 -22
- package/.docs/organized/code-examples/with-langgraph.md +35 -120
- package/.docs/organized/code-examples/with-parent-id-grouping.md +18 -18
- package/.docs/organized/code-examples/with-react-hook-form.md +27 -27
- package/.docs/raw/docs/api-reference/primitives/Thread.mdx +40 -8
- package/.docs/raw/docs/cloud/persistence/langgraph.mdx +64 -68
- package/.docs/raw/docs/getting-started.mdx +541 -152
- package/.docs/raw/docs/guides/Attachments.mdx +21 -0
- package/.docs/raw/docs/guides/ToolUI.mdx +112 -37
- package/.docs/raw/docs/guides/Tools.mdx +170 -6
- package/.docs/raw/docs/migrations/react-langgraph-v0-7.mdx +324 -0
- package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +2 -2
- package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +218 -0
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +31 -24
- package/.docs/raw/docs/runtimes/langgraph/index.mdx +55 -20
- package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +8 -3
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +1 -1
- package/.docs/raw/docs/ui/AssistantModal.mdx +21 -0
- package/.docs/raw/docs/ui/AssistantSidebar.mdx +21 -0
- package/.docs/raw/docs/ui/Attachment.mdx +21 -0
- package/.docs/raw/docs/ui/Markdown.mdx +22 -1
- package/.docs/raw/docs/ui/Mermaid.mdx +22 -1
- package/.docs/raw/docs/ui/SyntaxHighlighting.mdx +43 -2
- package/.docs/raw/docs/ui/Thread.mdx +374 -5
- package/.docs/raw/docs/ui/ThreadList.mdx +48 -2
- package/.docs/raw/docs/ui/ToolFallback.mdx +21 -0
- package/package.json +7 -7
- package/.docs/raw/docs/migrations/v0-7.mdx +0 -188
- package/.docs/raw/docs/migrations/v0-8.mdx +0 -160
- package/.docs/raw/docs/migrations/v0-9.mdx +0 -75
- package/.docs/raw/docs/ui/primitives/Thread.mdx +0 -197
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Migrating to react-langgraph v0.7
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
import { Callout } from "fumadocs-ui/components/callout";
|
|
6
|
+
import { Steps, Step } from "fumadocs-ui/components/steps";
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
This guide helps you migrate from the previous LangGraph integration pattern to the new simplified API introduced in `@assistant-ui/react-langgraph` v0.7. The new API consolidates thread management directly into `useLangGraphRuntime`, eliminating the need for separate runtime hooks and manual thread state management.
|
|
11
|
+
|
|
12
|
+
## Key Changes
|
|
13
|
+
|
|
14
|
+
### 1. Simplified Thread Management
|
|
15
|
+
|
|
16
|
+
The `useLangGraphRuntime` hook now directly handles thread lifecycle:
|
|
17
|
+
- No more `useRemoteThreadListRuntime` wrapper
|
|
18
|
+
- No more separate runtime hook functions
|
|
19
|
+
- Thread management is built into the core runtime
|
|
20
|
+
|
|
21
|
+
### 2. New `initialize` Parameter
|
|
22
|
+
|
|
23
|
+
The `stream` function now receives an `initialize` parameter that handles thread creation and loading automatically.
|
|
24
|
+
|
|
25
|
+
### 3. Direct Cloud Integration
|
|
26
|
+
|
|
27
|
+
Cloud persistence can now be configured directly in `useLangGraphRuntime` with the `cloud` parameter.
|
|
28
|
+
|
|
29
|
+
## Migration Steps
|
|
30
|
+
|
|
31
|
+
<Steps>
|
|
32
|
+
|
|
33
|
+
<Step>
|
|
34
|
+
|
|
35
|
+
### Update Your Runtime Implementation
|
|
36
|
+
|
|
37
|
+
#### Before (Old Pattern)
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import {
|
|
41
|
+
useCloudThreadListRuntime,
|
|
42
|
+
useThreadListItemRuntime,
|
|
43
|
+
} from "@assistant-ui/react";
|
|
44
|
+
import { useLangGraphRuntime } from "@assistant-ui/react-langgraph";
|
|
45
|
+
|
|
46
|
+
const useMyLangGraphRuntime = () => {
|
|
47
|
+
const threadListItemRuntime = useThreadListItemRuntime();
|
|
48
|
+
|
|
49
|
+
const runtime = useLangGraphRuntime({
|
|
50
|
+
stream: async function* (messages) {
|
|
51
|
+
const { externalId } = await threadListItemRuntime.initialize();
|
|
52
|
+
if (!externalId) throw new Error("Thread not found");
|
|
53
|
+
|
|
54
|
+
return sendMessage({
|
|
55
|
+
threadId: externalId,
|
|
56
|
+
messages,
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
onSwitchToThread: async (externalId) => {
|
|
60
|
+
const state = await getThreadState(externalId);
|
|
61
|
+
return {
|
|
62
|
+
messages: state.values.messages,
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return runtime;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// In your component:
|
|
71
|
+
const runtime = useCloudThreadListRuntime({
|
|
72
|
+
cloud,
|
|
73
|
+
runtimeHook: useMyLangGraphRuntime,
|
|
74
|
+
create: async () => {
|
|
75
|
+
const { thread_id } = await createThread();
|
|
76
|
+
return { externalId: thread_id };
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### After (New Pattern)
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { useLangGraphRuntime } from "@assistant-ui/react-langgraph";
|
|
85
|
+
|
|
86
|
+
// Directly in your component:
|
|
87
|
+
const runtime = useLangGraphRuntime({
|
|
88
|
+
cloud, // Optional: for cloud persistence
|
|
89
|
+
stream: async function* (messages, { initialize }) {
|
|
90
|
+
const { externalId } = await initialize();
|
|
91
|
+
if (!externalId) throw new Error("Thread not found");
|
|
92
|
+
|
|
93
|
+
return sendMessage({
|
|
94
|
+
threadId: externalId,
|
|
95
|
+
messages,
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
create: async () => {
|
|
99
|
+
const { thread_id } = await createThread();
|
|
100
|
+
return { externalId: thread_id };
|
|
101
|
+
},
|
|
102
|
+
load: async (externalId) => {
|
|
103
|
+
const state = await getThreadState(externalId);
|
|
104
|
+
return {
|
|
105
|
+
messages: state.values.messages,
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
</Step>
|
|
112
|
+
|
|
113
|
+
<Step>
|
|
114
|
+
|
|
115
|
+
### Update Import Statements
|
|
116
|
+
|
|
117
|
+
Remove unused imports:
|
|
118
|
+
|
|
119
|
+
```diff
|
|
120
|
+
- import {
|
|
121
|
+
- useCloudThreadListRuntime,
|
|
122
|
+
- useThreadListItemRuntime,
|
|
123
|
+
- } from "@assistant-ui/react";
|
|
124
|
+
+ import { AssistantCloud } from "@assistant-ui/react";
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
</Step>
|
|
128
|
+
|
|
129
|
+
<Step>
|
|
130
|
+
|
|
131
|
+
### Simplify Component Structure
|
|
132
|
+
|
|
133
|
+
You no longer need a separate runtime hook function. Everything can be defined directly in your component or provider:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
export function MyRuntimeProvider({ children }) {
|
|
137
|
+
const cloud = useMemo(
|
|
138
|
+
() => new AssistantCloud({
|
|
139
|
+
baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL,
|
|
140
|
+
}),
|
|
141
|
+
[]
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const runtime = useLangGraphRuntime({
|
|
145
|
+
cloud,
|
|
146
|
+
// All configuration inline
|
|
147
|
+
stream: async function* (messages, { initialize }) {
|
|
148
|
+
// Your stream implementation
|
|
149
|
+
},
|
|
150
|
+
create: async () => {
|
|
151
|
+
// Your create implementation
|
|
152
|
+
},
|
|
153
|
+
load: async (externalId) => {
|
|
154
|
+
// Your load implementation
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
160
|
+
{children}
|
|
161
|
+
</AssistantRuntimeProvider>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
</Step>
|
|
167
|
+
|
|
168
|
+
<Step>
|
|
169
|
+
|
|
170
|
+
### Update Method Names
|
|
171
|
+
|
|
172
|
+
Rename methods to match the new API:
|
|
173
|
+
|
|
174
|
+
- `onSwitchToThread` → `load`
|
|
175
|
+
- `onSwitchToNewThread` → handled automatically via `create`
|
|
176
|
+
- Thread ID management is now automatic
|
|
177
|
+
|
|
178
|
+
</Step>
|
|
179
|
+
|
|
180
|
+
</Steps>
|
|
181
|
+
|
|
182
|
+
## API Reference Changes
|
|
183
|
+
|
|
184
|
+
### Old API
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
type OldLangGraphRuntimeOptions = {
|
|
188
|
+
threadId?: string;
|
|
189
|
+
stream: (messages: Message[]) => AsyncGenerator;
|
|
190
|
+
onSwitchToNewThread?: () => Promise<void>;
|
|
191
|
+
onSwitchToThread?: (threadId: string) => Promise<ThreadState>;
|
|
192
|
+
};
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### New API
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
type NewLangGraphRuntimeOptions = {
|
|
199
|
+
cloud?: AssistantCloud;
|
|
200
|
+
stream: (
|
|
201
|
+
messages: Message[],
|
|
202
|
+
context: {
|
|
203
|
+
initialize: () => Promise<{
|
|
204
|
+
remoteId: string;
|
|
205
|
+
externalId: string | undefined;
|
|
206
|
+
}>
|
|
207
|
+
}
|
|
208
|
+
) => AsyncGenerator;
|
|
209
|
+
create?: () => Promise<{ externalId: string }>;
|
|
210
|
+
load?: (externalId: string) => Promise<ThreadState>;
|
|
211
|
+
delete?: (externalId: string) => Promise<void>;
|
|
212
|
+
};
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Benefits of the New API
|
|
216
|
+
|
|
217
|
+
1. **Simpler Setup**: No need for multiple runtime hooks and wrappers
|
|
218
|
+
2. **Cleaner Code**: All configuration in one place
|
|
219
|
+
3. **Better Type Safety**: More explicit types for thread management
|
|
220
|
+
4. **Automatic Thread Handling**: The runtime manages thread lifecycle internally
|
|
221
|
+
5. **Optional Cloud Integration**: Add cloud persistence with a single parameter
|
|
222
|
+
|
|
223
|
+
## Common Migration Issues
|
|
224
|
+
|
|
225
|
+
<Callout type="warning">
|
|
226
|
+
**Breaking Change**: The `threadId` and `onSwitchToNewThread` parameters are no longer supported. Use the new `create` and `load` methods instead.
|
|
227
|
+
</Callout>
|
|
228
|
+
|
|
229
|
+
### Issue: `threadListItemRuntime` is not defined
|
|
230
|
+
|
|
231
|
+
**Solution**: Remove references to `useThreadListItemRuntime()`. Use the `initialize` parameter in the stream function instead.
|
|
232
|
+
|
|
233
|
+
### Issue: Thread switching doesn't work
|
|
234
|
+
|
|
235
|
+
**Solution**: Ensure you've implemented both `create` and `load` functions. The runtime needs both to manage thread lifecycle.
|
|
236
|
+
|
|
237
|
+
### Issue: Cloud persistence not working
|
|
238
|
+
|
|
239
|
+
**Solution**: Pass the `AssistantCloud` instance directly to `useLangGraphRuntime` via the `cloud` parameter.
|
|
240
|
+
|
|
241
|
+
## Example: Complete Migration
|
|
242
|
+
|
|
243
|
+
Here's a complete before and after example for a typical LangGraph integration:
|
|
244
|
+
|
|
245
|
+
### Before
|
|
246
|
+
|
|
247
|
+
```tsx title="runtime-provider.tsx"
|
|
248
|
+
import {
|
|
249
|
+
AssistantCloud,
|
|
250
|
+
AssistantRuntimeProvider,
|
|
251
|
+
useCloudThreadListRuntime,
|
|
252
|
+
useThreadListItemRuntime,
|
|
253
|
+
} from "@assistant-ui/react";
|
|
254
|
+
import { useLangGraphRuntime } from "@assistant-ui/react-langgraph";
|
|
255
|
+
|
|
256
|
+
const useMyRuntime = () => {
|
|
257
|
+
const threadListItemRuntime = useThreadListItemRuntime();
|
|
258
|
+
|
|
259
|
+
return useLangGraphRuntime({
|
|
260
|
+
stream: async function* (messages) {
|
|
261
|
+
const { externalId } = await threadListItemRuntime.initialize();
|
|
262
|
+
// ... implementation
|
|
263
|
+
},
|
|
264
|
+
onSwitchToThread: async (externalId) => {
|
|
265
|
+
// ... implementation
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
export function Provider({ children }) {
|
|
271
|
+
const cloud = new AssistantCloud({ /* config */ });
|
|
272
|
+
|
|
273
|
+
const runtime = useCloudThreadListRuntime({
|
|
274
|
+
cloud,
|
|
275
|
+
runtimeHook: useMyRuntime,
|
|
276
|
+
create: async () => { /* ... */ },
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
281
|
+
{children}
|
|
282
|
+
</AssistantRuntimeProvider>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### After
|
|
288
|
+
|
|
289
|
+
```tsx title="runtime-provider.tsx"
|
|
290
|
+
import {
|
|
291
|
+
AssistantCloud,
|
|
292
|
+
AssistantRuntimeProvider,
|
|
293
|
+
} from "@assistant-ui/react";
|
|
294
|
+
import { useLangGraphRuntime } from "@assistant-ui/react-langgraph";
|
|
295
|
+
|
|
296
|
+
export function Provider({ children }) {
|
|
297
|
+
const cloud = new AssistantCloud({ /* config */ });
|
|
298
|
+
|
|
299
|
+
const runtime = useLangGraphRuntime({
|
|
300
|
+
cloud,
|
|
301
|
+
stream: async function* (messages, { initialize }) {
|
|
302
|
+
const { externalId } = await initialize();
|
|
303
|
+
// ... implementation
|
|
304
|
+
},
|
|
305
|
+
create: async () => { /* ... */ },
|
|
306
|
+
load: async (externalId) => {
|
|
307
|
+
// ... implementation (formerly onSwitchToThread)
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return (
|
|
312
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
313
|
+
{children}
|
|
314
|
+
</AssistantRuntimeProvider>
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Need Help?
|
|
320
|
+
|
|
321
|
+
If you encounter issues during migration:
|
|
322
|
+
1. Check the updated [LangGraph documentation](/docs/runtimes/langgraph)
|
|
323
|
+
2. Review the [example implementation](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-langgraph)
|
|
324
|
+
3. Report issues on [GitHub](https://github.com/assistant-ui/assistant-ui/issues)
|
|
@@ -41,7 +41,7 @@ npm install @assistant-ui/react @assistant-ui/react-ai-sdk ai @ai-sdk/openai
|
|
|
41
41
|
```tsx
|
|
42
42
|
import { openai } from "@ai-sdk/openai";
|
|
43
43
|
import { streamText, UIMessage, convertToModelMessages, tool } from "ai";
|
|
44
|
-
import { frontendTools } from "@assistant-ui/
|
|
44
|
+
import { frontendTools } from "@assistant-ui/react-ai-sdk";
|
|
45
45
|
import { z } from "zod";
|
|
46
46
|
|
|
47
47
|
// Allow streaming responses up to 30 seconds
|
|
@@ -173,7 +173,7 @@ const runtime = useChatRuntime({
|
|
|
173
173
|
When using `AssistantChatTransport`, frontend tools are forwarded to your backend. Use the `frontendTools` helper to properly integrate them:
|
|
174
174
|
|
|
175
175
|
```tsx
|
|
176
|
-
import { frontendTools } from "@assistant-ui/
|
|
176
|
+
import { frontendTools } from "@assistant-ui/react-ai-sdk";
|
|
177
177
|
|
|
178
178
|
export async function POST(req: Request) {
|
|
179
179
|
const { messages, system, tools } = await req.json();
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Custom Thread List
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
import { Callout } from "fumadocs-ui/components/callout";
|
|
6
|
+
import { Steps, Step } from "fumadocs-ui/components/steps";
|
|
7
|
+
import { ParametersTable } from "@/components/docs";
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`useRemoteThreadListRuntime` lets you plug a custom thread database into assistant-ui. It keeps the UI and local runtime logic in sync while you provide persistence, archiving, and metadata for every conversation. The hook is exported as `unstable_useRemoteThreadListRuntime`; we refer to it here as **Custom Thread List**.
|
|
12
|
+
|
|
13
|
+
## When to Use
|
|
14
|
+
|
|
15
|
+
Use a Custom Thread List when you need to:
|
|
16
|
+
|
|
17
|
+
- Persist conversations in your own database or multitenant backend
|
|
18
|
+
- Share threads across devices, teams, or long-lived sessions
|
|
19
|
+
- Control thread metadata (titles, archived state, external identifiers)
|
|
20
|
+
- Layer additional adapters (history, attachments) around each thread runtime
|
|
21
|
+
|
|
22
|
+
## How It Works
|
|
23
|
+
|
|
24
|
+
Custom Thread List merges two pieces of state:
|
|
25
|
+
|
|
26
|
+
1. **Per-thread runtime** – powered by any runtime hook (for example `useLocalRuntime` or `useAssistantTransportRuntime`).
|
|
27
|
+
2. **Thread list adapter** – your adapter that reads and writes thread metadata in a remote store.
|
|
28
|
+
|
|
29
|
+
When the hook mounts it calls `list()` on your adapter, hydrates existing threads, and uses your runtime hook to spawn a runtime whenever a thread is opened. Creating a new conversation calls `initialize(threadId)` so you can create a record server-side and return the canonical `remoteId`.
|
|
30
|
+
|
|
31
|
+
<Callout type="info">
|
|
32
|
+
The built-in Assistant Cloud runtime is implemented with the same API. Inspect
|
|
33
|
+
`useCloudThreadListAdapter` for a production-ready reference adapter.
|
|
34
|
+
</Callout>
|
|
35
|
+
|
|
36
|
+
## Build a Custom Thread List
|
|
37
|
+
|
|
38
|
+
<Steps>
|
|
39
|
+
<Step>
|
|
40
|
+
### Provide a runtime per thread
|
|
41
|
+
|
|
42
|
+
Use any runtime hook that returns an `AssistantRuntime`. In most custom setups this is `useLocalRuntime(modelAdapter)` or `useAssistantTransportRuntime(...)`.
|
|
43
|
+
|
|
44
|
+
</Step>
|
|
45
|
+
<Step>
|
|
46
|
+
### Implement the adapter contract
|
|
47
|
+
|
|
48
|
+
Your adapter decides how threads are stored. Implement the methods in the table below to connect to your database or API.
|
|
49
|
+
|
|
50
|
+
</Step>
|
|
51
|
+
<Step>
|
|
52
|
+
### Compose the provider
|
|
53
|
+
|
|
54
|
+
Wrap `AssistantRuntimeProvider` with the runtime returned from the Custom Thread List hook.
|
|
55
|
+
|
|
56
|
+
```tsx twoslash title="app/CustomThreadListProvider.tsx"
|
|
57
|
+
// @filename: app/model-adapter.ts
|
|
58
|
+
export const myModelAdapter = {} as any;
|
|
59
|
+
|
|
60
|
+
// @filename: app/CustomThreadListProvider.tsx
|
|
61
|
+
// ---cut---
|
|
62
|
+
"use client";
|
|
63
|
+
|
|
64
|
+
import type { ReactNode } from "react";
|
|
65
|
+
import {
|
|
66
|
+
AssistantRuntimeProvider,
|
|
67
|
+
useLocalRuntime,
|
|
68
|
+
unstable_useRemoteThreadListRuntime as useRemoteThreadListRuntime,
|
|
69
|
+
type unstable_RemoteThreadListAdapter as RemoteThreadListAdapter,
|
|
70
|
+
} from "@assistant-ui/react";
|
|
71
|
+
import { createAssistantStream } from "assistant-stream";
|
|
72
|
+
import { myModelAdapter } from "./model-adapter"; // your chat model adapter
|
|
73
|
+
|
|
74
|
+
const threadListAdapter: RemoteThreadListAdapter = {
|
|
75
|
+
async list() {
|
|
76
|
+
const response = await fetch("/api/threads");
|
|
77
|
+
const threads = await response.json();
|
|
78
|
+
return {
|
|
79
|
+
threads: threads.map((thread: any) => ({
|
|
80
|
+
remoteId: thread.id,
|
|
81
|
+
externalId: thread.external_id ?? undefined,
|
|
82
|
+
status: thread.is_archived ? "archived" : "regular",
|
|
83
|
+
title: thread.title ?? undefined,
|
|
84
|
+
})),
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
async initialize(localId) {
|
|
88
|
+
const response = await fetch("/api/threads", {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: { "Content-Type": "application/json" },
|
|
91
|
+
body: JSON.stringify({ localId }),
|
|
92
|
+
});
|
|
93
|
+
const result = await response.json();
|
|
94
|
+
return { remoteId: result.id, externalId: result.external_id };
|
|
95
|
+
},
|
|
96
|
+
async rename(remoteId, title) {
|
|
97
|
+
await fetch(`/api/threads/${remoteId}`, {
|
|
98
|
+
method: "PATCH",
|
|
99
|
+
headers: { "Content-Type": "application/json" },
|
|
100
|
+
body: JSON.stringify({ title }),
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
async archive(remoteId) {
|
|
104
|
+
await fetch(`/api/threads/${remoteId}/archive`, { method: "POST" });
|
|
105
|
+
},
|
|
106
|
+
async unarchive(remoteId) {
|
|
107
|
+
await fetch(`/api/threads/${remoteId}/unarchive`, { method: "POST" });
|
|
108
|
+
},
|
|
109
|
+
async delete(remoteId) {
|
|
110
|
+
await fetch(`/api/threads/${remoteId}`, { method: "DELETE" });
|
|
111
|
+
},
|
|
112
|
+
async generateTitle(remoteId, messages) {
|
|
113
|
+
return createAssistantStream(async (controller) => {
|
|
114
|
+
const response = await fetch(`/api/threads/${remoteId}/title`, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: { "Content-Type": "application/json" },
|
|
117
|
+
body: JSON.stringify({ messages }),
|
|
118
|
+
});
|
|
119
|
+
const { title } = await response.json();
|
|
120
|
+
controller.appendText(title);
|
|
121
|
+
});
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export function CustomThreadListProvider({
|
|
126
|
+
children,
|
|
127
|
+
}: Readonly<{ children: ReactNode }>) {
|
|
128
|
+
const runtime = useRemoteThreadListRuntime({
|
|
129
|
+
runtimeHook: () => useLocalRuntime(myModelAdapter),
|
|
130
|
+
adapter: threadListAdapter,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
135
|
+
{children}
|
|
136
|
+
</AssistantRuntimeProvider>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
</Step>
|
|
142
|
+
</Steps>
|
|
143
|
+
|
|
144
|
+
## Adapter Responsibilities
|
|
145
|
+
|
|
146
|
+
<ParametersTable
|
|
147
|
+
type="RemoteThreadListAdapter"
|
|
148
|
+
parameters={[
|
|
149
|
+
{
|
|
150
|
+
name: "list",
|
|
151
|
+
type: "() => Promise<{ threads: RemoteThreadMetadata[] }>",
|
|
152
|
+
description:
|
|
153
|
+
"Return the current threads. Each thread must include status, remoteId, and any metadata you want to show immediately.",
|
|
154
|
+
required: true,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "initialize",
|
|
158
|
+
type: "(localId: string) => Promise<{ remoteId: string; externalId?: string }>",
|
|
159
|
+
description:
|
|
160
|
+
"Create a new remote record when the user starts a conversation. Return the canonical ids so later operations target the right thread.",
|
|
161
|
+
required: true,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "rename",
|
|
165
|
+
type: "(remoteId: string, title: string) => Promise<void>",
|
|
166
|
+
description: "Persist title changes triggered from the UI.",
|
|
167
|
+
required: true,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "archive",
|
|
171
|
+
type: "(remoteId: string) => Promise<void>",
|
|
172
|
+
description: "Mark the thread as archived in your system.",
|
|
173
|
+
required: true,
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "unarchive",
|
|
177
|
+
type: "(remoteId: string) => Promise<void>",
|
|
178
|
+
description: "Restore an archived thread to the active list.",
|
|
179
|
+
required: true,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: "delete",
|
|
183
|
+
type: "(remoteId: string) => Promise<void>",
|
|
184
|
+
description: "Permanently remove the thread and stop rendering it.",
|
|
185
|
+
required: true,
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: "generateTitle",
|
|
189
|
+
type: "(remoteId: string, unstable_messages: readonly ThreadMessage[]) => Promise<AssistantStream>",
|
|
190
|
+
description:
|
|
191
|
+
"Return a streaming title generator. You can reuse your model endpoint or queue a background job.",
|
|
192
|
+
required: true,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "unstable_Provider",
|
|
196
|
+
type: "ComponentType<PropsWithChildren>",
|
|
197
|
+
description:
|
|
198
|
+
"Optional wrapper rendered around all thread runtimes. Use it to inject adapters such as history or attachments (see the Cloud adapter).",
|
|
199
|
+
},
|
|
200
|
+
]}
|
|
201
|
+
/>
|
|
202
|
+
|
|
203
|
+
## Thread Lifecycle Cheatsheet
|
|
204
|
+
|
|
205
|
+
- `list()` hydrates threads on mount and during refreshes.
|
|
206
|
+
- Creating a new conversation calls `initialize()` once the user sends the first message.
|
|
207
|
+
- `archive`, `unarchive`, and `delete` are called optimistically; throw to revert the UI.
|
|
208
|
+
- `generateTitle()` powers the automatic title button and expects an `AssistantStream`.
|
|
209
|
+
- Provide a `runtimeHook` that always returns a fresh runtime instance per active thread.
|
|
210
|
+
|
|
211
|
+
## Optional Adapters
|
|
212
|
+
|
|
213
|
+
If you need history or attachment support, expose them via `unstable_Provider`. The cloud implementation wraps each thread runtime with `RuntimeAdapterProvider` to inject:
|
|
214
|
+
|
|
215
|
+
- `history` – e.g. `useAssistantCloudThreadHistoryAdapter`
|
|
216
|
+
- `attachments` – e.g. `CloudFileAttachmentAdapter`
|
|
217
|
+
|
|
218
|
+
Reuse that pattern to register any capability your runtime requires.
|
|
@@ -164,45 +164,52 @@ graph TD
|
|
|
164
164
|
```tsx title="app/MyRuntimeProvider.tsx"
|
|
165
165
|
"use client";
|
|
166
166
|
|
|
167
|
-
import {
|
|
167
|
+
import { ThreadMessageLike } from "@assistant-ui/react";
|
|
168
|
+
import { AppendMessage } from "@assistant-ui/react";
|
|
168
169
|
import {
|
|
169
|
-
useExternalStoreRuntime,
|
|
170
|
-
ThreadMessageLike,
|
|
171
|
-
AppendMessage,
|
|
172
170
|
AssistantRuntimeProvider,
|
|
171
|
+
useExternalStoreRuntime,
|
|
173
172
|
} from "@assistant-ui/react";
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
173
|
+
import { useState } from "react";
|
|
174
|
+
|
|
175
|
+
const convertMessage = (message: ThreadMessageLike) => {
|
|
176
|
+
return message;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export function MyRuntimeProvider({
|
|
180
|
+
children,
|
|
181
|
+
}: Readonly<{
|
|
182
|
+
children: React.ReactNode;
|
|
183
|
+
}>) {
|
|
184
|
+
const [messages, setMessages] = useState<readonly ThreadMessageLike[]>([]);
|
|
185
|
+
|
|
179
186
|
const onNew = async (message: AppendMessage) => {
|
|
180
|
-
|
|
187
|
+
if (message.content.length !== 1 || message.content[0]?.type !== "text")
|
|
188
|
+
throw new Error("Only text content is supported");
|
|
189
|
+
|
|
181
190
|
const userMessage: ThreadMessageLike = {
|
|
182
191
|
role: "user",
|
|
183
|
-
content: message.content,
|
|
192
|
+
content: [{ type: "text", text: message.content[0].text }],
|
|
184
193
|
};
|
|
185
|
-
setMessages(
|
|
186
|
-
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
194
|
+
setMessages((currentMessages) => [...currentMessages, userMessage]);
|
|
195
|
+
|
|
196
|
+
// normally you would perform an API call here to get the assistant response
|
|
197
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
198
|
+
|
|
191
199
|
const assistantMessage: ThreadMessageLike = {
|
|
192
200
|
role: "assistant",
|
|
193
|
-
content:
|
|
201
|
+
content: [{ type: "text", text: "Hello, world!" }],
|
|
194
202
|
};
|
|
195
|
-
setMessages(
|
|
196
|
-
setIsRunning(false);
|
|
203
|
+
setMessages((currentMessages) => [...currentMessages, assistantMessage]);
|
|
197
204
|
};
|
|
198
|
-
|
|
199
|
-
const runtime = useExternalStoreRuntime({
|
|
205
|
+
|
|
206
|
+
const runtime = useExternalStoreRuntime<ThreadMessageLike>({
|
|
200
207
|
messages,
|
|
201
208
|
setMessages,
|
|
202
|
-
isRunning,
|
|
203
209
|
onNew,
|
|
210
|
+
convertMessage,
|
|
204
211
|
});
|
|
205
|
-
|
|
212
|
+
|
|
206
213
|
return (
|
|
207
214
|
<AssistantRuntimeProvider runtime={runtime}>
|
|
208
215
|
{children}
|