@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
|
@@ -4,7 +4,7 @@ description: Reference for projects still on AI SDK v6. New projects should use
|
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
<Callout type="warn">
|
|
7
|
-
AI SDK v6 is a legacy version. New projects should use [AI SDK v7](/docs/runtimes/ai-sdk/v7). v6 users
|
|
7
|
+
AI SDK v6 is a legacy version. New projects should use [AI SDK v7](/docs/runtimes/ai-sdk/v7). v6 users pin `@assistant-ui/react-ai-sdk@1.3.40` (the last v6-compatible release); later releases target v7.
|
|
8
8
|
</Callout>
|
|
9
9
|
|
|
10
10
|
Requires `ai@^6` and `@ai-sdk/react@^3`. For other versions see the [overview](/docs/runtimes/ai-sdk/overview).
|
|
@@ -52,17 +52,17 @@ npm init -y
|
|
|
52
52
|
<PlatformTabs>
|
|
53
53
|
<Tab value="React">
|
|
54
54
|
|
|
55
|
-
<InstallCommand npm={["@assistant-ui/react", "@assistant-ui/react-ai-sdk", "ai@^6", "@ai-sdk/react@^3", "@ai-sdk/openai", "zod"]} />
|
|
55
|
+
<InstallCommand npm={["@assistant-ui/react", "@assistant-ui/react-ai-sdk@1.3.40", "ai@^6", "@ai-sdk/react@^3", "@ai-sdk/openai", "zod"]} />
|
|
56
56
|
|
|
57
57
|
</Tab>
|
|
58
58
|
<Tab value="React Native">
|
|
59
59
|
|
|
60
|
-
<InstallCommand expo={["@assistant-ui/react-native", "@assistant-ui/react-ai-sdk", "ai@^6", "@ai-sdk/react@^3", "@ai-sdk/openai", "zod"]} />
|
|
60
|
+
<InstallCommand expo={["@assistant-ui/react-native", "@assistant-ui/react-ai-sdk@1.3.40", "ai@^6", "@ai-sdk/react@^3", "@ai-sdk/openai", "zod"]} />
|
|
61
61
|
|
|
62
62
|
</Tab>
|
|
63
63
|
<Tab value="React Ink">
|
|
64
64
|
|
|
65
|
-
<InstallCommand npm={["@assistant-ui/react-ink", "@assistant-ui/react-ai-sdk", "ai@^6", "@ai-sdk/react@^3", "@ai-sdk/openai", "zod", "ink", "react"]} />
|
|
65
|
+
<InstallCommand npm={["@assistant-ui/react-ink", "@assistant-ui/react-ai-sdk@1.3.40", "ai@^6", "@ai-sdk/react@^3", "@ai-sdk/openai", "zod", "ink", "react"]} />
|
|
66
66
|
|
|
67
67
|
</Tab>
|
|
68
68
|
</PlatformTabs>
|
|
@@ -92,7 +92,7 @@ export async function POST(req: Request) {
|
|
|
92
92
|
const { messages }: { messages: UIMessage[] } = await req.json();
|
|
93
93
|
|
|
94
94
|
const result = streamText({
|
|
95
|
-
model: openai("gpt-5.
|
|
95
|
+
model: openai("gpt-5.6-luna"),
|
|
96
96
|
messages: await convertToModelMessages(messages), // async in v6
|
|
97
97
|
tools: {
|
|
98
98
|
get_current_weather: tool({
|
|
@@ -245,7 +245,7 @@ export async function POST(req: Request) {
|
|
|
245
245
|
} = await req.json();
|
|
246
246
|
|
|
247
247
|
const result = streamText({
|
|
248
|
-
model: openai("gpt-5.
|
|
248
|
+
model: openai("gpt-5.6-luna"),
|
|
249
249
|
system,
|
|
250
250
|
messages: await convertToModelMessages(messages),
|
|
251
251
|
tools: {
|
|
@@ -258,7 +258,7 @@ export async function POST(req: Request) {
|
|
|
258
258
|
}
|
|
259
259
|
```
|
|
260
260
|
|
|
261
|
-
Frontend tools are registered through `
|
|
261
|
+
Frontend tools are registered through the provider's `config` (see the [tools guide](/docs/tools/defining-tools)) and serialized for the backend via `frontendTools`.
|
|
262
262
|
|
|
263
263
|
## Multi-step tool calls
|
|
264
264
|
|
|
@@ -276,7 +276,7 @@ export async function POST(req: Request) {
|
|
|
276
276
|
const { messages } = await req.json();
|
|
277
277
|
|
|
278
278
|
const result = streamText({
|
|
279
|
-
model: openai("gpt-5.
|
|
279
|
+
model: openai("gpt-5.6-luna"),
|
|
280
280
|
messages: await convertToModelMessages(messages),
|
|
281
281
|
tools: {
|
|
282
282
|
/* ... */
|
|
@@ -305,7 +305,7 @@ export async function POST(req: Request) {
|
|
|
305
305
|
const { messages } = await req.json();
|
|
306
306
|
|
|
307
307
|
const result = streamText({
|
|
308
|
-
model: openai("gpt-5.
|
|
308
|
+
model: openai("gpt-5.6-luna"),
|
|
309
309
|
messages: await convertToModelMessages(messages),
|
|
310
310
|
tools: {
|
|
311
311
|
deploy: tool({
|
|
@@ -356,9 +356,9 @@ Render the gate with a toolkit entry. The `approval` field carries the gate stat
|
|
|
356
356
|
import { useChat } from "@ai-sdk/react";
|
|
357
357
|
import {
|
|
358
358
|
AssistantRuntimeProvider,
|
|
359
|
+
AuiConfig,
|
|
359
360
|
defineToolkit,
|
|
360
361
|
Tools,
|
|
361
|
-
useAui,
|
|
362
362
|
} from "@assistant-ui/react";
|
|
363
363
|
import { useAISDKRuntime } from "@assistant-ui/react-ai-sdk";
|
|
364
364
|
import { lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
|
|
@@ -397,10 +397,9 @@ export default function Page() {
|
|
|
397
397
|
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
|
|
398
398
|
});
|
|
399
399
|
const runtime = useAISDKRuntime(chat);
|
|
400
|
-
const
|
|
401
|
-
|
|
400
|
+
const config = AuiConfig({ tools: Tools({ toolkit }) });
|
|
402
401
|
return (
|
|
403
|
-
<AssistantRuntimeProvider
|
|
402
|
+
<AssistantRuntimeProvider runtime={runtime} config={config}>
|
|
404
403
|
<Thread />
|
|
405
404
|
</AssistantRuntimeProvider>
|
|
406
405
|
);
|
|
@@ -424,7 +423,7 @@ import { openai } from "@ai-sdk/openai";
|
|
|
424
423
|
export async function POST(req: Request) {
|
|
425
424
|
const { messages } = await req.json();
|
|
426
425
|
const result = streamText({
|
|
427
|
-
model: openai("gpt-5.
|
|
426
|
+
model: openai("gpt-5.6-luna"),
|
|
428
427
|
messages: await convertToModelMessages(injectQuoteContext(messages)),
|
|
429
428
|
});
|
|
430
429
|
return result.toUIMessageStreamResponse();
|
|
@@ -3,7 +3,7 @@ title: AI SDK v7
|
|
|
3
3
|
description: Integrate Vercel AI SDK v7 with assistant-ui for streaming chat.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Current-version integration. Requires `ai@^7` and `@ai-sdk/react@^4`. For older versions see the [overview](/docs/runtimes/ai-sdk).
|
|
6
|
+
Current-version integration. Requires `ai@^7` and `@ai-sdk/react@^4`. For older versions see the [overview](/docs/runtimes/ai-sdk/overview).
|
|
7
7
|
|
|
8
8
|
## Quickstart
|
|
9
9
|
|
|
@@ -90,7 +90,7 @@ export async function POST(req: Request) {
|
|
|
90
90
|
const { messages }: { messages: UIMessage[] } = await req.json();
|
|
91
91
|
|
|
92
92
|
const result = streamText({
|
|
93
|
-
model: openai("gpt-5.
|
|
93
|
+
model: openai("gpt-5.6-luna"),
|
|
94
94
|
messages: await convertToModelMessages(messages), // async in v7
|
|
95
95
|
tools: {
|
|
96
96
|
get_current_weather: tool({
|
|
@@ -245,7 +245,7 @@ export async function POST(req: Request) {
|
|
|
245
245
|
} = await req.json();
|
|
246
246
|
|
|
247
247
|
const result = streamText({
|
|
248
|
-
model: openai("gpt-5.
|
|
248
|
+
model: openai("gpt-5.6-luna"),
|
|
249
249
|
system,
|
|
250
250
|
messages: await convertToModelMessages(messages),
|
|
251
251
|
tools: {
|
|
@@ -260,7 +260,7 @@ export async function POST(req: Request) {
|
|
|
260
260
|
}
|
|
261
261
|
```
|
|
262
262
|
|
|
263
|
-
Frontend tools are registered through `
|
|
263
|
+
Frontend tools are registered through the provider's `config` (see the [tools guide](/docs/tools/defining-tools)) and serialized for the backend via `frontendTools`.
|
|
264
264
|
|
|
265
265
|
## Multi-step tool calls
|
|
266
266
|
|
|
@@ -280,7 +280,7 @@ export async function POST(req: Request) {
|
|
|
280
280
|
const { messages } = await req.json();
|
|
281
281
|
|
|
282
282
|
const result = streamText({
|
|
283
|
-
model: openai("gpt-5.
|
|
283
|
+
model: openai("gpt-5.6-luna"),
|
|
284
284
|
messages: await convertToModelMessages(messages),
|
|
285
285
|
tools: {
|
|
286
286
|
/* ... */
|
|
@@ -311,7 +311,7 @@ export async function POST(req: Request) {
|
|
|
311
311
|
const { messages } = await req.json();
|
|
312
312
|
|
|
313
313
|
const result = streamText({
|
|
314
|
-
model: openai("gpt-5.
|
|
314
|
+
model: openai("gpt-5.6-luna"),
|
|
315
315
|
messages: await convertToModelMessages(messages),
|
|
316
316
|
tools: {
|
|
317
317
|
deploy: tool({
|
|
@@ -368,9 +368,9 @@ Render the gate with a toolkit entry. The `approval` field carries the gate stat
|
|
|
368
368
|
import { useChat } from "@ai-sdk/react";
|
|
369
369
|
import {
|
|
370
370
|
AssistantRuntimeProvider,
|
|
371
|
+
AuiConfig,
|
|
371
372
|
defineToolkit,
|
|
372
373
|
Tools,
|
|
373
|
-
useAui,
|
|
374
374
|
} from "@assistant-ui/react";
|
|
375
375
|
import { useAISDKRuntime } from "@assistant-ui/react-ai-sdk";
|
|
376
376
|
import { lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
|
|
@@ -410,10 +410,9 @@ export default function Page() {
|
|
|
410
410
|
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
|
|
411
411
|
});
|
|
412
412
|
const runtime = useAISDKRuntime(chat);
|
|
413
|
-
const
|
|
414
|
-
|
|
413
|
+
const config = AuiConfig({ tools: Tools({ toolkit }) });
|
|
415
414
|
return (
|
|
416
|
-
<AssistantRuntimeProvider
|
|
415
|
+
<AssistantRuntimeProvider runtime={runtime} config={config}>
|
|
417
416
|
<Thread />
|
|
418
417
|
</AssistantRuntimeProvider>
|
|
419
418
|
);
|
|
@@ -439,7 +438,7 @@ import { openai } from "@ai-sdk/openai";
|
|
|
439
438
|
export async function POST(req: Request) {
|
|
440
439
|
const { messages } = await req.json();
|
|
441
440
|
const result = streamText({
|
|
442
|
-
model: openai("gpt-5.
|
|
441
|
+
model: openai("gpt-5.6-luna"),
|
|
443
442
|
messages: await convertToModelMessages(injectQuoteContext(messages)),
|
|
444
443
|
});
|
|
445
444
|
return createUIMessageStreamResponse({
|
|
@@ -601,7 +600,7 @@ Beyond transports and adapters, `useChatRuntime` accepts options that control th
|
|
|
601
600
|
|
|
602
601
|
### onThreadIdChange
|
|
603
602
|
|
|
604
|
-
Called whenever the active thread's settled ID
|
|
603
|
+
Called whenever the runtime changes the active thread's settled ID, e.g. to sync the active thread to a URL query param. Changes supplied through the controlled `threadId` option are not echoed back through this callback.
|
|
605
604
|
|
|
606
605
|
```tsx
|
|
607
606
|
const runtime = useChatRuntime({
|
|
@@ -149,7 +149,7 @@ const adapterWithHistory: RemoteThreadListAdapter = {
|
|
|
149
149
|
const history = useMemo<ThreadHistoryAdapter>(
|
|
150
150
|
() => ({
|
|
151
151
|
async load() {
|
|
152
|
-
const { remoteId } = aui.threadListItem
|
|
152
|
+
const { remoteId } = aui.threadListItem.getState();
|
|
153
153
|
if (!remoteId) return { messages: [] };
|
|
154
154
|
const rows = await fetch(
|
|
155
155
|
`/api/threads/${remoteId}/messages`,
|
|
@@ -157,7 +157,7 @@ const adapterWithHistory: RemoteThreadListAdapter = {
|
|
|
157
157
|
return { messages: rows.map(toThreadMessage) };
|
|
158
158
|
},
|
|
159
159
|
async append({ message, parentId }) {
|
|
160
|
-
const { remoteId } = await aui.threadListItem
|
|
160
|
+
const { remoteId } = await aui.threadListItem.initialize();
|
|
161
161
|
await fetch(`/api/threads/${remoteId}/messages`, {
|
|
162
162
|
method: "POST",
|
|
163
163
|
body: JSON.stringify({ message, parentId }),
|
|
@@ -181,11 +181,11 @@ const adapterWithHistory: RemoteThreadListAdapter = {
|
|
|
181
181
|
|
|
182
182
|
### Avoiding the first-message race
|
|
183
183
|
|
|
184
|
-
`append` may be called before the thread record exists in your backend. Always await `aui.threadListItem
|
|
184
|
+
`append` may be called before the thread record exists in your backend. Always await `aui.threadListItem.initialize()` before writing:
|
|
185
185
|
|
|
186
186
|
```ts
|
|
187
187
|
async append({ message, parentId }) {
|
|
188
|
-
const { remoteId } = await aui.threadListItem
|
|
188
|
+
const { remoteId } = await aui.threadListItem.initialize();
|
|
189
189
|
await saveMessage(remoteId, parentId, message);
|
|
190
190
|
}
|
|
191
191
|
```
|
|
@@ -194,14 +194,14 @@ async append({ message, parentId }) {
|
|
|
194
194
|
|
|
195
195
|
### Reloading after async authentication
|
|
196
196
|
|
|
197
|
-
If your adapter depends on a user that resolves asynchronously (oidc, `next-auth`, `better-auth`), the initial `list()` may run before the user is available. Call `aui.threads
|
|
197
|
+
If your adapter depends on a user that resolves asynchronously (oidc, `next-auth`, `better-auth`), the initial `list()` may run before the user is available. Call `aui.threads.reload()` after auth completes:
|
|
198
198
|
|
|
199
199
|
```tsx
|
|
200
200
|
function ReloadOnAuth() {
|
|
201
201
|
const aui = useAui();
|
|
202
202
|
const { isLoading, user } = useAuth();
|
|
203
203
|
useEffect(() => {
|
|
204
|
-
if (!isLoading && user) aui.threads
|
|
204
|
+
if (!isLoading && user) aui.threads.reload();
|
|
205
205
|
}, [isLoading, user?.id]);
|
|
206
206
|
return null;
|
|
207
207
|
}
|
|
@@ -209,9 +209,48 @@ function ReloadOnAuth() {
|
|
|
209
209
|
|
|
210
210
|
`reload()` discards in-flight responses from superseded calls, so it is safe to invoke on every auth transition.
|
|
211
211
|
|
|
212
|
+
### Refetching the open thread
|
|
213
|
+
|
|
214
|
+
`reload()` re-runs `list()`, which refreshes thread list metadata only. It does not touch the messages of the thread the user is looking at. When the open thread's server state changes out of band, so that nothing arrives over the stream (a human-in-the-loop interrupt raised by another process, a stalled stream, a status change picked up by polling), call `aui.threads.reloadMainThread()`:
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
function RefetchOnInterrupt({ status }: { status: string }) {
|
|
218
|
+
const aui = useAui();
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
if (status !== "interrupted") return;
|
|
221
|
+
aui.threads.reloadMainThread().catch(reportError);
|
|
222
|
+
}, [status]);
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
What the promise means depends on the path. On the remount path, it resolves as soon as the replacement runtime attaches, which happens before that runtime's `load()` has started, so awaiting it does not mean the thread is fresh and a failed load cannot reach the caller. On the in-place path, it settles with the refetch itself and rejects when it fails, which is why the example above still handles the rejection.
|
|
228
|
+
|
|
229
|
+
A thread that has not been sent yet is left alone because it holds no remote state.
|
|
230
|
+
|
|
231
|
+
What happens to a run in progress depends on the path. The remount path drops the runtime that was rendering the run; whether the run itself stops is up to that hook's unmount cleanup, which core cannot enforce. On the in-place path the runtime that declared the capability decides, since core does not stop the run for it. Either way this belongs on an event rather than a short timer: drive it from a state change like the one above, or skip the call while `useAuiState((s) => s.thread.isRunning)` is true.
|
|
232
|
+
|
|
233
|
+
How the refetch happens depends on the runtime, in one of three ways. When it declares the capability, the thread runtime is reused: composer drafts survive, existing messages stay rendered while the fresh state loads, and the returned promise settles with the refetch, rejecting if it fails. A remote thread list without the capability remounts the runtime hook instead, which re-runs `load()` at the cost of discarding unsent composer input, and resolves once the new runtime attaches. The single and in-memory thread lists hold no remote state and have no hook to remount, so there it resolves without doing anything.
|
|
234
|
+
|
|
235
|
+
`useAuiState((s) => s.thread.capabilities.refetchThread)` reports which of those you would get, in place or not. It is not a signal for whether to offer a refresh at all: it is false on the remount path, where the call still does the work, and false again where the call does nothing.
|
|
236
|
+
|
|
237
|
+
Both the LangGraph and Google ADK adapters register the in-place refetch capability when their runtime hook receives a `load` function; without one they fall back to the remount path. Other remote adapters take the remount path unless they provide the capability themselves.
|
|
238
|
+
|
|
239
|
+
For an external store runtime, declare it with `onRefetchThread`, which is unrelated to `onReload` (that one re-generates an assistant message):
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
useExternalStoreRuntime({
|
|
243
|
+
messages,
|
|
244
|
+
onNew,
|
|
245
|
+
onRefetchThread: async () => {
|
|
246
|
+
setMessages(await fetchMessages(threadId));
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
212
251
|
### Paginating the thread list
|
|
213
252
|
|
|
214
|
-
If your backend returns thread pages, return a `nextCursor` from `list()` and consume `aui.threads
|
|
253
|
+
If your backend returns thread pages, return a `nextCursor` from `list()` and consume `aui.threads.hasMore` plus `aui.threads.loadMore()` in the UI. The runtime threads `params.after` back through `list()` on every `loadMore()`; the initial call passes no `params`, so treat a missing `after` as "first page". `reload()` resets the cursor so the next load starts from page 1 again.
|
|
215
254
|
|
|
216
255
|
```ts title="threadListAdapter.ts (excerpt)"
|
|
217
256
|
async list({ after } = {}) {
|
|
@@ -271,7 +310,7 @@ A few invariants worth knowing when wiring a custom UI on top of `loadMore()`:
|
|
|
271
310
|
name: "list",
|
|
272
311
|
type: "(params?: { after?: string }) => Promise<{ threads: RemoteThreadMetadata[]; nextCursor?: string }>",
|
|
273
312
|
description:
|
|
274
|
-
"Hydrate threads on mount. Each thread must include status and remoteId; title, externalId, and custom are optional. Return a `nextCursor` to enable `aui.threads
|
|
313
|
+
"Hydrate threads on mount. Each thread must include status and remoteId; title, externalId, and custom are optional. Return a `nextCursor` to enable `aui.threads.loadMore()`; the runtime will pass it back as `params.after` on the next call.",
|
|
275
314
|
required: true,
|
|
276
315
|
},
|
|
277
316
|
{
|
|
@@ -291,7 +330,7 @@ A few invariants worth knowing when wiring a custom UI on top of `loadMore()`:
|
|
|
291
330
|
name: "updateCustom",
|
|
292
331
|
type: "(remoteId: string, custom: Record<string, unknown> | undefined) => Promise<void>",
|
|
293
332
|
description:
|
|
294
|
-
"Optional. Persist replacement custom metadata from `aui.threadListItem
|
|
333
|
+
"Optional. Persist replacement custom metadata from `aui.threadListItem.updateCustom(custom)`.",
|
|
295
334
|
},
|
|
296
335
|
{
|
|
297
336
|
name: "archive",
|
|
@@ -326,7 +365,7 @@ A few invariants worth knowing when wiring a custom UI on top of `loadMore()`:
|
|
|
326
365
|
},
|
|
327
366
|
{
|
|
328
367
|
name: "unstable_Provider",
|
|
329
|
-
type: "
|
|
368
|
+
type: "RemoteThreadListProviderComponent",
|
|
330
369
|
description:
|
|
331
370
|
"Optional wrapper rendered around each active thread. Inject thread-scoped adapters (history, attachments) here.",
|
|
332
371
|
},
|
|
@@ -361,7 +400,7 @@ function ThreadListItemMeta() {
|
|
|
361
400
|
}
|
|
362
401
|
```
|
|
363
402
|
|
|
364
|
-
`custom` is preserved across `rename`, `archive`, `unarchive`, and `generateTitle`. To replace it from your UI, implement `RemoteThreadListAdapter.updateCustom` and call `aui.threadListItem
|
|
403
|
+
`custom` is preserved across `rename`, `archive`, `unarchive`, and `generateTitle`. To replace it from your UI, implement `RemoteThreadListAdapter.updateCustom` and call `aui.threadListItem.updateCustom(custom)`. The cloud adapter persists this through `cloud.threads.update(threadId, { metadata })`. If your adapter mutates thread metadata through a separate application path, return the updated values from `fetch()` or call `aui.threads.reload()` to re-run `list()`.
|
|
365
404
|
|
|
366
405
|
## ExternalStoreThreadListAdapter
|
|
367
406
|
|
|
@@ -300,6 +300,7 @@ aui-state:[{"type":"set","path":["status"],"value":"completed"}]
|
|
|
300
300
|
initialState: T,
|
|
301
301
|
api: string,
|
|
302
302
|
resumeApi?: string,
|
|
303
|
+
resumeStateApi?: string,
|
|
303
304
|
protocol?: "data-stream" | "assistant-transport",
|
|
304
305
|
converter: (state: T, connectionMetadata: ConnectionMetadata) => AssistantTransportState,
|
|
305
306
|
headers?: Record<string, string> | Headers | (() => Promise<Record<string, string> | Headers>),
|
|
@@ -519,6 +520,23 @@ const runtime = useAssistantTransportRuntime({
|
|
|
519
520
|
});
|
|
520
521
|
```
|
|
521
522
|
|
|
523
|
+
Setting `resumeStateApi` additionally hydrates resumed runs from the server-retained starting state. It requires a sync server that serves the initial-state route; do not configure it against a server without one, since a failing preflight fails the resume closed:
|
|
524
|
+
|
|
525
|
+
```tsx
|
|
526
|
+
const runtime = useAssistantTransportRuntime({
|
|
527
|
+
// ...
|
|
528
|
+
resumeStateApi: "http://localhost:8010/initial-state",
|
|
529
|
+
});
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Before resuming, the runtime posts `{ threadId }` to `resumeStateApi`. The endpoint must return the state that started the active run together with its identity:
|
|
533
|
+
|
|
534
|
+
```json
|
|
535
|
+
{ "runId": "8b3a...", "state": { "messages": [] } }
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
The runtime replaces its local base with this snapshot and includes `runId` in the resume request; the request carries no `state`, since the server replays from the snapshot it retained. A sync server should reject the resume when that ID no longer identifies the same run, preventing replay operations from being applied to a drifted or replaced base state. When no run is active, the endpoint responds `204 No Content` and the runtime skips the resume without raising an error. On a resume, `runId` takes precedence over fields supplied through `body` and survives `prepareSendCommandsRequest`; any `state` either of them supplies is stripped.
|
|
539
|
+
|
|
522
540
|
```tsx
|
|
523
541
|
import { useAui } from "@assistant-ui/react";
|
|
524
542
|
import { useEffect, useRef } from "react";
|
|
@@ -536,8 +554,8 @@ function useResumeOnMount(threadId: string) {
|
|
|
536
554
|
r.json(),
|
|
537
555
|
);
|
|
538
556
|
if (status.isRunning) {
|
|
539
|
-
const parentId = aui.thread
|
|
540
|
-
aui.thread
|
|
557
|
+
const parentId = aui.thread.getState().messages.at(-1)?.id ?? null;
|
|
558
|
+
aui.thread.resumeRun({ parentId });
|
|
541
559
|
}
|
|
542
560
|
})();
|
|
543
561
|
}, [aui, threadId]);
|
|
@@ -207,6 +207,7 @@ Each handler enables a specific UI feature.
|
|
|
207
207
|
| `onEdit` | Message edit button |
|
|
208
208
|
| `onReload` | Regenerate button |
|
|
209
209
|
| `onCancel` | Cancel button while generating |
|
|
210
|
+
| `onRefetchThread` | `threads.reloadMainThread()` refetching the open thread in place |
|
|
210
211
|
| `onAddToolResult` | Client-side tool result handoff |
|
|
211
212
|
| `queue` | Queueing messages sent while a run is in progress |
|
|
212
213
|
|
|
@@ -395,7 +396,9 @@ const runtime = useExternalStoreRuntime({
|
|
|
395
396
|
|
|
396
397
|
By default, sending while the thread is running is disabled. Provide a `queue` adapter to buffer a message sent during a run and process it once the run settles. The pending message is exposed on `composer.queue` and renders through [`ComposerPrimitive.Queue`](/docs/api-reference/primitives/composer).
|
|
397
398
|
|
|
398
|
-
The `createMessageQueue` helper owns the
|
|
399
|
+
The `createMessageQueue` helper owns the two-lane ordering and the in-flight guard: `steerItems` drain before `items`, and within each lane items drain in order. Supply a driver that runs a message, pass its `adapter` to the runtime, and tell the queue when a run starts (`notifyBusy()`, so concurrent sends buffer) and ends (`notifyIdle()`).
|
|
400
|
+
|
|
401
|
+
A hand-rolled adapter must implement the same contract: `items` and `steerItems` expose the lanes (each item carries a required `parts` projection of its content), `enqueue`/`steer` add to a lane, and `move(queueItemId, placement)` repositions with fail-fast anchors — unknown ids or anchors throw rather than being coerced. Individual items are addressable through `composer.queueItem({ id })` (or by index).
|
|
399
402
|
|
|
400
403
|
```tsx
|
|
401
404
|
import { useEffect, useRef, useState } from "react";
|
|
@@ -418,6 +421,26 @@ useEffect(() => {
|
|
|
418
421
|
}, [isRunning, queue]);
|
|
419
422
|
```
|
|
420
423
|
|
|
424
|
+
Queue policy on cancel, edit, and reload is host-owned. Call `queue.notifyCancelled()` in your `onCancel` handler before aborting so the cancelled run's settle pauses draining instead of dispatching the next item (the next send or run start resumes it), or call `queue.clear()` to drop the pending items. Call `queue.clear()` in your `onEdit` and `onReload` handlers so stale items do not drain onto the new branch.
|
|
425
|
+
|
|
426
|
+
```tsx
|
|
427
|
+
const runtime = useExternalStoreRuntime({
|
|
428
|
+
// ...
|
|
429
|
+
onCancel: async () => {
|
|
430
|
+
queue.notifyCancelled(); // or queue.clear() to drop pending items
|
|
431
|
+
await cancelRun();
|
|
432
|
+
},
|
|
433
|
+
onEdit: async (message) => {
|
|
434
|
+
queue.clear();
|
|
435
|
+
// ...
|
|
436
|
+
},
|
|
437
|
+
onReload: async (parentId) => {
|
|
438
|
+
queue.clear();
|
|
439
|
+
// ...
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
421
444
|
## Multi-thread
|
|
422
445
|
|
|
423
446
|
`ExternalStoreRuntime` uses `ExternalStoreThreadListAdapter` (synchronous, inline). See [threads](/docs/runtimes/concepts/threads#externalstorethreadlistadapter) for the contract and best practices on keeping `currentThreadId` in sync with your store.
|
|
@@ -741,7 +764,7 @@ useExternalStoreRuntime({
|
|
|
741
764
|
name: "isSendDisabled",
|
|
742
765
|
type: "boolean",
|
|
743
766
|
description:
|
|
744
|
-
"Blocks new-message sending while leaving the input usable. When true, the thread composer's canSend becomes false, the Send button is disabled, Enter and the steer hotkey are no-ops, and aui.composer
|
|
767
|
+
"Blocks new-message sending while leaving the input usable. When true, the thread composer's canSend becomes false, the Send button is disabled, Enter and the steer hotkey are no-ops, and aui.composer.send() short-circuits. Edit composers (saving message edits) ignore this flag. Use this to gate sending on external React state (e.g. while tools or auth are still loading).",
|
|
745
768
|
default: "false",
|
|
746
769
|
},
|
|
747
770
|
{
|
|
@@ -787,6 +810,12 @@ useExternalStoreRuntime({
|
|
|
787
810
|
type: "() => Promise<void>",
|
|
788
811
|
description: "Handler for cancelling the current generation.",
|
|
789
812
|
},
|
|
813
|
+
{
|
|
814
|
+
name: "onRefetchThread",
|
|
815
|
+
type: "() => Promise<void>",
|
|
816
|
+
description:
|
|
817
|
+
"Handler for re-fetching this thread's state in place, driving threads.reloadMainThread(). Unrelated to onReload, which re-generates an assistant message.",
|
|
818
|
+
},
|
|
790
819
|
{
|
|
791
820
|
name: "onAddToolResult",
|
|
792
821
|
type: "(options: AddToolResultOptions) => Promise<void> | Void",
|
|
@@ -353,7 +353,7 @@ const openai = new OpenAI();
|
|
|
353
353
|
const MyModelAdapter: ChatModelAdapter = {
|
|
354
354
|
async *run({ messages, abortSignal, context }) {
|
|
355
355
|
const stream = await openai.chat.completions.create({
|
|
356
|
-
model: "gpt-5.
|
|
356
|
+
model: "gpt-5.6-luna",
|
|
357
357
|
messages: convertToOpenAIMessages(messages),
|
|
358
358
|
stream: true,
|
|
359
359
|
signal: abortSignal,
|
|
@@ -372,6 +372,34 @@ const MyModelAdapter: ChatModelAdapter = {
|
|
|
372
372
|
|
|
373
373
|
Each yield replaces the previous content. Yield the full state every time, not deltas.
|
|
374
374
|
|
|
375
|
+
### Only the last part streams
|
|
376
|
+
|
|
377
|
+
A text or reasoning part reports `status.type === "running"` only while it is the last part of the message. Every earlier one reads as complete, and the components that react to streaming follow that status: the reasoning disclosure auto-opens and pins its live preview while its part is running, and part status indicators resolve the same way. Tool-call parts are exempt, since their status comes from whether they have a result.
|
|
378
|
+
|
|
379
|
+
That makes an empty trailing part destructive. Yielding `[reasoning, text("")]` to reserve the text slot marks the reasoning part complete the moment the empty text part appears, even though reasoning is still streaming, and nothing in the UI signals why. Add a part on its first token instead of reserving it:
|
|
380
|
+
|
|
381
|
+
```tsx
|
|
382
|
+
let reasoning = "";
|
|
383
|
+
let text = "";
|
|
384
|
+
|
|
385
|
+
for await (const chunk of stream) {
|
|
386
|
+
const delta = chunk.choices[0]?.delta;
|
|
387
|
+
text += delta?.content ?? "";
|
|
388
|
+
// Reasoning deltas are provider-specific; read whichever field yours sends
|
|
389
|
+
reasoning += (delta as { reasoning?: string })?.reasoning ?? "";
|
|
390
|
+
|
|
391
|
+
// Include each part only once it has content
|
|
392
|
+
yield {
|
|
393
|
+
content: [
|
|
394
|
+
...(reasoning ? [{ type: "reasoning" as const, text: reasoning }] : []),
|
|
395
|
+
...(text ? [{ type: "text" as const, text }] : []),
|
|
396
|
+
],
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Guard every text or reasoning part this way, not just the trailing one. An empty part that is never filled also renders as an empty block, and whichever part ends up last is the only one that can report `running`.
|
|
402
|
+
|
|
375
403
|
### Streaming with tool calls
|
|
376
404
|
|
|
377
405
|
Accumulate tool calls in a `Map` outside the streaming loop so they persist across chunks:
|
|
@@ -379,7 +407,7 @@ Accumulate tool calls in a `Map` outside the streaming loop so they persist acro
|
|
|
379
407
|
```tsx
|
|
380
408
|
async *run({ messages, abortSignal, context }) {
|
|
381
409
|
const stream = await openai.chat.completions.create({
|
|
382
|
-
model: "gpt-5.
|
|
410
|
+
model: "gpt-5.6-luna",
|
|
383
411
|
messages: convertToOpenAIMessages(messages),
|
|
384
412
|
tools: context.tools,
|
|
385
413
|
stream: true,
|
|
@@ -415,7 +443,7 @@ If you build the `content` array fresh from the current chunk each iteration, to
|
|
|
415
443
|
|
|
416
444
|
## Tool calling
|
|
417
445
|
|
|
418
|
-
`LocalRuntime` supports OpenAI-compatible function calling. Register tools through `
|
|
446
|
+
`LocalRuntime` supports OpenAI-compatible function calling. Register tools through the provider's `config` so the runtime exposes them to your adapter via `context.tools`:
|
|
419
447
|
|
|
420
448
|
```tsx title="app/toolkit.tsx"
|
|
421
449
|
"use generative";
|
|
@@ -440,15 +468,14 @@ export default defineToolkit({
|
|
|
440
468
|
```
|
|
441
469
|
|
|
442
470
|
```tsx title="app/MyRuntimeProvider.tsx"
|
|
443
|
-
import {
|
|
471
|
+
import { AuiConfig, Tools } from "@assistant-ui/react";
|
|
444
472
|
import toolkit from "./toolkit";
|
|
445
473
|
|
|
446
474
|
function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
|
|
447
475
|
const runtime = useLocalRuntime(MyModelAdapter);
|
|
448
|
-
const
|
|
449
|
-
|
|
476
|
+
const config = AuiConfig({ tools: Tools({ toolkit }) });
|
|
450
477
|
return (
|
|
451
|
-
<AssistantRuntimeProvider
|
|
478
|
+
<AssistantRuntimeProvider runtime={runtime} config={config}>
|
|
452
479
|
{children}
|
|
453
480
|
</AssistantRuntimeProvider>
|
|
454
481
|
);
|
|
@@ -595,7 +622,7 @@ async function* createCustomStream(): AsyncGenerator<ChatModelRunResult> {
|
|
|
595
622
|
};
|
|
596
623
|
}
|
|
597
624
|
|
|
598
|
-
aui.thread
|
|
625
|
+
aui.thread.resumeRun({
|
|
599
626
|
parentId: "message-id",
|
|
600
627
|
stream: createCustomStream,
|
|
601
628
|
});
|
|
@@ -617,8 +644,8 @@ function useStreamReconnect(threadId: string) {
|
|
|
617
644
|
r.json(),
|
|
618
645
|
);
|
|
619
646
|
if (status.isRunning) {
|
|
620
|
-
const parentId = aui.thread
|
|
621
|
-
aui.thread
|
|
647
|
+
const parentId = aui.thread.getState().messages.at(-1)?.id ?? null;
|
|
648
|
+
aui.thread.resumeRun({ parentId });
|
|
622
649
|
}
|
|
623
650
|
})();
|
|
624
651
|
}, [aui, threadId]);
|
|
@@ -627,7 +654,7 @@ function useStreamReconnect(threadId: string) {
|
|
|
627
654
|
|
|
628
655
|
## Queueing messages during a run
|
|
629
656
|
|
|
630
|
-
Set `unstable_enableMessageQueue` to keep the composer usable while a run is in progress. A message sent during a run
|
|
657
|
+
Set `unstable_enableMessageQueue` to keep the composer usable while a run is in progress. A message sent during a queued run steers by default — it lands in the steer lane, ahead of previously queued items; `send({ steer: false })` queues a follow-up behind them instead. Pending items are held in `composer.queue` and drain once the run settles.
|
|
631
658
|
|
|
632
659
|
```tsx
|
|
633
660
|
const runtime = useLocalRuntime(MyModelAdapter, {
|
|
@@ -674,7 +701,7 @@ const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
|
674
701
|
const OpenAIAdapter: ChatModelAdapter = {
|
|
675
702
|
async *run({ messages, abortSignal, context }) {
|
|
676
703
|
const stream = await openai.chat.completions.create({
|
|
677
|
-
model: "gpt-5.
|
|
704
|
+
model: "gpt-5.6-luna",
|
|
678
705
|
messages: messages.map((m) => ({
|
|
679
706
|
role: m.role,
|
|
680
707
|
content: m.content
|
|
@@ -49,6 +49,57 @@ export default function Home() {
|
|
|
49
49
|
}
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
The `custom` bag of a per-turn `runConfig` is forwarded to Eve as
|
|
53
|
+
`clientContext`, so `runConfig: { custom: { page: "/pricing" } }` arrives as
|
|
54
|
+
`clientContext: { page: "/pricing" }`. An empty or absent bag is omitted, and
|
|
55
|
+
every value must be JSON-serializable.
|
|
56
|
+
|
|
57
|
+
Eve turns that object into a model context message, which is why the
|
|
58
|
+
assistant-ui `custom` envelope is unwrapped: the envelope would put a literal
|
|
59
|
+
`custom` key in the prompt. It is page or client context rather than run
|
|
60
|
+
configuration, and it cannot select a model or change how the turn executes.
|
|
61
|
+
Keep secrets, credentials, and personal data out of it, since the model reads
|
|
62
|
+
it as message text.
|
|
63
|
+
|
|
64
|
+
## Connector authorization
|
|
65
|
+
|
|
66
|
+
When Eve pauses for connector authorization, the runtime emits an assistant
|
|
67
|
+
data part named `authorization`. Its data includes `state`, `name`, and the
|
|
68
|
+
available display fields such as `displayName`, `description`, `url`,
|
|
69
|
+
`userCode`, `instructions`, and `expiresAt`. Completed parts may also include
|
|
70
|
+
`outcome` and `reason`. `url` is present only when the connector supplied an
|
|
71
|
+
`http(s)` address; the adapter drops other schemes so a renderer can link to it
|
|
72
|
+
directly.
|
|
73
|
+
|
|
74
|
+
The Eve template ships this renderer as `components/eve-authorization.tsx` and
|
|
75
|
+
mounts it next to `<Thread />`. To add it to an existing app, register a data UI
|
|
76
|
+
for the part and type it with the exported `EveAuthorizationData`:
|
|
77
|
+
|
|
78
|
+
```tsx title="authorization-ui.tsx"
|
|
79
|
+
import type { EveAuthorizationData } from "@assistant-ui/eve";
|
|
80
|
+
import { makeAssistantDataUI } from "@assistant-ui/react";
|
|
81
|
+
|
|
82
|
+
export const AuthorizationUI = makeAssistantDataUI<EveAuthorizationData>({
|
|
83
|
+
name: "authorization",
|
|
84
|
+
render: ({ data }) => (
|
|
85
|
+
<div>
|
|
86
|
+
{data.state === "required" ? (
|
|
87
|
+
<>
|
|
88
|
+
<p>{data.instructions ?? `Sign in to ${data.displayName ?? data.name}`}</p>
|
|
89
|
+
{data.userCode && <code>{data.userCode}</code>}
|
|
90
|
+
{data.url && <a href={data.url}>Continue to sign in</a>}
|
|
91
|
+
</>
|
|
92
|
+
) : (
|
|
93
|
+
<p>{data.outcome ?? "Authorization completed"}</p>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
),
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Mount `<AuthorizationUI />` inside the `AssistantRuntimeProvider` tree. The
|
|
101
|
+
standard thread intentionally leaves unmatched data parts unrendered.
|
|
102
|
+
|
|
52
103
|
## Requirements
|
|
53
104
|
|
|
54
105
|
- Node.js 24 or higher.
|