@assistant-ui/mcp-docs-server 0.1.39 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.docs/organized/code-examples/waterfall.md +17 -18
- package/.docs/organized/code-examples/with-a2a.md +20 -15
- package/.docs/organized/code-examples/with-ag-ui.md +19 -17
- package/.docs/organized/code-examples/with-ai-sdk-v7.md +17 -16
- package/.docs/organized/code-examples/with-artifacts.md +471 -145
- package/.docs/organized/code-examples/with-assistant-transport.md +23 -31
- package/.docs/organized/code-examples/with-browser-extension.md +21 -14
- package/.docs/organized/code-examples/with-chain-of-thought.md +22 -20
- package/.docs/organized/code-examples/with-cloud-standalone.md +13 -14
- package/.docs/organized/code-examples/with-cloud.md +22 -17
- package/.docs/organized/code-examples/with-custom-thread-list.md +17 -16
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +17 -17
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +21 -19
- package/.docs/organized/code-examples/with-eve.md +70 -16
- package/.docs/organized/code-examples/with-expo.md +32 -51
- package/.docs/organized/code-examples/with-external-store.md +20 -15
- package/.docs/organized/code-examples/with-ffmpeg.md +24 -18
- package/.docs/organized/code-examples/with-generative-ui.md +20 -21
- package/.docs/organized/code-examples/with-google-adk.md +21 -16
- package/.docs/organized/code-examples/with-heat-graph.md +10 -11
- package/.docs/organized/code-examples/with-image-generation.md +13 -14
- package/.docs/organized/code-examples/with-interactables.md +17 -16
- package/.docs/organized/code-examples/with-langchain.md +13 -14
- package/.docs/organized/code-examples/with-langgraph.md +23 -17
- package/.docs/organized/code-examples/with-livekit.md +17 -17
- package/.docs/organized/code-examples/with-mcp.md +36 -32
- package/.docs/organized/code-examples/with-nuxt.md +2492 -0
- package/.docs/organized/code-examples/with-opencode.md +27 -21
- package/.docs/organized/code-examples/with-pi.md +69 -67
- package/.docs/organized/code-examples/with-react-hook-form.md +18 -17
- package/.docs/organized/code-examples/with-react-ink-web.md +10 -11
- package/.docs/organized/code-examples/with-react-ink.md +7 -7
- package/.docs/organized/code-examples/with-react-router.md +23 -17
- package/.docs/organized/code-examples/with-resumable-stream.md +15 -16
- package/.docs/organized/code-examples/with-store.md +31 -20
- package/.docs/organized/code-examples/with-tanstack.md +22 -16
- package/.docs/organized/code-examples/with-tap-runtime.md +19 -19
- package/.docs/organized/code-examples/with-virtualized-thread.md +12 -13
- package/.docs/organized/code-examples/with-vue.md +408 -0
- package/.docs/raw/docs/(docs)/cli.mdx +3 -1
- package/.docs/raw/docs/(docs)/devtools.mdx +7 -2
- package/.docs/raw/docs/(docs)/index.mdx +9 -76
- package/.docs/raw/docs/(docs)/installation.mdx +4 -18
- package/.docs/raw/docs/(reference)/api-reference/context-providers/assistant-runtime-provider.mdx +24 -4
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/a2ui.mdx +40 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/index.mdx +3 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +19 -420
- package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +4 -1
- package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +8 -0
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +21 -0
- package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -9
- package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +1 -18
- package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +24 -1
- package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +1 -1
- package/.docs/raw/docs/cloud/ai-sdk.mdx +2 -2
- package/.docs/raw/docs/cloud/langgraph.mdx +1 -1
- package/.docs/raw/docs/copilots/model-context.mdx +5 -4
- package/.docs/raw/docs/copilots/motivation.mdx +5 -5
- package/.docs/raw/docs/guides/attachments.mdx +3 -3
- package/.docs/raw/docs/guides/branching.mdx +2 -2
- package/.docs/raw/docs/guides/chain-of-thought.mdx +1 -1
- package/.docs/raw/docs/guides/context-api.mdx +89 -111
- package/.docs/raw/docs/guides/editing.mdx +5 -5
- package/.docs/raw/docs/guides/electron.mdx +369 -0
- package/.docs/raw/docs/guides/index.mdx +10 -0
- package/.docs/raw/docs/guides/quoting.mdx +3 -3
- package/.docs/raw/docs/guides/resumable-stream-deployment.mdx +2 -2
- package/.docs/raw/docs/guides/resumable-streams.mdx +74 -3
- package/.docs/raw/docs/guides/suggestions.mdx +9 -9
- package/.docs/raw/docs/ink/hooks.mdx +12 -7
- package/.docs/raw/docs/ink/primitives.mdx +18 -10
- package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +16 -0
- package/.docs/raw/docs/integrations/auth/better-auth.mdx +3 -3
- package/.docs/raw/docs/integrations/auth/clerk.mdx +3 -3
- package/.docs/raw/docs/integrations/auth/next-auth.mdx +4 -4
- package/.docs/raw/docs/integrations/frameworks/cloudflare-agents/overview.mdx +1 -1
- package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +2 -2
- package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +1 -1
- package/.docs/raw/docs/integrations/gateways/index.mdx +2 -2
- package/.docs/raw/docs/integrations/observability/helicone.mdx +2 -2
- package/.docs/raw/docs/integrations/observability/langfuse.mdx +1 -1
- package/.docs/raw/docs/integrations/observability/langsmith.mdx +3 -3
- package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +36 -9
- package/.docs/raw/docs/migrations/toolkit-tools.mdx +15 -13
- package/.docs/raw/docs/migrations/v0-15.mdx +236 -0
- package/.docs/raw/docs/primitives/attachment.mdx +2 -2
- package/.docs/raw/docs/primitives/composer.mdx +3 -3
- package/.docs/raw/docs/primitives/message.mdx +33 -1
- package/.docs/raw/docs/primitives/suggestion.mdx +1 -1
- package/.docs/raw/docs/primitives/thread-list.mdx +2 -2
- package/.docs/raw/docs/react-native/hooks.mdx +17 -7
- package/.docs/raw/docs/react-native/index.mdx +3 -3
- package/.docs/raw/docs/react-native/primitives.mdx +41 -7
- package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +3 -3
- package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +5 -5
- package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +5 -5
- package/.docs/raw/docs/runtimes/ai-sdk/v6-legacy.mdx +13 -14
- package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +11 -12
- package/.docs/raw/docs/runtimes/concepts/threads.mdx +50 -11
- package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +20 -2
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +31 -2
- package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +39 -12
- package/.docs/raw/docs/runtimes/eve/overview.mdx +51 -0
- package/.docs/raw/docs/runtimes/eve/quickstart.mdx +51 -2
- package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +9 -12
- package/.docs/raw/docs/runtimes/langchain.mdx +2 -2
- package/.docs/raw/docs/runtimes/langgraph/overview.mdx +1 -1
- package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +1 -1
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +7 -7
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +4 -4
- package/.docs/raw/docs/runtimes/opencode/hooks.mdx +4 -1
- package/.docs/raw/docs/runtimes/opencode/overview.mdx +10 -0
- package/.docs/raw/docs/tools/a2ui.mdx +107 -0
- package/.docs/raw/docs/tools/backend.mdx +2 -2
- package/.docs/raw/docs/tools/defining-tools.mdx +9 -7
- package/.docs/raw/docs/tools/dynamic-tools.mdx +6 -4
- package/.docs/raw/docs/tools/interactables-legacy.mdx +28 -15
- package/.docs/raw/docs/tools/interactables.mdx +27 -16
- package/.docs/raw/docs/tools/mcp-apps.mdx +96 -8
- package/.docs/raw/docs/tools/mcp.mdx +9 -7
- package/.docs/raw/docs/tools/tool-ui.mdx +26 -22
- package/.docs/raw/docs/tools/user-managed-mcp.mdx +64 -14
- package/.docs/raw/docs/ui/file.mdx +6 -1
- package/.docs/raw/docs/ui/follow-up-suggestions.mdx +2 -0
- package/.docs/raw/docs/ui/mcp-config.mdx +8 -3
- package/.docs/raw/docs/ui/model-selector.mdx +9 -9
- package/.docs/raw/docs/ui/part-grouping.mdx +1 -5
- package/.docs/raw/docs/ui/reasoning.mdx +1 -1
- package/.docs/raw/docs/ui/thread.mdx +24 -5
- package/.docs/raw/docs/utilities/react-o11y.mdx +7 -9
- package/dist/constants.js +2 -2
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/prepare-docs/code-examples.js.map +1 -1
- package/dist/prepare-docs/prepare.d.ts +1 -1
- package/dist/prepare-docs/prepare.js.map +1 -1
- package/dist/stdio.d.ts +1 -1
- package/dist/tools/docs.d.ts +6 -10
- package/dist/tools/docs.d.ts.map +1 -1
- package/dist/tools/docs.js +6 -4
- package/dist/tools/docs.js.map +1 -1
- package/dist/tools/examples.d.ts +4 -8
- package/dist/tools/examples.d.ts.map +1 -1
- package/dist/tools/examples.js +4 -3
- package/dist/tools/examples.js.map +1 -1
- package/dist/tools/resources.d.ts +1 -1
- package/dist/tools/resources.d.ts.map +1 -1
- package/dist/tools/resources.js +3 -2
- package/dist/tools/resources.js.map +1 -1
- package/dist/tools/search.d.ts +4 -10
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +2 -2
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/tests/mcp-test-client.d.ts +15 -0
- package/dist/tools/tests/mcp-test-client.d.ts.map +1 -0
- package/dist/tools/tests/mcp-test-client.js +68 -0
- package/dist/tools/tests/mcp-test-client.js.map +1 -0
- package/dist/tools/tests/test-setup.d.ts.map +1 -1
- package/dist/tools/tests/test-setup.js +2 -1
- package/dist/tools/tests/test-setup.js.map +1 -1
- package/dist/tools/xulux-templates.d.ts +9 -23
- package/dist/tools/xulux-templates.d.ts.map +1 -1
- package/dist/tools/xulux-templates.js +8 -6
- package/dist/tools/xulux-templates.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/mdx.js +2 -1
- package/dist/utils/mdx.js.map +1 -1
- package/dist/utils/security.js.map +1 -1
- package/dist/xulux/catalog-client.js +1 -1
- package/dist/xulux/catalog-client.js.map +1 -1
- package/package.json +6 -5
- package/src/index.ts +2 -2
- package/src/tools/docs.ts +2 -2
- package/src/tools/examples.ts +2 -2
- package/src/tools/resources.ts +1 -4
- package/src/tools/search.ts +2 -2
- package/src/tools/tests/completions.test.ts +40 -26
- package/src/tools/tests/docs.test.ts +2 -2
- package/src/tools/tests/integration.test.ts +3 -4
- package/src/tools/tests/mcp-protocol.test.ts +160 -175
- package/src/tools/tests/mcp-test-client.ts +111 -0
- package/src/tools/tests/resources.test.ts +97 -66
- package/src/tools/xulux-templates.ts +4 -4
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Electron
|
|
3
|
+
description: Run assistant-ui in an Electron renderer with a hosted backend or a secure, streaming preload and IPC bridge.
|
|
4
|
+
platforms: ["react"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
assistant-ui works in Electron through `@assistant-ui/react`. The renderer is a React DOM environment, so there is no separate `@assistant-ui/electron` package to install. The Electron-specific decision is where model requests run and how the renderer reaches them.
|
|
8
|
+
|
|
9
|
+
<Callout type="warn">
|
|
10
|
+
Never put a provider API key in renderer code, a `VITE_*` variable, or a preload script. Bundled values are readable by anyone with the app. Keep remote credentials on your backend, or keep local credentials in the main process and provision them with an OS-backed secret store.
|
|
11
|
+
</Callout>
|
|
12
|
+
|
|
13
|
+
## Choose a connection pattern
|
|
14
|
+
|
|
15
|
+
| Pattern | Use it when | Runtime |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| Hosted backend | You already have an AI SDK endpoint, need server auth or persistence, or ship the app to other people | `useChatRuntime` with an absolute HTTPS URL |
|
|
18
|
+
| Local main process | The desktop app owns the provider or agent process and must work without your web backend | `useLocalRuntime` with a narrow preload/IPC bridge |
|
|
19
|
+
|
|
20
|
+
```mermaid
|
|
21
|
+
flowchart LR
|
|
22
|
+
Renderer["Electron renderer<br/>assistant-ui React"] -->|"absolute HTTPS"| Backend["Hosted chat backend"]
|
|
23
|
+
Renderer -->|"window.assistantAI"| Preload["context-isolated preload"]
|
|
24
|
+
Preload -->|"transferred MessagePort"| Main["Electron main process"]
|
|
25
|
+
Backend --> Provider["Model or agent"]
|
|
26
|
+
Main --> Provider
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Do not send an SDK client, assistant-ui runtime, callback, `AbortSignal`, or `File` through IPC. Electron IPC uses structured clone semantics; define a small data-only protocol instead.
|
|
30
|
+
|
|
31
|
+
## Pattern 1: hosted backend
|
|
32
|
+
|
|
33
|
+
This is the smallest integration. Keep your existing AI SDK chat route and point `AssistantChatTransport` at its public URL.
|
|
34
|
+
|
|
35
|
+
```tsx title="renderer/assistant-runtime.tsx"
|
|
36
|
+
import type { ReactNode } from "react";
|
|
37
|
+
import { AssistantRuntimeProvider } from "@assistant-ui/react";
|
|
38
|
+
import {
|
|
39
|
+
AssistantChatTransport,
|
|
40
|
+
useChatRuntime,
|
|
41
|
+
} from "@assistant-ui/react-ai-sdk";
|
|
42
|
+
|
|
43
|
+
const transport = new AssistantChatTransport({
|
|
44
|
+
api: "https://api.example.com/chat",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export function ElectronRuntimeProvider({ children }: { children: ReactNode }) {
|
|
48
|
+
const runtime = useChatRuntime({ transport });
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
52
|
+
{children}
|
|
53
|
+
</AssistantRuntimeProvider>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The URL must be absolute in a packaged app. A relative value such as `/api/chat` targets the renderer's `file://` or custom-protocol origin, not your deployed backend. Configure the backend's CORS policy for the packaged origin, allow the endpoint in `connect-src`, and authenticate requests as you would from any desktop client.
|
|
59
|
+
|
|
60
|
+
The endpoint must return the AI SDK UI message stream consumed by `AssistantChatTransport`. See the [AI SDK runtime guide](/docs/runtimes/ai-sdk/v7) for the backend route and tool-calling setup.
|
|
61
|
+
|
|
62
|
+
## Pattern 2: local main process
|
|
63
|
+
|
|
64
|
+
Use this pattern when the Electron main process calls the model or runs a local agent. Keep `contextIsolation: true`, `sandbox: true`, and `nodeIntegration: false` on the `BrowserWindow`. Register `registerAssistantIpc(mainWindow)` after creating the trusted window.
|
|
65
|
+
|
|
66
|
+
The following example is intentionally text-only. It streams one request over a dedicated `MessagePort`; closing that port propagates assistant-ui's Stop action to an `AbortController` in the main process.
|
|
67
|
+
|
|
68
|
+
### 1. Define a data-only protocol
|
|
69
|
+
|
|
70
|
+
Place the shared types somewhere all three Electron bundles can import.
|
|
71
|
+
|
|
72
|
+
```ts title="shared/assistant-ipc.ts"
|
|
73
|
+
export const ASSISTANT_STREAM_CHANNEL = "assistant:stream";
|
|
74
|
+
|
|
75
|
+
export type ChatMessage =
|
|
76
|
+
| { role: "user"; content: string }
|
|
77
|
+
| { role: "assistant"; content: string };
|
|
78
|
+
|
|
79
|
+
export type ChatRequest = {
|
|
80
|
+
system?: string;
|
|
81
|
+
messages: ChatMessage[];
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type ChatEvent =
|
|
85
|
+
| { type: "delta"; text: string }
|
|
86
|
+
| { type: "done" }
|
|
87
|
+
| { type: "error"; message: string };
|
|
88
|
+
|
|
89
|
+
export type AssistantAI = {
|
|
90
|
+
streamChat(
|
|
91
|
+
request: ChatRequest,
|
|
92
|
+
onEvent: (event: ChatEvent) => void,
|
|
93
|
+
): () => void;
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 2. Expose one preload capability
|
|
98
|
+
|
|
99
|
+
Expose the smallest API the renderer needs, not `ipcRenderer` itself. The callback receives only validated event data, never Electron's privileged IPC event object.
|
|
100
|
+
|
|
101
|
+
```ts title="preload/assistant.ts"
|
|
102
|
+
import { contextBridge, ipcRenderer } from "electron";
|
|
103
|
+
import {
|
|
104
|
+
ASSISTANT_STREAM_CHANNEL,
|
|
105
|
+
type AssistantAI,
|
|
106
|
+
type ChatEvent,
|
|
107
|
+
} from "./shared";
|
|
108
|
+
|
|
109
|
+
const assistantAI: AssistantAI = {
|
|
110
|
+
streamChat(request, onEvent) {
|
|
111
|
+
const { port1, port2 } = new MessageChannel();
|
|
112
|
+
const onMessage = (event: MessageEvent<ChatEvent>) => onEvent(event.data);
|
|
113
|
+
|
|
114
|
+
port1.addEventListener("message", onMessage);
|
|
115
|
+
port1.start();
|
|
116
|
+
ipcRenderer.postMessage(ASSISTANT_STREAM_CHANNEL, request, [port2]);
|
|
117
|
+
|
|
118
|
+
let stopped = false;
|
|
119
|
+
return () => {
|
|
120
|
+
if (stopped) return;
|
|
121
|
+
stopped = true;
|
|
122
|
+
port1.removeEventListener("message", onMessage);
|
|
123
|
+
port1.close();
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
contextBridge.exposeInMainWorld("assistantAI", assistantAI);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Declare the context-bridge API for renderer TypeScript:
|
|
132
|
+
|
|
133
|
+
```ts title="renderer/electron.d.ts"
|
|
134
|
+
import type { AssistantAI } from "./shared";
|
|
135
|
+
|
|
136
|
+
declare global {
|
|
137
|
+
interface Window {
|
|
138
|
+
assistantAI: AssistantAI;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export {};
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 3. Stream from the main process
|
|
146
|
+
|
|
147
|
+
Install the provider packages in the Electron main-process bundle:
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
pnpm add ai @ai-sdk/openai
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The handler checks both the sending `WebContents` and its main frame, validates the untrusted payload and bounds its size before calling the model. Set `OPENAI_API_KEY` only in the main-process environment.
|
|
154
|
+
|
|
155
|
+
```ts title="main/assistant-ipc.ts"
|
|
156
|
+
import { openai } from "@ai-sdk/openai";
|
|
157
|
+
import { streamText } from "ai";
|
|
158
|
+
import { ipcMain, type BrowserWindow, type IpcMainEvent } from "electron";
|
|
159
|
+
import {
|
|
160
|
+
ASSISTANT_STREAM_CHANNEL,
|
|
161
|
+
type ChatEvent,
|
|
162
|
+
type ChatRequest,
|
|
163
|
+
} from "./shared";
|
|
164
|
+
|
|
165
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
166
|
+
typeof value === "object" && value !== null;
|
|
167
|
+
|
|
168
|
+
const isChatRequest = (value: unknown): value is ChatRequest => {
|
|
169
|
+
if (!isRecord(value) || !Array.isArray(value.messages)) return false;
|
|
170
|
+
if (value.system !== undefined && typeof value.system !== "string") {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
if (value.messages.length > 200) return false;
|
|
174
|
+
|
|
175
|
+
let totalLength = typeof value.system === "string" ? value.system.length : 0;
|
|
176
|
+
for (const message of value.messages) {
|
|
177
|
+
if (!isRecord(message)) return false;
|
|
178
|
+
if (message.role !== "user" && message.role !== "assistant") return false;
|
|
179
|
+
if (typeof message.content !== "string") return false;
|
|
180
|
+
totalLength += message.content.length;
|
|
181
|
+
if (totalLength > 1_000_000) return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return true;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export function registerAssistantIpc(mainWindow: BrowserWindow) {
|
|
188
|
+
const handleStream = (event: IpcMainEvent, request: unknown) => {
|
|
189
|
+
const [port] = event.ports;
|
|
190
|
+
if (!port) return;
|
|
191
|
+
|
|
192
|
+
if (
|
|
193
|
+
event.sender !== mainWindow.webContents ||
|
|
194
|
+
event.senderFrame !== mainWindow.webContents.mainFrame
|
|
195
|
+
) {
|
|
196
|
+
port.close();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
port.start();
|
|
201
|
+
if (!isChatRequest(request)) {
|
|
202
|
+
port.postMessage({
|
|
203
|
+
type: "error",
|
|
204
|
+
message: "Invalid chat request.",
|
|
205
|
+
} satisfies ChatEvent);
|
|
206
|
+
port.close();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const abortController = new AbortController();
|
|
211
|
+
port.once("close", () => abortController.abort());
|
|
212
|
+
const send = (message: ChatEvent) => port.postMessage(message);
|
|
213
|
+
|
|
214
|
+
void (async () => {
|
|
215
|
+
try {
|
|
216
|
+
const result = streamText({
|
|
217
|
+
model: openai("gpt-5.6-luna"),
|
|
218
|
+
messages: request.messages,
|
|
219
|
+
...(request.system ? { system: request.system } : {}),
|
|
220
|
+
abortSignal: abortController.signal,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
for await (const text of result.textStream) {
|
|
224
|
+
send({ type: "delta", text });
|
|
225
|
+
}
|
|
226
|
+
send({ type: "done" });
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (abortController.signal.aborted) return;
|
|
229
|
+
console.error("Assistant stream failed", error);
|
|
230
|
+
send({ type: "error", message: "The model request failed." });
|
|
231
|
+
}
|
|
232
|
+
})();
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
ipcMain.on(ASSISTANT_STREAM_CHANNEL, handleStream);
|
|
236
|
+
mainWindow.once("closed", () => {
|
|
237
|
+
ipcMain.removeListener(ASSISTANT_STREAM_CHANNEL, handleStream);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
If your app can open more than one trusted assistant window, register a unique channel per window or route requests through one application-level registry. A global `ipcMain.on` listener should not be re-registered under the same channel for every window.
|
|
243
|
+
|
|
244
|
+
### 4. Adapt IPC to assistant-ui
|
|
245
|
+
|
|
246
|
+
`ChatModelAdapter` yields complete snapshots, so the renderer accumulates each IPC delta before yielding it. Its cleanup function closes the port on Stop, unmount, or completion.
|
|
247
|
+
|
|
248
|
+
```tsx title="renderer/assistant-runtime.tsx"
|
|
249
|
+
import type { ReactNode } from "react";
|
|
250
|
+
import {
|
|
251
|
+
AssistantRuntimeProvider,
|
|
252
|
+
useLocalRuntime,
|
|
253
|
+
type ChatModelAdapter,
|
|
254
|
+
} from "@assistant-ui/react";
|
|
255
|
+
import type { ChatMessage } from "./shared";
|
|
256
|
+
|
|
257
|
+
const ipcChatModel: ChatModelAdapter = {
|
|
258
|
+
async *run({ messages, context, abortSignal }) {
|
|
259
|
+
abortSignal.throwIfAborted();
|
|
260
|
+
|
|
261
|
+
const system = [context.system];
|
|
262
|
+
const serializedMessages: ChatMessage[] = [];
|
|
263
|
+
|
|
264
|
+
for (const message of messages) {
|
|
265
|
+
const text = message.content
|
|
266
|
+
.flatMap((part) => (part.type === "text" ? [part.text] : []))
|
|
267
|
+
.join("\n");
|
|
268
|
+
if (!text) continue;
|
|
269
|
+
|
|
270
|
+
if (message.role === "system") system.push(text);
|
|
271
|
+
if (message.role === "user") {
|
|
272
|
+
serializedMessages.push({ role: "user", content: text });
|
|
273
|
+
}
|
|
274
|
+
if (message.role === "assistant") {
|
|
275
|
+
serializedMessages.push({ role: "assistant", content: text });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const systemText = system.filter(Boolean).join("\n\n");
|
|
280
|
+
let stop: (() => void) | undefined;
|
|
281
|
+
let removeAbortListener: (() => void) | undefined;
|
|
282
|
+
const deltas = new ReadableStream<string>({
|
|
283
|
+
start(controller) {
|
|
284
|
+
let settled = false;
|
|
285
|
+
const close = () => {
|
|
286
|
+
if (settled) return;
|
|
287
|
+
settled = true;
|
|
288
|
+
controller.close();
|
|
289
|
+
};
|
|
290
|
+
const fail = (error: unknown) => {
|
|
291
|
+
if (settled) return;
|
|
292
|
+
settled = true;
|
|
293
|
+
controller.error(error);
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
stop = window.assistantAI.streamChat(
|
|
297
|
+
{
|
|
298
|
+
...(systemText ? { system: systemText } : {}),
|
|
299
|
+
messages: serializedMessages,
|
|
300
|
+
},
|
|
301
|
+
(event) => {
|
|
302
|
+
if (event.type === "delta") controller.enqueue(event.text);
|
|
303
|
+
if (event.type === "done") close();
|
|
304
|
+
if (event.type === "error") fail(new Error(event.message));
|
|
305
|
+
},
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const onAbort = () => {
|
|
309
|
+
stop?.();
|
|
310
|
+
fail(abortSignal.reason);
|
|
311
|
+
};
|
|
312
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
313
|
+
removeAbortListener = () =>
|
|
314
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
315
|
+
if (abortSignal.aborted) onAbort();
|
|
316
|
+
},
|
|
317
|
+
cancel() {
|
|
318
|
+
stop?.();
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const reader = deltas.getReader();
|
|
323
|
+
let fullText = "";
|
|
324
|
+
try {
|
|
325
|
+
while (true) {
|
|
326
|
+
const { done, value } = await reader.read();
|
|
327
|
+
if (done) return;
|
|
328
|
+
fullText += value;
|
|
329
|
+
yield { content: [{ type: "text", text: fullText }] };
|
|
330
|
+
}
|
|
331
|
+
} finally {
|
|
332
|
+
removeAbortListener?.();
|
|
333
|
+
stop?.();
|
|
334
|
+
reader.releaseLock();
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
export function ElectronRuntimeProvider({ children }: { children: ReactNode }) {
|
|
340
|
+
const runtime = useLocalRuntime(ipcChatModel);
|
|
341
|
+
return (
|
|
342
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
343
|
+
{children}
|
|
344
|
+
</AssistantRuntimeProvider>
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Wrap your existing assistant-ui thread with `ElectronRuntimeProvider`. Primitives and installed UI components work exactly as they do in a browser React app.
|
|
350
|
+
|
|
351
|
+
## Extend the protocol deliberately
|
|
352
|
+
|
|
353
|
+
The local example drops every non-text content part. Add explicit structured-clone-safe fields before claiming support for more features:
|
|
354
|
+
|
|
355
|
+
- Attachments: pass bounded `ArrayBuffer` data or an app-owned file reference after validating type, size, and path in the main process. Do not pass browser `File` objects.
|
|
356
|
+
- Tools: define serializable tool-call and tool-result events, and validate every tool invocation in the privileged process. Never expose a generic shell or filesystem IPC method.
|
|
357
|
+
- Reasoning and metadata: add distinct event variants and map them to the corresponding assistant-ui content parts.
|
|
358
|
+
- Thread persistence: store serializable thread data in the main process or use a remote thread-list adapter; do not try to transfer a runtime object.
|
|
359
|
+
|
|
360
|
+
## Packaged-app checklist
|
|
361
|
+
|
|
362
|
+
- Use an absolute HTTPS endpoint, a privileged custom protocol, or the preload bridge. Relative `/api/*` routes that worked against a development server will not follow your backend after packaging.
|
|
363
|
+
- Serve local content through a custom protocol instead of `file://` when possible, and define a restrictive Content Security Policy. Do not disable `webSecurity` to work around origin errors.
|
|
364
|
+
- Validate every IPC sender and every payload in the main process. Treat renderer data as untrusted even when the renderer is local.
|
|
365
|
+
- Intercept new windows and external links with `webContents.setWindowOpenHandler`; do not let model-generated links create unrestricted Electron windows.
|
|
366
|
+
- If you render remote HTML with `SafeContentFrame`, allow its documented host in `frame-src`. Ordinary text and Markdown rendering need no Electron-specific change.
|
|
367
|
+
- Test the packaged build, not only the Vite or Webpack development server. The scheme, origin, preload path, CSP, and environment loading can all differ.
|
|
368
|
+
|
|
369
|
+
For the underlying platform constraints, see Electron's official guides to [IPC and MessagePorts](https://www.electronjs.org/docs/latest/tutorial/message-ports), [context isolation](https://www.electronjs.org/docs/latest/tutorial/context-isolation), and the [security checklist](https://www.electronjs.org/docs/latest/tutorial/security).
|
|
@@ -105,3 +105,13 @@ Alternative ways to connect a model.
|
|
|
105
105
|
Run your app locally on a ChatGPT Plus or Pro plan via Codex OAuth, no API key required.
|
|
106
106
|
</Card>
|
|
107
107
|
</Cards>
|
|
108
|
+
|
|
109
|
+
## Environments
|
|
110
|
+
|
|
111
|
+
Run assistant-ui outside a conventional browser deployment.
|
|
112
|
+
|
|
113
|
+
<Cards>
|
|
114
|
+
<Card title="Electron" href="/docs/guides/electron">
|
|
115
|
+
Connect an Electron renderer to a hosted backend or a secure, streaming preload/IPC bridge.
|
|
116
|
+
</Card>
|
|
117
|
+
</Cards>
|
|
@@ -127,7 +127,7 @@ function CustomQuoteDisplay() {
|
|
|
127
127
|
|
|
128
128
|
## Programmatic API
|
|
129
129
|
|
|
130
|
-
Set or clear quotes via `useAui` from `@assistant-ui/react`. Call `aui.thread
|
|
130
|
+
Set or clear quotes via `useAui` from `@assistant-ui/react`. Call `aui.thread.composer().setQuote()` when your component is rendered outside of a specific thread context, or `aui.composer.setQuote()` when it is rendered inside a thread:
|
|
131
131
|
|
|
132
132
|
```tsx
|
|
133
133
|
import { useAui } from "@assistant-ui/react";
|
|
@@ -136,14 +136,14 @@ function MyComponent() {
|
|
|
136
136
|
const aui = useAui();
|
|
137
137
|
|
|
138
138
|
const quoteText = () => {
|
|
139
|
-
aui.thread
|
|
139
|
+
aui.thread.composer().setQuote({
|
|
140
140
|
text: "The text to quote",
|
|
141
141
|
messageId: "msg-123",
|
|
142
142
|
});
|
|
143
143
|
};
|
|
144
144
|
|
|
145
145
|
const clearQuote = () => {
|
|
146
|
-
aui.thread
|
|
146
|
+
aui.thread.composer().setQuote(undefined);
|
|
147
147
|
};
|
|
148
148
|
|
|
149
149
|
return (
|
|
@@ -143,7 +143,7 @@ Per-tenant prefixes also make incident response cheaper. A `SCAN MATCH aui:app:t
|
|
|
143
143
|
|
|
144
144
|
## Observability hooks
|
|
145
145
|
|
|
146
|
-
`ResumableStreamContextOptions` exposes lifecycle hooks for structured logging, metrics, and tracing.
|
|
146
|
+
`ResumableStreamContextOptions` exposes lifecycle hooks for structured logging, metrics, and tracing. Hook exceptions and rejected promises are reported through `console.error` without affecting stream output or stored lifecycle status.
|
|
147
147
|
|
|
148
148
|
```ts title="/lib/resumable-context.ts"
|
|
149
149
|
import { createResumableStreamContext } from "assistant-stream/resumable";
|
|
@@ -170,7 +170,7 @@ export const resumableContext = createResumableStreamContext({
|
|
|
170
170
|
});
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
-
Keep hook
|
|
173
|
+
Keep synchronous hook work cheap. `onAcquire` runs on the `run()` request path for producers and consumers alike, while the remaining hooks run on the producer's streaming hot path. Returned promises are observed for failures but are not awaited.
|
|
174
174
|
|
|
175
175
|
## Resource limits
|
|
176
176
|
|
|
@@ -84,7 +84,7 @@ The context exposes two more verbs: `ctx.status(streamId)` returns `"streaming"
|
|
|
84
84
|
|
|
85
85
|
## Client side: native integration
|
|
86
86
|
|
|
87
|
-
`@assistant-ui/react-ai-sdk` ships a `resumable` option on `AssistantChatTransport`. It captures the stream id from the response header, redirects `chat.resumeStream()` reconnects to your resume route, and clears the stored id when the response finishes naturally. Pair it with `useChatRuntime`, which fires `chat.resumeStream()`
|
|
87
|
+
`@assistant-ui/react-ai-sdk` ships a `resumable` option on `AssistantChatTransport`. It captures the stream id from the response header, redirects `chat.resumeStream()` reconnects to your resume route, and clears the stored id when the response finishes naturally. Pair it with `useChatRuntime`, which fires `chat.resumeStream()` whenever its resumable storage reports a pending id, including ids discovered after mount.
|
|
88
88
|
|
|
89
89
|
```tsx title="/app/page.tsx"
|
|
90
90
|
"use client";
|
|
@@ -127,12 +127,83 @@ export default function Page() {
|
|
|
127
127
|
}
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
`onResumeError` runs when the client finds a stored stream id but the reconnect attempt fails. Use it to show a toast, report telemetry, or mark the thread as needing retry
|
|
130
|
+
`onResumeError` runs when the client finds a stored stream id but the reconnect attempt fails. Use it to show a toast, report telemetry, or mark the thread as needing retry. Assistant-ui clears the failed stream id after the callback unless a newer id has replaced it.
|
|
131
131
|
|
|
132
|
-
`createResumableSessionStorage` returns a `ResumableClientStorage` backed by `window.sessionStorage`. Pass `{ key }` to namespace per route or
|
|
132
|
+
`createResumableSessionStorage` returns a `ResumableClientStorage` backed by `window.sessionStorage`. Pass `{ key }` to namespace per route or chat surface; `key` also accepts a getter that is read lazily on every access, so you can derive it from the active thread's identity (see [Multiple threads](#multiple-threads)). While the getter returns `undefined`, reads report no pending stream and writes are dropped. Reuse one storage instance per key, because separate instances do not synchronize their in-memory caches. The persisted entry survives reloads, while in-memory ownership prevents one mounted thread from attaching to another thread's pending stream. An idle follower that learns a stream id from its backend can call `storage.setStreamId(streamId, threadId)` after the corresponding message exists in local history; the runtime attaches when that thread is current, without requiring a remount. The built-in storage holds one pending stream id per key; the [Multiple threads](#multiple-threads) pattern gives each thread its own key, so several threads stay resumable at once. A custom `ResumableClientStorage` can instead key its records by the optional `threadId` arguments and implement `subscribe(listener, threadId)` for post-mount updates, or replace the storage methods entirely. If you are running on a transport that already wraps `fetch` or `prepareReconnectToStreamRequest`, the `resumable` option composes with your existing handlers.
|
|
133
133
|
|
|
134
134
|
The default finish detector scans the SSE body for the AI SDK `"type":"finish"` marker. Override `isFinishEvent` on the `resumable` option when you ship a custom encoder.
|
|
135
135
|
|
|
136
|
+
### Multiple threads
|
|
137
|
+
|
|
138
|
+
The snippet above stores the pending stream id under one `sessionStorage` key, which is correct for a single chat surface. Under a thread list runtime (`useRemoteThreadListRuntime`, which `useChatRuntime` wraps) with more than one alive thread, a single shared key is written and cleared by whichever thread acts last: switch threads while a response is in flight and the response header writes its stream id to the shared key, which the thread you switched to reads on mount and resumes — replaying the other conversation's stream into this one. An unscoped clear when a stream finishes can also drop another thread's pending id. And after a reload, a pending id under the shared key is claimed by whichever thread is main at mount, which with a thread list is a fresh empty thread rather than the one that started the stream.
|
|
139
|
+
|
|
140
|
+
Scope the key to the thread, and construct the transport and storage per thread inside the per-thread runtime hook rather than once at module level. Derive the key from the thread's identity with a getter so it is read lazily on every access:
|
|
141
|
+
|
|
142
|
+
```tsx title="/app/page.tsx"
|
|
143
|
+
"use client";
|
|
144
|
+
|
|
145
|
+
import {
|
|
146
|
+
AssistantCloud,
|
|
147
|
+
AssistantRuntimeProvider,
|
|
148
|
+
useAui,
|
|
149
|
+
useCloudThreadListAdapter,
|
|
150
|
+
useRemoteThreadListRuntime,
|
|
151
|
+
} from "@assistant-ui/react";
|
|
152
|
+
import {
|
|
153
|
+
AssistantChatTransport,
|
|
154
|
+
createResumableSessionStorage,
|
|
155
|
+
useChatRuntime,
|
|
156
|
+
} from "@assistant-ui/react-ai-sdk";
|
|
157
|
+
import { useMemo } from "react";
|
|
158
|
+
import { Thread } from "@/components/assistant-ui/thread";
|
|
159
|
+
import { ThreadList } from "@/components/assistant-ui/thread-list";
|
|
160
|
+
|
|
161
|
+
const cloud = new AssistantCloud({
|
|
162
|
+
baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
|
|
163
|
+
anonymous: true,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
function ResumableThreadRuntime() {
|
|
167
|
+
const aui = useAui();
|
|
168
|
+
const transport = useMemo(
|
|
169
|
+
() =>
|
|
170
|
+
new AssistantChatTransport({
|
|
171
|
+
api: "/api/chat",
|
|
172
|
+
resumable: {
|
|
173
|
+
storage: createResumableSessionStorage({
|
|
174
|
+
key: () => {
|
|
175
|
+
const item = aui.threadListItem.getState();
|
|
176
|
+
return `aui-resumable-stream-id:${item.remoteId ?? item.id}`;
|
|
177
|
+
},
|
|
178
|
+
}),
|
|
179
|
+
resumeApi: (streamId) => `/api/chat/resume/${streamId}`,
|
|
180
|
+
},
|
|
181
|
+
}),
|
|
182
|
+
[aui],
|
|
183
|
+
);
|
|
184
|
+
return useChatRuntime({ transport });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export default function Page() {
|
|
188
|
+
const adapter = useCloudThreadListAdapter({ cloud });
|
|
189
|
+
const runtime = useRemoteThreadListRuntime({
|
|
190
|
+
adapter,
|
|
191
|
+
runtimeHook: ResumableThreadRuntime,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
196
|
+
<ThreadList />
|
|
197
|
+
<Thread />
|
|
198
|
+
</AssistantRuntimeProvider>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`useCloudThreadListAdapter` is the same adapter `useChatRuntime({ cloud })` builds internally, so cloud thread history and attachments keep working; to bring your own backend, pass a custom `RemoteThreadListAdapter` instead. Each thread's runtime hook constructs its own transport and storage, keyed by that thread's identity, so one conversation's stream id can never be read or cleared by another. `useChatRuntime` is a no-op thread list when nested as a `runtimeHook`, so it only runs the per-thread chat runtime against the per-thread transport; the real thread list is the outer `useRemoteThreadListRuntime`.
|
|
204
|
+
|
|
205
|
+
Key by `remoteId ?? id` under a recognizable prefix, read lazily. A brand-new thread has no `remoteId` yet, but the transport initializes the thread before the request goes out, so by the time the response header delivers the stream id the getter resolves to the `remoteId`. After a reload the same conversation comes back with that `remoteId` as its id, so the stored entry is found again and the pending stream resumes. The local-id fallback covers a thread that has never sent, whose key nothing else reads or writes. Keying by the local id alone would break resume across reloads for threads created in the current session, because their local `__LOCALID_` id is replaced by the `remoteId` when the list reloads.
|
|
206
|
+
|
|
136
207
|
## Storage choices
|
|
137
208
|
|
|
138
209
|
The core package ships `createInMemoryResumableStreamStore` for development and tests. State lives in a process-local `Map`, so it does not survive a server restart. Useful options include `defaultTtlMs`, `maxChunkBytes`, `maxEntriesPerStream`, `maxStreams`, and `gcIntervalMs` for periodic eviction.
|
|
@@ -15,13 +15,13 @@ The Suggestions API allows you to configure a list of suggested prompts that are
|
|
|
15
15
|
Configure suggestions using the `Suggestions()` API in your runtime provider:
|
|
16
16
|
|
|
17
17
|
```tsx
|
|
18
|
-
import {
|
|
18
|
+
import { AuiConfig, Tools, Suggestions } from "@assistant-ui/react";
|
|
19
19
|
import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
|
|
20
20
|
|
|
21
21
|
function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
|
|
22
22
|
const runtime = useChatRuntime();
|
|
23
23
|
|
|
24
|
-
const
|
|
24
|
+
const config = AuiConfig({
|
|
25
25
|
tools: Tools({ toolkit: myToolkit }),
|
|
26
26
|
suggestions: Suggestions([
|
|
27
27
|
"What can you help me with?",
|
|
@@ -31,7 +31,7 @@ function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
return (
|
|
34
|
-
<AssistantRuntimeProvider
|
|
34
|
+
<AssistantRuntimeProvider runtime={runtime} config={config}>
|
|
35
35
|
{children}
|
|
36
36
|
</AssistantRuntimeProvider>
|
|
37
37
|
);
|
|
@@ -45,7 +45,7 @@ Suggestions can be provided as either strings or objects with title, label, and
|
|
|
45
45
|
### Simple Strings
|
|
46
46
|
|
|
47
47
|
```tsx
|
|
48
|
-
|
|
48
|
+
AuiConfig({
|
|
49
49
|
suggestions: Suggestions([
|
|
50
50
|
"What's the weather today?",
|
|
51
51
|
"Help me write an email",
|
|
@@ -59,7 +59,7 @@ const aui = useAui({
|
|
|
59
59
|
For more detailed suggestions with separate display text and prompts:
|
|
60
60
|
|
|
61
61
|
```tsx
|
|
62
|
-
|
|
62
|
+
AuiConfig({
|
|
63
63
|
suggestions: Suggestions([
|
|
64
64
|
{
|
|
65
65
|
title: "Weather",
|
|
@@ -315,13 +315,13 @@ function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
|
|
|
315
315
|
];
|
|
316
316
|
}, [user.isPremium]);
|
|
317
317
|
|
|
318
|
-
const
|
|
318
|
+
const config = AuiConfig({
|
|
319
319
|
tools: Tools({ toolkit: myToolkit }),
|
|
320
320
|
suggestions: Suggestions(suggestions),
|
|
321
321
|
});
|
|
322
322
|
|
|
323
323
|
return (
|
|
324
|
-
<AssistantRuntimeProvider
|
|
324
|
+
<AssistantRuntimeProvider runtime={runtime} config={config}>
|
|
325
325
|
{children}
|
|
326
326
|
</AssistantRuntimeProvider>
|
|
327
327
|
);
|
|
@@ -352,10 +352,10 @@ If your codebase uses the inline `ThreadPrimitive.Suggestion` component (which r
|
|
|
352
352
|
|
|
353
353
|
### Runtime-driven form
|
|
354
354
|
|
|
355
|
-
1. Configure suggestions in your runtime provider
|
|
355
|
+
1. Configure suggestions in your runtime provider's `config`:
|
|
356
356
|
|
|
357
357
|
```tsx
|
|
358
|
-
|
|
358
|
+
AuiConfig({
|
|
359
359
|
suggestions: Suggestions(["What's the weather?"]),
|
|
360
360
|
});
|
|
361
361
|
```
|
|
@@ -41,11 +41,11 @@ import { useAui } from "@assistant-ui/react-ink";
|
|
|
41
41
|
const aui = useAui();
|
|
42
42
|
|
|
43
43
|
// Composer actions
|
|
44
|
-
aui.composer
|
|
45
|
-
aui.composer
|
|
44
|
+
aui.composer.setText("Hello");
|
|
45
|
+
aui.composer.send();
|
|
46
46
|
|
|
47
47
|
// Thread actions
|
|
48
|
-
aui.thread
|
|
48
|
+
aui.thread.cancelRun();
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
### useAuiEvent
|
|
@@ -187,12 +187,17 @@ export default defineToolkit({
|
|
|
187
187
|
```
|
|
188
188
|
|
|
189
189
|
```tsx title="ToolProvider.tsx"
|
|
190
|
-
import { AuiProvider, Tools, useAui } from "@assistant-ui/react-ink";
|
|
190
|
+
import { AuiConfig, AuiProvider, Tools, useAui } from "@assistant-ui/react-ink";
|
|
191
191
|
import toolkit from "./weather-toolkit";
|
|
192
192
|
|
|
193
193
|
function ToolProvider({ children }: { children: React.ReactNode }) {
|
|
194
|
-
const aui = useAui(
|
|
195
|
-
|
|
194
|
+
const aui = useAui();
|
|
195
|
+
const config = AuiConfig({ tools: Tools({ toolkit }) });
|
|
196
|
+
return (
|
|
197
|
+
<AuiProvider extends={aui} config={config}>
|
|
198
|
+
{children}
|
|
199
|
+
</AuiProvider>
|
|
200
|
+
);
|
|
196
201
|
}
|
|
197
202
|
```
|
|
198
203
|
|
|
@@ -271,7 +276,7 @@ export function useWeatherToolkit() {
|
|
|
271
276
|
}
|
|
272
277
|
```
|
|
273
278
|
|
|
274
|
-
Import the toolkit hook
|
|
279
|
+
Import the toolkit hook and mount it with `<AuiProvider extends={aui} config={config}>` where `const aui = useAui()` and `const config = AuiConfig({ tools: Tools({ toolkit }) })`, as shown in the [Tools](#tools) section above.
|
|
275
280
|
|
|
276
281
|
## Runtime Providers
|
|
277
282
|
|