@assistant-ui/mcp-docs-server 0.2.1 → 0.2.3
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 +3 -3
- package/.docs/organized/code-examples/with-a2a.md +5 -4
- package/.docs/organized/code-examples/with-ag-ui.md +7 -6
- package/.docs/organized/code-examples/with-ai-sdk-v7.md +14 -13
- package/.docs/organized/code-examples/with-artifacts.md +12 -11
- package/.docs/organized/code-examples/with-assistant-transport.md +6 -5
- package/.docs/organized/code-examples/with-browser-extension.md +6 -6
- package/.docs/organized/code-examples/with-chain-of-thought.md +16 -14
- package/.docs/organized/code-examples/with-cloud-standalone.md +7 -7
- package/.docs/organized/code-examples/with-cloud.md +11 -10
- package/.docs/organized/code-examples/with-custom-thread-list.md +11 -10
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +14 -13
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +15 -14
- package/.docs/organized/code-examples/with-eve.md +6 -5
- package/.docs/organized/code-examples/with-expo.md +213 -36
- package/.docs/organized/code-examples/with-external-store.md +6 -5
- package/.docs/organized/code-examples/with-ffmpeg.md +11 -10
- package/.docs/organized/code-examples/with-generative-ui.md +263 -29
- package/.docs/organized/code-examples/with-google-adk.md +5 -4
- package/.docs/organized/code-examples/with-heat-graph.md +3 -3
- package/.docs/organized/code-examples/with-image-generation.md +8 -7
- package/.docs/organized/code-examples/with-interactables.md +11 -10
- package/.docs/organized/code-examples/with-langchain.md +10 -9
- package/.docs/organized/code-examples/with-langgraph.md +7 -6
- package/.docs/organized/code-examples/with-livekit.md +15 -14
- package/.docs/organized/code-examples/with-mcp.md +14 -12
- package/.docs/organized/code-examples/with-nuxt.md +511 -575
- package/.docs/organized/code-examples/with-opencode.md +4 -4
- package/.docs/organized/code-examples/with-openui.md +450 -0
- package/.docs/organized/code-examples/with-pi.md +6 -5
- package/.docs/organized/code-examples/with-react-hook-form.md +16 -13
- package/.docs/organized/code-examples/with-react-ink-web.md +65 -15
- package/.docs/organized/code-examples/with-react-ink.md +5 -4
- package/.docs/organized/code-examples/with-react-router.md +8 -7
- package/.docs/organized/code-examples/with-resumable-stream.md +13 -12
- package/.docs/organized/code-examples/with-store.md +3 -3
- package/.docs/organized/code-examples/with-svelte.md +415 -0
- package/.docs/organized/code-examples/with-sveltekit.md +1061 -0
- package/.docs/organized/code-examples/with-tanstack.md +10 -9
- package/.docs/organized/code-examples/with-tap-runtime.md +5 -4
- package/.docs/organized/code-examples/with-virtualized-thread.md +6 -5
- package/.docs/organized/code-examples/with-vue.md +2 -2
- package/.docs/raw/docs/{(docs) → (getting-started)}/cli.mdx +6 -1
- package/.docs/raw/docs/{(docs) → (getting-started)}/devtools.mdx +1 -1
- package/.docs/raw/docs/{(docs) → (getting-started)}/index.mdx +4 -2
- package/.docs/raw/docs/{(docs) → (getting-started)}/installation.mdx +26 -26
- package/.docs/raw/docs/(reference)/api-reference/adapters/suggestions.mdx +6 -0
- package/.docs/raw/docs/(reference)/api-reference/context-providers/assistant-runtime-provider.mdx +1 -1
- package/.docs/raw/docs/(reference)/api-reference/external-store/message-conversion.mdx +6 -1
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/slack.mdx +5 -1
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/teams.mdx +9 -2
- package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +2 -2
- package/.docs/raw/docs/(reference)/api-reference/integrations/{react-ai-sdk.mdx → ai-sdk.mdx} +43 -18
- package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +61 -2
- package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +3 -3
- package/.docs/raw/docs/(reference)/api-reference/overview.mdx +2 -2
- package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +20 -2
- package/.docs/raw/docs/(reference)/api-reference/tools/interactables.mdx +1 -1
- package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +1 -1
- package/.docs/raw/docs/(reference)/api-reference/transport/frame.mdx +1 -1
- package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +32 -1
- package/.docs/raw/docs/(reference)/api-reference/voice/session.mdx +1 -1
- package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +10 -10
- package/.docs/raw/docs/cloud/ai-sdk.mdx +4 -2
- package/.docs/raw/docs/cloud/index.mdx +1 -1
- package/.docs/raw/docs/copilots/assistant-frame.mdx +19 -8
- package/.docs/raw/docs/guides/attachments.mdx +4 -4
- package/.docs/raw/docs/guides/branching.mdx +1 -1
- package/.docs/raw/docs/guides/chatgpt-subscription.mdx +1 -1
- package/.docs/raw/docs/guides/context-api.mdx +15 -17
- package/.docs/raw/docs/guides/dictation.mdx +2 -2
- package/.docs/raw/docs/guides/electron.mdx +1 -1
- package/.docs/raw/docs/guides/latex.mdx +1 -1
- package/.docs/raw/docs/guides/mentions.mdx +2 -0
- package/.docs/raw/docs/guides/message-timing.mdx +11 -5
- package/.docs/raw/docs/guides/quoting.mdx +1 -1
- package/.docs/raw/docs/guides/resumable-streams.mdx +3 -3
- package/.docs/raw/docs/guides/speech.mdx +1 -1
- package/.docs/raw/docs/guides/suggestions.mdx +8 -5
- package/.docs/raw/docs/guides/voice.mdx +1 -1
- package/.docs/raw/docs/ink/hooks.mdx +1 -1
- package/.docs/raw/docs/ink/index.mdx +3 -2
- package/.docs/raw/docs/ink/migration.mdx +1 -1
- package/.docs/raw/docs/ink/primitives.mdx +1 -1
- package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +3 -3
- package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +3 -3
- package/.docs/raw/docs/integrations/frameworks/{cloudflare-agents/overview.mdx → cloudflare-agents.mdx} +4 -3
- package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +1 -1
- package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +1 -1
- package/.docs/raw/docs/integrations/index.mdx +2 -2
- package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +149 -131
- package/.docs/raw/docs/migrations/toolkit-tools.mdx +2 -2
- package/.docs/raw/docs/migrations/v0-15.mdx +34 -0
- package/.docs/raw/docs/primitives/action-bar.mdx +1 -1
- package/.docs/raw/docs/primitives/assistant-modal.mdx +1 -1
- package/.docs/raw/docs/primitives/attachment.mdx +1 -1
- package/.docs/raw/docs/primitives/branch-picker.mdx +1 -1
- package/.docs/raw/docs/primitives/chain-of-thought.mdx +3 -3
- package/.docs/raw/docs/primitives/composer.mdx +1 -1
- package/.docs/raw/docs/primitives/error.mdx +1 -1
- package/.docs/raw/docs/primitives/message.mdx +1 -1
- package/.docs/raw/docs/primitives/selection-toolbar.mdx +1 -1
- package/.docs/raw/docs/primitives/suggestion.mdx +4 -2
- package/.docs/raw/docs/primitives/thread-list.mdx +1 -1
- package/.docs/raw/docs/primitives/thread.mdx +2 -2
- package/.docs/raw/docs/react-native/adapters.mdx +1 -1
- package/.docs/raw/docs/react-native/index.mdx +2 -2
- package/.docs/raw/docs/react-native/migration.mdx +2 -2
- package/.docs/raw/docs/react-native/primitives.mdx +142 -5
- package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +48 -6
- package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +3 -3
- package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +4 -4
- package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +31 -16
- package/.docs/raw/docs/runtimes/claude-managed-agents.mdx +118 -0
- package/.docs/raw/docs/runtimes/concepts/adapters.mdx +3 -3
- package/.docs/raw/docs/runtimes/concepts/architecture.mdx +3 -3
- package/.docs/raw/docs/runtimes/concepts/stability.mdx +2 -1
- package/.docs/raw/docs/runtimes/concepts/threads.mdx +73 -33
- package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +1 -1
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +6 -2
- package/.docs/raw/docs/runtimes/langchain.mdx +2 -2
- package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +4 -0
- package/.docs/raw/docs/runtimes/opencode/quickstart.mdx +1 -1
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +9 -2
- package/.docs/raw/docs/tools/backend.mdx +4 -4
- package/.docs/raw/docs/tools/defining-tools.mdx +21 -2
- package/.docs/raw/docs/tools/generative-ui-primitive.mdx +180 -0
- package/.docs/raw/docs/tools/generative-ui-slack.mdx +167 -0
- package/.docs/raw/docs/tools/generative-ui-teams.mdx +160 -0
- package/.docs/raw/docs/tools/generative-ui.mdx +224 -211
- package/.docs/raw/docs/tools/index.mdx +2 -1
- package/.docs/raw/docs/tools/interactables.mdx +8 -7
- package/.docs/raw/docs/tools/mcp-apps.mdx +18 -1
- package/.docs/raw/docs/tools/mcp.mdx +2 -2
- package/.docs/raw/docs/tools/openui.mdx +175 -0
- package/.docs/raw/docs/tools/tool-ui.mdx +18 -7
- package/.docs/raw/docs/tools/user-managed-mcp.mdx +7 -3
- package/.docs/raw/docs/ui/assistant-modal.mdx +3 -3
- package/.docs/raw/docs/ui/assistant-sidebar.mdx +1 -1
- package/.docs/raw/docs/ui/attachment.mdx +50 -3
- package/.docs/raw/docs/ui/composer-trigger-popover.mdx +36 -1
- package/.docs/raw/docs/ui/context-display.mdx +1 -1
- package/.docs/raw/docs/ui/directive-text.mdx +1 -1
- package/.docs/raw/docs/ui/file.mdx +2 -2
- package/.docs/raw/docs/ui/follow-up-suggestions.mdx +1 -1
- package/.docs/raw/docs/ui/image.mdx +2 -2
- package/.docs/raw/docs/ui/markdown.mdx +40 -1
- package/.docs/raw/docs/ui/mermaid.mdx +1 -1
- package/.docs/raw/docs/ui/message-timing.mdx +1 -1
- package/.docs/raw/docs/ui/model-selector.mdx +4 -4
- package/.docs/raw/docs/ui/part-grouping.mdx +1 -1
- package/.docs/raw/docs/ui/quote.mdx +3 -3
- package/.docs/raw/docs/ui/reasoning.mdx +31 -3
- package/.docs/raw/docs/ui/scrollbar.mdx +1 -1
- package/.docs/raw/docs/ui/sources.mdx +10 -1
- package/.docs/raw/docs/ui/streamdown.mdx +2 -2
- package/.docs/raw/docs/ui/syntax-highlighting.mdx +1 -1
- package/.docs/raw/docs/ui/thread-list.mdx +40 -1
- package/.docs/raw/docs/ui/thread.mdx +49 -2
- package/.docs/raw/docs/ui/tool-fallback.mdx +23 -9
- package/.docs/raw/docs/ui/tool-group.mdx +1 -1
- package/.docs/raw/docs/ui/voice.mdx +1 -1
- package/.docs/raw/docs/utilities/react-o11y.mdx +2 -2
- package/dist/xulux/catalog-client.d.ts.map +1 -1
- package/dist/xulux/catalog-client.js +51 -6
- package/dist/xulux/catalog-client.js.map +1 -1
- package/dist/xulux/types.d.ts +7 -7
- package/dist/xulux/types.d.ts.map +1 -1
- package/dist/xulux/types.js.map +1 -1
- package/package.json +5 -5
- package/src/tools/tests/docs.test.ts +10 -7
- package/src/tools/tests/path-traversal.test.ts +5 -2
- package/src/tools/tests/xulux-templates.test.ts +63 -0
- package/src/xulux/catalog-client.ts +68 -23
- package/src/xulux/types.ts +17 -13
- package/.docs/raw/docs/tools/interactables-legacy.mdx +0 -423
- package/.docs/raw/docs/ui/accordion.mdx +0 -266
- package/.docs/raw/docs/ui/badge.mdx +0 -150
- package/.docs/raw/docs/ui/diff-viewer.mdx +0 -280
- package/.docs/raw/docs/ui/dot-matrix.mdx +0 -133
- package/.docs/raw/docs/ui/number-roll.mdx +0 -154
- package/.docs/raw/docs/ui/select.mdx +0 -254
- package/.docs/raw/docs/ui/tabs.mdx +0 -271
- /package/.docs/raw/docs/{(docs) → (getting-started)}/architecture.mdx +0 -0
- /package/.docs/raw/docs/{(docs) → (getting-started)}/base-ui.mdx +0 -0
- /package/.docs/raw/docs/{(docs) → (getting-started)}/llm.mdx +0 -0
- /package/.docs/raw/docs/{(docs) → (getting-started)}/rtl.mdx +0 -0
|
@@ -48,17 +48,17 @@ npm init -y
|
|
|
48
48
|
<PlatformTabs>
|
|
49
49
|
<Tab value="React">
|
|
50
50
|
|
|
51
|
-
<InstallCommand npm={["@assistant-ui/react", "@assistant-ui/
|
|
51
|
+
<InstallCommand npm={["@assistant-ui/react", "@assistant-ui/ai-sdk", "ai@^7", "@ai-sdk/react@^4", "@ai-sdk/openai", "zod"]} />
|
|
52
52
|
|
|
53
53
|
</Tab>
|
|
54
54
|
<Tab value="React Native">
|
|
55
55
|
|
|
56
|
-
<InstallCommand expo={["@assistant-ui/react-native", "@assistant-ui/
|
|
56
|
+
<InstallCommand expo={["@assistant-ui/react-native", "@assistant-ui/ai-sdk", "ai@^7", "@ai-sdk/react@^4", "@ai-sdk/openai", "zod"]} />
|
|
57
57
|
|
|
58
58
|
</Tab>
|
|
59
59
|
<Tab value="React Ink">
|
|
60
60
|
|
|
61
|
-
<InstallCommand npm={["@assistant-ui/react-ink", "@assistant-ui/
|
|
61
|
+
<InstallCommand npm={["@assistant-ui/react-ink", "@assistant-ui/ai-sdk", "ai@^7", "@ai-sdk/react@^4", "@ai-sdk/openai", "zod", "ink", "react"]} />
|
|
62
62
|
|
|
63
63
|
</Tab>
|
|
64
64
|
</PlatformTabs>
|
|
@@ -122,7 +122,7 @@ export async function POST(req: Request) {
|
|
|
122
122
|
|
|
123
123
|
import { Thread } from "@/components/assistant-ui/thread";
|
|
124
124
|
import { AssistantRuntimeProvider } from "@assistant-ui/react";
|
|
125
|
-
import { useChatRuntime } from "@assistant-ui/
|
|
125
|
+
import { useChatRuntime } from "@assistant-ui/ai-sdk";
|
|
126
126
|
|
|
127
127
|
export default function Home() {
|
|
128
128
|
const runtime = useChatRuntime();
|
|
@@ -144,7 +144,7 @@ import { AssistantRuntimeProvider } from "@assistant-ui/react-native";
|
|
|
144
144
|
import {
|
|
145
145
|
AssistantChatTransport,
|
|
146
146
|
useChatRuntime,
|
|
147
|
-
} from "@assistant-ui/
|
|
147
|
+
} from "@assistant-ui/ai-sdk";
|
|
148
148
|
import { View } from "react-native";
|
|
149
149
|
import { Thread } from "@/components/assistant-ui/thread";
|
|
150
150
|
|
|
@@ -174,7 +174,7 @@ import { AssistantRuntimeProvider } from "@assistant-ui/react-ink";
|
|
|
174
174
|
import {
|
|
175
175
|
AssistantChatTransport,
|
|
176
176
|
useChatRuntime,
|
|
177
|
-
} from "@assistant-ui/
|
|
177
|
+
} from "@assistant-ui/ai-sdk";
|
|
178
178
|
import { Box } from "ink";
|
|
179
179
|
import { Thread } from "./components/thread.js";
|
|
180
180
|
|
|
@@ -231,7 +231,7 @@ Follow the [Ink setup](/docs/ink) to add a terminal Thread, composer, and suppor
|
|
|
231
231
|
import { openai } from "@ai-sdk/openai";
|
|
232
232
|
import { streamText, convertToModelMessages, zodSchema, createUIMessageStreamResponse, toUIMessageStream } from "ai";
|
|
233
233
|
import type { UIMessage } from "ai";
|
|
234
|
-
import { frontendTools } from "@assistant-ui/
|
|
234
|
+
import { frontendTools } from "@assistant-ui/ai-sdk";
|
|
235
235
|
|
|
236
236
|
export async function POST(req: Request) {
|
|
237
237
|
const {
|
|
@@ -341,7 +341,7 @@ import { useChat } from "@ai-sdk/react";
|
|
|
341
341
|
import {
|
|
342
342
|
AssistantRuntimeProvider,
|
|
343
343
|
useAISDKRuntime,
|
|
344
|
-
} from "@assistant-ui/
|
|
344
|
+
} from "@assistant-ui/ai-sdk";
|
|
345
345
|
import { lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
|
|
346
346
|
import { Thread } from "@/components/assistant-ui/thread";
|
|
347
347
|
|
|
@@ -372,7 +372,7 @@ import {
|
|
|
372
372
|
defineToolkit,
|
|
373
373
|
Tools,
|
|
374
374
|
} from "@assistant-ui/react";
|
|
375
|
-
import { useAISDKRuntime } from "@assistant-ui/
|
|
375
|
+
import { useAISDKRuntime } from "@assistant-ui/ai-sdk";
|
|
376
376
|
import { lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
|
|
377
377
|
import { Thread } from "@/components/assistant-ui/thread";
|
|
378
378
|
|
|
@@ -432,7 +432,7 @@ import {
|
|
|
432
432
|
createUIMessageStreamResponse,
|
|
433
433
|
toUIMessageStream,
|
|
434
434
|
} from "ai";
|
|
435
|
-
import { injectQuoteContext } from "@assistant-ui/
|
|
435
|
+
import { injectQuoteContext } from "@assistant-ui/ai-sdk";
|
|
436
436
|
import { openai } from "@ai-sdk/openai";
|
|
437
437
|
|
|
438
438
|
export async function POST(req: Request) {
|
|
@@ -455,7 +455,7 @@ export async function POST(req: Request) {
|
|
|
455
455
|
|
|
456
456
|
```ts title="@/app/api/chat/route.ts"
|
|
457
457
|
import { streamText, convertToModelMessages, createUIMessageStreamResponse, toUIMessageStream } from "ai";
|
|
458
|
-
import { frontendTools } from "@assistant-ui/
|
|
458
|
+
import { frontendTools } from "@assistant-ui/ai-sdk";
|
|
459
459
|
|
|
460
460
|
export async function POST(req: Request) {
|
|
461
461
|
const { messages, tools, config } = await req.json();
|
|
@@ -484,7 +484,7 @@ export async function POST(req: Request) {
|
|
|
484
484
|
```tsx title="@/components/TokenCounter.tsx"
|
|
485
485
|
"use client";
|
|
486
486
|
|
|
487
|
-
import { useThreadTokenUsage } from "@assistant-ui/
|
|
487
|
+
import { useThreadTokenUsage } from "@assistant-ui/ai-sdk";
|
|
488
488
|
|
|
489
489
|
export function TokenCounter() {
|
|
490
490
|
const usage = useThreadTokenUsage();
|
|
@@ -539,7 +539,7 @@ For server-side cloud persistence with zero adapter code, see the [AssistantClou
|
|
|
539
539
|
```tsx
|
|
540
540
|
"use client";
|
|
541
541
|
|
|
542
|
-
import { useChatRuntime } from "@assistant-ui/
|
|
542
|
+
import { useChatRuntime } from "@assistant-ui/ai-sdk";
|
|
543
543
|
import type { ThreadHistoryAdapter } from "@assistant-ui/react";
|
|
544
544
|
|
|
545
545
|
const historyAdapter: ThreadHistoryAdapter = {
|
|
@@ -585,7 +585,7 @@ Each persisted row follows `{ id, parent_id, format, content }`. `fmt.encode` pr
|
|
|
585
585
|
import {
|
|
586
586
|
useChatRuntime,
|
|
587
587
|
AssistantChatTransport,
|
|
588
|
-
} from "@assistant-ui/
|
|
588
|
+
} from "@assistant-ui/ai-sdk";
|
|
589
589
|
|
|
590
590
|
const runtime = useChatRuntime({
|
|
591
591
|
transport: new AssistantChatTransport({ api: "/my-custom-api/chat" }),
|
|
@@ -644,13 +644,28 @@ const runtime = useChatRuntime({
|
|
|
644
644
|
|
|
645
645
|
When omitted, `resumeRun` throws `"Runtime does not support resuming runs."`. For streams that should survive a page reload without a custom channel, use the built-in transport-level path instead: see [Resumable streams](/docs/guides/resumable-streams), which reconnects automatically on mount and reports failures via `onResumeError`.
|
|
646
646
|
|
|
647
|
+
### messageRepository
|
|
648
|
+
|
|
649
|
+
Import a branch-aware AI SDK message tree once, and only when `useChat` is empty. After that seed, live updates come only from `useChat`. Clearing the chat or passing a new object identity does not reload the tree. Persisted threads that already go through `adapters.history` do not need this.
|
|
650
|
+
|
|
651
|
+
```tsx
|
|
652
|
+
const runtime = useChatRuntime({
|
|
653
|
+
messageRepository: storedTree,
|
|
654
|
+
unstable_onBranchChange: ({ headId }) => {
|
|
655
|
+
saveSelectedBranch(headId);
|
|
656
|
+
},
|
|
657
|
+
});
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
`unstable_onBranchChange` fires after an explicit `switchToBranch` (for example a BranchPicker click). It complements `setMessages` and does not enable switching by itself. The callback is unstable and may change without notice.
|
|
661
|
+
|
|
647
662
|
## Advanced: useAISDKRuntime
|
|
648
663
|
|
|
649
664
|
When you need direct access to the `useChat` instance (e.g. to share it with non-assistant-ui code), drop down to `useAISDKRuntime`:
|
|
650
665
|
|
|
651
666
|
```tsx
|
|
652
667
|
import { useChat } from "@ai-sdk/react";
|
|
653
|
-
import { useAISDKRuntime } from "@assistant-ui/
|
|
668
|
+
import { useAISDKRuntime } from "@assistant-ui/ai-sdk";
|
|
654
669
|
|
|
655
670
|
const chat = useChat();
|
|
656
671
|
const runtime = useAISDKRuntime(chat);
|
|
@@ -658,7 +673,7 @@ const runtime = useAISDKRuntime(chat);
|
|
|
658
673
|
|
|
659
674
|
`useAISDKRuntime` does not provide `cloud` or the higher-level adapter slots; those are part of `useChatRuntime`. If you need both your own `useChat` access AND cloud thread support, you generally want `useChatRuntime` and to read the chat state through assistant-ui hooks instead.
|
|
660
675
|
|
|
661
|
-
`useAISDKRuntime` accepts `joinStrategy` and `
|
|
676
|
+
`useAISDKRuntime` accepts `joinStrategy`, `onResume`, `messageRepository`, and `unstable_onBranchChange` (see [Runtime options](#runtime-options)) plus one option of its own, `cancelPendingToolCallsOnSend`:
|
|
662
677
|
|
|
663
678
|
```tsx
|
|
664
679
|
const runtime = useAISDKRuntime(chat, {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Claude Managed Agents
|
|
3
|
+
description: Connect Anthropic's Managed Agents sessions to assistant-ui with the external store runtime, folding the session event log into messages, rendering approval gates, and using sessions as the thread list.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
[Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) is Anthropic's hosted agent platform: Anthropic runs the agent loop and provisions a sandboxed container per session, and your client drives the session over an event stream. A session holds the entire conversation server-side as a durable event log, streams every step (text, tool calls, approval stops, status) as typed events, and accepts user messages and tool confirmations back.
|
|
7
|
+
|
|
8
|
+
That shape maps directly onto two assistant-ui primitives, with no adapter package in between:
|
|
9
|
+
|
|
10
|
+
- [`useExternalStoreRuntime`](/docs/runtimes/custom/external-store) renders messages you derive from the session's event log. The log is the single source of truth, so replaying a stored session and tailing a live one run through the same pure function and can never disagree.
|
|
11
|
+
- [`useRemoteThreadListRuntime`](/docs/api-reference/hooks/runtimes#useremotethreadlistruntime) turns the session list into the thread sidebar. A thread is a session; there is no conversations table anywhere in the app.
|
|
12
|
+
|
|
13
|
+
Anthropic ships an official reference implementation of this integration: the [Claude Managed Agents quickstart](https://github.com/anthropics/claude-quickstarts/tree/main/managed-agents/assistant-ui) is a complete Next.js app (composer, thread, sidebar, tool cards, approval gate) built exactly this way. This page teaches the pattern; the quickstart is the runnable proof.
|
|
14
|
+
|
|
15
|
+
<Callout type="info">
|
|
16
|
+
Managed Agents is a beta API (`managed-agents-2026-04-01`; the SDK sets the header automatically). The quickstart declares `@anthropic-ai/sdk` `^0.113.0` (the session event helpers and token previews need 0.109.0 or later), and `@assistant-ui/react` 0.14.27 or later for the toolkit API and the approval gate on the external store runtime.
|
|
17
|
+
</Callout>
|
|
18
|
+
|
|
19
|
+
## The event-to-message mapping
|
|
20
|
+
|
|
21
|
+
Everything the session emits arrives as a typed event. The integration is one pure fold from the event array to assistant-ui's message model:
|
|
22
|
+
|
|
23
|
+
| Managed Agents event | assistant-ui |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `user.message` | A user message |
|
|
26
|
+
| `agent.message` | Assistant text (buffered, authoritative) |
|
|
27
|
+
| `event_start` / `event_delta` | The same text, streamed early as token previews |
|
|
28
|
+
| `agent.thinking` | A reasoning part (progress signal only; the API sends no reasoning text) |
|
|
29
|
+
| `agent.tool_use` / `agent.mcp_tool_use` / `agent.custom_tool_use` | A tool-call part; the `toolCallId` is the event id |
|
|
30
|
+
| `agent.tool_result` (and mcp / custom variants) | That part's result |
|
|
31
|
+
| `session.status_idle` with `stop_reason: requires_action` | `requires-action` message status, plus an approval on each blocked tool part |
|
|
32
|
+
| `user.tool_confirmation` | The approval, settled (allowed or denied) |
|
|
33
|
+
| `session.status_running` / `status_idle` | Whether the turn is live (`isRunning`) |
|
|
34
|
+
| `session.error` | An error status on the message, or a retry banner |
|
|
35
|
+
|
|
36
|
+
Because the fold is pure, opening an old chat replays `sessions.events.list()` through it, and a live chat feeds the SSE tail through it, and the two paths cannot render differently. Approvals, denials, and charts all come back after a reload because they are in the log, not in browser state.
|
|
37
|
+
|
|
38
|
+
The runtime wiring is a structural subset of `ThreadMessageLike`, handed to the external store as-is:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
const runtime = useExternalStoreRuntime<ThreadMessageLike>({
|
|
42
|
+
messages: snapshot.messages,
|
|
43
|
+
convertMessage: (m) => m,
|
|
44
|
+
isRunning: isBusy(snapshot),
|
|
45
|
+
|
|
46
|
+
onNew: async (message) => {
|
|
47
|
+
const id = await ensureSession();
|
|
48
|
+
await sendMessage(id, textOf(message));
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// The Stop button becomes a real server-side interrupt.
|
|
52
|
+
onCancel: async () => controller.interrupt(),
|
|
53
|
+
|
|
54
|
+
// The Allow / Deny click on a gated tool call. approvalId is the
|
|
55
|
+
// tool_use event id from session.status_idle { requires_action }.
|
|
56
|
+
onRespondToToolApproval: async ({ approvalId, approved, reason }) => {
|
|
57
|
+
controller.respondToApproval(approvalId, approved, reason);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Sessions are the thread list
|
|
63
|
+
|
|
64
|
+
The sidebar is a `RemoteThreadListAdapter` over the Managed Agents session API. Thread id and session id are the same string, so nothing maps between the two worlds:
|
|
65
|
+
|
|
66
|
+
| Adapter method | Managed Agents call |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `list` | `sessions.list()`, filtered to the sessions this app created (a `metadata` tag) |
|
|
69
|
+
| `initialize` | `sessions.create()`, invoked by assistant-ui on the first message of a new chat |
|
|
70
|
+
| `rename` | `sessions.update({ title })` |
|
|
71
|
+
| `archive` | `sessions.archive()` |
|
|
72
|
+
| `unarchive` | Throws. Managed Agents sessions cannot be unarchived, and switching to an archived thread auto-unarchives by default, so do not render archived sessions as switchable (the quickstart's ownership gate rejects archived ids outright) |
|
|
73
|
+
| `delete` | `sessions.delete()` |
|
|
74
|
+
| `fetch` | `sessions.retrieve()` |
|
|
75
|
+
| `generateTitle` | Reads the title back after the server retitles the session from the first message, so the sidebar row updates without a second model call |
|
|
76
|
+
|
|
77
|
+
A brand-new chat has no session until the first send: the composer works immediately, and `initialize()` creates the session lazily when the first message (or first attachment upload) needs one. Kill the server, restart, reload, and every conversation comes back, because none of it ever lived in the app.
|
|
78
|
+
|
|
79
|
+
## The approval gate
|
|
80
|
+
|
|
81
|
+
Managed Agents supports per-tool [permission policies](https://platform.claude.com/docs/en/managed-agents/permission-policies). A tool configured as `always_ask` (the quickstart gates `bash` this way) does not run when the agent reaches for it. The session emits the `agent.tool_use` event, then parks:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{ "type": "session.status_idle", "stop_reason": { "type": "requires_action", "event_ids": ["sevt_..."] } }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The fold stamps an approval onto that tool part and sets the message status to `requires-action`, which is everything assistant-ui needs to render Allow and Deny on the tool card. The click flows back through `onRespondToToolApproval` as a `user.tool_confirmation` event:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{ "type": "user.tool_confirmation", "tool_use_id": "sevt_...", "result": "deny", "deny_message": "Not on this box." }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Two wire details matter. The `tool_use_id` is the tool-use event id, not an Anthropic `toolu_` id. And a denial reaches the agent as the tool's result, so it adjusts course instead of retrying the same command.
|
|
94
|
+
|
|
95
|
+
Custom client-executed tools ride the same `requires_action` stop but take a `user.custom_tool_result` instead of a confirmation; sending a confirmation for one is a 400. The quickstart's inline chart tool is the worked example: the session parks, the card renders the chart from the tool's input, and the client answers so the agent continues.
|
|
96
|
+
|
|
97
|
+
## Token streaming
|
|
98
|
+
|
|
99
|
+
By default assistant text arrives as whole `agent.message` events when a model request finishes. Opting the stream into `event_deltas: ["agent.message", "agent.thinking"]` adds token previews: an `event_start` announces the upcoming event, `event_delta` fragments stream the text, and the buffered event lands last as the authoritative record. Concatenating a preview's deltas in arrival order yields a prefix of the final text, but under load the server may shed the remaining deltas for an event, so the prefix is not necessarily the whole message. The fold therefore appends fragments for display and discards the accumulated preview when the buffered event arrives; never treat a preview as final. When a turn errors or is interrupted, the buffered event may never arrive at all, but `span.model_request_end` still does, so close any unreconciled preview when you see it.
|
|
100
|
+
|
|
101
|
+
Previews are best-effort and gated per organization. Build against the buffered events and treat deltas as an enhancement: an org without the streaming gate runs the identical code path with replies arriving whole.
|
|
102
|
+
|
|
103
|
+
## Security boundary
|
|
104
|
+
|
|
105
|
+
The Anthropic API key stays server-side; the browser talks only to your own route handlers, which relay to Managed Agents. Because a session id arrives from the browser and becomes an API path parameter, validate ownership on every route: the id must resolve, belong to your agent, and carry your app's metadata tag before any read or write. The API key can see the whole workspace; that gate is what keeps a guessed id from reading it. The quickstart's [`ownedSession()`](https://github.com/anthropics/claude-quickstarts/blob/main/managed-agents/assistant-ui/lib/owned-session.ts) is the reference shape.
|
|
106
|
+
|
|
107
|
+
## Run the reference
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
git clone https://github.com/anthropics/claude-quickstarts
|
|
111
|
+
cd claude-quickstarts/managed-agents/assistant-ui
|
|
112
|
+
npm install
|
|
113
|
+
cp .env.example .env # add ANTHROPIC_API_KEY, or `ant auth login` once
|
|
114
|
+
npm run setup # one-time: creates the agent + environment, paste the IDs into .env
|
|
115
|
+
npm run dev # drop sample_data/sales.csv into the chat
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The quickstart's [README](https://github.com/anthropics/claude-quickstarts/tree/main/managed-agents/assistant-ui#readme) walks through every file: the reducer, the session controller, the thread list adapter, the tool cards, and the attachment adapter that uploads composer files into the session sandbox. For the platform itself, start with Anthropic's [Managed Agents overview](https://platform.claude.com/docs/en/managed-agents/overview) and [events reference](https://platform.claude.com/docs/en/managed-agents/events-and-streaming).
|
|
@@ -21,7 +21,7 @@ const runtime = useChatRuntime({ adapters: { attachments, history } });
|
|
|
21
21
|
|
|
22
22
|
## Support matrix
|
|
23
23
|
|
|
24
|
-
| Adapter | LocalRuntime | ExternalStoreRuntime | DataStream | AssistantTransport |
|
|
24
|
+
| Adapter | LocalRuntime | ExternalStoreRuntime | DataStream | AssistantTransport | ai-sdk | react-langgraph | react-langchain | react-google-adk | react-a2a | react-ag-ui | react-opencode |
|
|
25
25
|
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
26
26
|
| Attachments | Yes | Yes | Yes | Yes | Yes | (via thread state) | (via thread state) | Yes | Yes | Yes | (no) |
|
|
27
27
|
| Speech | Yes | Yes | Yes | (no) | Yes | Yes | Yes | Yes | Yes | Yes | (no) |
|
|
@@ -173,7 +173,7 @@ const feedbackAdapter: FeedbackAdapter = {
|
|
|
173
173
|
|
|
174
174
|
## History adapter
|
|
175
175
|
|
|
176
|
-
Per-thread message persistence. Used by `LocalRuntime` and adapters built on it (`
|
|
176
|
+
Per-thread message persistence. Used by `LocalRuntime` and adapters built on it (`ai-sdk`, `react-google-adk`, `react-a2a`, `useDataStreamRuntime`).
|
|
177
177
|
|
|
178
178
|
`ExternalStoreRuntime` does not use a history adapter directly, since you already own the message array. Persist via your store instead. `react-langgraph` and `react-langchain` source persistence from server-side thread state, exposed through their `load` callbacks.
|
|
179
179
|
|
|
@@ -196,7 +196,7 @@ type ThreadHistoryAdapter = {
|
|
|
196
196
|
`load` runs when a thread opens. `append` runs after each message completes.
|
|
197
197
|
|
|
198
198
|
<Callout type="info">
|
|
199
|
-
`
|
|
199
|
+
`ai-sdk` requires `withFormat` so messages round-trip as AI SDK `UIMessage` objects. An adapter without `withFormat` throws at runtime in the AI SDK path. See the [AI SDK history docs](/docs/runtimes/ai-sdk/v7) for the full pattern.
|
|
200
200
|
</Callout>
|
|
201
201
|
|
|
202
202
|
## Suggestion adapter
|
|
@@ -10,7 +10,7 @@ assistant-ui exposes runtime integrations at three layers. Understanding which l
|
|
|
10
10
|
<Flow.Root
|
|
11
11
|
llm={`graph TD
|
|
12
12
|
subgraph Framework["Framework adapters"]
|
|
13
|
-
A1[
|
|
13
|
+
A1[ai-sdk]
|
|
14
14
|
A2[react-langgraph]
|
|
15
15
|
A3[react-langchain]
|
|
16
16
|
A4[react-google-adk]
|
|
@@ -58,7 +58,7 @@ assistant-ui exposes runtime integrations at three layers. Understanding which l
|
|
|
58
58
|
<Flow.Group flowId="adapters">
|
|
59
59
|
<Flow.GroupLabel>Framework adapters</Flow.GroupLabel>
|
|
60
60
|
<Flow.Row className="flex-wrap justify-start">
|
|
61
|
-
<Flow.Node>
|
|
61
|
+
<Flow.Node>ai-sdk</Flow.Node>
|
|
62
62
|
<Flow.Node>react-langgraph</Flow.Node>
|
|
63
63
|
<Flow.Node>react-langchain</Flow.Node>
|
|
64
64
|
<Flow.Node>react-google-adk</Flow.Node>
|
|
@@ -118,7 +118,7 @@ The fastest path. Each adapter wraps one of the core or protocol layers and adds
|
|
|
118
118
|
|
|
119
119
|
| Adapter | Layered on | Targets |
|
|
120
120
|
| --- | --- | --- |
|
|
121
|
-
| `
|
|
121
|
+
| `ai-sdk` | `ExternalStoreRuntime` | Vercel AI SDK v7 (`useChat`) |
|
|
122
122
|
| `react-langgraph` | `ExternalStoreRuntime` | LangGraph Cloud via `@langchain/langgraph-sdk` |
|
|
123
123
|
| `react-langchain` | `ExternalStoreRuntime` | LangGraph Cloud via `@langchain/react`'s `useStream` |
|
|
124
124
|
| `react-google-adk` | `ExternalStoreRuntime` | Google ADK JS or Python agents |
|
|
@@ -33,7 +33,8 @@ A non-exhaustive list of `unstable_` exports surfaced in the runtime docs.
|
|
|
33
33
|
| `unstable_humanToolNames` | `@assistant-ui/react` | Tool names that pause the run until a result is added via `addResult`. Only available on LocalRuntime; not supported in DataStream. |
|
|
34
34
|
| `unstable_threadListAdapter` | `@assistant-ui/react-langgraph` | LangGraph thread-list adapter slot on `useLangGraphRuntime`. |
|
|
35
35
|
| `unstable_createLangGraphStream` | `@assistant-ui/react-langgraph` | End-to-end cancellation primitive. |
|
|
36
|
-
| `unstable_Provider` | Various adapters |
|
|
36
|
+
| `unstable_Provider` | Various adapters | React-component face on `RemoteThreadListAdapter`. Must render children synchronously. `useRemoteThreadListRuntime` renders it when present; the `RemoteThreadList` store entry ignores it. |
|
|
37
|
+
| `unstable_useAdapters` | Various adapters | Hook face on `RemoteThreadListAdapter`. The `RemoteThreadList` store entry calls it. `useRemoteThreadListRuntime` synthesizes a `RuntimeAdapterProvider` from it when `unstable_Provider` is omitted. |
|
|
37
38
|
| `unstable_capabilities` | `ExternalStoreRuntime` | Toggle copy and other thread capabilities. |
|
|
38
39
|
| `unstable_state`, `unstable_annotations`, `unstable_data` | Message metadata | Runtime-internal fields exposed for advanced use cases. |
|
|
39
40
|
| `unstable_assistantMessageId`, `unstable_threadId`, `unstable_parentId`, `unstable_getMessage` | `ChatModelRunOptions` | Identifiers and accessors passed to your `ChatModelAdapter.run`. |
|
|
@@ -37,10 +37,11 @@ const cloud = new AssistantCloud({
|
|
|
37
37
|
const runtime = useLocalRuntime(modelAdapter, { cloud });
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Framework adapters take `cloud` directly
|
|
40
|
+
Framework adapters take `cloud` directly. `AISDKThreads({ cloud })` is the store-entry list for `AuiConfig` hosts. It mounts only the visible thread, so a switch cancels an in-flight run and that exchange is not persisted.
|
|
41
41
|
|
|
42
42
|
```tsx
|
|
43
43
|
const runtime = useChatRuntime({ cloud });
|
|
44
|
+
const threads = AISDKThreads({ cloud });
|
|
44
45
|
const runtime = useLangGraphRuntime({ cloud /* stream, load, ... */ });
|
|
45
46
|
const runtime = useAdkRuntime({ cloud, stream });
|
|
46
47
|
```
|
|
@@ -49,9 +50,9 @@ See the [cloud documentation](/docs/cloud) for setup, auth, and self-host option
|
|
|
49
50
|
|
|
50
51
|
## RemoteThreadListRuntime (custom database)
|
|
51
52
|
|
|
52
|
-
`useRemoteThreadListRuntime` lets you back the thread list with any database while keeping the per-thread runtime simple. You provide a `RemoteThreadListAdapter` describing how to list, create, rename, archive, and delete threads.
|
|
53
|
+
`useRemoteThreadListRuntime` lets you back the thread list with any database while keeping the per-thread runtime simple. You provide a `RemoteThreadListAdapter` describing how to list, create, rename, archive, and delete threads. Keep that adapter reference stable across renders (module scope or `useMemo`). Replacing it reloads the list and drops cached threads that are not in the replacement page.
|
|
53
54
|
|
|
54
|
-
Works with any `LocalRuntime`-based runtime, including framework adapters that build on it (`
|
|
55
|
+
Works with any `LocalRuntime`-based runtime, including framework adapters that build on it (`ai-sdk`, `react-google-adk`, `react-a2a`, `useDataStreamRuntime`).
|
|
55
56
|
|
|
56
57
|
```tsx title="app/MyProvider.tsx"
|
|
57
58
|
"use client";
|
|
@@ -130,44 +131,68 @@ export function MyProvider({ children }: { children: React.ReactNode }) {
|
|
|
130
131
|
}
|
|
131
132
|
```
|
|
132
133
|
|
|
133
|
-
### Persisting messages
|
|
134
|
+
### Persisting messages
|
|
134
135
|
|
|
135
|
-
`RemoteThreadListAdapter` only manages thread metadata.
|
|
136
|
+
`RemoteThreadListAdapter` only manages thread metadata. Per-thread history and attachments are a separate seam with two faces:
|
|
137
|
+
|
|
138
|
+
- `unstable_useAdapters` is a hook. The `RemoteThreadList` store entry calls it inside the client tree, so any `createAssistantClient` host gets the same adapters as a React hook host. `useRemoteThreadListRuntime` also calls it when `unstable_Provider` is omitted.
|
|
139
|
+
- `unstable_Provider` is a React component. `useRemoteThreadListRuntime` renders it when present. The store entry ignores it.
|
|
140
|
+
|
|
141
|
+
On the store entry, wrap the thread factory with `withKey` so the thread remounts on a switch. History adapters load once per mount. An unkeyed factory keeps one instance, and the next thread's messages never appear.
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { withKey } from "@assistant-ui/tap";
|
|
145
|
+
import { RemoteThreadList } from "@assistant-ui/react";
|
|
146
|
+
|
|
147
|
+
threads: RemoteThreadList({
|
|
148
|
+
adapter,
|
|
149
|
+
thread: (id) => withKey(id, MyThread({ threadId: id })),
|
|
150
|
+
}),
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Share one hook between both faces:
|
|
136
154
|
|
|
137
155
|
```tsx
|
|
138
156
|
import {
|
|
139
157
|
RuntimeAdapterProvider,
|
|
140
158
|
useAui,
|
|
159
|
+
type RemoteThreadListAdapter,
|
|
141
160
|
type ThreadHistoryAdapter,
|
|
142
161
|
} from "@assistant-ui/react";
|
|
143
162
|
import { useMemo } from "react";
|
|
144
163
|
|
|
164
|
+
function useThreadListAdapters() {
|
|
165
|
+
const aui = useAui();
|
|
166
|
+
const history = useMemo<ThreadHistoryAdapter>(
|
|
167
|
+
() => ({
|
|
168
|
+
async load() {
|
|
169
|
+
const { remoteId } = aui.threadListItem.getState();
|
|
170
|
+
if (!remoteId) return { messages: [] };
|
|
171
|
+
const rows = await fetch(
|
|
172
|
+
`/api/threads/${remoteId}/messages`,
|
|
173
|
+
).then((r) => r.json());
|
|
174
|
+
return { messages: rows.map(toThreadMessage) };
|
|
175
|
+
},
|
|
176
|
+
async append({ message, parentId }) {
|
|
177
|
+
const { remoteId } = await aui.threadListItem.initialize();
|
|
178
|
+
await fetch(`/api/threads/${remoteId}/messages`, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
body: JSON.stringify({ message, parentId }),
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
}),
|
|
184
|
+
[aui],
|
|
185
|
+
);
|
|
186
|
+
return useMemo(() => ({ history }), [history]);
|
|
187
|
+
}
|
|
188
|
+
|
|
145
189
|
const adapterWithHistory: RemoteThreadListAdapter = {
|
|
146
190
|
// ...metadata methods above...
|
|
191
|
+
unstable_useAdapters: useThreadListAdapters,
|
|
147
192
|
unstable_Provider({ children }) {
|
|
148
|
-
const
|
|
149
|
-
const history = useMemo<ThreadHistoryAdapter>(
|
|
150
|
-
() => ({
|
|
151
|
-
async load() {
|
|
152
|
-
const { remoteId } = aui.threadListItem.getState();
|
|
153
|
-
if (!remoteId) return { messages: [] };
|
|
154
|
-
const rows = await fetch(
|
|
155
|
-
`/api/threads/${remoteId}/messages`,
|
|
156
|
-
).then((r) => r.json());
|
|
157
|
-
return { messages: rows.map(toThreadMessage) };
|
|
158
|
-
},
|
|
159
|
-
async append({ message, parentId }) {
|
|
160
|
-
const { remoteId } = await aui.threadListItem.initialize();
|
|
161
|
-
await fetch(`/api/threads/${remoteId}/messages`, {
|
|
162
|
-
method: "POST",
|
|
163
|
-
body: JSON.stringify({ message, parentId }),
|
|
164
|
-
});
|
|
165
|
-
},
|
|
166
|
-
}),
|
|
167
|
-
[aui],
|
|
168
|
-
);
|
|
193
|
+
const adapters = useThreadListAdapters();
|
|
169
194
|
return (
|
|
170
|
-
<RuntimeAdapterProvider adapters={
|
|
195
|
+
<RuntimeAdapterProvider adapters={adapters}>
|
|
171
196
|
{children}
|
|
172
197
|
</RuntimeAdapterProvider>
|
|
173
198
|
);
|
|
@@ -192,6 +217,15 @@ async append({ message, parentId }) {
|
|
|
192
217
|
|
|
193
218
|
`initialize()` is safe to call multiple times. It always resolves to the same `remoteId` for the active thread.
|
|
194
219
|
|
|
220
|
+
The same rule applies to a custom external store's dispatch. The runtime does not hold `onNew` or `onEdit` until the thread record exists (that would keep the user's message off screen for the whole roundtrip), so a handler that talks to a backend keyed by the remote identity must await `initialize()` itself:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
onNew: async (message) => {
|
|
224
|
+
const { remoteId } = await aui.threadListItem.initialize();
|
|
225
|
+
await sendToBackend(remoteId, message);
|
|
226
|
+
},
|
|
227
|
+
```
|
|
228
|
+
|
|
195
229
|
### Reloading after async authentication
|
|
196
230
|
|
|
197
231
|
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:
|
|
@@ -207,11 +241,11 @@ function ReloadOnAuth() {
|
|
|
207
241
|
}
|
|
208
242
|
```
|
|
209
243
|
|
|
210
|
-
`reload()` discards in-flight responses from superseded calls, so it is safe to invoke on every auth transition.
|
|
244
|
+
`reload()` discards in-flight responses from superseded calls, so it is safe to invoke on every auth transition. On the `RemoteThreadList` store entry, a recreated adapter object is a no-op until you call `reload()`. `reload()` against the same instance refreshes the list and keeps the open thread. `reload()` after a different adapter instance resets selection and cached records, then loads the replacement store.
|
|
211
245
|
|
|
212
246
|
### Refetching the open thread
|
|
213
247
|
|
|
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()`:
|
|
248
|
+
`reload()` against the same adapter instance 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
249
|
|
|
216
250
|
```tsx
|
|
217
251
|
function RefetchOnInterrupt({ status }: { status: string }) {
|
|
@@ -230,13 +264,13 @@ A thread that has not been sent yet is left alone because it holds no remote sta
|
|
|
230
264
|
|
|
231
265
|
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
266
|
|
|
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
|
|
267
|
+
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 have no hook to remount: they take the in-place path when their tap `ExternalThread` was given `onRefetchThread`, and resolve without doing anything when it was not.
|
|
234
268
|
|
|
235
269
|
`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
270
|
|
|
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.
|
|
271
|
+
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. The Eve adapter registers it when the installed `eve` exposes `resume()` (0.44.1 and later), with no option to pass; on older releases it falls back like any other adapter, which for the single and in-memory thread lists it is usually paired with means the call resolves without doing anything. A refetch that lands during a run also settles differently across the three: LangGraph and Google ADK issue the load right away, so the promise settles at fetch latency and each decides on arrival how much of the snapshot a racing run leaves standing, while Eve queues the replay behind the in-flight turn, so its promise settles only once that turn parks. Other remote adapters take the remount path unless they provide the capability themselves.
|
|
238
272
|
|
|
239
|
-
For an external store runtime, declare it with `onRefetchThread`, which is unrelated to `onReload` (that one re-generates an assistant message):
|
|
273
|
+
For an external store runtime, declare it with `onRefetchThread`, which is unrelated to `onReload` (that one re-generates an assistant message); the tap `ExternalThread` accepts the same prop:
|
|
240
274
|
|
|
241
275
|
```ts
|
|
242
276
|
useExternalStoreRuntime({
|
|
@@ -367,7 +401,13 @@ A few invariants worth knowing when wiring a custom UI on top of `loadMore()`:
|
|
|
367
401
|
name: "unstable_Provider",
|
|
368
402
|
type: "RemoteThreadListProviderComponent",
|
|
369
403
|
description:
|
|
370
|
-
"Optional wrapper rendered around each active thread. Inject thread-scoped adapters
|
|
404
|
+
"Optional React wrapper rendered around each active thread by useRemoteThreadListRuntime when present. Inject thread-scoped adapters here. Omit it to let that host use unstable_useAdapters.",
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
name: "unstable_useAdapters",
|
|
408
|
+
type: "() => RuntimeAdapters | null | undefined",
|
|
409
|
+
description:
|
|
410
|
+
"Optional hook called by the RemoteThreadList store entry, and by useRemoteThreadListRuntime when unstable_Provider is omitted. Per-thread history requires the thread factory to be keyed with withKey.",
|
|
371
411
|
},
|
|
372
412
|
]}
|
|
373
413
|
/>
|
|
@@ -308,7 +308,7 @@ aui-state:[{"type":"set","path":["status"],"value":"completed"}]
|
|
|
308
308
|
prepareSendCommandsRequest?: (body: SendCommandsRequestBody) => Record<string, unknown> | Promise<Record<string, unknown>>,
|
|
309
309
|
capabilities?: { edit?: boolean },
|
|
310
310
|
adapters?: { attachments?: AttachmentAdapter; history?: ThreadHistoryAdapter },
|
|
311
|
-
onResponse?: (response: Response) => void
|
|
311
|
+
onResponse?: (response: Response) => void | Promise<void>,
|
|
312
312
|
onFinish?: () => void,
|
|
313
313
|
onError?: (error: Error, params: { commands: AssistantTransportCommand[]; updateState: (updater: (state: T) => T) => void }) => void | Promise<void>,
|
|
314
314
|
onCancel?: (params: { commands: AssistantTransportCommand[]; updateState: (updater: (state: T) => T) => void; error?: Error }) => void
|
|
@@ -421,13 +421,13 @@ useEffect(() => {
|
|
|
421
421
|
}, [isRunning, queue]);
|
|
422
422
|
```
|
|
423
423
|
|
|
424
|
-
|
|
424
|
+
With a `createMessageQueue` adapter, cancelling pauses the queue for you: the runtime tells the queue before your `onCancel` runs, so the cancelled run's settle keeps the pending items instead of dispatching the next one, and the next send resumes draining. Call `queue.clear()` in `onCancel` instead if you want a cancel to drop them. A hand-rolled adapter has no such channel, so it owns its cancel policy the same way it owns the rest. Edit and reload stay host-owned: call `queue.clear()` in your `onEdit` and `onReload` handlers so stale items do not drain onto the new branch.
|
|
425
425
|
|
|
426
426
|
```tsx
|
|
427
427
|
const runtime = useExternalStoreRuntime({
|
|
428
428
|
// ...
|
|
429
429
|
onCancel: async () => {
|
|
430
|
-
|
|
430
|
+
// the runtime already paused the queue; clear() here to drop the items
|
|
431
431
|
await cancelRun();
|
|
432
432
|
},
|
|
433
433
|
onEdit: async (message) => {
|
|
@@ -447,6 +447,10 @@ const runtime = useExternalStoreRuntime({
|
|
|
447
447
|
|
|
448
448
|
## Integration examples
|
|
449
449
|
|
|
450
|
+
<Callout type="info">
|
|
451
|
+
**Real-world example:** the [Claude Managed Agents guide](/docs/runtimes/claude-managed-agents) maps Anthropic-hosted agent sessions onto this runtime, with Anthropic's official [quickstart](https://github.com/anthropics/claude-quickstarts/tree/main/managed-agents/assistant-ui) as the runnable reference.
|
|
452
|
+
</Callout>
|
|
453
|
+
|
|
450
454
|
### Redux
|
|
451
455
|
|
|
452
456
|
```tsx title="app/chatSlice.ts"
|
|
@@ -262,7 +262,7 @@ Follow the [Ink setup](/docs/ink).
|
|
|
262
262
|
|
|
263
263
|
### Forwarding per-run config
|
|
264
264
|
|
|
265
|
-
When a message is sent, its `runConfig.custom` (for example a selected mode or model) is forwarded on the underlying `useStream().submit` call as `config.configurable`. Read it in the graph from `config["configurable"]`; on LangGraph v1 the same values are also reachable through the Runtime `context`, which `config.configurable` is aliased to. This lets per-run app config reach the graph without extra wiring.
|
|
265
|
+
When a message is sent, its `runConfig.custom` (for example a selected mode or model) is forwarded on the underlying `useStream().submit` call as `config.configurable`. Automatic tool-result resumes and the interrupt helpers (`useLangChainRespond`, `useLangChainRespondAll`, and `useLangChainSubmit(null, { command })`) reuse that same recorded `configurable` unless the caller passes `config`. Raw `useLangChainSubmit` / `useLangChainSend` calls that start a new run do not inherit it. The recording is session-scoped and does not survive a reload. Read it in the graph from `config["configurable"]`; on LangGraph v1 the same values are also reachable through the Runtime `context`, which `config.configurable` is aliased to. This lets per-run app config reach the graph without extra wiring.
|
|
266
266
|
|
|
267
267
|
## Reading custom state keys
|
|
268
268
|
|
|
@@ -744,8 +744,8 @@ Register the components with `makeAssistantDataUI`, keyed by the `UIMessage`'s `
|
|
|
744
744
|
import {
|
|
745
745
|
AssistantRuntimeProvider,
|
|
746
746
|
makeAssistantDataUI,
|
|
747
|
-
Thread,
|
|
748
747
|
} from "@assistant-ui/react";
|
|
748
|
+
import { Thread } from "@/components/assistant-ui/thread";
|
|
749
749
|
import { useStreamRuntime } from "@assistant-ui/react-langchain";
|
|
750
750
|
|
|
751
751
|
const ChartUI = makeAssistantDataUI({
|
|
@@ -119,6 +119,10 @@ const runtime = useLangGraphRuntime({
|
|
|
119
119
|
|
|
120
120
|
Render the pending messages with [`ComposerPrimitive.Queue`](/docs/api-reference/primitives/composer) and [`QueueItemPrimitive`](/docs/api-reference/primitives/queue-item).
|
|
121
121
|
|
|
122
|
+
## Per-run config
|
|
123
|
+
|
|
124
|
+
When a message is sent, its `runConfig` is forwarded to `stream` and, if you use `unstable_createLangGraphStream`, posted as the LangGraph SDK run `config`. Automatic frontend tool-result resumes and `useLangGraphSendCommand` reuse the `runConfig` of the run that produced the pending tool call or interrupt. An explicit `runConfig` on `useLangGraphSend` still wins. A thread refetch that still carries the interrupt keeps that owner. History loaded without local ownership stays unconfigured on resume; the recording is session-scoped and does not survive a reload.
|
|
125
|
+
|
|
122
126
|
## Next
|
|
123
127
|
|
|
124
128
|
<Cards>
|
|
@@ -41,7 +41,7 @@ export function MyRuntimeProvider({
|
|
|
41
41
|
### Render the Thread
|
|
42
42
|
|
|
43
43
|
```tsx title="app/page.tsx"
|
|
44
|
-
import { Thread } from "
|
|
44
|
+
import { Thread } from "@/components/assistant-ui/thread";
|
|
45
45
|
import { MyRuntimeProvider } from "./MyRuntimeProvider";
|
|
46
46
|
|
|
47
47
|
export default function Page() {
|