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