@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
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Custom Resumable Stream Stores
|
|
3
|
+
description: Implement the ResumableStreamStore interface to back resumable streams with Postgres, Cloudflare Durable Objects, Upstash REST, InstantDB, or any other backend.
|
|
4
|
+
platforms: ["react"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The built-in InMemory and Redis adapters cover most deployments. Write your own `ResumableStreamStore` when you need a backend you already operate (Postgres, MySQL), an edge-native primitive (Cloudflare Durable Objects, Workers KV), an HTTP-only key-value service (Upstash REST), or a realtime database (InstantDB). The contract is six async methods over an opaque `streamId` and a monotonic byte log.
|
|
8
|
+
|
|
9
|
+
## Interface walkthrough
|
|
10
|
+
|
|
11
|
+
The full interface lives in `assistant-stream/resumable`:
|
|
12
|
+
|
|
13
|
+
```ts title="packages/assistant-stream/src/resumable/types.ts"
|
|
14
|
+
export interface ResumableStreamStore {
|
|
15
|
+
acquire(
|
|
16
|
+
streamId: string,
|
|
17
|
+
options?: ResumableStreamAcquireOptions,
|
|
18
|
+
): Promise<ResumableStreamRole>;
|
|
19
|
+
append(streamId: string, chunk: Uint8Array): Promise<void>;
|
|
20
|
+
finalize(
|
|
21
|
+
streamId: string,
|
|
22
|
+
status: "done" | "error",
|
|
23
|
+
error?: string,
|
|
24
|
+
): Promise<void>;
|
|
25
|
+
read(
|
|
26
|
+
streamId: string,
|
|
27
|
+
cursor: string,
|
|
28
|
+
signal: AbortSignal,
|
|
29
|
+
): AsyncIterable<ResumableStreamEntry>;
|
|
30
|
+
status(streamId: string): Promise<ResumableStreamStatus>;
|
|
31
|
+
delete(streamId: string): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`acquire(streamId, options?)` arbitrates ownership. The first caller for a given `streamId` resolves to `"producer"`; every later caller, including those arriving after `finalize`, resolves to `"consumer"`. Implementations must perform the check and the insert atomically (see below). `options.ttlMs` overrides the store default for this stream; honor it when you set the expiration timestamp.
|
|
36
|
+
|
|
37
|
+
`append(streamId, chunk)` adds a `Uint8Array` to the log under a fresh, monotonically increasing cursor. Callers expect the chunk to be observable to `read` before the promise resolves. Implementations should refresh the TTL on each call so a stream that is still actively producing does not expire mid-flight, and should reject when the stream is missing or already finalized.
|
|
38
|
+
|
|
39
|
+
`finalize(streamId, status, error?)` flips the stream into a terminal state. Pending and future `read` iterables drain buffered entries and then either complete (`"done"`) or throw with `error` (`"error"`). Implementations must make `finalize` idempotent: a duplicate call with the same status is a no-op, and the producer task may retry on transient errors.
|
|
40
|
+
|
|
41
|
+
`read(streamId, cursor, signal)` is the only streaming method. It yields every entry whose cursor sorts strictly after the supplied `cursor`, then waits for new appends, then completes when the stream finalizes. Aborting `signal` resolves the iterable cleanly without throwing. Networked stores typically combine a bounded fetch loop with pub/sub, long-poll, or notify wakeups; do not busy-loop.
|
|
42
|
+
|
|
43
|
+
`status(streamId)` returns one of `"streaming" | "done" | "error" | "missing"` synchronously with respect to the underlying store. It exists so the context can decide whether to start a new producer or attach a consumer without holding a `read` iterator open.
|
|
44
|
+
|
|
45
|
+
`delete(streamId)` removes all state for the stream. It must be a no-op when the stream does not exist, and it should cause active `read` iterables to terminate (treat outstanding readers as if the stream finalized).
|
|
46
|
+
|
|
47
|
+
## Acquire semantics
|
|
48
|
+
|
|
49
|
+
`acquire` is the only method that requires linearizability across processes. Two route handlers that race to start the same `streamId` must see exactly one `"producer"` result; the loser becomes a `"consumer"` and replays the winner's bytes. A single-process store can guard a `Map` with a synchronous `if (!map.has(id)) map.set(id, ...)`. Networked stores need a primitive that does the check and the insert in one round trip:
|
|
50
|
+
|
|
51
|
+
- Redis: `SET key value NX EX ttl`, or `INCR` against a per-stream counter.
|
|
52
|
+
- Postgres: `INSERT ... ON CONFLICT (stream_id) DO NOTHING RETURNING ...`.
|
|
53
|
+
- Durable Objects: a single object instance per `streamId` plus a boolean field.
|
|
54
|
+
- Upstash REST: `set` with `nx=true`.
|
|
55
|
+
|
|
56
|
+
If your backend cannot offer atomicity, do not paper over it with read-then-write; you will silently produce two writers for the same stream under contention, and consumers will observe interleaved bytes.
|
|
57
|
+
|
|
58
|
+
## The cursor contract
|
|
59
|
+
|
|
60
|
+
Cursors are opaque strings. Callers never inspect them; the store assigns them, the context echoes them back on the next `read` call, and the store uses them to resume from the correct position. Two rules:
|
|
61
|
+
|
|
62
|
+
- Cursors must be strictly monotonic per stream. Whatever scheme you pick (sequence number, ULID, Postgres `bigserial`, Redis stream id), entry N+1 sorts after entry N.
|
|
63
|
+
- The empty string means start from the beginning. `read(streamId, "", signal)` yields every entry the store has, oldest first.
|
|
64
|
+
|
|
65
|
+
You do not need cross-stream ordering. You do need a deterministic mapping from cursor back to position so that `read` can resume a consumer that disconnected mid-replay.
|
|
66
|
+
|
|
67
|
+
## A worked example
|
|
68
|
+
|
|
69
|
+
A `Map`-backed implementation suitable for a single-process server. It is deliberately small and skips TTL eviction; treat it as a starting point for a custom backend rather than a replacement for `createInMemoryResumableStreamStore`.
|
|
70
|
+
|
|
71
|
+
```ts title="/lib/map-resumable-store.ts"
|
|
72
|
+
import type { ResumableStreamStore } from "assistant-stream/resumable";
|
|
73
|
+
|
|
74
|
+
type State = {
|
|
75
|
+
entries: { cursor: string; chunk: Uint8Array }[];
|
|
76
|
+
seq: number;
|
|
77
|
+
final?: { status: "done" | "error"; error?: string };
|
|
78
|
+
waiters: Array<() => void>;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export function createMapResumableStreamStore(): ResumableStreamStore {
|
|
82
|
+
const streams = new Map<string, State>();
|
|
83
|
+
const wake = (s: State) => s.waiters.splice(0).forEach((fn) => fn());
|
|
84
|
+
return {
|
|
85
|
+
async acquire(id) {
|
|
86
|
+
if (streams.has(id)) return "consumer";
|
|
87
|
+
streams.set(id, { entries: [], seq: 0, waiters: [] });
|
|
88
|
+
return "producer";
|
|
89
|
+
},
|
|
90
|
+
async append(id, chunk) {
|
|
91
|
+
const s = streams.get(id);
|
|
92
|
+
if (!s || s.final) throw new Error(`Cannot append: ${id}`);
|
|
93
|
+
s.entries.push({ cursor: (++s.seq).toString(36), chunk });
|
|
94
|
+
wake(s);
|
|
95
|
+
},
|
|
96
|
+
async finalize(id, status, error) {
|
|
97
|
+
const s = streams.get(id);
|
|
98
|
+
if (!s || s.final) return;
|
|
99
|
+
s.final = { status, error };
|
|
100
|
+
wake(s);
|
|
101
|
+
},
|
|
102
|
+
async *read(id, cursor, signal) {
|
|
103
|
+
const s = streams.get(id);
|
|
104
|
+
if (!s) throw new Error(`Stream not found: ${id}`);
|
|
105
|
+
let i = cursor === "" ? 0 : Number.parseInt(cursor, 36);
|
|
106
|
+
while (!signal.aborted) {
|
|
107
|
+
while (i < s.entries.length) yield s.entries[i++]!;
|
|
108
|
+
if (s.final) {
|
|
109
|
+
if (s.final.status === "error") throw new Error(s.final.error);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
await new Promise<void>((r) => {
|
|
113
|
+
s.waiters.push(r);
|
|
114
|
+
signal.addEventListener("abort", () => r(), { once: true });
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
async status(id) {
|
|
119
|
+
const s = streams.get(id);
|
|
120
|
+
return !s ? "missing" : s.final ? s.final.status : "streaming";
|
|
121
|
+
},
|
|
122
|
+
async delete(id) {
|
|
123
|
+
const s = streams.get(id);
|
|
124
|
+
if (!s) return;
|
|
125
|
+
streams.delete(id);
|
|
126
|
+
s.final ??= { status: "done" };
|
|
127
|
+
wake(s);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## TTL and eviction
|
|
134
|
+
|
|
135
|
+
`acquire` receives `options.ttlMs`; if absent, fall back to a store-level default (the built-in stores use 24 hours). Refresh the expiration on every `append` and on `finalize` so a stream that finishes near the deadline still has time to be consumed. Persist the TTL alongside the entries so a worker reading the stream much later can decide whether the data is still valid.
|
|
136
|
+
|
|
137
|
+
When a stream expires, treat it the same as `finalize(streamId, "error", "Stream expired")`: any active `read` iterable must throw or terminate, and `status` must transition to `"missing"` once the eviction has run. Stores backed by Redis or a similar TTL-aware engine can lean on the engine's own expiration; SQL-backed stores need a periodic sweep, and Durable Objects can use `setAlarm`.
|
|
138
|
+
|
|
139
|
+
## Wiring it up
|
|
140
|
+
|
|
141
|
+
`createResumableStreamContext` takes any object that satisfies `ResumableStreamStore`. There is no registry and no extra configuration; pass your instance as `store`:
|
|
142
|
+
|
|
143
|
+
```ts title="/lib/resumable-context.ts"
|
|
144
|
+
import { createResumableStreamContext } from "assistant-stream/resumable";
|
|
145
|
+
import { createMapResumableStreamStore } from "@/lib/map-resumable-store";
|
|
146
|
+
|
|
147
|
+
export const resumableContext = createResumableStreamContext({
|
|
148
|
+
store: createMapResumableStreamStore(),
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
From this point the route handlers in [Resumable Streams](/docs/guides/resumable-streams) work unchanged: `resumableContext.run(streamId, makeStream)` calls your `acquire`, `append`, and `finalize`, and `resumableContext.resume(streamId)` calls your `read`.
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Resumable Streams"
|
|
3
|
+
description: Persist an in-flight LLM response on the server so the client can reload, lose its connection, or open a new tab and pick up the same stream.
|
|
4
|
+
platforms: ["react"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
`assistant-stream/resumable` lets you continue a streaming LLM response across client reconnects. The server keeps writing to a store while the original request is in flight; if the browser reloads or loses its connection, a follow-up request replays the persisted bytes plus any new ones until the producer finalizes.
|
|
8
|
+
|
|
9
|
+
It works with any encoder that already ships in `assistant-stream` (the AI SDK UI message stream, the data stream protocol, the assistant transport SSE format, or your own), because persistence happens at the byte level after encoding.
|
|
10
|
+
|
|
11
|
+
## What it solves
|
|
12
|
+
|
|
13
|
+
A user sends a long prompt, walks away, and reloads the tab. Without resumable streams the LLM call is wasted; with them the client picks up where it left off. The same flow handles dropped mobile connections and lets a stream started on one device be read on another, gated by an opaque stream id.
|
|
14
|
+
|
|
15
|
+
If your responses are short or you do not care about reload survival, the standard `streamText().toUIMessageStreamResponse()` path is enough.
|
|
16
|
+
|
|
17
|
+
## Server side: minimum wiring
|
|
18
|
+
|
|
19
|
+
Construct a `ResumableStreamContext` once per process and reuse it across requests. The context is the seam between your route handlers and the storage backend.
|
|
20
|
+
|
|
21
|
+
```ts title="/lib/resumable-context.ts"
|
|
22
|
+
import {
|
|
23
|
+
createInMemoryResumableStreamStore,
|
|
24
|
+
createResumableStreamContext,
|
|
25
|
+
} from "assistant-stream/resumable";
|
|
26
|
+
|
|
27
|
+
const store = createInMemoryResumableStreamStore();
|
|
28
|
+
export const resumableContext = createResumableStreamContext({ store });
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
In your chat route, wrap the response body in `ctx.run(streamId, makeStream)`. The first caller for `streamId` becomes the producer (your `makeStream` callback runs); later callers and reconnects become consumers that replay the persisted bytes.
|
|
32
|
+
|
|
33
|
+
```ts title="/app/api/chat/route.ts"
|
|
34
|
+
import { streamText } from "ai";
|
|
35
|
+
import { RESUMABLE_STREAM_ID_HEADER } from "assistant-stream/resumable";
|
|
36
|
+
import { resumableContext } from "@/lib/resumable-context";
|
|
37
|
+
|
|
38
|
+
export async function POST(req: Request) {
|
|
39
|
+
const { messages } = await req.json();
|
|
40
|
+
const streamId = crypto.randomUUID();
|
|
41
|
+
|
|
42
|
+
const result = streamText({ /* model, messages, tools, ... */ });
|
|
43
|
+
const sourceBody = result.toUIMessageStreamResponse().body!;
|
|
44
|
+
|
|
45
|
+
const stream = await resumableContext.run(streamId, () => sourceBody);
|
|
46
|
+
|
|
47
|
+
return new Response(stream, {
|
|
48
|
+
headers: {
|
|
49
|
+
"Content-Type": "text/event-stream",
|
|
50
|
+
[RESUMABLE_STREAM_ID_HEADER]: streamId,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
A separate GET endpoint replays the persisted bytes for reconnecting clients. `ctx.resume(streamId)` returns `null` when no stream exists; use `ctx.requireResume(streamId)` if you prefer to surface a `ResumableStreamError` with code `"missing"` instead.
|
|
57
|
+
|
|
58
|
+
```ts title="/app/api/chat/resume/[streamId]/route.ts"
|
|
59
|
+
import { RESUMABLE_STREAM_ID_HEADER } from "assistant-stream/resumable";
|
|
60
|
+
import { resumableContext } from "@/lib/resumable-context";
|
|
61
|
+
|
|
62
|
+
export async function GET(
|
|
63
|
+
_req: Request,
|
|
64
|
+
ctx: { params: Promise<{ streamId: string }> },
|
|
65
|
+
) {
|
|
66
|
+
const { streamId } = await ctx.params;
|
|
67
|
+
const stream = await resumableContext.resume(streamId);
|
|
68
|
+
if (!stream) {
|
|
69
|
+
return new Response(JSON.stringify({ error: "stream not found" }), {
|
|
70
|
+
status: 404,
|
|
71
|
+
headers: { "Content-Type": "application/json" },
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return new Response(stream, {
|
|
75
|
+
headers: {
|
|
76
|
+
"Content-Type": "text/event-stream",
|
|
77
|
+
[RESUMABLE_STREAM_ID_HEADER]: streamId,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The context exposes two more verbs: `ctx.status(streamId)` returns `"streaming" | "done" | "error" | "missing"`, and `ctx.delete(streamId)` removes all persisted state for a stream and terminates active readers. The remaining options on `createResumableStreamContext` (`onAcquire`, `onAppend`, `onFinalize`, `onError`) are observability hooks covered in [Resumable Stream Deployment](/docs/guides/resumable-stream-deployment).
|
|
84
|
+
|
|
85
|
+
## Client side: native integration
|
|
86
|
+
|
|
87
|
+
`@assistant-ui/react-ai-sdk` ships a `resumable` option on `AssistantChatTransport`. It captures the stream id from the response header, redirects `chat.resumeStream()` reconnects to your resume route, and clears the stored id when the response finishes naturally. Pair it with `useChatRuntime`, which fires `chat.resumeStream()` on mount whenever a pending id is present in storage.
|
|
88
|
+
|
|
89
|
+
```tsx title="/app/page.tsx"
|
|
90
|
+
"use client";
|
|
91
|
+
|
|
92
|
+
import { AssistantRuntimeProvider } from "@assistant-ui/react";
|
|
93
|
+
import {
|
|
94
|
+
AssistantChatTransport,
|
|
95
|
+
createResumableSessionStorage,
|
|
96
|
+
useChatRuntime,
|
|
97
|
+
} from "@assistant-ui/react-ai-sdk";
|
|
98
|
+
import { useMemo } from "react";
|
|
99
|
+
import { Thread } from "@/components/assistant-ui/thread";
|
|
100
|
+
|
|
101
|
+
const storage = createResumableSessionStorage();
|
|
102
|
+
|
|
103
|
+
export default function Page() {
|
|
104
|
+
const transport = useMemo(
|
|
105
|
+
() =>
|
|
106
|
+
new AssistantChatTransport({
|
|
107
|
+
api: "/api/chat",
|
|
108
|
+
resumable: {
|
|
109
|
+
storage,
|
|
110
|
+
resumeApi: (streamId) => `/api/chat/resume/${streamId}`,
|
|
111
|
+
},
|
|
112
|
+
}),
|
|
113
|
+
[],
|
|
114
|
+
);
|
|
115
|
+
const runtime = useChatRuntime({ transport });
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
119
|
+
<Thread />
|
|
120
|
+
</AssistantRuntimeProvider>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`createResumableSessionStorage` returns a `ResumableClientStorage` backed by `window.sessionStorage`. Pass `{ key }` to namespace per route or per chat surface, or supply your own implementation of the three methods (`getStreamId`, `setStreamId`, `clear`). If you are running on a transport that already wraps `fetch` or `prepareReconnectToStreamRequest`, the `resumable` option composes with your existing handlers.
|
|
126
|
+
|
|
127
|
+
The default finish detector scans the SSE body for the AI SDK `"type":"finish"` marker. Override `isFinishEvent` on the `resumable` option when you ship a custom encoder.
|
|
128
|
+
|
|
129
|
+
## Storage choices
|
|
130
|
+
|
|
131
|
+
The core package ships `createInMemoryResumableStreamStore` for development and tests. State lives in a process-local `Map`, so it does not survive a server restart. Useful options include `defaultTtlMs`, `maxChunkBytes`, `maxEntriesPerStream`, `maxStreams`, and `gcIntervalMs` for periodic eviction.
|
|
132
|
+
|
|
133
|
+
For production, use one of the optional Redis adapters via the `assistant-stream/resumable/redis` (node-redis v5) or `assistant-stream/resumable/ioredis` sub-paths. Both adapters batch the per-append `XADD` and TTL refresh into a single pipelined round trip, store chunk values as binary, and accept the same `keyPrefix`, `defaultTtlMs`, `pollIntervalMs`, and `maxChunkBytes` options. Cluster routing works because each stream's keys share a `{streamId}` hash tag.
|
|
134
|
+
|
|
135
|
+
```ts title="/lib/resumable-context.ts"
|
|
136
|
+
import {
|
|
137
|
+
createResumableStreamContext,
|
|
138
|
+
type ResumableStreamStore,
|
|
139
|
+
} from "assistant-stream/resumable";
|
|
140
|
+
|
|
141
|
+
async function createStore(): Promise<ResumableStreamStore> {
|
|
142
|
+
if (!process.env.REDIS_URL) {
|
|
143
|
+
const { createInMemoryResumableStreamStore } = await import(
|
|
144
|
+
"assistant-stream/resumable"
|
|
145
|
+
);
|
|
146
|
+
return createInMemoryResumableStreamStore();
|
|
147
|
+
}
|
|
148
|
+
const { createClient } = await import("redis");
|
|
149
|
+
const { createRedisResumableStreamStore } = await import(
|
|
150
|
+
"assistant-stream/resumable/redis"
|
|
151
|
+
);
|
|
152
|
+
const client = createClient({ url: process.env.REDIS_URL });
|
|
153
|
+
await client.connect();
|
|
154
|
+
return createRedisResumableStreamStore(client);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const resumableContext = createResumableStreamContext({
|
|
158
|
+
store: await createStore(),
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
For Postgres, Cloudflare Durable Objects, Upstash REST, or any other backend, implement the `ResumableStreamStore` interface directly. See [Custom Resumable Stream Stores](/docs/guides/resumable-stream-stores) for the contract walkthrough and a worked example.
|
|
163
|
+
|
|
164
|
+
## Production checklist
|
|
165
|
+
|
|
166
|
+
- **Auth.** The resume route in the snippets above will serve any caller that knows the stream id. Bind `streamId` to the requesting user at acquire time and verify the binding inside the resume handler. Treat the id as opaque, not as a credential; it leaks via response headers, `sessionStorage`, browser history, and access logs.
|
|
167
|
+
- **`waitUntil` on serverless.** On Vercel and Cloudflare the request handler is killed once the response returns, which interrupts the producer task. Pass `after` from `next/server` (or your platform's `ctx.waitUntil`) when constructing the context so the task survives past the response: `createResumableStreamContext({ store, waitUntil: after })`.
|
|
168
|
+
- **TTL.** Streams expire 24 hours after the last write by default. Configure with `defaultTtlMs` on the store, or override per deployment via `ttlMs` on the context. Match TTLs across the store, any owner-binding key, and any signed cookie that references a `streamId`.
|
|
169
|
+
- **Stream id format.** The Redis adapters validate `streamId` against `/^[A-Za-z0-9_.:-]{1,256}$/` to keep keys well-formed. UUIDv4 is fine.
|
|
170
|
+
|
|
171
|
+
For the full treatment of authorization, multi-tenant key prefixes, observability hooks, resource limits, and incident response, see [Resumable Stream Deployment](/docs/guides/resumable-stream-deployment).
|
|
172
|
+
|
|
173
|
+
A new `ResumableStreamError` class is exported from `assistant-stream/resumable` with codes `"missing" | "exists" | "finalized" | "invalid-id"`; catch it in the resume route to distinguish "stream gone" from other failures.
|
|
174
|
+
|
|
175
|
+
## Helpers for `AssistantStreamController` callbacks
|
|
176
|
+
|
|
177
|
+
If you produce streams via `createAssistantStream` rather than the AI SDK, the package ships two helpers that bridge the controller-callback style and any encoder to the store:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import {
|
|
181
|
+
createResumableAssistantStreamResponse,
|
|
182
|
+
createResumeAssistantStreamResponse,
|
|
183
|
+
} from "assistant-stream/resumable";
|
|
184
|
+
import { resumableContext } from "@/lib/resumable-context";
|
|
185
|
+
|
|
186
|
+
// POST handler
|
|
187
|
+
return createResumableAssistantStreamResponse({
|
|
188
|
+
context: resumableContext,
|
|
189
|
+
streamId,
|
|
190
|
+
callback: (controller) => {
|
|
191
|
+
/* same shape as createAssistantStreamResponse */
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// GET resume handler
|
|
196
|
+
return createResumeAssistantStreamResponse({
|
|
197
|
+
context: resumableContext,
|
|
198
|
+
streamId,
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Both helpers default to the data-stream encoder; pass `encoder: () => new AssistantTransportEncoder()` (or any custom encoder) to override. They set the `x-resumable-stream-id` response header automatically, which is what `AssistantChatTransport`'s `resumable` adapter looks for.
|
|
203
|
+
|
|
204
|
+
## Example app
|
|
205
|
+
|
|
206
|
+
[`examples/with-resumable-stream`](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-resumable-stream) is a runnable Next.js app that uses `useChat`, the `resumable` transport option, and `useChatRuntime`. It falls back to a built-in mock when `OPENAI_API_KEY` is unset, and switches the store from in-memory to Redis when `REDIS_URL` is set.
|
|
207
|
+
|
|
208
|
+
```sh
|
|
209
|
+
npx assistant-ui create my-app -e with-resumable-stream
|
|
210
|
+
```
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Slash Commands
|
|
3
|
-
description:
|
|
3
|
+
description: Trigger predefined actions in your AI chat by typing / — slash command palette with popover, search, and action handlers in React via assistant-ui.
|
|
4
|
+
platforms: ["react"]
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
Slash commands let users type `/` in the composer to open a popover, browse available commands, and execute one. Unlike [mentions](/docs/guides/mentions) (which only insert a directive into the message), slash commands additionally fire an **action callback** at the moment of selection.
|
|
@@ -89,6 +90,17 @@ function MyComposer() {
|
|
|
89
90
|
|
|
90
91
|
The label defaults to `/${id}`; override via `label` on the command. Icons are strings that your `iconMap` on the picker UI resolves to components (see [ComposerTriggerPopover](/docs/ui/composer-trigger-popover)).
|
|
91
92
|
|
|
93
|
+
### `unstable_useSlashCommandAdapter` options
|
|
94
|
+
|
|
95
|
+
| Option | Type | Default | Description |
|
|
96
|
+
| --- | --- | --- | --- |
|
|
97
|
+
| `commands` | `Unstable_SlashCommand[]` | — | Command definitions — each has `id`, optional `label`, `description`, `icon`, and an `execute` callback (required) |
|
|
98
|
+
| `removeOnExecute` | `boolean` | `false` | When `true`, strips the trigger text from the composer after executing instead of leaving a directive chip |
|
|
99
|
+
| `iconMap` | `Record<string, IconComponent>` | — | Maps `metadata.icon` / category `id` strings to React icon components; forwarded to `ComposerTriggerPopover` |
|
|
100
|
+
| `fallbackIcon` | `IconComponent` | — | Fallback when no `iconMap` entry matches; forwarded to `ComposerTriggerPopover` |
|
|
101
|
+
|
|
102
|
+
The hook returns `{ adapter, action, iconMap?, fallbackIcon? }` — spread directly into `<ComposerTriggerPopover char="/" {...slash} />` for one-line wiring.
|
|
103
|
+
|
|
92
104
|
### 2. Controlling the Chip
|
|
93
105
|
|
|
94
106
|
By default, a selected `/summarize` is converted into a directive chip (`:command[/summarize]{name=summarize}`) in the composer text and the command's `execute` fires. This keeps an audit trail of which commands were invoked.
|
|
@@ -236,57 +248,111 @@ Slash commands and mentions live under the same `TriggerPopoverRoot`. Declare on
|
|
|
236
248
|
|
|
237
249
|
Each `TriggerPopover` is its own scope — the `@` popover and the `/` popover read state from their own declaration and never collide. Keyboard events route to whichever popover is currently active.
|
|
238
250
|
|
|
239
|
-
##
|
|
251
|
+
## Commands with Arguments
|
|
252
|
+
|
|
253
|
+
Some commands accept inline arguments typed after the command word — for example `/translate en` or `/ask what is TypeScript`. Because the adapter's `search` method receives the full text after `/`, you can split on the first space to separate the command from its arguments:
|
|
240
254
|
|
|
241
|
-
|
|
255
|
+
```tsx
|
|
256
|
+
const SLASH_COMMANDS: readonly Unstable_SlashCommand[] = [
|
|
257
|
+
{
|
|
258
|
+
id: "translate",
|
|
259
|
+
description: "Translate to a language, e.g. /translate en",
|
|
260
|
+
execute: () => {/* arguments extracted separately, see below */},
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
id: "ask",
|
|
264
|
+
description: "Ask about a topic, e.g. /ask what is TypeScript",
|
|
265
|
+
execute: () => {/* arguments extracted separately, see below */},
|
|
266
|
+
},
|
|
267
|
+
];
|
|
242
268
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
269
|
+
function MyComposer() {
|
|
270
|
+
const composerRef = useRef<HTMLTextAreaElement>(null);
|
|
271
|
+
const slash = unstable_useSlashCommandAdapter({
|
|
272
|
+
commands: SLASH_COMMANDS.map((cmd) => ({
|
|
273
|
+
...cmd,
|
|
274
|
+
execute: () => {
|
|
275
|
+
// Read the full composer text to extract arguments
|
|
276
|
+
const raw = composerRef.current?.value ?? "";
|
|
277
|
+
// Match "/<id> <args>" at start of input
|
|
278
|
+
const match = raw.match(new RegExp(`^\\/${cmd.id}\\s+(.*)`));
|
|
279
|
+
const args = match?.[1]?.trim() ?? "";
|
|
280
|
+
handleCommand(cmd.id, args);
|
|
281
|
+
},
|
|
282
|
+
})),
|
|
283
|
+
removeOnExecute: true,
|
|
284
|
+
});
|
|
250
285
|
|
|
251
|
-
|
|
286
|
+
return (
|
|
287
|
+
<ComposerPrimitive.Unstable_TriggerPopoverRoot>
|
|
288
|
+
<ComposerPrimitive.Root>
|
|
289
|
+
<ComposerPrimitive.Input ref={composerRef} placeholder="Type / for commands..." />
|
|
290
|
+
<ComposerPrimitive.Unstable_TriggerPopover char="/" adapter={slash.adapter}>
|
|
291
|
+
<ComposerPrimitive.Unstable_TriggerPopover.Action {...slash.action} />
|
|
292
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
293
|
+
{(items) => items.map((item, i) => (
|
|
294
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItem key={item.id} item={item} index={i}>
|
|
295
|
+
<strong>{item.label}</strong>
|
|
296
|
+
{item.description && <span>{item.description}</span>}
|
|
297
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItem>
|
|
298
|
+
))}
|
|
299
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
300
|
+
</ComposerPrimitive.Unstable_TriggerPopover>
|
|
301
|
+
<ComposerPrimitive.Send>Send</ComposerPrimitive.Send>
|
|
302
|
+
</ComposerPrimitive.Root>
|
|
303
|
+
</ComposerPrimitive.Unstable_TriggerPopoverRoot>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
`removeOnExecute: true` strips the `/translate en` text from the composer so the argument is consumed by the handler rather than sent to the LLM.
|
|
309
|
+
|
|
310
|
+
## Async Command Loading
|
|
252
311
|
|
|
253
|
-
|
|
312
|
+
The adapter interface is synchronous, but the command list can come from any async source. Load commands into state (or a query cache) and pass the current snapshot to the hook. Because `unstable_useSlashCommandAdapter` re-runs on every render, the adapter always reflects the latest list.
|
|
254
313
|
|
|
255
|
-
|
|
256
|
-
- `ComposerPrimitive.Unstable_TriggerPopover` — declares one trigger (id, char, adapter) and renders its popover container
|
|
257
|
-
- Behavior sub-primitives — exactly one per `TriggerPopover`:
|
|
258
|
-
- `Unstable_TriggerPopover.Directive` — writes a formatted directive on selection ("mention" path)
|
|
259
|
-
- `Unstable_TriggerPopover.Action` — fires a callback on selection ("slash" path); inserts a chip by default, strip with `removeOnExecute`
|
|
260
|
-
- Shared sub-primitives (`TriggerPopoverCategories`, `TriggerPopoverItems`, `TriggerPopoverBack`) live inside a `TriggerPopover`
|
|
314
|
+
**With React state:**
|
|
261
315
|
|
|
262
|
-
|
|
316
|
+
```tsx
|
|
317
|
+
function MyComposer() {
|
|
318
|
+
const [commands, setCommands] = useState<Unstable_SlashCommand[]>([]);
|
|
263
319
|
|
|
264
|
-
|
|
320
|
+
useEffect(() => {
|
|
321
|
+
fetchAvailableCommands().then(setCommands);
|
|
322
|
+
}, []);
|
|
265
323
|
|
|
266
|
-
|
|
324
|
+
const slash = unstable_useSlashCommandAdapter({ commands });
|
|
267
325
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
326
|
+
return (/* ... */);
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**With React Query:**
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
function MyComposer() {
|
|
334
|
+
const { data: commands = [] } = useQuery({
|
|
335
|
+
queryKey: ["slash-commands"],
|
|
336
|
+
queryFn: fetchAvailableCommands,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const slash = unstable_useSlashCommandAdapter({ commands });
|
|
340
|
+
|
|
341
|
+
return (/* ... */);
|
|
342
|
+
}
|
|
273
343
|
```
|
|
274
344
|
|
|
275
|
-
|
|
345
|
+
## Keyboard Navigation
|
|
346
|
+
|
|
347
|
+
See [ComposerTriggerPopover keyboard navigation](/docs/ui/composer-trigger-popover#keyboard-navigation) for the full key bindings table.
|
|
348
|
+
|
|
349
|
+
## Trigger Popover Architecture
|
|
350
|
+
|
|
351
|
+
Both mentions and slash commands are built on a generic trigger popover system where each `Unstable_TriggerPopover` declares one trigger character, an adapter, and exactly one behavior sub-primitive (`Directive` or `Action`). Multiple triggers coexist under a single `Unstable_TriggerPopoverRoot`. See the [Composer Primitives](/docs/primitives/composer) reference for the complete API.
|
|
276
352
|
|
|
277
353
|
## Primitives Reference
|
|
278
354
|
|
|
279
|
-
|
|
280
|
-
| --- | --- |
|
|
281
|
-
| `Unstable_TriggerPopoverRoot` | Root — groups triggers, provides input plugin registry |
|
|
282
|
-
| `Unstable_TriggerPopover` | Declares a trigger and renders its popover container |
|
|
283
|
-
| `Unstable_TriggerPopover.Directive` | Behavior sub-primitive — inserts a formatted directive on selection |
|
|
284
|
-
| `Unstable_TriggerPopover.Action` | Behavior sub-primitive — runs `onExecute` on selection; chip-by-default |
|
|
285
|
-
| `Unstable_TriggerPopoverCategories` | Render-function for the top-level category list |
|
|
286
|
-
| `Unstable_TriggerPopoverCategoryItem` | Button that drills into a category (`role="option"`, auto `data-highlighted`) |
|
|
287
|
-
| `Unstable_TriggerPopoverItems` | Render-function for items within the active category or search results |
|
|
288
|
-
| `Unstable_TriggerPopoverItem` | Button that selects an item (`role="option"`, auto `data-highlighted`) |
|
|
289
|
-
| `Unstable_TriggerPopoverBack` | Button that navigates back from items to categories |
|
|
355
|
+
See the [Composer Primitives](/docs/primitives/composer) reference for the full list of trigger popover primitives and their props.
|
|
290
356
|
|
|
291
357
|
## Related
|
|
292
358
|
|