@assistant-ui/mcp-docs-server 0.1.30 → 0.1.32
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 +8 -8
- package/.docs/organized/code-examples/with-a2a.md +10 -10
- package/.docs/organized/code-examples/with-ag-ui.md +12 -12
- package/.docs/organized/code-examples/with-ai-sdk-v6.md +14 -14
- package/.docs/organized/code-examples/with-artifacts.md +14 -14
- package/.docs/organized/code-examples/with-assistant-transport.md +11 -11
- package/.docs/organized/code-examples/with-browser-extension.md +345 -0
- package/.docs/organized/code-examples/with-chain-of-thought.md +129 -63
- package/.docs/organized/code-examples/with-cloud-standalone.md +13 -13
- package/.docs/organized/code-examples/with-cloud.md +13 -13
- package/.docs/organized/code-examples/with-custom-thread-list.md +65 -20
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +18 -17
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +18 -17
- package/.docs/organized/code-examples/with-expo.md +25 -25
- package/.docs/organized/code-examples/with-external-store.md +10 -10
- package/.docs/organized/code-examples/with-ffmpeg.md +14 -14
- package/.docs/organized/code-examples/with-generative-ui.md +211 -14
- package/.docs/organized/code-examples/with-google-adk.md +12 -12
- package/.docs/organized/code-examples/with-heat-graph.md +8 -8
- package/.docs/organized/code-examples/with-image-generation.md +454 -0
- package/.docs/organized/code-examples/with-interactables.md +14 -14
- package/.docs/organized/code-examples/with-langchain.md +12 -12
- package/.docs/organized/code-examples/with-langgraph.md +15 -12
- package/.docs/organized/code-examples/with-livekit.md +19 -18
- package/.docs/organized/code-examples/with-mcp.md +748 -0
- package/.docs/organized/code-examples/with-opencode.md +107 -62
- package/.docs/organized/code-examples/with-parent-id-grouping.md +12 -12
- package/.docs/organized/code-examples/with-react-hook-form.md +14 -14
- package/.docs/organized/code-examples/with-react-ink.md +4 -4
- package/.docs/organized/code-examples/with-react-router.md +16 -16
- package/.docs/organized/code-examples/with-resumable-stream.md +660 -0
- package/.docs/organized/code-examples/with-store.md +8 -8
- package/.docs/organized/code-examples/with-tanstack.md +14 -14
- package/.docs/organized/code-examples/with-tap-runtime.md +11 -10
- package/.docs/raw/docs/(docs)/cli.mdx +3 -1
- package/.docs/raw/docs/(docs)/copilots/assistant-frame.mdx +1 -0
- package/.docs/raw/docs/(docs)/copilots/make-assistant-tool-ui.mdx +10 -3
- package/.docs/raw/docs/(docs)/copilots/make-assistant-tool.mdx +8 -3
- package/.docs/raw/docs/(docs)/copilots/make-assistant-visible.mdx +1 -0
- package/.docs/raw/docs/(docs)/copilots/model-context.mdx +1 -0
- package/.docs/raw/docs/(docs)/copilots/motivation.mdx +1 -0
- package/.docs/raw/docs/(docs)/copilots/use-assistant-instructions.mdx +1 -0
- package/.docs/raw/docs/(docs)/devtools.mdx +1 -0
- package/.docs/raw/docs/(docs)/index.mdx +3 -2
- package/.docs/raw/docs/(docs)/installation.mdx +2 -1
- package/.docs/raw/docs/(docs)/rtl.mdx +1 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/attachments.mdx +36 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/feedback.mdx +20 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/index.mdx +34 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/model.mdx +44 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/persistence.mdx +55 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/runtime.mdx +20 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/suggestions.mdx +20 -0
- package/.docs/raw/docs/(reference)/api-reference/context-providers/assistant-runtime-provider.mdx +37 -8
- package/.docs/raw/docs/(reference)/api-reference/context-providers/index.mdx +22 -0
- package/.docs/raw/docs/(reference)/api-reference/context-providers/scoped-providers.mdx +64 -0
- package/.docs/raw/docs/(reference)/api-reference/external-store/index.mdx +22 -0
- package/.docs/raw/docs/(reference)/api-reference/external-store/message-conversion.mdx +52 -0
- package/.docs/raw/docs/(reference)/api-reference/external-store/runtime.mdx +36 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/composer-triggers.mdx +98 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/index.mdx +31 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/model-context.mdx +33 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +640 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/runtimes.mdx +28 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +94 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/utilities.mdx +464 -0
- package/.docs/raw/docs/(reference)/api-reference/integrations/cloud-ai-sdk.mdx +24 -0
- package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +22 -0
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +79 -0
- package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +52 -0
- package/.docs/raw/docs/(reference)/api-reference/model-context/index.mdx +22 -0
- package/.docs/raw/docs/(reference)/api-reference/model-context/registry.mdx +20 -0
- package/.docs/raw/docs/(reference)/api-reference/overview.mdx +125 -125
- package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar-more.mdx +78 -221
- package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar.mdx +127 -242
- package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +46 -20
- package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-modal.mdx +66 -87
- package/.docs/raw/docs/(reference)/api-reference/primitives/attachment.mdx +50 -58
- package/.docs/raw/docs/(reference)/api-reference/primitives/branch-picker.mdx +80 -48
- package/.docs/raw/docs/(reference)/api-reference/primitives/chain-of-thought.mdx +67 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +323 -461
- package/.docs/raw/docs/(reference)/api-reference/primitives/error.mdx +36 -43
- package/.docs/raw/docs/(reference)/api-reference/primitives/index.mdx +70 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +63 -245
- package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +182 -554
- package/.docs/raw/docs/(reference)/api-reference/primitives/queue-item.mdx +65 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +35 -22
- package/.docs/raw/docs/(reference)/api-reference/primitives/suggestion.mdx +57 -140
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list-item-more.mdx +70 -161
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list-item.mdx +84 -108
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list.mdx +82 -86
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +173 -314
- package/.docs/raw/docs/(reference)/api-reference/runtimes/assistant-runtime.mdx +9 -21
- package/.docs/raw/docs/(reference)/api-reference/runtimes/attachment-runtime.mdx +10 -21
- package/.docs/raw/docs/(reference)/api-reference/runtimes/composer-runtime.mdx +15 -70
- package/.docs/raw/docs/(reference)/api-reference/runtimes/index.mdx +43 -0
- package/.docs/raw/docs/(reference)/api-reference/runtimes/message-part-runtime.mdx +25 -28
- package/.docs/raw/docs/(reference)/api-reference/runtimes/message-runtime.mdx +11 -63
- package/.docs/raw/docs/(reference)/api-reference/runtimes/queue-state.mdx +20 -0
- package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-item-runtime.mdx +11 -48
- package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-runtime.mdx +10 -42
- package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-runtime.mdx +18 -30
- package/.docs/raw/docs/(reference)/api-reference/tools/component-tools.mdx +68 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/index.mdx +39 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/rendering.mdx +79 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +42 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +92 -0
- package/.docs/raw/docs/(reference)/api-reference/transport/assistant-transport.mdx +48 -0
- package/.docs/raw/docs/(reference)/api-reference/transport/frame.mdx +62 -0
- package/.docs/raw/docs/(reference)/api-reference/transport/index.mdx +22 -0
- package/.docs/raw/docs/(reference)/api-reference/utilities/index.mdx +19 -0
- package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +131 -0
- package/.docs/raw/docs/(reference)/api-reference/voice/index.mdx +22 -0
- package/.docs/raw/docs/(reference)/api-reference/voice/session.mdx +54 -0
- package/.docs/raw/docs/(reference)/api-reference/voice/speech-dictation.mdx +36 -0
- package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +230 -2
- package/.docs/raw/docs/cloud/ai-sdk.mdx +222 -4
- package/.docs/raw/docs/cloud/index.mdx +2 -2
- package/.docs/raw/docs/cloud/langgraph.mdx +274 -2
- package/.docs/raw/docs/{(docs)/guides → guides}/attachments.mdx +45 -40
- package/.docs/raw/docs/guides/branching.mdx +76 -0
- package/.docs/raw/docs/guides/chain-of-thought.mdx +166 -0
- package/.docs/raw/docs/{(docs)/guides → guides}/context-api.mdx +54 -26
- package/.docs/raw/docs/{(docs)/guides → guides}/dictation.mdx +3 -1
- package/.docs/raw/docs/guides/editing.mdx +102 -0
- package/.docs/raw/docs/guides/generative-ui.mdx +142 -0
- package/.docs/raw/docs/guides/image-generation.mdx +74 -0
- package/.docs/raw/docs/guides/index.mdx +103 -0
- package/.docs/raw/docs/{(docs)/guides → guides}/interactables.mdx +51 -2
- package/.docs/raw/docs/{(docs)/guides → guides}/latex.mdx +53 -10
- package/.docs/raw/docs/guides/mcp-apps.mdx +231 -0
- package/.docs/raw/docs/{(docs)/guides → guides}/mentions.mdx +63 -88
- package/.docs/raw/docs/{(docs)/guides → guides}/message-timing.mdx +45 -6
- package/.docs/raw/docs/{(docs)/guides → guides}/multi-agent.mdx +66 -6
- package/.docs/raw/docs/{(docs)/guides → guides}/quoting.mdx +11 -18
- package/.docs/raw/docs/guides/resumable-stream-deployment.mdx +212 -0
- package/.docs/raw/docs/guides/resumable-stream-stores.mdx +152 -0
- package/.docs/raw/docs/guides/resumable-streams.mdx +210 -0
- package/.docs/raw/docs/{(docs)/guides → guides}/slash-commands.mdx +104 -38
- package/.docs/raw/docs/guides/speech.mdx +156 -0
- package/.docs/raw/docs/{(docs)/guides → guides}/suggestions.mdx +91 -69
- package/.docs/raw/docs/{(docs)/guides → guides}/tool-ui.mdx +110 -38
- package/.docs/raw/docs/{(docs)/guides → guides}/tools.mdx +197 -40
- package/.docs/raw/docs/{(docs)/guides → guides}/voice.mdx +41 -2
- package/.docs/raw/docs/ink/adapters.mdx +37 -1
- package/.docs/raw/docs/ink/custom-backend.mdx +59 -8
- package/.docs/raw/docs/ink/index.mdx +11 -14
- package/.docs/raw/docs/ink/migration.mdx +1 -3
- package/.docs/raw/docs/ink/primitives.mdx +386 -9
- package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +520 -0
- package/.docs/raw/docs/integrations/auth/better-auth.mdx +191 -0
- package/.docs/raw/docs/integrations/auth/clerk.mdx +172 -0
- package/.docs/raw/docs/integrations/auth/next-auth.mdx +196 -0
- package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +79 -0
- package/.docs/raw/docs/integrations/frameworks/cloudflare-agents/overview.mdx +282 -0
- package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +188 -0
- package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +57 -0
- package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +201 -0
- package/.docs/raw/docs/integrations/gateways/index.mdx +162 -0
- package/.docs/raw/docs/integrations/index.mdx +185 -0
- package/.docs/raw/docs/integrations/observability/helicone.mdx +130 -0
- package/.docs/raw/docs/integrations/observability/langfuse.mdx +163 -0
- package/.docs/raw/docs/integrations/observability/langsmith.mdx +150 -0
- package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +712 -0
- package/.docs/raw/docs/integrations/tools/mcp.mdx +267 -0
- package/.docs/raw/docs/integrations/tools/react-mcp.mdx +337 -0
- package/.docs/raw/docs/migrations/v0-14.mdx +297 -0
- package/.docs/raw/docs/primitives/action-bar.mdx +1 -0
- package/.docs/raw/docs/primitives/assistant-modal.mdx +1 -0
- package/.docs/raw/docs/primitives/attachment.mdx +1 -0
- package/.docs/raw/docs/primitives/branch-picker.mdx +1 -0
- package/.docs/raw/docs/primitives/chain-of-thought.mdx +90 -85
- package/.docs/raw/docs/primitives/composer.mdx +55 -1
- package/.docs/raw/docs/primitives/error.mdx +1 -0
- package/.docs/raw/docs/primitives/index.mdx +4 -3
- package/.docs/raw/docs/primitives/message.mdx +68 -5
- package/.docs/raw/docs/primitives/selection-toolbar.mdx +1 -0
- package/.docs/raw/docs/primitives/suggestion.mdx +10 -0
- package/.docs/raw/docs/primitives/thread-list.mdx +39 -0
- package/.docs/raw/docs/primitives/thread.mdx +16 -13
- package/.docs/raw/docs/react-native/hooks.mdx +2 -2
- package/.docs/raw/docs/react-native/index.mdx +5 -7
- package/.docs/raw/docs/react-native/migration.mdx +1 -3
- package/.docs/raw/docs/runtimes/a2a/client-and-hooks.mdx +396 -0
- package/.docs/raw/docs/runtimes/a2a/overview.mdx +60 -0
- package/.docs/raw/docs/runtimes/a2a/quickstart.mdx +216 -0
- package/.docs/raw/docs/runtimes/ag-ui/overview.mdx +70 -0
- package/.docs/raw/docs/runtimes/ag-ui/quickstart.mdx +243 -0
- package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +154 -0
- package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +52 -0
- package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +73 -128
- package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +70 -64
- package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +337 -123
- package/.docs/raw/docs/runtimes/concepts/adapters.mdx +265 -0
- package/.docs/raw/docs/runtimes/concepts/architecture.mdx +125 -0
- package/.docs/raw/docs/runtimes/concepts/stability.mdx +67 -0
- package/.docs/raw/docs/runtimes/concepts/threads.mdx +428 -0
- package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +703 -0
- package/.docs/raw/docs/runtimes/custom/data-stream.mdx +348 -0
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +261 -1236
- package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +746 -0
- package/.docs/raw/docs/runtimes/custom/overview.mdx +71 -0
- package/.docs/raw/docs/runtimes/google-adk/api.mdx +256 -0
- package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +717 -0
- package/.docs/raw/docs/runtimes/google-adk/overview.mdx +69 -0
- package/.docs/raw/docs/runtimes/google-adk/quickstart.mdx +229 -0
- package/.docs/raw/docs/runtimes/langchain.mdx +533 -0
- package/.docs/raw/docs/runtimes/langgraph/generative-ui.mdx +305 -0
- package/.docs/raw/docs/runtimes/langgraph/interrupts.mdx +104 -0
- package/.docs/raw/docs/runtimes/langgraph/overview.mdx +84 -0
- package/.docs/raw/docs/runtimes/langgraph/quickstart.mdx +496 -0
- package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +127 -0
- package/.docs/raw/docs/runtimes/langgraph/threads.mdx +113 -0
- package/.docs/raw/docs/runtimes/langgraph/tutorial/introduction.mdx +3 -3
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +0 -23
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +1 -1
- package/.docs/raw/docs/runtimes/opencode/hooks.mdx +191 -0
- package/.docs/raw/docs/runtimes/opencode/overview.mdx +48 -0
- package/.docs/raw/docs/runtimes/opencode/quickstart.mdx +119 -0
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +78 -203
- package/.docs/raw/docs/ui/accordion.mdx +1 -0
- package/.docs/raw/docs/ui/assistant-modal.mdx +1 -0
- package/.docs/raw/docs/ui/assistant-sidebar.mdx +1 -0
- package/.docs/raw/docs/ui/attachment.mdx +1 -0
- package/.docs/raw/docs/ui/badge.mdx +1 -0
- package/.docs/raw/docs/ui/composer-trigger-popover.mdx +12 -1
- package/.docs/raw/docs/ui/context-display.mdx +1 -0
- package/.docs/raw/docs/ui/diff-viewer.mdx +1 -0
- package/.docs/raw/docs/ui/directive-text.mdx +1 -0
- package/.docs/raw/docs/ui/file.mdx +1 -0
- package/.docs/raw/docs/ui/image.mdx +1 -0
- package/.docs/raw/docs/ui/markdown.mdx +2 -14
- package/.docs/raw/docs/ui/mcp-config.mdx +102 -0
- package/.docs/raw/docs/ui/mermaid.mdx +1 -0
- package/.docs/raw/docs/ui/message-timing.mdx +3 -2
- package/.docs/raw/docs/ui/model-selector.mdx +9 -8
- package/.docs/raw/docs/ui/part-grouping.mdx +325 -313
- package/.docs/raw/docs/ui/quote.mdx +1 -0
- package/.docs/raw/docs/ui/reasoning.mdx +66 -33
- package/.docs/raw/docs/ui/scrollbar.mdx +1 -0
- package/.docs/raw/docs/ui/select.mdx +1 -0
- package/.docs/raw/docs/ui/sources.mdx +18 -0
- package/.docs/raw/docs/ui/streamdown.mdx +35 -2
- package/.docs/raw/docs/ui/syntax-highlighting.mdx +1 -0
- package/.docs/raw/docs/ui/tabs.mdx +1 -0
- package/.docs/raw/docs/ui/thread-list.mdx +19 -2
- package/.docs/raw/docs/ui/thread.mdx +58 -3
- package/.docs/raw/docs/ui/tool-fallback.mdx +1 -0
- package/.docs/raw/docs/ui/tool-group.mdx +39 -11
- package/.docs/raw/docs/ui/voice.mdx +1 -0
- package/.docs/raw/docs/utilities/heat-graph.mdx +1 -0
- package/.docs/raw/docs/utilities/react-o11y.mdx +278 -0
- package/.docs/raw/docs/utilities/tw-shimmer.mdx +1 -0
- package/README.md +14 -72
- package/dist/constants.d.ts +12 -9
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +13 -9
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -24
- package/dist/index.js.map +1 -1
- package/dist/prepare-docs/code-examples.d.ts +4 -1
- package/dist/prepare-docs/code-examples.d.ts.map +1 -1
- package/dist/prepare-docs/code-examples.js +109 -121
- package/dist/prepare-docs/code-examples.js.map +1 -1
- package/dist/prepare-docs/copy-raw.d.ts +4 -1
- package/dist/prepare-docs/copy-raw.d.ts.map +1 -1
- package/dist/prepare-docs/copy-raw.js +45 -42
- package/dist/prepare-docs/copy-raw.js.map +1 -1
- package/dist/prepare-docs/prepare.d.ts +1 -2
- package/dist/prepare-docs/prepare.js +17 -17
- package/dist/prepare-docs/prepare.js.map +1 -1
- package/dist/stdio.d.ts +1 -3
- package/dist/stdio.js +6 -3
- package/dist/stdio.js.map +1 -1
- package/dist/tools/docs.d.ts +20 -15
- package/dist/tools/docs.d.ts.map +1 -1
- package/dist/tools/docs.js +140 -161
- package/dist/tools/docs.js.map +1 -1
- package/dist/tools/examples.d.ts +20 -15
- package/dist/tools/examples.d.ts.map +1 -1
- package/dist/tools/examples.js +74 -86
- package/dist/tools/examples.js.map +1 -1
- package/dist/tools/tests/test-setup.d.ts +5 -2
- package/dist/tools/tests/test-setup.d.ts.map +1 -1
- package/dist/tools/tests/test-setup.js +21 -28
- package/dist/tools/tests/test-setup.js.map +1 -1
- package/dist/utils/logger.d.ts +8 -5
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +17 -17
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/mcp-format.d.ts +8 -5
- package/dist/utils/mcp-format.d.ts.map +1 -1
- package/dist/utils/mcp-format.js +9 -9
- package/dist/utils/mcp-format.js.map +1 -1
- package/dist/utils/mdx.d.ts +8 -6
- package/dist/utils/mdx.d.ts.map +1 -1
- package/dist/utils/mdx.js +22 -22
- package/dist/utils/mdx.js.map +1 -1
- package/dist/utils/paths.d.ts +9 -6
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +66 -76
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/security.d.ts +4 -1
- package/dist/utils/security.d.ts.map +1 -1
- package/dist/utils/security.js +19 -40
- package/dist/utils/security.js.map +1 -1
- package/package.json +6 -6
- package/src/tools/tests/path-traversal.test.ts +1 -1
- package/.docs/raw/docs/(docs)/guides/branching.mdx +0 -65
- package/.docs/raw/docs/(docs)/guides/chain-of-thought.mdx +0 -164
- package/.docs/raw/docs/(docs)/guides/editing.mdx +0 -66
- package/.docs/raw/docs/(docs)/guides/speech.mdx +0 -38
- package/.docs/raw/docs/(reference)/api-reference/context-providers/text-message-part-provider.mdx +0 -40
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-data-stream.mdx +0 -260
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-hook-form.mdx +0 -103
- package/.docs/raw/docs/(reference)/api-reference/integrations/vercel-ai-sdk.mdx +0 -254
- package/.docs/raw/docs/(reference)/migrations/v0-14.mdx +0 -159
- package/.docs/raw/docs/runtimes/a2a/index.mdx +0 -298
- package/.docs/raw/docs/runtimes/assistant-transport.mdx +0 -1033
- package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +0 -314
- package/.docs/raw/docs/runtimes/custom/local.mdx +0 -1464
- package/.docs/raw/docs/runtimes/data-stream.mdx +0 -422
- package/.docs/raw/docs/runtimes/google-adk/index.mdx +0 -686
- package/.docs/raw/docs/runtimes/helicone.mdx +0 -61
- package/.docs/raw/docs/runtimes/langchain/comparison.mdx +0 -60
- package/.docs/raw/docs/runtimes/langchain/index.mdx +0 -210
- package/.docs/raw/docs/runtimes/langgraph/index.mdx +0 -699
- package/.docs/raw/docs/runtimes/langgraph/tutorial/index.mdx +0 -12
- package/.docs/raw/docs/runtimes/langserve.mdx +0 -116
- package/.docs/raw/docs/runtimes/mastra/full-stack-integration.mdx +0 -218
- package/.docs/raw/docs/runtimes/mastra/overview.mdx +0 -18
- package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +0 -217
- package/dist/prepare-docs/prepare.d.ts.map +0 -1
- package/dist/stdio.d.ts.map +0 -1
- /package/.docs/raw/docs/{(reference)/migrations → migrations}/deprecation-policy.mdx +0 -0
- /package/.docs/raw/docs/{(reference) → migrations}/react-compatibility.mdx +0 -0
- /package/.docs/raw/docs/{(reference)/migrations → migrations}/react-langgraph-v0-7.mdx +0 -0
- /package/.docs/raw/docs/{(reference)/migrations → migrations}/v0-11.mdx +0 -0
- /package/.docs/raw/docs/{(reference)/migrations → migrations}/v0-12.mdx +0 -0
|
@@ -1,36 +1,50 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: ExternalStoreRuntime
|
|
3
|
-
description: Bring your own
|
|
3
|
+
description: Bring your own redux, zustand, or state manager.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
`ExternalStoreRuntime` bridges your existing state management with assistant-ui. You provide messages and callbacks; the runtime renders whatever you give it. UI features turn on based on which callbacks are present.
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## When to use it
|
|
8
9
|
|
|
9
|
-
`ExternalStoreRuntime`
|
|
10
|
+
Pick `ExternalStoreRuntime` when:
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
- You already keep messages in redux, zustand, tanstack-query, or another store, and want to keep them there.
|
|
13
|
+
- You want full control over message state, persistence, and synchronization.
|
|
14
|
+
- You have a custom message format and need automatic conversion to assistant-ui's format.
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
- **Bring your own state management** - Works with Redux, Zustand, TanStack Query, or any React state library
|
|
15
|
-
- **Custom message formats** - Use your backend's message structure with automatic conversion
|
|
16
|
+
If you do not have an existing store, use [`LocalRuntime`](/docs/runtimes/custom/local-runtime) instead; it is lower-friction.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
`ExternalStoreRuntime` gives you total control over state (persist, sync,
|
|
19
|
-
share), but you must wire up every callback.
|
|
20
|
-
</Callout>
|
|
18
|
+
## Architecture
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
```mermaid
|
|
21
|
+
graph TD
|
|
22
|
+
A[Your state] -->|messages| B[ExternalStoreAdapter]
|
|
23
|
+
B --> C[ExternalStoreRuntime]
|
|
24
|
+
C --> D[assistant-ui components]
|
|
25
|
+
D -->|user actions| B
|
|
26
|
+
B -->|state updates| A
|
|
27
|
+
```
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
Key idea: you own the state, the adapter translates between your format and assistant-ui's. UI features are capability-based; if you provide `setMessages`, branching turns on; if you provide `onEdit`, editing turns on; etc.
|
|
30
|
+
|
|
31
|
+
## Quickstart
|
|
32
|
+
|
|
33
|
+
<Steps>
|
|
34
|
+
<Step>
|
|
35
|
+
|
|
36
|
+
### Install
|
|
37
|
+
|
|
38
|
+
<InstallCommand npm={["@assistant-ui/react"]} />
|
|
39
|
+
|
|
40
|
+
</Step>
|
|
41
|
+
<Step>
|
|
42
|
+
|
|
43
|
+
### Create the runtime provider
|
|
44
|
+
|
|
45
|
+
```tsx title="app/MyRuntimeProvider.tsx"
|
|
46
|
+
"use client";
|
|
32
47
|
|
|
33
|
-
// ---cut---
|
|
34
48
|
import { useState, ReactNode } from "react";
|
|
35
49
|
import {
|
|
36
50
|
useExternalStoreRuntime,
|
|
@@ -39,37 +53,33 @@ import {
|
|
|
39
53
|
AssistantRuntimeProvider,
|
|
40
54
|
} from "@assistant-ui/react";
|
|
41
55
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
56
|
+
type MyMessage = { role: "user" | "assistant"; content: string };
|
|
57
|
+
|
|
58
|
+
const convertMessage = (message: MyMessage): ThreadMessageLike => ({
|
|
59
|
+
role: message.role,
|
|
60
|
+
content: [{ type: "text", text: message.content }],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const backendApi = async (input: string): Promise<MyMessage> => {
|
|
64
|
+
return { role: "assistant", content: "Hello, world!" };
|
|
47
65
|
};
|
|
48
66
|
|
|
49
67
|
export function MyRuntimeProvider({
|
|
50
68
|
children,
|
|
51
|
-
}: Readonly<{
|
|
52
|
-
children: ReactNode;
|
|
53
|
-
}>) {
|
|
69
|
+
}: Readonly<{ children: ReactNode }>) {
|
|
54
70
|
const [isRunning, setIsRunning] = useState(false);
|
|
55
71
|
const [messages, setMessages] = useState<MyMessage[]>([]);
|
|
56
72
|
|
|
57
73
|
const onNew = async (message: AppendMessage) => {
|
|
58
|
-
if (message.content[0]?.type !== "text")
|
|
74
|
+
if (message.content[0]?.type !== "text") {
|
|
59
75
|
throw new Error("Only text messages are supported");
|
|
60
|
-
|
|
76
|
+
}
|
|
61
77
|
const input = message.content[0].text;
|
|
62
|
-
setMessages((
|
|
63
|
-
...currentConversation,
|
|
64
|
-
{ role: "user", content: input },
|
|
65
|
-
]);
|
|
78
|
+
setMessages((prev) => [...prev, { role: "user", content: input }]);
|
|
66
79
|
|
|
67
80
|
setIsRunning(true);
|
|
68
|
-
const
|
|
69
|
-
setMessages((
|
|
70
|
-
...currentConversation,
|
|
71
|
-
assistantMessage,
|
|
72
|
-
]);
|
|
81
|
+
const assistant = await backendApi(input);
|
|
82
|
+
setMessages((prev) => [...prev, assistant]);
|
|
73
83
|
setIsRunning(false);
|
|
74
84
|
};
|
|
75
85
|
|
|
@@ -88,159 +98,32 @@ export function MyRuntimeProvider({
|
|
|
88
98
|
}
|
|
89
99
|
```
|
|
90
100
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
Use `ExternalStoreRuntime` if you need:
|
|
94
|
-
|
|
95
|
-
- **Full control over message state** - Manage messages with Redux, Zustand, TanStack Query, or any React state management library
|
|
96
|
-
- **Custom multi-thread implementation** - Build your own thread management system with custom storage
|
|
97
|
-
- **Integration with existing state** - Keep chat state in your existing state management solution
|
|
98
|
-
- **Custom message formats** - Use your backend's message structure with automatic conversion
|
|
99
|
-
- **Complex synchronization** - Sync messages with external data sources, databases, or multiple clients
|
|
100
|
-
- **Custom persistence logic** - Implement your own storage patterns and caching strategies
|
|
101
|
-
|
|
102
|
-
## Key Features
|
|
103
|
-
|
|
104
|
-
<Cards>
|
|
105
|
-
<Card
|
|
106
|
-
title="State Management Integration"
|
|
107
|
-
description="Works seamlessly with Redux, Zustand, TanStack Query, and more"
|
|
108
|
-
/>
|
|
109
|
-
<Card
|
|
110
|
-
title="Message Conversion"
|
|
111
|
-
description="Automatic conversion between your message format and assistant-ui's format"
|
|
112
|
-
/>
|
|
113
|
-
<Card
|
|
114
|
-
title="Real-time Streaming"
|
|
115
|
-
description="Built-in support for streaming responses and progressive updates"
|
|
116
|
-
/>
|
|
117
|
-
<Card
|
|
118
|
-
title="Thread Management"
|
|
119
|
-
description="Multi-conversation support with archiving and thread switching"
|
|
120
|
-
/>
|
|
121
|
-
</Cards>
|
|
122
|
-
|
|
123
|
-
## Architecture
|
|
101
|
+
</Step>
|
|
102
|
+
<Step>
|
|
124
103
|
|
|
125
|
-
###
|
|
104
|
+
### Use in your app
|
|
126
105
|
|
|
127
|
-
|
|
106
|
+
```tsx title="app/page.tsx"
|
|
107
|
+
import { Thread } from "@/components/assistant-ui/thread";
|
|
108
|
+
import { MyRuntimeProvider } from "./MyRuntimeProvider";
|
|
128
109
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
110
|
+
export default function Page() {
|
|
111
|
+
return (
|
|
112
|
+
<MyRuntimeProvider>
|
|
113
|
+
<Thread />
|
|
114
|
+
</MyRuntimeProvider>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
136
117
|
```
|
|
137
118
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
1. **State Ownership** - You own and control all message state
|
|
141
|
-
2. **Adapter Pattern** - The adapter translates between your state and assistant-ui
|
|
142
|
-
3. **Capability-Based Features** - UI features are enabled based on which handlers you provide
|
|
143
|
-
4. **Message Conversion** - Automatic conversion between your message format and assistant-ui's format
|
|
144
|
-
5. **Optimistic Updates** - Built-in handling for streaming and loading states
|
|
145
|
-
|
|
146
|
-
## Getting Started
|
|
147
|
-
|
|
148
|
-
<Steps>
|
|
149
|
-
<Step>
|
|
150
|
-
### Install Dependencies
|
|
151
|
-
|
|
152
|
-
<InstallCommand npm={["@assistant-ui/react"]} />
|
|
153
|
-
|
|
154
|
-
</Step>
|
|
155
|
-
|
|
156
|
-
<Step>
|
|
157
|
-
### Create Runtime Provider
|
|
158
|
-
|
|
159
|
-
```tsx title="app/MyRuntimeProvider.tsx"
|
|
160
|
-
"use client";
|
|
161
|
-
|
|
162
|
-
import { ThreadMessageLike } from "@assistant-ui/react";
|
|
163
|
-
import { AppendMessage } from "@assistant-ui/react";
|
|
164
|
-
import {
|
|
165
|
-
AssistantRuntimeProvider,
|
|
166
|
-
useExternalStoreRuntime,
|
|
167
|
-
} from "@assistant-ui/react";
|
|
168
|
-
import { useState } from "react";
|
|
169
|
-
|
|
170
|
-
const convertMessage = (message: ThreadMessageLike, idx: number) => {
|
|
171
|
-
return message;
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
export function MyRuntimeProvider({
|
|
175
|
-
children,
|
|
176
|
-
}: Readonly<{
|
|
177
|
-
children: React.ReactNode;
|
|
178
|
-
}>) {
|
|
179
|
-
const [messages, setMessages] = useState<readonly ThreadMessageLike[]>([]);
|
|
180
|
-
|
|
181
|
-
const onNew = async (message: AppendMessage) => {
|
|
182
|
-
if (message.content.length !== 1 || message.content[0]?.type !== "text")
|
|
183
|
-
throw new Error("Only text content is supported");
|
|
184
|
-
|
|
185
|
-
const userMessage: ThreadMessageLike = {
|
|
186
|
-
role: "user",
|
|
187
|
-
content: [{ type: "text", text: message.content[0].text }],
|
|
188
|
-
};
|
|
189
|
-
setMessages((currentMessages) => [...currentMessages, userMessage]);
|
|
190
|
-
|
|
191
|
-
// normally you would perform an API call here to get the assistant response
|
|
192
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
193
|
-
|
|
194
|
-
const assistantMessage: ThreadMessageLike = {
|
|
195
|
-
role: "assistant",
|
|
196
|
-
content: [{ type: "text", text: "Hello, world!" }],
|
|
197
|
-
};
|
|
198
|
-
setMessages((currentMessages) => [...currentMessages, assistantMessage]);
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
const runtime = useExternalStoreRuntime<ThreadMessageLike>({
|
|
202
|
-
messages,
|
|
203
|
-
setMessages,
|
|
204
|
-
onNew,
|
|
205
|
-
convertMessage,
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
return (
|
|
209
|
-
<AssistantRuntimeProvider runtime={runtime}>
|
|
210
|
-
{children}
|
|
211
|
-
</AssistantRuntimeProvider>
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
</Step>
|
|
217
|
-
|
|
218
|
-
<Step>
|
|
219
|
-
### Use in Your App
|
|
220
|
-
|
|
221
|
-
```tsx title="app/page.tsx"
|
|
222
|
-
import { Thread } from "@/components/assistant-ui/thread";
|
|
223
|
-
import { MyRuntimeProvider } from "./MyRuntimeProvider";
|
|
224
|
-
|
|
225
|
-
export default function Page() {
|
|
226
|
-
return (
|
|
227
|
-
<MyRuntimeProvider>
|
|
228
|
-
<Thread />
|
|
229
|
-
</MyRuntimeProvider>
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
</Step>
|
|
119
|
+
</Step>
|
|
235
120
|
</Steps>
|
|
236
121
|
|
|
237
|
-
##
|
|
122
|
+
## Message conversion
|
|
238
123
|
|
|
239
|
-
|
|
124
|
+
Two approaches.
|
|
240
125
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
#### 1. Simple Conversion (Recommended)
|
|
126
|
+
### Inline `convertMessage`
|
|
244
127
|
|
|
245
128
|
```tsx
|
|
246
129
|
const convertMessage = (message: MyMessage): ThreadMessageLike => ({
|
|
@@ -257,9 +140,9 @@ const runtime = useExternalStoreRuntime({
|
|
|
257
140
|
});
|
|
258
141
|
```
|
|
259
142
|
|
|
260
|
-
|
|
143
|
+
### `useExternalMessageConverter` (with join strategy)
|
|
261
144
|
|
|
262
|
-
For
|
|
145
|
+
For performance optimization or when you need to merge adjacent assistant messages:
|
|
263
146
|
|
|
264
147
|
```tsx
|
|
265
148
|
import { useExternalMessageConverter } from "@assistant-ui/react";
|
|
@@ -269,98 +152,53 @@ const convertedMessages = useExternalMessageConverter({
|
|
|
269
152
|
role: message.role,
|
|
270
153
|
content: [{ type: "text", text: message.text }],
|
|
271
154
|
id: message.id,
|
|
272
|
-
createdAt: new Date(message.timestamp),
|
|
273
155
|
}),
|
|
274
156
|
messages,
|
|
275
157
|
isRunning: false,
|
|
276
|
-
joinStrategy: "concat-content", //
|
|
158
|
+
joinStrategy: "concat-content", // merges adjacent assistant messages
|
|
277
159
|
});
|
|
278
160
|
|
|
279
161
|
const runtime = useExternalStoreRuntime({
|
|
280
162
|
messages: convertedMessages,
|
|
281
163
|
onNew,
|
|
282
|
-
// No convertMessage needed - already converted
|
|
283
164
|
});
|
|
284
165
|
```
|
|
285
166
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
Controls how adjacent assistant messages are combined:
|
|
289
|
-
|
|
290
|
-
- **`concat-content`** (default): Merges adjacent assistant messages into one
|
|
291
|
-
- **`none`**: Keeps all messages separate
|
|
292
|
-
|
|
293
|
-
This is useful when your backend sends multiple message chunks that should appear as a single message in the UI.
|
|
294
|
-
|
|
295
|
-
<Callout type="info">
|
|
296
|
-
`useExternalMessageConverter` provides performance optimization for complex
|
|
297
|
-
message conversion scenarios. For simpler cases, consider using the basic
|
|
298
|
-
`convertMessage` approach shown above.
|
|
299
|
-
</Callout>
|
|
300
|
-
|
|
301
|
-
### Essential Handlers
|
|
302
|
-
|
|
303
|
-
#### Basic Chat (onNew only)
|
|
304
|
-
|
|
305
|
-
```tsx
|
|
306
|
-
const runtime = useExternalStoreRuntime({
|
|
307
|
-
messages,
|
|
308
|
-
onNew: async (message) => {
|
|
309
|
-
// Add user message to state
|
|
310
|
-
const userMsg = { role: "user", content: message.content };
|
|
311
|
-
setMessages([...messages, userMsg]);
|
|
312
|
-
|
|
313
|
-
// Get AI response
|
|
314
|
-
const response = await callAI(message);
|
|
315
|
-
setMessages([...messages, userMsg, response]);
|
|
316
|
-
},
|
|
317
|
-
});
|
|
318
|
-
```
|
|
167
|
+
`joinStrategy` controls how adjacent assistant messages combine: `concat-content` (default) merges them into one; `none` keeps them separate.
|
|
319
168
|
|
|
320
|
-
|
|
169
|
+
## Handler matrix
|
|
321
170
|
|
|
322
|
-
|
|
323
|
-
const runtime = useExternalStoreRuntime({
|
|
324
|
-
messages,
|
|
325
|
-
setMessages, // Enables branch switching
|
|
326
|
-
onNew, // Required
|
|
327
|
-
onEdit, // Enables message editing
|
|
328
|
-
onReload, // Enables regeneration
|
|
329
|
-
onCancel, // Enables cancellation
|
|
330
|
-
});
|
|
331
|
-
```
|
|
171
|
+
Each handler enables a specific UI feature.
|
|
332
172
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
173
|
+
| Handler | Enables |
|
|
174
|
+
| --- | --- |
|
|
175
|
+
| `onNew` | Sending new user messages (required) |
|
|
176
|
+
| `setMessages` | Branch switching |
|
|
177
|
+
| `onEdit` | Message edit button |
|
|
178
|
+
| `onReload` | Regenerate button |
|
|
179
|
+
| `onCancel` | Cancel button while generating |
|
|
180
|
+
| `onAddToolResult` | Client-side tool result handoff |
|
|
338
181
|
|
|
339
|
-
|
|
182
|
+
## Streaming responses
|
|
340
183
|
|
|
341
|
-
|
|
184
|
+
Stream by mutating the assistant message in place:
|
|
342
185
|
|
|
343
186
|
```tsx
|
|
344
187
|
const onNew = async (message: AppendMessage) => {
|
|
345
|
-
|
|
346
|
-
const userMessage: ThreadMessageLike = {
|
|
188
|
+
const userMsg: ThreadMessageLike = {
|
|
347
189
|
role: "user",
|
|
348
190
|
content: message.content,
|
|
349
191
|
id: generateId(),
|
|
350
192
|
};
|
|
351
|
-
setMessages((prev) => [...prev,
|
|
193
|
+
setMessages((prev) => [...prev, userMsg]);
|
|
352
194
|
|
|
353
|
-
// Create placeholder for assistant message
|
|
354
195
|
setIsRunning(true);
|
|
355
196
|
const assistantId = generateId();
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
content: [{ type: "text", text: "" }],
|
|
359
|
-
|
|
360
|
-
};
|
|
361
|
-
setMessages((prev) => [...prev, assistantMessage]);
|
|
197
|
+
setMessages((prev) => [
|
|
198
|
+
...prev,
|
|
199
|
+
{ role: "assistant", content: [{ type: "text", text: "" }], id: assistantId },
|
|
200
|
+
]);
|
|
362
201
|
|
|
363
|
-
// Stream response
|
|
364
202
|
const stream = await api.streamChat(message);
|
|
365
203
|
for await (const chunk of stream) {
|
|
366
204
|
setMessages((prev) =>
|
|
@@ -369,10 +207,7 @@ const onNew = async (message: AppendMessage) => {
|
|
|
369
207
|
? {
|
|
370
208
|
...m,
|
|
371
209
|
content: [
|
|
372
|
-
{
|
|
373
|
-
type: "text",
|
|
374
|
-
text: (m.content[0] as any).text + chunk,
|
|
375
|
-
},
|
|
210
|
+
{ type: "text", text: (m.content[0] as any).text + chunk },
|
|
376
211
|
],
|
|
377
212
|
}
|
|
378
213
|
: m,
|
|
@@ -383,52 +218,30 @@ const onNew = async (message: AppendMessage) => {
|
|
|
383
218
|
};
|
|
384
219
|
```
|
|
385
220
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
Enable message editing by implementing the `onEdit` handler:
|
|
389
|
-
|
|
390
|
-
<Callout type="info">
|
|
391
|
-
You can implement `onEdit(editedMessage)` to handle user-initiated edits in
|
|
392
|
-
your external store. This enables features like "edit and re-run" on your
|
|
393
|
-
backend.
|
|
394
|
-
</Callout>
|
|
221
|
+
## Message editing
|
|
395
222
|
|
|
396
223
|
```tsx
|
|
397
224
|
const onEdit = async (message: AppendMessage) => {
|
|
398
|
-
// Find the index where to insert the edited message
|
|
399
225
|
const index = messages.findIndex((m) => m.id === message.parentId) + 1;
|
|
400
|
-
|
|
401
|
-
// Keep messages up to the parent
|
|
402
226
|
const newMessages = [...messages.slice(0, index)];
|
|
403
|
-
|
|
404
|
-
// Add the edited message
|
|
405
|
-
const editedMessage: ThreadMessageLike = {
|
|
227
|
+
newMessages.push({
|
|
406
228
|
role: "user",
|
|
407
229
|
content: message.content,
|
|
408
|
-
id: message.id
|
|
409
|
-
};
|
|
410
|
-
newMessages.push(editedMessage);
|
|
411
|
-
|
|
230
|
+
id: message.id ?? generateId(),
|
|
231
|
+
});
|
|
412
232
|
setMessages(newMessages);
|
|
413
233
|
|
|
414
|
-
// Generate new response
|
|
415
234
|
setIsRunning(true);
|
|
416
235
|
const response = await api.chat(message);
|
|
417
|
-
newMessages.push({
|
|
418
|
-
role: "assistant",
|
|
419
|
-
content: response.content,
|
|
420
|
-
id: generateId(),
|
|
421
|
-
});
|
|
236
|
+
newMessages.push({ role: "assistant", content: response.content, id: generateId() });
|
|
422
237
|
setMessages(newMessages);
|
|
423
238
|
setIsRunning(false);
|
|
424
239
|
};
|
|
425
240
|
```
|
|
426
241
|
|
|
427
|
-
|
|
242
|
+
## Branching
|
|
428
243
|
|
|
429
|
-
The `messages` array
|
|
430
|
-
|
|
431
|
-
Each message must have an explicit `id` and `parentId`. Messages with the same `parentId` create branches:
|
|
244
|
+
The linear `messages` array assumes each message's parent is the previous one. For branching (e.g. multiple regenerations), use `ExportedMessageRepository.fromBranchableArray()` and import via `thread.import()`:
|
|
432
245
|
|
|
433
246
|
```tsx
|
|
434
247
|
import {
|
|
@@ -436,60 +249,45 @@ import {
|
|
|
436
249
|
useExternalStoreRuntime,
|
|
437
250
|
} from "@assistant-ui/react";
|
|
438
251
|
|
|
439
|
-
// Your messages from the backend, each with an id and parentId
|
|
440
252
|
const backendMessages = [
|
|
441
253
|
{ id: "user-1", role: "user", content: "Hello", parentId: null },
|
|
442
254
|
{ id: "asst-1", role: "assistant", content: "Hi!", parentId: "user-1" },
|
|
443
|
-
|
|
444
|
-
{ id: "asst-2", role: "assistant", content: "Hey!", parentId: "user-1" },
|
|
255
|
+
{ id: "asst-2", role: "assistant", content: "Hey!", parentId: "user-1" }, // branch
|
|
445
256
|
];
|
|
446
257
|
|
|
447
|
-
// Convert to ExportedMessageRepository
|
|
448
258
|
const repo = ExportedMessageRepository.fromBranchableArray(
|
|
449
259
|
backendMessages.map((m) => ({
|
|
450
260
|
message: { id: m.id, role: m.role, content: m.content },
|
|
451
261
|
parentId: m.parentId,
|
|
452
262
|
})),
|
|
453
|
-
{ headId: "asst-1" },
|
|
263
|
+
{ headId: "asst-1" },
|
|
454
264
|
);
|
|
455
265
|
|
|
456
|
-
// Import into the runtime
|
|
457
266
|
runtime.thread.import(repo);
|
|
458
267
|
```
|
|
459
268
|
|
|
460
|
-
|
|
461
|
-
Messages in the array must be ordered so that parents appear before their
|
|
462
|
-
children. Each message **must** have an `id` field set.
|
|
463
|
-
</Callout>
|
|
269
|
+
Each message must have an explicit `id` and `parentId`; messages with the same `parentId` create branches. Parents must appear before children in the array.
|
|
464
270
|
|
|
465
|
-
|
|
271
|
+
## Tool calling
|
|
466
272
|
|
|
467
|
-
|
|
273
|
+
Handle tool results by updating the matching tool-call entry:
|
|
468
274
|
|
|
469
275
|
```tsx
|
|
470
276
|
const onAddToolResult = (options: AddToolResultOptions) => {
|
|
471
277
|
setMessages((prev) =>
|
|
472
|
-
prev.map((message) =>
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
content: message.content.map((part) => {
|
|
478
|
-
if (
|
|
278
|
+
prev.map((message) =>
|
|
279
|
+
message.id === options.messageId
|
|
280
|
+
? {
|
|
281
|
+
...message,
|
|
282
|
+
content: message.content.map((part) =>
|
|
479
283
|
part.type === "tool-call" &&
|
|
480
284
|
part.toolCallId === options.toolCallId
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
return part;
|
|
488
|
-
}),
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
return message;
|
|
492
|
-
}),
|
|
285
|
+
? { ...part, result: options.result }
|
|
286
|
+
: part,
|
|
287
|
+
),
|
|
288
|
+
}
|
|
289
|
+
: message,
|
|
290
|
+
),
|
|
493
291
|
);
|
|
494
292
|
};
|
|
495
293
|
|
|
@@ -497,336 +295,38 @@ const runtime = useExternalStoreRuntime({
|
|
|
497
295
|
messages,
|
|
498
296
|
onNew,
|
|
499
297
|
onAddToolResult,
|
|
500
|
-
// ... other props
|
|
501
298
|
});
|
|
502
299
|
```
|
|
503
300
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
The runtime automatically matches tool results with their corresponding tool calls. When messages are converted and joined:
|
|
507
|
-
|
|
508
|
-
1. **Tool Call Tracking** - The runtime tracks tool calls by their `toolCallId`
|
|
509
|
-
2. **Result Association** - Tool results are automatically associated with their corresponding calls
|
|
510
|
-
3. **Message Grouping** - Related tool messages are intelligently grouped together
|
|
301
|
+
The runtime automatically matches tool results to their tool calls by `toolCallId` and groups related messages for display.
|
|
511
302
|
|
|
512
|
-
|
|
513
|
-
// Example: Tool call and result in separate messages
|
|
514
|
-
const messages = [
|
|
515
|
-
{
|
|
516
|
-
role: "assistant",
|
|
517
|
-
content: [
|
|
518
|
-
{
|
|
519
|
-
type: "tool-call",
|
|
520
|
-
toolCallId: "call_123",
|
|
521
|
-
toolName: "get_weather",
|
|
522
|
-
args: { location: "San Francisco" },
|
|
523
|
-
},
|
|
524
|
-
],
|
|
525
|
-
},
|
|
526
|
-
{
|
|
527
|
-
role: "tool",
|
|
528
|
-
content: [
|
|
529
|
-
{
|
|
530
|
-
type: "tool-result",
|
|
531
|
-
toolCallId: "call_123",
|
|
532
|
-
result: { temperature: 72, condition: "sunny" },
|
|
533
|
-
},
|
|
534
|
-
],
|
|
535
|
-
},
|
|
536
|
-
];
|
|
537
|
-
|
|
538
|
-
// These are automatically matched and grouped by the runtime
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
### File Attachments
|
|
303
|
+
## Attachments
|
|
542
304
|
|
|
543
|
-
|
|
305
|
+
Attachments use the standard adapter contract, see [adapters](/docs/runtimes/concepts/adapters#attachment-adapter):
|
|
544
306
|
|
|
545
307
|
```tsx
|
|
546
|
-
const attachmentAdapter: AttachmentAdapter = {
|
|
547
|
-
accept: "image/*,application/pdf,.txt,.md",
|
|
548
|
-
async add({ file }) {
|
|
549
|
-
// Upload file to your server
|
|
550
|
-
const formData = new FormData();
|
|
551
|
-
formData.append("file", file);
|
|
552
|
-
|
|
553
|
-
const response = await fetch("/api/upload", {
|
|
554
|
-
method: "POST",
|
|
555
|
-
body: formData,
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
const { id } = await response.json();
|
|
559
|
-
return {
|
|
560
|
-
id,
|
|
561
|
-
type: "document",
|
|
562
|
-
name: file.name,
|
|
563
|
-
file,
|
|
564
|
-
status: { type: "requires-action", reason: "composer-send" },
|
|
565
|
-
};
|
|
566
|
-
},
|
|
567
|
-
async remove(attachment) {
|
|
568
|
-
// Remove file from server
|
|
569
|
-
await fetch(`/api/upload/${attachment.id}`, {
|
|
570
|
-
method: "DELETE",
|
|
571
|
-
});
|
|
572
|
-
},
|
|
573
|
-
async send(attachment) {
|
|
574
|
-
// Convert pending attachment to complete attachment when message is sent
|
|
575
|
-
return {
|
|
576
|
-
...attachment,
|
|
577
|
-
status: { type: "complete" },
|
|
578
|
-
content: [{ type: "text", text: `File: ${attachment.name}` }],
|
|
579
|
-
};
|
|
580
|
-
},
|
|
581
|
-
};
|
|
582
|
-
|
|
583
308
|
const runtime = useExternalStoreRuntime({
|
|
584
309
|
messages,
|
|
585
310
|
onNew,
|
|
586
|
-
adapters: {
|
|
587
|
-
attachments: attachmentAdapter,
|
|
588
|
-
},
|
|
589
|
-
});
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
### Thread Management
|
|
593
|
-
|
|
594
|
-
#### Managing Thread Context
|
|
595
|
-
|
|
596
|
-
When implementing multi-thread support with `ExternalStoreRuntime`, you need to carefully manage thread context across your application. Here's a comprehensive approach:
|
|
597
|
-
|
|
598
|
-
```tsx
|
|
599
|
-
// Create a context for thread management
|
|
600
|
-
const ThreadContext = createContext<{
|
|
601
|
-
currentThreadId: string;
|
|
602
|
-
setCurrentThreadId: (id: string) => void;
|
|
603
|
-
threads: Map<string, ThreadMessageLike[]>;
|
|
604
|
-
setThreads: React.Dispatch<
|
|
605
|
-
React.SetStateAction<Map<string, ThreadMessageLike[]>>
|
|
606
|
-
>;
|
|
607
|
-
}>({
|
|
608
|
-
currentThreadId: "default",
|
|
609
|
-
setCurrentThreadId: () => {},
|
|
610
|
-
threads: new Map(),
|
|
611
|
-
setThreads: () => {},
|
|
311
|
+
adapters: { attachments: myAttachmentAdapter },
|
|
612
312
|
});
|
|
613
|
-
|
|
614
|
-
// Thread provider component
|
|
615
|
-
export function ThreadProvider({ children }: { children: ReactNode }) {
|
|
616
|
-
const [threads, setThreads] = useState<Map<string, ThreadMessageLike[]>>(
|
|
617
|
-
new Map([["default", []]]),
|
|
618
|
-
);
|
|
619
|
-
const [currentThreadId, setCurrentThreadId] = useState("default");
|
|
620
|
-
|
|
621
|
-
return (
|
|
622
|
-
<ThreadContext.Provider
|
|
623
|
-
value={{ currentThreadId, setCurrentThreadId, threads, setThreads }}
|
|
624
|
-
>
|
|
625
|
-
{children}
|
|
626
|
-
</ThreadContext.Provider>
|
|
627
|
-
);
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// Hook for accessing thread context
|
|
631
|
-
export function useThreadContext() {
|
|
632
|
-
const context = useContext(ThreadContext);
|
|
633
|
-
if (!context) {
|
|
634
|
-
throw new Error("useThreadContext must be used within ThreadProvider");
|
|
635
|
-
}
|
|
636
|
-
return context;
|
|
637
|
-
}
|
|
638
|
-
```
|
|
639
|
-
|
|
640
|
-
#### Complete Thread Implementation
|
|
641
|
-
|
|
642
|
-
Here's a full implementation with proper context management:
|
|
643
|
-
|
|
644
|
-
```tsx
|
|
645
|
-
function ChatWithThreads() {
|
|
646
|
-
const { currentThreadId, setCurrentThreadId, threads, setThreads } =
|
|
647
|
-
useThreadContext();
|
|
648
|
-
const [threadList, setThreadList] = useState<ExternalStoreThreadData[]>([
|
|
649
|
-
{ id: "default", status: "regular", title: "New Chat" },
|
|
650
|
-
]);
|
|
651
|
-
|
|
652
|
-
// Get messages for current thread
|
|
653
|
-
const currentMessages = threads.get(currentThreadId) || [];
|
|
654
|
-
|
|
655
|
-
const threadListAdapter: ExternalStoreThreadListAdapter = {
|
|
656
|
-
threadId: currentThreadId,
|
|
657
|
-
threads: threadList.filter((t) => t.status === "regular"),
|
|
658
|
-
archivedThreads: threadList.filter((t) => t.status === "archived"),
|
|
659
|
-
|
|
660
|
-
onSwitchToNewThread: () => {
|
|
661
|
-
const newId = `thread-${Date.now()}`;
|
|
662
|
-
setThreadList((prev) => [
|
|
663
|
-
...prev,
|
|
664
|
-
{
|
|
665
|
-
id: newId,
|
|
666
|
-
status: "regular",
|
|
667
|
-
title: "New Chat",
|
|
668
|
-
},
|
|
669
|
-
]);
|
|
670
|
-
setThreads((prev) => new Map(prev).set(newId, []));
|
|
671
|
-
setCurrentThreadId(newId);
|
|
672
|
-
},
|
|
673
|
-
|
|
674
|
-
onSwitchToThread: (threadId) => {
|
|
675
|
-
setCurrentThreadId(threadId);
|
|
676
|
-
},
|
|
677
|
-
|
|
678
|
-
onRename: (threadId, newTitle) => {
|
|
679
|
-
setThreadList((prev) =>
|
|
680
|
-
prev.map((t) =>
|
|
681
|
-
t.id === threadId ? { ...t, title: newTitle } : t,
|
|
682
|
-
),
|
|
683
|
-
);
|
|
684
|
-
},
|
|
685
|
-
|
|
686
|
-
onArchive: (threadId) => {
|
|
687
|
-
setThreadList((prev) =>
|
|
688
|
-
prev.map((t) =>
|
|
689
|
-
t.id === threadId ? { ...t, status: "archived" } : t,
|
|
690
|
-
),
|
|
691
|
-
);
|
|
692
|
-
},
|
|
693
|
-
|
|
694
|
-
onDelete: (threadId) => {
|
|
695
|
-
setThreadList((prev) => prev.filter((t) => t.id !== threadId));
|
|
696
|
-
setThreads((prev) => {
|
|
697
|
-
const next = new Map(prev);
|
|
698
|
-
next.delete(threadId);
|
|
699
|
-
return next;
|
|
700
|
-
});
|
|
701
|
-
if (currentThreadId === threadId) {
|
|
702
|
-
setCurrentThreadId("default");
|
|
703
|
-
}
|
|
704
|
-
},
|
|
705
|
-
};
|
|
706
|
-
|
|
707
|
-
const runtime = useExternalStoreRuntime({
|
|
708
|
-
messages: currentMessages,
|
|
709
|
-
setMessages: (messages) => {
|
|
710
|
-
setThreads((prev) => new Map(prev).set(currentThreadId, messages));
|
|
711
|
-
},
|
|
712
|
-
onNew: async (message) => {
|
|
713
|
-
// Handle new message for current thread
|
|
714
|
-
// Your implementation here
|
|
715
|
-
},
|
|
716
|
-
adapters: {
|
|
717
|
-
threadList: threadListAdapter,
|
|
718
|
-
},
|
|
719
|
-
});
|
|
720
|
-
|
|
721
|
-
return (
|
|
722
|
-
<AssistantRuntimeProvider runtime={runtime}>
|
|
723
|
-
<ThreadList />
|
|
724
|
-
<Thread />
|
|
725
|
-
</AssistantRuntimeProvider>
|
|
726
|
-
);
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
// App component with proper context wrapping
|
|
730
|
-
export function App() {
|
|
731
|
-
return (
|
|
732
|
-
<ThreadProvider>
|
|
733
|
-
<ChatWithThreads />
|
|
734
|
-
</ThreadProvider>
|
|
735
|
-
);
|
|
736
|
-
}
|
|
737
|
-
```
|
|
738
|
-
|
|
739
|
-
#### Thread Context Best Practices
|
|
740
|
-
|
|
741
|
-
<Callout type="info">
|
|
742
|
-
**Critical**: When using `ExternalStoreRuntime` with threads, the
|
|
743
|
-
`currentThreadId` must be consistent across all components and handlers.
|
|
744
|
-
Mismatched thread IDs will cause messages to appear in wrong threads or
|
|
745
|
-
disappear entirely.
|
|
746
|
-
</Callout>
|
|
747
|
-
|
|
748
|
-
1. **Centralize Thread State**: Always use a context or global state management solution to ensure thread ID consistency:
|
|
749
|
-
|
|
750
|
-
```tsx
|
|
751
|
-
// ❌ Bad: Local state in multiple components
|
|
752
|
-
function ThreadList() {
|
|
753
|
-
const [currentThreadId, setCurrentThreadId] = useState("default");
|
|
754
|
-
// This won't sync with the runtime!
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// ✅ Good: Shared context
|
|
758
|
-
function ThreadList() {
|
|
759
|
-
const { currentThreadId, setCurrentThreadId } = useThreadContext();
|
|
760
|
-
// Thread ID is synchronized everywhere
|
|
761
|
-
}
|
|
762
|
-
```
|
|
763
|
-
|
|
764
|
-
2. **Sync Thread Changes**: Ensure all thread-related operations update both the thread ID and messages:
|
|
765
|
-
|
|
766
|
-
```tsx
|
|
767
|
-
// ❌ Bad: Only updating thread ID
|
|
768
|
-
onSwitchToThread: (threadId) => {
|
|
769
|
-
setCurrentThreadId(threadId);
|
|
770
|
-
// Messages won't update!
|
|
771
|
-
};
|
|
772
|
-
|
|
773
|
-
// ✅ Good: Complete state update
|
|
774
|
-
onSwitchToThread: (threadId) => {
|
|
775
|
-
setCurrentThreadId(threadId);
|
|
776
|
-
// Messages automatically update via currentMessages = threads.get(currentThreadId)
|
|
777
|
-
};
|
|
778
|
-
```
|
|
779
|
-
|
|
780
|
-
3. **Handle Edge Cases**: Always provide fallbacks for missing threads:
|
|
781
|
-
|
|
782
|
-
```tsx
|
|
783
|
-
// Ensure thread always exists
|
|
784
|
-
const currentMessages = threads.get(currentThreadId) || [];
|
|
785
|
-
|
|
786
|
-
// Initialize new threads properly
|
|
787
|
-
const initializeThread = (threadId: string) => {
|
|
788
|
-
if (!threads.has(threadId)) {
|
|
789
|
-
setThreads((prev) => new Map(prev).set(threadId, []));
|
|
790
|
-
}
|
|
791
|
-
};
|
|
792
313
|
```
|
|
793
314
|
|
|
794
|
-
|
|
315
|
+
## Multi-thread
|
|
795
316
|
|
|
796
|
-
|
|
797
|
-
// Save thread state to backend
|
|
798
|
-
useEffect(() => {
|
|
799
|
-
const saveThread = async () => {
|
|
800
|
-
await api.saveThread(currentThreadId, threads.get(currentThreadId) || []);
|
|
801
|
-
};
|
|
802
|
-
|
|
803
|
-
const debounced = debounce(saveThread, 1000);
|
|
804
|
-
debounced();
|
|
805
|
-
|
|
806
|
-
return () => debounced.cancel();
|
|
807
|
-
}, [currentThreadId, threads]);
|
|
808
|
-
```
|
|
317
|
+
`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.
|
|
809
318
|
|
|
810
|
-
## Integration
|
|
319
|
+
## Integration examples
|
|
811
320
|
|
|
812
|
-
### Redux
|
|
321
|
+
### Redux
|
|
813
322
|
|
|
814
323
|
```tsx title="app/chatSlice.ts"
|
|
815
|
-
// Using Redux Toolkit (recommended)
|
|
816
324
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
|
817
325
|
import { ThreadMessageLike } from "@assistant-ui/react";
|
|
818
326
|
|
|
819
|
-
interface ChatState {
|
|
820
|
-
messages: ThreadMessageLike[];
|
|
821
|
-
isRunning: boolean;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
327
|
const chatSlice = createSlice({
|
|
825
328
|
name: "chat",
|
|
826
|
-
initialState: {
|
|
827
|
-
messages: [] as ThreadMessageLike[],
|
|
828
|
-
isRunning: false,
|
|
829
|
-
},
|
|
329
|
+
initialState: { messages: [] as ThreadMessageLike[], isRunning: false },
|
|
830
330
|
reducers: {
|
|
831
331
|
setMessages: (state, action: PayloadAction<ThreadMessageLike[]>) => {
|
|
832
332
|
state.messages = action.payload;
|
|
@@ -841,23 +341,15 @@ const chatSlice = createSlice({
|
|
|
841
341
|
});
|
|
842
342
|
|
|
843
343
|
export const { setMessages, addMessage, setIsRunning } = chatSlice.actions;
|
|
844
|
-
|
|
845
|
-
export const selectIsRunning = (state: RootState) => state.chat.isRunning;
|
|
846
|
-
export default chatSlice.reducer;
|
|
344
|
+
```
|
|
847
345
|
|
|
848
|
-
|
|
346
|
+
```tsx title="app/ReduxRuntimeProvider.tsx"
|
|
849
347
|
import { useSelector, useDispatch } from "react-redux";
|
|
850
|
-
import {
|
|
851
|
-
selectMessages,
|
|
852
|
-
selectIsRunning,
|
|
853
|
-
addMessage,
|
|
854
|
-
setMessages,
|
|
855
|
-
setIsRunning,
|
|
856
|
-
} from "./chatSlice";
|
|
348
|
+
import { useExternalStoreRuntime, AssistantRuntimeProvider } from "@assistant-ui/react";
|
|
857
349
|
|
|
858
350
|
export function ReduxRuntimeProvider({ children }) {
|
|
859
|
-
const messages = useSelector(
|
|
860
|
-
const isRunning = useSelector(
|
|
351
|
+
const messages = useSelector((s: RootState) => s.chat.messages);
|
|
352
|
+
const isRunning = useSelector((s: RootState) => s.chat.isRunning);
|
|
861
353
|
const dispatch = useDispatch();
|
|
862
354
|
|
|
863
355
|
const runtime = useExternalStoreRuntime({
|
|
@@ -865,7 +357,6 @@ export function ReduxRuntimeProvider({ children }) {
|
|
|
865
357
|
isRunning,
|
|
866
358
|
setMessages: (messages) => dispatch(setMessages(messages)),
|
|
867
359
|
onNew: async (message) => {
|
|
868
|
-
// Add user message
|
|
869
360
|
dispatch(
|
|
870
361
|
addMessage({
|
|
871
362
|
role: "user",
|
|
@@ -874,8 +365,6 @@ export function ReduxRuntimeProvider({ children }) {
|
|
|
874
365
|
createdAt: new Date(),
|
|
875
366
|
}),
|
|
876
367
|
);
|
|
877
|
-
|
|
878
|
-
// Generate response
|
|
879
368
|
dispatch(setIsRunning(true));
|
|
880
369
|
const response = await api.chat(message);
|
|
881
370
|
dispatch(
|
|
@@ -898,10 +387,9 @@ export function ReduxRuntimeProvider({ children }) {
|
|
|
898
387
|
}
|
|
899
388
|
```
|
|
900
389
|
|
|
901
|
-
### Zustand
|
|
390
|
+
### Zustand
|
|
902
391
|
|
|
903
392
|
```tsx title="app/chatStore.ts"
|
|
904
|
-
// Using Zustand v5 with TypeScript
|
|
905
393
|
import { create } from "zustand";
|
|
906
394
|
import { immer } from "zustand/middleware/immer";
|
|
907
395
|
import { ThreadMessageLike } from "@assistant-ui/react";
|
|
@@ -912,53 +400,32 @@ interface ChatState {
|
|
|
912
400
|
addMessage: (message: ThreadMessageLike) => void;
|
|
913
401
|
setMessages: (messages: ThreadMessageLike[]) => void;
|
|
914
402
|
setIsRunning: (isRunning: boolean) => void;
|
|
915
|
-
updateMessage: (id: string, updates: Partial<ThreadMessageLike>) => void;
|
|
916
403
|
}
|
|
917
404
|
|
|
918
|
-
|
|
919
|
-
const useChatStore = create<ChatState>()(
|
|
405
|
+
export const useChatStore = create<ChatState>()(
|
|
920
406
|
immer((set) => ({
|
|
921
407
|
messages: [],
|
|
922
408
|
isRunning: false,
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
state.messages.push(message);
|
|
927
|
-
}),
|
|
928
|
-
|
|
929
|
-
setMessages: (messages) =>
|
|
930
|
-
set((state) => {
|
|
931
|
-
state.messages = messages;
|
|
932
|
-
}),
|
|
933
|
-
|
|
934
|
-
setIsRunning: (isRunning) =>
|
|
935
|
-
set((state) => {
|
|
936
|
-
state.isRunning = isRunning;
|
|
937
|
-
}),
|
|
938
|
-
|
|
939
|
-
updateMessage: (id, updates) =>
|
|
940
|
-
set((state) => {
|
|
941
|
-
const index = state.messages.findIndex((m) => m.id === id);
|
|
942
|
-
if (index !== -1) {
|
|
943
|
-
Object.assign(state.messages[index], updates);
|
|
944
|
-
}
|
|
945
|
-
}),
|
|
409
|
+
addMessage: (message) => set((s) => { s.messages.push(message); }),
|
|
410
|
+
setMessages: (messages) => set((s) => { s.messages = messages; }),
|
|
411
|
+
setIsRunning: (isRunning) => set((s) => { s.isRunning = isRunning; }),
|
|
946
412
|
})),
|
|
947
413
|
);
|
|
414
|
+
```
|
|
948
415
|
|
|
949
|
-
|
|
416
|
+
```tsx title="app/ZustandRuntimeProvider.tsx"
|
|
950
417
|
import { useShallow } from "zustand/shallow";
|
|
418
|
+
import { useExternalStoreRuntime, AssistantRuntimeProvider } from "@assistant-ui/react";
|
|
951
419
|
|
|
952
420
|
export function ZustandRuntimeProvider({ children }) {
|
|
953
|
-
// Use useShallow to prevent unnecessary re-renders
|
|
954
421
|
const { messages, isRunning, addMessage, setMessages, setIsRunning } =
|
|
955
422
|
useChatStore(
|
|
956
|
-
useShallow((
|
|
957
|
-
messages:
|
|
958
|
-
isRunning:
|
|
959
|
-
addMessage:
|
|
960
|
-
setMessages:
|
|
961
|
-
setIsRunning:
|
|
423
|
+
useShallow((s) => ({
|
|
424
|
+
messages: s.messages,
|
|
425
|
+
isRunning: s.isRunning,
|
|
426
|
+
addMessage: s.addMessage,
|
|
427
|
+
setMessages: s.setMessages,
|
|
428
|
+
setIsRunning: s.setIsRunning,
|
|
962
429
|
})),
|
|
963
430
|
);
|
|
964
431
|
|
|
@@ -967,21 +434,18 @@ export function ZustandRuntimeProvider({ children }) {
|
|
|
967
434
|
isRunning,
|
|
968
435
|
setMessages,
|
|
969
436
|
onNew: async (message) => {
|
|
970
|
-
// Add user message
|
|
971
437
|
addMessage({
|
|
972
438
|
role: "user",
|
|
973
439
|
content: message.content,
|
|
974
440
|
id: `msg-${Date.now()}`,
|
|
975
441
|
createdAt: new Date(),
|
|
976
442
|
});
|
|
977
|
-
|
|
978
|
-
// Generate response
|
|
979
443
|
setIsRunning(true);
|
|
980
444
|
const response = await api.chat(message);
|
|
981
445
|
addMessage({
|
|
982
446
|
role: "assistant",
|
|
983
447
|
content: response.content,
|
|
984
|
-
id: `msg-${Date.now()}-
|
|
448
|
+
id: `msg-${Date.now()}-a`,
|
|
985
449
|
createdAt: new Date(),
|
|
986
450
|
});
|
|
987
451
|
setIsRunning(false);
|
|
@@ -996,98 +460,56 @@ export function ZustandRuntimeProvider({ children }) {
|
|
|
996
460
|
}
|
|
997
461
|
```
|
|
998
462
|
|
|
999
|
-
### TanStack Query
|
|
463
|
+
### TanStack Query
|
|
1000
464
|
|
|
1001
|
-
```tsx
|
|
1002
|
-
// Using TanStack Query v5 with TypeScript
|
|
465
|
+
```tsx
|
|
1003
466
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
1004
|
-
import {
|
|
467
|
+
import { useExternalStoreRuntime } from "@assistant-ui/react";
|
|
1005
468
|
|
|
1006
|
-
|
|
1007
|
-
export const messageKeys = {
|
|
469
|
+
const messageKeys = {
|
|
1008
470
|
all: ["messages"] as const,
|
|
1009
471
|
thread: (threadId: string) => [...messageKeys.all, threadId] as const,
|
|
1010
472
|
};
|
|
1011
473
|
|
|
1012
|
-
// TanStackQueryRuntimeProvider.tsx
|
|
1013
474
|
export function TanStackQueryRuntimeProvider({ children }) {
|
|
1014
475
|
const queryClient = useQueryClient();
|
|
1015
|
-
const threadId = "main";
|
|
476
|
+
const threadId = "main";
|
|
1016
477
|
|
|
1017
478
|
const { data: messages = [] } = useQuery({
|
|
1018
479
|
queryKey: messageKeys.thread(threadId),
|
|
1019
480
|
queryFn: () => fetchMessages(threadId),
|
|
1020
|
-
staleTime: 1000 * 60 * 5, // Consider data fresh for 5 minutes
|
|
1021
481
|
});
|
|
1022
482
|
|
|
1023
483
|
const sendMessage = useMutation({
|
|
1024
484
|
mutationFn: api.chat,
|
|
1025
|
-
|
|
1026
|
-
// Optimistic updates with proper TypeScript types
|
|
1027
485
|
onMutate: async (message: AppendMessage) => {
|
|
1028
|
-
// Cancel any outgoing refetches
|
|
1029
486
|
await queryClient.cancelQueries({
|
|
1030
487
|
queryKey: messageKeys.thread(threadId),
|
|
1031
488
|
});
|
|
1032
|
-
|
|
1033
|
-
// Snapshot the previous value
|
|
1034
|
-
const previousMessages = queryClient.getQueryData<ThreadMessageLike[]>(
|
|
1035
|
-
messageKeys.thread(threadId),
|
|
1036
|
-
);
|
|
1037
|
-
|
|
1038
|
-
// Optimistically update with typed data
|
|
1039
|
-
const optimisticMessage: ThreadMessageLike = {
|
|
1040
|
-
role: "user",
|
|
1041
|
-
content: message.content,
|
|
1042
|
-
id: `temp-${Date.now()}`,
|
|
1043
|
-
createdAt: new Date(),
|
|
1044
|
-
};
|
|
1045
|
-
|
|
1046
|
-
queryClient.setQueryData<ThreadMessageLike[]>(
|
|
489
|
+
const previous = queryClient.getQueryData<ThreadMessageLike[]>(
|
|
1047
490
|
messageKeys.thread(threadId),
|
|
1048
|
-
(old = []) => [...old, optimisticMessage],
|
|
1049
491
|
);
|
|
1050
|
-
|
|
1051
|
-
return { previousMessages, tempId: optimisticMessage.id };
|
|
1052
|
-
},
|
|
1053
|
-
|
|
1054
|
-
onSuccess: (response, variables, context) => {
|
|
1055
|
-
// Replace optimistic message with real data
|
|
1056
492
|
queryClient.setQueryData<ThreadMessageLike[]>(
|
|
1057
493
|
messageKeys.thread(threadId),
|
|
1058
|
-
(old = []) =>
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
.
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
createdAt: new Date(),
|
|
1068
|
-
},
|
|
1069
|
-
response,
|
|
1070
|
-
]);
|
|
1071
|
-
},
|
|
494
|
+
(old = []) => [
|
|
495
|
+
...old,
|
|
496
|
+
{
|
|
497
|
+
role: "user",
|
|
498
|
+
content: message.content,
|
|
499
|
+
id: `temp-${Date.now()}`,
|
|
500
|
+
createdAt: new Date(),
|
|
501
|
+
},
|
|
502
|
+
],
|
|
1072
503
|
);
|
|
504
|
+
return { previous };
|
|
1073
505
|
},
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
if (context?.previousMessages) {
|
|
1078
|
-
queryClient.setQueryData(
|
|
1079
|
-
messageKeys.thread(threadId),
|
|
1080
|
-
context.previousMessages,
|
|
1081
|
-
);
|
|
506
|
+
onError: (_err, _msg, context) => {
|
|
507
|
+
if (context?.previous) {
|
|
508
|
+
queryClient.setQueryData(messageKeys.thread(threadId), context.previous);
|
|
1082
509
|
}
|
|
1083
510
|
},
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
// Always refetch after error or success
|
|
1087
|
-
queryClient.invalidateQueries({
|
|
1088
|
-
queryKey: messageKeys.thread(threadId),
|
|
1089
|
-
});
|
|
1090
|
-
},
|
|
511
|
+
onSettled: () =>
|
|
512
|
+
queryClient.invalidateQueries({ queryKey: messageKeys.thread(threadId) }),
|
|
1091
513
|
});
|
|
1092
514
|
|
|
1093
515
|
const runtime = useExternalStoreRuntime({
|
|
@@ -1096,7 +518,6 @@ export function TanStackQueryRuntimeProvider({ children }) {
|
|
|
1096
518
|
onNew: async (message) => {
|
|
1097
519
|
await sendMessage.mutateAsync(message);
|
|
1098
520
|
},
|
|
1099
|
-
// Enable message editing
|
|
1100
521
|
setMessages: (newMessages) => {
|
|
1101
522
|
queryClient.setQueryData(messageKeys.thread(threadId), newMessages);
|
|
1102
523
|
},
|
|
@@ -1110,103 +531,28 @@ export function TanStackQueryRuntimeProvider({ children }) {
|
|
|
1110
531
|
}
|
|
1111
532
|
```
|
|
1112
533
|
|
|
1113
|
-
##
|
|
1114
|
-
|
|
1115
|
-
### Automatic Optimistic Updates
|
|
1116
|
-
|
|
1117
|
-
When `isRunning` becomes true, the runtime automatically shows an optimistic assistant message:
|
|
1118
|
-
|
|
1119
|
-
```tsx
|
|
1120
|
-
// Your code
|
|
1121
|
-
setIsRunning(true);
|
|
1122
|
-
|
|
1123
|
-
// Runtime automatically:
|
|
1124
|
-
// 1. Shows empty assistant message with { type: "running" } status
|
|
1125
|
-
// 2. Displays typing indicator
|
|
1126
|
-
// 3. Updates status to { type: "complete", reason: "unknown" } when isRunning becomes false
|
|
1127
|
-
```
|
|
1128
|
-
|
|
1129
|
-
### Message Status Management
|
|
1130
|
-
|
|
1131
|
-
Assistant messages get automatic status updates:
|
|
1132
|
-
|
|
1133
|
-
- `{ type: "running" }` - When `isRunning` is true
|
|
1134
|
-
- `{ type: "complete", reason: "unknown" }` - When `isRunning` becomes false
|
|
1135
|
-
- `{ type: "incomplete", reason: "cancelled" }` - When cancelled via `onCancel`
|
|
1136
|
-
|
|
1137
|
-
### Tool Result Matching
|
|
1138
|
-
|
|
1139
|
-
The runtime automatically matches tool results with their calls:
|
|
1140
|
-
|
|
1141
|
-
```tsx
|
|
1142
|
-
// Tool call and result can be in separate messages
|
|
1143
|
-
const messages = [
|
|
1144
|
-
{
|
|
1145
|
-
role: "assistant",
|
|
1146
|
-
content: [
|
|
1147
|
-
{
|
|
1148
|
-
type: "tool-call",
|
|
1149
|
-
toolCallId: "call_123",
|
|
1150
|
-
toolName: "get_weather",
|
|
1151
|
-
args: { location: "SF" },
|
|
1152
|
-
},
|
|
1153
|
-
],
|
|
1154
|
-
},
|
|
1155
|
-
{
|
|
1156
|
-
role: "tool",
|
|
1157
|
-
content: [
|
|
1158
|
-
{
|
|
1159
|
-
type: "tool-result",
|
|
1160
|
-
toolCallId: "call_123",
|
|
1161
|
-
result: { temp: 72 },
|
|
1162
|
-
},
|
|
1163
|
-
],
|
|
1164
|
-
},
|
|
1165
|
-
];
|
|
1166
|
-
// Runtime automatically associates these
|
|
1167
|
-
```
|
|
1168
|
-
|
|
1169
|
-
## Working with External Messages
|
|
534
|
+
## Working with external messages
|
|
1170
535
|
|
|
1171
|
-
###
|
|
536
|
+
### `getExternalStoreMessages`
|
|
1172
537
|
|
|
1173
|
-
|
|
538
|
+
Retrieve your original message format from any assistant-ui state:
|
|
1174
539
|
|
|
1175
540
|
```tsx
|
|
1176
|
-
import { getExternalStoreMessages } from "@assistant-ui/react";
|
|
541
|
+
import { getExternalStoreMessages, useAuiState } from "@assistant-ui/react";
|
|
1177
542
|
|
|
1178
|
-
|
|
543
|
+
function MyComponent() {
|
|
1179
544
|
const originalMessages = useAuiState((s) => getExternalStoreMessages(s.message));
|
|
1180
545
|
// originalMessages is MyMessage[] (your original type)
|
|
1181
|
-
}
|
|
546
|
+
}
|
|
1182
547
|
```
|
|
1183
548
|
|
|
1184
|
-
<Callout type="
|
|
1185
|
-
|
|
1186
|
-
back to your domain model. Refer to the API reference for return structures
|
|
1187
|
-
and edge-case behaviors.
|
|
549
|
+
<Callout type="warn">
|
|
550
|
+
`getExternalStoreMessages` may return multiple messages for a single UI message; assistant-ui merges adjacent assistant and tool messages for display.
|
|
1188
551
|
</Callout>
|
|
1189
552
|
|
|
1190
|
-
|
|
1191
|
-
`getExternalStoreMessages` may return multiple messages for a single UI
|
|
1192
|
-
message. This happens because assistant-ui merges adjacent assistant and tool
|
|
1193
|
-
messages for display.
|
|
1194
|
-
</Callout>
|
|
553
|
+
### `bindExternalStoreMessage`
|
|
1195
554
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
```tsx
|
|
1199
|
-
const ToolUI = makeAssistantToolUI({
|
|
1200
|
-
render: () => {
|
|
1201
|
-
const originalMessages = useAuiState((s) => getExternalStoreMessages(s.part));
|
|
1202
|
-
// Access original message data for this message part
|
|
1203
|
-
},
|
|
1204
|
-
});
|
|
1205
|
-
```
|
|
1206
|
-
|
|
1207
|
-
### Binding External Messages Manually
|
|
1208
|
-
|
|
1209
|
-
Use `bindExternalStoreMessage` to attach your original message to a `ThreadMessage` or message part object. This is useful when you construct `ThreadMessage` objects yourself (outside of the built-in message converter) and want `getExternalStoreMessages` to work with them.
|
|
555
|
+
Attach your original message to a `ThreadMessage` you constructed manually (outside the built-in converter):
|
|
1210
556
|
|
|
1211
557
|
```tsx
|
|
1212
558
|
import {
|
|
@@ -1214,574 +560,253 @@ import {
|
|
|
1214
560
|
getExternalStoreMessages,
|
|
1215
561
|
} from "@assistant-ui/react";
|
|
1216
562
|
|
|
1217
|
-
// Attach your original message to a ThreadMessage
|
|
1218
563
|
bindExternalStoreMessage(threadMessage, originalMessage);
|
|
1219
|
-
|
|
1220
|
-
// Later, retrieve it
|
|
1221
564
|
const original = getExternalStoreMessages(threadMessage);
|
|
1222
565
|
```
|
|
1223
566
|
|
|
567
|
+
`bindExternalStoreMessage` is a no-op if the target already has a bound message. It mutates the target in place.
|
|
568
|
+
|
|
1224
569
|
<Callout type="warn">
|
|
1225
|
-
|
|
570
|
+
This API is experimental and may change without notice.
|
|
1226
571
|
</Callout>
|
|
1227
572
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
## Debugging
|
|
1231
|
-
|
|
1232
|
-
### Common Debugging Scenarios
|
|
1233
|
-
|
|
1234
|
-
```tsx
|
|
1235
|
-
// Debug message conversion
|
|
1236
|
-
const convertMessage = (message: MyMessage): ThreadMessageLike => {
|
|
1237
|
-
console.log("Converting message:", message);
|
|
1238
|
-
const converted = {
|
|
1239
|
-
role: message.role,
|
|
1240
|
-
content: [{ type: "text", text: message.content }],
|
|
1241
|
-
};
|
|
1242
|
-
console.log("Converted to:", converted);
|
|
1243
|
-
return converted;
|
|
1244
|
-
};
|
|
1245
|
-
|
|
1246
|
-
// Debug adapter calls
|
|
1247
|
-
const onNew = async (message: AppendMessage) => {
|
|
1248
|
-
console.log("onNew called with:", message);
|
|
1249
|
-
// ... implementation
|
|
1250
|
-
};
|
|
1251
|
-
|
|
1252
|
-
// Enable verbose logging
|
|
1253
|
-
const runtime = useExternalStoreRuntime({
|
|
1254
|
-
messages,
|
|
1255
|
-
onNew: (...args) => {
|
|
1256
|
-
console.log("Runtime onNew:", args);
|
|
1257
|
-
return onNew(...args);
|
|
1258
|
-
},
|
|
1259
|
-
// ... other props
|
|
1260
|
-
});
|
|
1261
|
-
```
|
|
1262
|
-
|
|
1263
|
-
## Best Practices
|
|
1264
|
-
|
|
1265
|
-
### 1. Immutable Updates
|
|
1266
|
-
|
|
1267
|
-
Always create new arrays when updating messages:
|
|
573
|
+
## Best practices
|
|
1268
574
|
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
messages.push(newMessage)
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
setMessages([...messages, newMessage]);
|
|
1276
|
-
```
|
|
575
|
+
1. **Immutable updates.** Always create new arrays:
|
|
576
|
+
```tsx
|
|
577
|
+
setMessages([...messages, newMessage]); // not messages.push(newMessage)
|
|
578
|
+
```
|
|
579
|
+
2. **Stable handler references.** Memoize `onNew`, `onEdit`, etc. with `useCallback` to avoid recreating the runtime.
|
|
580
|
+
3. **Use `useShallow`** with zustand to prevent unnecessary re-renders.
|
|
1277
581
|
|
|
1278
|
-
|
|
582
|
+
## Common pitfalls
|
|
1279
583
|
|
|
1280
|
-
|
|
584
|
+
**Edit / regenerate / cancel buttons missing.** Each requires its handler:
|
|
1281
585
|
|
|
1282
586
|
```tsx
|
|
1283
|
-
|
|
1284
|
-
async (message: AppendMessage) => {
|
|
1285
|
-
// Handle new message
|
|
1286
|
-
},
|
|
1287
|
-
[
|
|
1288
|
-
/* dependencies */
|
|
1289
|
-
],
|
|
1290
|
-
);
|
|
1291
|
-
|
|
1292
|
-
const runtime = useExternalStoreRuntime({
|
|
587
|
+
useExternalStoreRuntime({
|
|
1293
588
|
messages,
|
|
1294
|
-
onNew, //
|
|
589
|
+
onNew, // required
|
|
590
|
+
setMessages, // branch switching
|
|
591
|
+
onEdit, // edit
|
|
592
|
+
onReload, // regenerate
|
|
593
|
+
onCancel, // cancel
|
|
1295
594
|
});
|
|
1296
595
|
```
|
|
1297
596
|
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
```tsx
|
|
1301
|
-
// For large message lists
|
|
1302
|
-
const recentMessages = useMemo(
|
|
1303
|
-
() => messages.slice(-50), // Show last 50 messages
|
|
1304
|
-
[messages],
|
|
1305
|
-
);
|
|
1306
|
-
|
|
1307
|
-
// For expensive conversions
|
|
1308
|
-
const convertMessage = useCallback((msg) => {
|
|
1309
|
-
// Conversion logic
|
|
1310
|
-
}, []);
|
|
1311
|
-
```
|
|
1312
|
-
|
|
1313
|
-
## `LocalRuntime` vs `ExternalStoreRuntime`
|
|
1314
|
-
|
|
1315
|
-
### When to Choose Which
|
|
1316
|
-
|
|
1317
|
-
| Scenario | Recommendation |
|
|
1318
|
-
| -------------------------------- | ------------------------------------------------------------ |
|
|
1319
|
-
| Quick prototype | `LocalRuntime` |
|
|
1320
|
-
| Using Redux/Zustand | `ExternalStoreRuntime` |
|
|
1321
|
-
| Need Assistant Cloud integration | `LocalRuntime` |
|
|
1322
|
-
| Custom thread storage | Both (`LocalRuntime` with adapter or `ExternalStoreRuntime`) |
|
|
1323
|
-
| Simple single thread | `LocalRuntime` |
|
|
1324
|
-
| Complex state logic | `ExternalStoreRuntime` |
|
|
1325
|
-
|
|
1326
|
-
### Feature Comparison
|
|
1327
|
-
|
|
1328
|
-
| Feature | `LocalRuntime` | `ExternalStoreRuntime` |
|
|
1329
|
-
| ---------------- | --------------------------- | ---------------------- |
|
|
1330
|
-
| State Management | Built-in | You provide |
|
|
1331
|
-
| Multi-thread | Via Cloud or custom adapter | Via adapter |
|
|
1332
|
-
| Message Format | ThreadMessage | Any (with conversion) |
|
|
1333
|
-
| Setup Complexity | Low | Medium |
|
|
1334
|
-
| Flexibility | Medium | High |
|
|
1335
|
-
|
|
1336
|
-
## Common Pitfalls
|
|
1337
|
-
|
|
1338
|
-
<Callout type="error">
|
|
1339
|
-
**Features not appearing**: Each UI feature requires its corresponding handler:
|
|
1340
|
-
|
|
1341
|
-
```tsx
|
|
1342
|
-
// ❌ No edit button
|
|
1343
|
-
const runtime = useExternalStoreRuntime({ messages, onNew });
|
|
1344
|
-
|
|
1345
|
-
// ✅ Edit button appears
|
|
1346
|
-
const runtime = useExternalStoreRuntime({ messages, onNew, onEdit });
|
|
1347
|
-
```
|
|
1348
|
-
|
|
1349
|
-
</Callout>
|
|
1350
|
-
|
|
1351
|
-
<Callout type="warning">
|
|
1352
|
-
|
|
1353
|
-
**State not updating**: Common causes:
|
|
1354
|
-
|
|
1355
|
-
1. Mutating arrays instead of creating new ones
|
|
1356
|
-
2. Missing `setMessages` for branch switching
|
|
1357
|
-
3. Not handling async operations properly
|
|
1358
|
-
4. Incorrect message format conversion
|
|
1359
|
-
|
|
1360
|
-
</Callout>
|
|
1361
|
-
|
|
1362
|
-
### Debugging Checklist
|
|
597
|
+
**State not updating.** check for: array mutation instead of new arrays, missing `setMessages`, broken async handling, or invalid `convertMessage` output.
|
|
1363
598
|
|
|
1364
|
-
|
|
1365
|
-
- Did you provide all required handlers for desired features?
|
|
1366
|
-
- Is your `convertMessage` returning valid `ThreadMessageLike`?
|
|
1367
|
-
- Are you properly handling `isRunning` state?
|
|
1368
|
-
- For threads: Is your thread list adapter complete?
|
|
599
|
+
**Messages going to the wrong thread.** the runtime's `currentThreadId` and your store's selected thread must stay in sync. Centralize thread id in a context, never in component-local state. See [threads](/docs/runtimes/concepts/threads#externalstorethreadlistadapter).
|
|
1369
600
|
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
Common thread context issues and solutions:
|
|
1373
|
-
|
|
1374
|
-
**Messages disappearing when switching threads:**
|
|
1375
|
-
|
|
1376
|
-
```tsx
|
|
1377
|
-
// Check 1: Ensure currentThreadId is consistent
|
|
1378
|
-
console.log("Runtime threadId:", threadListAdapter.threadId);
|
|
1379
|
-
console.log("Current threadId:", currentThreadId);
|
|
1380
|
-
console.log("Messages for thread:", threads.get(currentThreadId));
|
|
1381
|
-
|
|
1382
|
-
// Check 2: Verify setMessages uses correct thread
|
|
1383
|
-
setMessages: (messages) => {
|
|
1384
|
-
console.log("Setting messages for thread:", currentThreadId);
|
|
1385
|
-
setThreads((prev) => new Map(prev).set(currentThreadId, messages));
|
|
1386
|
-
};
|
|
1387
|
-
```
|
|
1388
|
-
|
|
1389
|
-
**Thread list not updating:**
|
|
1390
|
-
|
|
1391
|
-
```tsx
|
|
1392
|
-
// Ensure threadList state is properly managed
|
|
1393
|
-
onSwitchToNewThread: () => {
|
|
1394
|
-
const newId = `thread-${Date.now()}`;
|
|
1395
|
-
console.log("Creating new thread:", newId);
|
|
1396
|
-
|
|
1397
|
-
// All three updates must happen together
|
|
1398
|
-
setThreadList((prev) => [...prev, newThreadData]);
|
|
1399
|
-
setThreads((prev) => new Map(prev).set(newId, []));
|
|
1400
|
-
setCurrentThreadId(newId);
|
|
1401
|
-
};
|
|
1402
|
-
```
|
|
1403
|
-
|
|
1404
|
-
**Messages going to wrong thread:**
|
|
1405
|
-
|
|
1406
|
-
```tsx
|
|
1407
|
-
// Add validation to prevent race conditions
|
|
1408
|
-
const validateThreadContext = () => {
|
|
1409
|
-
const runtimeThread = threadListAdapter.threadId;
|
|
1410
|
-
const contextThread = currentThreadId;
|
|
1411
|
-
|
|
1412
|
-
if (runtimeThread !== contextThread) {
|
|
1413
|
-
console.error("Thread mismatch!", { runtimeThread, contextThread });
|
|
1414
|
-
throw new Error("Thread context mismatch");
|
|
1415
|
-
}
|
|
1416
|
-
};
|
|
1417
|
-
|
|
1418
|
-
// Call before any message operation
|
|
1419
|
-
onNew: async (message) => {
|
|
1420
|
-
validateThreadContext();
|
|
1421
|
-
// ... handle message
|
|
1422
|
-
};
|
|
1423
|
-
```
|
|
1424
|
-
|
|
1425
|
-
## API Reference
|
|
601
|
+
## API reference
|
|
1426
602
|
|
|
1427
603
|
### `ExternalStoreAdapter`
|
|
1428
604
|
|
|
1429
|
-
The main interface for connecting your state to assistant-ui.
|
|
1430
|
-
|
|
1431
605
|
<ParametersTable
|
|
1432
606
|
type="ExternalStoreAdapter<T>"
|
|
1433
607
|
parameters={[
|
|
1434
608
|
{
|
|
1435
609
|
name: "messages",
|
|
1436
610
|
type: "readonly T[]",
|
|
1437
|
-
description: "Array of messages from your state",
|
|
611
|
+
description: "Array of messages from your state.",
|
|
1438
612
|
required: true,
|
|
1439
613
|
},
|
|
1440
614
|
{
|
|
1441
615
|
name: "onNew",
|
|
1442
616
|
type: "(message: AppendMessage) => Promise<void>",
|
|
1443
|
-
description: "Handler for new messages from the user",
|
|
617
|
+
description: "Handler for new messages from the user.",
|
|
1444
618
|
required: true,
|
|
1445
619
|
},
|
|
1446
620
|
{
|
|
1447
621
|
name: "isRunning",
|
|
1448
622
|
type: "boolean",
|
|
1449
623
|
description:
|
|
1450
|
-
"Whether the assistant is currently generating a response. When true, shows an optimistic assistant message and flows directly to
|
|
624
|
+
"Whether the assistant is currently generating a response. When true, shows an optimistic assistant message and flows directly to thread.isRunning.",
|
|
1451
625
|
default: "false",
|
|
1452
626
|
},
|
|
1453
627
|
{
|
|
1454
628
|
name: "isDisabled",
|
|
1455
629
|
type: "boolean",
|
|
1456
|
-
description:
|
|
630
|
+
description:
|
|
631
|
+
"Disables the entire composer, including the text input. For a narrower gate that keeps the input usable but blocks only sending, use isSendDisabled.",
|
|
1457
632
|
default: "false",
|
|
1458
633
|
},
|
|
634
|
+
{
|
|
635
|
+
name: "isSendDisabled",
|
|
636
|
+
type: "boolean",
|
|
637
|
+
description:
|
|
638
|
+
"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).",
|
|
639
|
+
default: "false",
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
name: "isLoading",
|
|
643
|
+
type: "boolean",
|
|
644
|
+
description:
|
|
645
|
+
"Whether the adapter is in a loading state. Displays a loading indicator instead of the composer.",
|
|
646
|
+
},
|
|
1459
647
|
{
|
|
1460
648
|
name: "suggestions",
|
|
1461
649
|
type: "readonly ThreadSuggestion[]",
|
|
1462
|
-
description: "Suggested prompts to display",
|
|
650
|
+
description: "Suggested prompts to display.",
|
|
1463
651
|
},
|
|
1464
652
|
{
|
|
1465
653
|
name: "extras",
|
|
1466
654
|
type: "unknown",
|
|
1467
|
-
description: "Additional data accessible via runtime.extras",
|
|
655
|
+
description: "Additional data accessible via runtime.extras.",
|
|
1468
656
|
},
|
|
1469
657
|
{
|
|
1470
658
|
name: "setMessages",
|
|
1471
659
|
type: "(messages: readonly T[]) => void",
|
|
1472
|
-
description: "Update messages (required for branch switching)",
|
|
660
|
+
description: "Update messages (required for branch switching).",
|
|
1473
661
|
},
|
|
1474
662
|
{
|
|
1475
663
|
name: "onEdit",
|
|
1476
664
|
type: "(message: AppendMessage) => Promise<void>",
|
|
1477
|
-
description: "Handler for message edits (required for edit feature)",
|
|
665
|
+
description: "Handler for message edits (required for edit feature).",
|
|
1478
666
|
},
|
|
1479
667
|
{
|
|
1480
668
|
name: "onReload",
|
|
1481
|
-
type: "(parentId: string |
|
|
669
|
+
type: "(parentId: string | Null, config: StartRunConfig) => Promise<void>",
|
|
1482
670
|
description:
|
|
1483
|
-
"Handler for regenerating messages (required for reload feature)",
|
|
671
|
+
"Handler for regenerating messages (required for reload feature).",
|
|
1484
672
|
},
|
|
1485
673
|
{
|
|
1486
674
|
name: "onCancel",
|
|
1487
675
|
type: "() => Promise<void>",
|
|
1488
|
-
description: "Handler for cancelling the current generation",
|
|
676
|
+
description: "Handler for cancelling the current generation.",
|
|
1489
677
|
},
|
|
1490
678
|
{
|
|
1491
679
|
name: "onAddToolResult",
|
|
1492
|
-
type: "(options: AddToolResultOptions) => Promise<void> |
|
|
1493
|
-
description: "Handler for adding tool call results",
|
|
680
|
+
type: "(options: AddToolResultOptions) => Promise<void> | Void",
|
|
681
|
+
description: "Handler for adding tool call results.",
|
|
1494
682
|
},
|
|
1495
683
|
{
|
|
1496
684
|
name: "onResume",
|
|
1497
685
|
type: "(config: ResumeRunConfig) => Promise<void>",
|
|
1498
686
|
description:
|
|
1499
|
-
"Handler for resuming an interrupted run (e.g. after a page reload mid-generation)",
|
|
687
|
+
"Handler for resuming an interrupted run (e.g. after a page reload mid-generation).",
|
|
1500
688
|
},
|
|
1501
689
|
{
|
|
1502
690
|
name: "onResumeToolCall",
|
|
1503
691
|
type: "(options: { toolCallId: string; payload: unknown }) => void",
|
|
1504
692
|
description:
|
|
1505
|
-
"Handler for resuming a suspended tool call (used with human-in-the-loop tool execution)",
|
|
1506
|
-
},
|
|
1507
|
-
{
|
|
1508
|
-
name: "isLoading",
|
|
1509
|
-
type: "boolean",
|
|
1510
|
-
description:
|
|
1511
|
-
"Whether the adapter is in a loading state (e.g. initial data fetch). Displays a loading indicator instead of the composer",
|
|
693
|
+
"Handler for resuming a suspended tool call (used with human-in-the-loop tool execution).",
|
|
1512
694
|
},
|
|
1513
695
|
{
|
|
1514
696
|
name: "messageRepository",
|
|
1515
697
|
type: "ExportedMessageRepository",
|
|
1516
698
|
description:
|
|
1517
|
-
"Pre-built message repository with branching history. Use instead of
|
|
699
|
+
"Pre-built message repository with branching history. Use instead of messages when you need to restore branch state.",
|
|
1518
700
|
},
|
|
1519
701
|
{
|
|
1520
702
|
name: "state",
|
|
1521
703
|
type: "ReadonlyJSONValue",
|
|
1522
704
|
description:
|
|
1523
|
-
"Opaque serializable state passed to
|
|
705
|
+
"Opaque serializable state passed to onLoadExternalState during thread import.",
|
|
1524
706
|
},
|
|
1525
707
|
{
|
|
1526
708
|
name: "onImport",
|
|
1527
709
|
type: "(messages: readonly ThreadMessage[]) => void",
|
|
1528
710
|
description:
|
|
1529
|
-
"Called when the runtime imports messages into the external store (e.g. on thread switch)",
|
|
711
|
+
"Called when the runtime imports messages into the external store (e.g. on thread switch).",
|
|
1530
712
|
},
|
|
1531
713
|
{
|
|
1532
714
|
name: "onExportExternalState",
|
|
1533
715
|
type: "() => any",
|
|
1534
716
|
description:
|
|
1535
|
-
"Called to retrieve external state when the runtime exports a thread snapshot",
|
|
717
|
+
"Called to retrieve external state when the runtime exports a thread snapshot.",
|
|
1536
718
|
},
|
|
1537
719
|
{
|
|
1538
720
|
name: "onLoadExternalState",
|
|
1539
721
|
type: "(state: any) => void",
|
|
1540
722
|
description:
|
|
1541
|
-
"Called with previously exported external state when restoring a thread snapshot",
|
|
723
|
+
"Called with previously exported external state when restoring a thread snapshot.",
|
|
1542
724
|
},
|
|
1543
725
|
{
|
|
1544
726
|
name: "convertMessage",
|
|
1545
727
|
type: "(message: T, index: number) => ThreadMessageLike",
|
|
1546
728
|
description:
|
|
1547
|
-
"Convert your message format to assistant-ui format. Not needed if using ThreadMessage type",
|
|
729
|
+
"Convert your message format to assistant-ui format. Not needed if using ThreadMessage type.",
|
|
1548
730
|
},
|
|
1549
731
|
{
|
|
1550
732
|
name: "adapters",
|
|
1551
733
|
type: "object",
|
|
1552
|
-
description:
|
|
1553
|
-
|
|
1554
|
-
{
|
|
1555
|
-
type: "adapters",
|
|
1556
|
-
parameters: [
|
|
1557
|
-
{
|
|
1558
|
-
name: "attachments",
|
|
1559
|
-
type: "AttachmentAdapter",
|
|
1560
|
-
description: "Enable file attachments",
|
|
1561
|
-
},
|
|
1562
|
-
{
|
|
1563
|
-
name: "speech",
|
|
1564
|
-
type: "SpeechSynthesisAdapter",
|
|
1565
|
-
description: "Enable text-to-speech",
|
|
1566
|
-
},
|
|
1567
|
-
{
|
|
1568
|
-
name: "dictation",
|
|
1569
|
-
type: "DictationAdapter",
|
|
1570
|
-
description: "Enable speech-to-text dictation",
|
|
1571
|
-
},
|
|
1572
|
-
{
|
|
1573
|
-
name: "feedback",
|
|
1574
|
-
type: "FeedbackAdapter",
|
|
1575
|
-
description: "Enable message feedback",
|
|
1576
|
-
},
|
|
1577
|
-
{
|
|
1578
|
-
name: "threadList",
|
|
1579
|
-
type: "ExternalStoreThreadListAdapter",
|
|
1580
|
-
description: "Enable multi-thread management",
|
|
1581
|
-
},
|
|
1582
|
-
],
|
|
1583
|
-
},
|
|
1584
|
-
],
|
|
734
|
+
description:
|
|
735
|
+
"Capability adapters: attachments, speech, dictation, feedback, threadList. See /docs/runtimes/concepts/adapters.",
|
|
1585
736
|
},
|
|
1586
737
|
{
|
|
1587
738
|
name: "unstable_capabilities",
|
|
1588
739
|
type: "object",
|
|
1589
|
-
description:
|
|
1590
|
-
|
|
1591
|
-
{
|
|
1592
|
-
type: "unstable_capabilities",
|
|
1593
|
-
parameters: [
|
|
1594
|
-
{
|
|
1595
|
-
name: "copy",
|
|
1596
|
-
type: "boolean",
|
|
1597
|
-
description: "Enable message copy feature",
|
|
1598
|
-
default: "true",
|
|
1599
|
-
},
|
|
1600
|
-
],
|
|
1601
|
-
},
|
|
1602
|
-
],
|
|
740
|
+
description:
|
|
741
|
+
"Configure runtime capabilities (e.g. copy). Unstable, may change.",
|
|
1603
742
|
},
|
|
1604
743
|
]}
|
|
1605
744
|
/>
|
|
1606
745
|
|
|
1607
746
|
### `ThreadMessageLike`
|
|
1608
747
|
|
|
1609
|
-
A flexible message format that can be converted to assistant-ui's internal format.
|
|
1610
|
-
|
|
1611
748
|
<ParametersTable
|
|
1612
749
|
type="ThreadMessageLike"
|
|
1613
750
|
parameters={[
|
|
1614
751
|
{
|
|
1615
752
|
name: "role",
|
|
1616
753
|
type: '"assistant" | "user" | "system"',
|
|
1617
|
-
description: "The role of the message sender",
|
|
754
|
+
description: "The role of the message sender.",
|
|
1618
755
|
required: true,
|
|
1619
756
|
},
|
|
1620
757
|
{
|
|
1621
758
|
name: "content",
|
|
1622
|
-
type: "string |
|
|
1623
|
-
description:
|
|
759
|
+
type: "string | Readonly MessagePart[]",
|
|
760
|
+
description:
|
|
761
|
+
"Message content as string or structured message parts. Supports data-* prefixed types (e.g. { type: \"data-workflow\", data: {...} }) which are automatically converted to DataMessagePart.",
|
|
1624
762
|
required: true,
|
|
1625
763
|
},
|
|
1626
764
|
{
|
|
1627
765
|
name: "id",
|
|
1628
766
|
type: "string",
|
|
1629
|
-
description: "Unique identifier for the message",
|
|
767
|
+
description: "Unique identifier for the message.",
|
|
1630
768
|
},
|
|
1631
769
|
{
|
|
1632
770
|
name: "createdAt",
|
|
1633
771
|
type: "Date",
|
|
1634
|
-
description: "Timestamp when the message was created",
|
|
772
|
+
description: "Timestamp when the message was created.",
|
|
1635
773
|
},
|
|
1636
774
|
{
|
|
1637
775
|
name: "status",
|
|
1638
776
|
type: "MessageStatus",
|
|
1639
777
|
description:
|
|
1640
|
-
|
|
778
|
+
'Status of assistant messages ({ type: "running" }, { type: "complete" }, { type: "incomplete" }).',
|
|
1641
779
|
},
|
|
1642
780
|
{
|
|
1643
781
|
name: "attachments",
|
|
1644
782
|
type: "readonly CompleteAttachment[]",
|
|
1645
|
-
description:
|
|
783
|
+
description:
|
|
784
|
+
'File attachments (user messages only). Type accepts custom strings beyond "image" | "document" | "file"; contentType is optional.',
|
|
1646
785
|
},
|
|
1647
786
|
{
|
|
1648
787
|
name: "metadata",
|
|
1649
788
|
type: "object",
|
|
1650
|
-
description: "Additional message metadata",
|
|
1651
|
-
children: [
|
|
1652
|
-
{
|
|
1653
|
-
type: "metadata",
|
|
1654
|
-
parameters: [
|
|
1655
|
-
{
|
|
1656
|
-
name: "steps",
|
|
1657
|
-
type: "readonly ThreadStep[]",
|
|
1658
|
-
description: "Tool call steps for assistant messages",
|
|
1659
|
-
},
|
|
1660
|
-
{
|
|
1661
|
-
name: "custom",
|
|
1662
|
-
type: "Record<string, unknown>",
|
|
1663
|
-
description: "Custom metadata for your application",
|
|
1664
|
-
},
|
|
1665
|
-
],
|
|
1666
|
-
},
|
|
1667
|
-
],
|
|
1668
|
-
},
|
|
1669
|
-
]}
|
|
1670
|
-
/>
|
|
1671
|
-
|
|
1672
|
-
### `ExternalStoreThreadListAdapter`
|
|
1673
|
-
|
|
1674
|
-
Enable multi-thread support with custom thread management.
|
|
1675
|
-
|
|
1676
|
-
<ParametersTable
|
|
1677
|
-
type="ExternalStoreThreadListAdapter"
|
|
1678
|
-
parameters={[
|
|
1679
|
-
{
|
|
1680
|
-
name: "threadId",
|
|
1681
|
-
type: "string",
|
|
1682
|
-
description:
|
|
1683
|
-
"ID of the current active thread. **Deprecated** — this API is still under active development and might change without notice.",
|
|
1684
|
-
},
|
|
1685
|
-
{
|
|
1686
|
-
name: "isLoading",
|
|
1687
|
-
type: "boolean",
|
|
1688
|
-
description: "Whether the thread list is currently loading",
|
|
1689
|
-
},
|
|
1690
|
-
{
|
|
1691
|
-
name: "threads",
|
|
1692
|
-
type: "readonly ExternalStoreThreadData<\"regular\">[]",
|
|
1693
|
-
description: "Array of active threads. Each entry is an `ExternalStoreThreadData` object.",
|
|
1694
|
-
},
|
|
1695
|
-
{
|
|
1696
|
-
name: "archivedThreads",
|
|
1697
|
-
type: "readonly ExternalStoreThreadData<\"archived\">[]",
|
|
1698
|
-
description: "Array of archived threads. Each entry is an `ExternalStoreThreadData` object.",
|
|
1699
|
-
},
|
|
1700
|
-
{
|
|
1701
|
-
name: "onSwitchToNewThread",
|
|
1702
|
-
type: "() => Promise<void> | void",
|
|
1703
|
-
description:
|
|
1704
|
-
"Handler for creating a new thread. **Deprecated** — this API is still under active development and might change without notice.",
|
|
1705
|
-
},
|
|
1706
|
-
{
|
|
1707
|
-
name: "onSwitchToThread",
|
|
1708
|
-
type: "(threadId: string) => Promise<void> | void",
|
|
1709
|
-
description:
|
|
1710
|
-
"Handler for switching to an existing thread. **Deprecated** — this API is still under active development and might change without notice.",
|
|
1711
|
-
},
|
|
1712
|
-
{
|
|
1713
|
-
name: "onRename",
|
|
1714
|
-
type: "(threadId: string, newTitle: string) => Promise<void> | void",
|
|
1715
|
-
description: "Handler for renaming a thread",
|
|
1716
|
-
},
|
|
1717
|
-
{
|
|
1718
|
-
name: "onArchive",
|
|
1719
|
-
type: "(threadId: string) => Promise<void> | void",
|
|
1720
|
-
description: "Handler for archiving a thread",
|
|
1721
|
-
},
|
|
1722
|
-
{
|
|
1723
|
-
name: "onUnarchive",
|
|
1724
|
-
type: "(threadId: string) => Promise<void> | void",
|
|
1725
|
-
description: "Handler for unarchiving a thread",
|
|
1726
|
-
},
|
|
1727
|
-
{
|
|
1728
|
-
name: "onDelete",
|
|
1729
|
-
type: "(threadId: string) => Promise<void> | void",
|
|
1730
|
-
description: "Handler for deleting a thread",
|
|
1731
|
-
},
|
|
1732
|
-
]}
|
|
1733
|
-
/>
|
|
1734
|
-
|
|
1735
|
-
<Callout type="info">
|
|
1736
|
-
The thread list adapter enables multi-thread support. Without it, the runtime
|
|
1737
|
-
only manages the current conversation.
|
|
1738
|
-
</Callout>
|
|
1739
|
-
|
|
1740
|
-
### `ExternalStoreThreadData`
|
|
1741
|
-
|
|
1742
|
-
Represents a single thread entry in the thread list.
|
|
1743
|
-
|
|
1744
|
-
<ParametersTable
|
|
1745
|
-
type="ExternalStoreThreadData<TState>"
|
|
1746
|
-
parameters={[
|
|
1747
|
-
{
|
|
1748
|
-
name: "id",
|
|
1749
|
-
type: "string",
|
|
1750
|
-
description: "Unique local identifier for the thread",
|
|
1751
|
-
required: true,
|
|
1752
|
-
},
|
|
1753
|
-
{
|
|
1754
|
-
name: "status",
|
|
1755
|
-
type: '"regular" | "archived"',
|
|
1756
|
-
description: "Whether the thread is active or archived",
|
|
1757
|
-
required: true,
|
|
1758
|
-
},
|
|
1759
|
-
{
|
|
1760
|
-
name: "title",
|
|
1761
|
-
type: "string",
|
|
1762
|
-
description: "Display title for the thread",
|
|
1763
|
-
},
|
|
1764
|
-
{
|
|
1765
|
-
name: "remoteId",
|
|
1766
|
-
type: "string",
|
|
1767
|
-
description: "Remote/server-side identifier for the thread (used for persistence)",
|
|
1768
|
-
},
|
|
1769
|
-
{
|
|
1770
|
-
name: "externalId",
|
|
1771
|
-
type: "string",
|
|
1772
|
-
description: "External system identifier for the thread (e.g. from a third-party service)",
|
|
789
|
+
description: "Additional message metadata (steps, custom fields).",
|
|
1773
790
|
},
|
|
1774
791
|
]}
|
|
1775
792
|
/>
|
|
1776
793
|
|
|
1777
|
-
|
|
794
|
+
## Related
|
|
1778
795
|
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
796
|
+
<Cards>
|
|
797
|
+
<Card
|
|
798
|
+
title="LocalRuntime"
|
|
799
|
+
description="Simpler core runtime when you do not have your own state store."
|
|
800
|
+
href="/docs/runtimes/custom/local-runtime"
|
|
801
|
+
/>
|
|
802
|
+
<Card
|
|
803
|
+
title="Adapters"
|
|
804
|
+
description="Attachments, speech, feedback, history, suggestions."
|
|
805
|
+
href="/docs/runtimes/concepts/adapters"
|
|
806
|
+
/>
|
|
807
|
+
<Card
|
|
808
|
+
title="Threads"
|
|
809
|
+
description="ExternalStoreThreadListAdapter for multi-thread."
|
|
810
|
+
href="/docs/runtimes/concepts/threads"
|
|
811
|
+
/>
|
|
812
|
+
</Cards>
|