@assistant-ui/mcp-docs-server 0.1.38 → 0.2.0
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 +11 -12
- package/.docs/organized/code-examples/with-a2a.md +18 -13
- package/.docs/organized/code-examples/with-ag-ui.md +19 -14
- package/.docs/organized/code-examples/{with-ai-sdk-v6.md → with-ai-sdk-v7.md} +34 -23
- package/.docs/organized/code-examples/with-artifacts.md +473 -141
- package/.docs/organized/code-examples/with-assistant-transport.md +17 -10
- package/.docs/organized/code-examples/with-browser-extension.md +17 -10
- package/.docs/organized/code-examples/with-chain-of-thought.md +21 -14
- package/.docs/organized/code-examples/with-cloud-standalone.md +12 -11
- package/.docs/organized/code-examples/with-cloud.md +20 -15
- package/.docs/organized/code-examples/with-custom-thread-list.md +19 -12
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +22 -15
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +22 -15
- package/.docs/organized/code-examples/with-eve.md +18 -11
- package/.docs/organized/code-examples/with-expo.md +35 -52
- package/.docs/organized/code-examples/with-external-store.md +18 -13
- package/.docs/organized/code-examples/with-ffmpeg.md +20 -15
- package/.docs/organized/code-examples/with-generative-ui.md +22 -17
- package/.docs/organized/code-examples/with-google-adk.md +18 -11
- package/.docs/organized/code-examples/with-heat-graph.md +10 -11
- package/.docs/organized/code-examples/with-image-generation.md +19 -12
- package/.docs/organized/code-examples/with-interactables.md +21 -17
- package/.docs/organized/code-examples/with-langchain.md +19 -12
- package/.docs/organized/code-examples/with-langgraph.md +19 -12
- package/.docs/organized/code-examples/with-livekit.md +23 -16
- package/.docs/organized/code-examples/with-mcp.md +46 -28
- package/.docs/organized/code-examples/with-opencode.md +25 -23
- package/.docs/organized/code-examples/with-pi.md +54 -19
- package/.docs/organized/code-examples/with-react-hook-form.md +21 -16
- package/.docs/organized/code-examples/with-react-ink-web.md +9 -9
- package/.docs/organized/code-examples/with-react-ink.md +4 -4
- package/.docs/organized/code-examples/with-react-router.md +22 -17
- package/.docs/organized/code-examples/with-resumable-stream.md +21 -14
- package/.docs/organized/code-examples/with-store.md +10 -11
- package/.docs/organized/code-examples/with-tanstack.md +19 -13
- package/.docs/organized/code-examples/with-tap-runtime.md +18 -13
- package/.docs/organized/code-examples/with-virtualized-thread.md +19 -14
- package/.docs/raw/docs/(docs)/base-ui.mdx +39 -0
- package/.docs/raw/docs/(docs)/cli.mdx +19 -1
- package/.docs/raw/docs/(docs)/devtools.mdx +7 -2
- package/.docs/raw/docs/(docs)/installation.mdx +15 -1
- package/.docs/raw/docs/(docs)/rtl.mdx +2 -4
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/a2ui.mdx +40 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/actions.mdx +56 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/components.mdx +86 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/index.mdx +22 -1
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/json-generative-ui.mdx +42 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/rendering.mdx +53 -2
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/slack.mdx +81 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/teams.mdx +86 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/tokens.mdx +62 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +19 -420
- package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +4 -1
- package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +4 -1
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +25 -2
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-data-stream.mdx +37 -0
- package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -9
- package/.docs/raw/docs/(reference)/api-reference/overview.mdx +1 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +14 -31
- package/.docs/raw/docs/(reference)/api-reference/primitives/composition.mdx +1 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/index.mdx +3 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +2 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +0 -2
- package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +12 -9
- package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +4 -0
- package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +7 -1
- package/.docs/raw/docs/cloud/langgraph.mdx +4 -2
- package/.docs/raw/docs/copilots/model-context.mdx +1 -1
- package/.docs/raw/docs/copilots/motivation.mdx +1 -1
- package/.docs/raw/docs/guides/attachments.mdx +3 -3
- package/.docs/raw/docs/guides/branching.mdx +2 -2
- package/.docs/raw/docs/guides/chatgpt-subscription.mdx +108 -0
- package/.docs/raw/docs/guides/context-api.mdx +89 -111
- package/.docs/raw/docs/guides/dictation.mdx +185 -257
- package/.docs/raw/docs/guides/editing.mdx +5 -5
- package/.docs/raw/docs/guides/electron.mdx +369 -0
- package/.docs/raw/docs/guides/index.mdx +20 -0
- package/.docs/raw/docs/guides/mentions.mdx +31 -3
- package/.docs/raw/docs/guides/quoting.mdx +3 -3
- package/.docs/raw/docs/guides/resumable-stream-deployment.mdx +2 -2
- package/.docs/raw/docs/guides/resumable-streams.mdx +12 -1
- package/.docs/raw/docs/guides/speech.mdx +47 -29
- package/.docs/raw/docs/guides/suggestions.mdx +70 -1
- package/.docs/raw/docs/guides/voice.mdx +197 -267
- package/.docs/raw/docs/ink/hooks.mdx +3 -3
- package/.docs/raw/docs/ink/primitives.mdx +49 -8
- package/.docs/raw/docs/integrations/auth/better-auth.mdx +2 -2
- package/.docs/raw/docs/integrations/auth/clerk.mdx +2 -2
- package/.docs/raw/docs/integrations/auth/next-auth.mdx +3 -3
- package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +10 -4
- package/.docs/raw/docs/integrations/frameworks/cloudflare-agents/overview.mdx +3 -3
- package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +1 -1
- package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +3 -3
- package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +1 -1
- package/.docs/raw/docs/integrations/gateways/index.mdx +2 -2
- package/.docs/raw/docs/integrations/observability/helicone.mdx +1 -1
- package/.docs/raw/docs/integrations/observability/langfuse.mdx +1 -1
- package/.docs/raw/docs/integrations/observability/langsmith.mdx +2 -2
- package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +36 -9
- package/.docs/raw/docs/migrations/index.mdx +50 -0
- package/.docs/raw/docs/migrations/toolkit-tools.mdx +4 -2
- package/.docs/raw/docs/migrations/v0-15.mdx +156 -0
- package/.docs/raw/docs/primitives/chain-of-thought.mdx +6 -1
- package/.docs/raw/docs/primitives/composer.mdx +17 -1
- package/.docs/raw/docs/primitives/selection-toolbar.mdx +25 -0
- package/.docs/raw/docs/primitives/thread-list.mdx +2 -2
- package/.docs/raw/docs/react-native/hooks.mdx +3 -3
- package/.docs/raw/docs/react-native/index.mdx +2 -2
- package/.docs/raw/docs/react-native/primitives.mdx +62 -5
- package/.docs/raw/docs/runtimes/ag-ui/agent-state.mdx +124 -0
- package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +10 -1
- package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +14 -5
- package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +15 -15
- package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +8 -8
- package/.docs/raw/docs/runtimes/ai-sdk/{v6.mdx → v6-legacy.mdx} +11 -9
- package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +717 -0
- package/.docs/raw/docs/runtimes/concepts/adapters.mdx +1 -1
- package/.docs/raw/docs/runtimes/concepts/architecture.mdx +1 -1
- package/.docs/raw/docs/runtimes/concepts/threads.mdx +10 -10
- package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +2 -2
- package/.docs/raw/docs/runtimes/custom/data-stream.mdx +10 -19
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +2 -2
- package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +5 -3
- package/.docs/raw/docs/runtimes/eve/overview.mdx +1 -1
- package/.docs/raw/docs/runtimes/langchain.mdx +2 -2
- package/.docs/raw/docs/runtimes/langgraph/agent-state.mdx +181 -0
- package/.docs/raw/docs/runtimes/langgraph/overview.mdx +1 -1
- package/.docs/raw/docs/runtimes/opencode/hooks.mdx +3 -1
- package/.docs/raw/docs/tools/a2ui.mdx +107 -0
- package/.docs/raw/docs/tools/backend.mdx +6 -3
- package/.docs/raw/docs/tools/defining-tools.mdx +7 -1
- package/.docs/raw/docs/tools/generative-ui.mdx +60 -2
- package/.docs/raw/docs/tools/interactables-legacy.mdx +4 -4
- package/.docs/raw/docs/tools/interactables.mdx +3 -3
- package/.docs/raw/docs/tools/mcp-apps.mdx +90 -13
- package/.docs/raw/docs/tools/mcp.mdx +100 -3
- package/.docs/raw/docs/tools/tool-ui.mdx +6 -4
- package/.docs/raw/docs/tools/user-managed-mcp.mdx +77 -9
- package/.docs/raw/docs/ui/accordion.mdx +16 -10
- package/.docs/raw/docs/ui/assistant-modal.mdx +8 -4
- package/.docs/raw/docs/ui/attachment.mdx +5 -1
- package/.docs/raw/docs/ui/badge.mdx +23 -12
- package/.docs/raw/docs/ui/follow-up-suggestions.mdx +4 -2
- package/.docs/raw/docs/ui/model-selector.mdx +33 -3
- package/.docs/raw/docs/ui/part-grouping.mdx +0 -4
- package/.docs/raw/docs/ui/reasoning.mdx +1 -1
- package/.docs/raw/docs/ui/select.mdx +22 -14
- package/.docs/raw/docs/ui/sources.mdx +1 -1
- package/.docs/raw/docs/ui/tabs.mdx +25 -14
- package/.docs/raw/docs/utilities/heat-graph.mdx +2 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -2
- package/dist/index.js.map +1 -1
- package/dist/prepare-docs/code-examples.d.ts.map +1 -1
- package/dist/prepare-docs/code-examples.js.map +1 -1
- package/dist/prepare-docs/copy-raw.d.ts.map +1 -1
- package/dist/prepare-docs/prepare.d.ts +1 -1
- package/dist/prompts/xulux-playground.d.ts +12 -0
- package/dist/prompts/xulux-playground.d.ts.map +1 -0
- package/dist/prompts/xulux-playground.js +33 -0
- package/dist/prompts/xulux-playground.js.map +1 -0
- package/dist/stdio.d.ts +1 -1
- package/dist/tools/docs.d.ts +8 -14
- package/dist/tools/docs.d.ts.map +1 -1
- package/dist/tools/docs.js +26 -10
- package/dist/tools/docs.js.map +1 -1
- package/dist/tools/examples.d.ts +6 -12
- package/dist/tools/examples.d.ts.map +1 -1
- package/dist/tools/examples.js +11 -8
- package/dist/tools/examples.js.map +1 -1
- package/dist/tools/resources.d.ts +1 -2
- package/dist/tools/resources.d.ts.map +1 -1
- package/dist/tools/resources.js +1 -1
- package/dist/tools/resources.js.map +1 -1
- package/dist/tools/search.d.ts +6 -15
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +2 -2
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/tests/mcp-test-client.d.ts +15 -0
- package/dist/tools/tests/mcp-test-client.d.ts.map +1 -0
- package/dist/tools/tests/mcp-test-client.js +68 -0
- package/dist/tools/tests/mcp-test-client.js.map +1 -0
- package/dist/tools/tests/test-setup.d.ts.map +1 -1
- package/dist/tools/tests/test-setup.js +5 -1
- package/dist/tools/tests/test-setup.js.map +1 -1
- package/dist/tools/xulux-templates.d.ts +58 -0
- package/dist/tools/xulux-templates.d.ts.map +1 -0
- package/dist/tools/xulux-templates.js +82 -0
- package/dist/tools/xulux-templates.js.map +1 -0
- package/dist/utils/cache.d.ts +5 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +18 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/mcp-format.d.ts +1 -0
- package/dist/utils/mcp-format.d.ts.map +1 -1
- package/dist/utils/mcp-format.js +7 -4
- package/dist/utils/mcp-format.js.map +1 -1
- package/dist/utils/mdx.d.ts.map +1 -1
- package/dist/utils/paths.d.ts +1 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +3 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/search.d.ts.map +1 -1
- package/dist/utils/security.d.ts.map +1 -1
- package/dist/utils/security.js.map +1 -1
- package/dist/xulux/catalog-client.d.ts +14 -0
- package/dist/xulux/catalog-client.d.ts.map +1 -0
- package/dist/xulux/catalog-client.js +67 -0
- package/dist/xulux/catalog-client.js.map +1 -0
- package/dist/xulux/fallback-catalog.d.ts +7 -0
- package/dist/xulux/fallback-catalog.d.ts.map +1 -0
- package/dist/xulux/fallback-catalog.js +47 -0
- package/dist/xulux/fallback-catalog.js.map +1 -0
- package/dist/xulux/fetch-sandbox.d.ts +5 -0
- package/dist/xulux/fetch-sandbox.d.ts.map +1 -0
- package/dist/xulux/fetch-sandbox.js +40 -0
- package/dist/xulux/fetch-sandbox.js.map +1 -0
- package/dist/xulux/template-service.d.ts +84 -0
- package/dist/xulux/template-service.d.ts.map +1 -0
- package/dist/xulux/template-service.js +223 -0
- package/dist/xulux/template-service.js.map +1 -0
- package/dist/xulux/types.d.ts +55 -0
- package/dist/xulux/types.d.ts.map +1 -0
- package/dist/xulux/types.js +6 -0
- package/dist/xulux/types.js.map +1 -0
- package/package.json +7 -6
- package/src/index.ts +55 -2
- package/src/prompts/xulux-playground.ts +36 -0
- package/src/tools/docs.ts +27 -5
- package/src/tools/examples.ts +17 -12
- package/src/tools/resources.ts +1 -4
- package/src/tools/search.ts +2 -2
- package/src/tools/tests/completions.test.ts +40 -26
- package/src/tools/tests/docs.test.ts +20 -0
- package/src/tools/tests/examples.test.ts +5 -5
- package/src/tools/tests/integration.test.ts +3 -4
- package/src/tools/tests/listings-cache.test.ts +19 -0
- package/src/tools/tests/mcp-protocol.test.ts +173 -108
- package/src/tools/tests/mcp-test-client.ts +111 -0
- package/src/tools/tests/resources.test.ts +97 -66
- package/src/tools/tests/test-setup.ts +8 -0
- package/src/tools/tests/xulux-templates.test.ts +262 -0
- package/src/tools/xulux-templates.ts +141 -0
- package/src/utils/cache.ts +20 -0
- package/src/utils/mcp-format.ts +8 -6
- package/src/utils/paths.ts +4 -1
- package/src/utils/tests/cache.test.ts +51 -0
- package/src/utils/tests/mcp-format.test.ts +22 -0
- package/src/utils/tests/security.test.ts +1 -1
- package/src/xulux/catalog-client.ts +105 -0
- package/src/xulux/fallback-catalog.ts +63 -0
- package/src/xulux/fetch-sandbox.ts +56 -0
- package/src/xulux/template-service.ts +406 -0
- package/src/xulux/types.ts +60 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/utilities.mdx +0 -464
|
@@ -196,7 +196,7 @@ type ThreadHistoryAdapter = {
|
|
|
196
196
|
`load` runs when a thread opens. `append` runs after each message completes.
|
|
197
197
|
|
|
198
198
|
<Callout type="info">
|
|
199
|
-
`react-ai-sdk` requires `withFormat` so messages round-trip as AI SDK `UIMessage` objects. An adapter without `withFormat` throws at runtime in the AI SDK path. See the [AI SDK history docs](/docs/runtimes/ai-sdk/
|
|
199
|
+
`react-ai-sdk` requires `withFormat` so messages round-trip as AI SDK `UIMessage` objects. An adapter without `withFormat` throws at runtime in the AI SDK path. See the [AI SDK history docs](/docs/runtimes/ai-sdk/v7) for the full pattern.
|
|
200
200
|
</Callout>
|
|
201
201
|
|
|
202
202
|
## Suggestion adapter
|
|
@@ -118,7 +118,7 @@ The fastest path. Each adapter wraps one of the core or protocol layers and adds
|
|
|
118
118
|
|
|
119
119
|
| Adapter | Layered on | Targets |
|
|
120
120
|
| --- | --- | --- |
|
|
121
|
-
| `react-ai-sdk` | `ExternalStoreRuntime` | Vercel AI SDK
|
|
121
|
+
| `react-ai-sdk` | `ExternalStoreRuntime` | Vercel AI SDK v7 (`useChat`) |
|
|
122
122
|
| `react-langgraph` | `ExternalStoreRuntime` | LangGraph Cloud via `@langchain/langgraph-sdk` |
|
|
123
123
|
| `react-langchain` | `ExternalStoreRuntime` | LangGraph Cloud via `@langchain/react`'s `useStream` |
|
|
124
124
|
| `react-google-adk` | `ExternalStoreRuntime` | Google ADK JS or Python agents |
|
|
@@ -149,7 +149,7 @@ const adapterWithHistory: RemoteThreadListAdapter = {
|
|
|
149
149
|
const history = useMemo<ThreadHistoryAdapter>(
|
|
150
150
|
() => ({
|
|
151
151
|
async load() {
|
|
152
|
-
const { remoteId } = aui.threadListItem
|
|
152
|
+
const { remoteId } = aui.threadListItem.getState();
|
|
153
153
|
if (!remoteId) return { messages: [] };
|
|
154
154
|
const rows = await fetch(
|
|
155
155
|
`/api/threads/${remoteId}/messages`,
|
|
@@ -157,7 +157,7 @@ const adapterWithHistory: RemoteThreadListAdapter = {
|
|
|
157
157
|
return { messages: rows.map(toThreadMessage) };
|
|
158
158
|
},
|
|
159
159
|
async append({ message, parentId }) {
|
|
160
|
-
const { remoteId } = await aui.threadListItem
|
|
160
|
+
const { remoteId } = await aui.threadListItem.initialize();
|
|
161
161
|
await fetch(`/api/threads/${remoteId}/messages`, {
|
|
162
162
|
method: "POST",
|
|
163
163
|
body: JSON.stringify({ message, parentId }),
|
|
@@ -181,11 +181,11 @@ const adapterWithHistory: RemoteThreadListAdapter = {
|
|
|
181
181
|
|
|
182
182
|
### Avoiding the first-message race
|
|
183
183
|
|
|
184
|
-
`append` may be called before the thread record exists in your backend. Always await `aui.threadListItem
|
|
184
|
+
`append` may be called before the thread record exists in your backend. Always await `aui.threadListItem.initialize()` before writing:
|
|
185
185
|
|
|
186
186
|
```ts
|
|
187
187
|
async append({ message, parentId }) {
|
|
188
|
-
const { remoteId } = await aui.threadListItem
|
|
188
|
+
const { remoteId } = await aui.threadListItem.initialize();
|
|
189
189
|
await saveMessage(remoteId, parentId, message);
|
|
190
190
|
}
|
|
191
191
|
```
|
|
@@ -194,14 +194,14 @@ async append({ message, parentId }) {
|
|
|
194
194
|
|
|
195
195
|
### Reloading after async authentication
|
|
196
196
|
|
|
197
|
-
If your adapter depends on a user that resolves asynchronously (oidc, `next-auth`, `better-auth`), the initial `list()` may run before the user is available. Call `aui.threads
|
|
197
|
+
If your adapter depends on a user that resolves asynchronously (oidc, `next-auth`, `better-auth`), the initial `list()` may run before the user is available. Call `aui.threads.reload()` after auth completes:
|
|
198
198
|
|
|
199
199
|
```tsx
|
|
200
200
|
function ReloadOnAuth() {
|
|
201
201
|
const aui = useAui();
|
|
202
202
|
const { isLoading, user } = useAuth();
|
|
203
203
|
useEffect(() => {
|
|
204
|
-
if (!isLoading && user) aui.threads
|
|
204
|
+
if (!isLoading && user) aui.threads.reload();
|
|
205
205
|
}, [isLoading, user?.id]);
|
|
206
206
|
return null;
|
|
207
207
|
}
|
|
@@ -211,7 +211,7 @@ function ReloadOnAuth() {
|
|
|
211
211
|
|
|
212
212
|
### Paginating the thread list
|
|
213
213
|
|
|
214
|
-
If your backend returns thread pages, return a `nextCursor` from `list()` and consume `aui.threads
|
|
214
|
+
If your backend returns thread pages, return a `nextCursor` from `list()` and consume `aui.threads.hasMore` plus `aui.threads.loadMore()` in the UI. The runtime threads `params.after` back through `list()` on every `loadMore()`; the initial call passes no `params`, so treat a missing `after` as "first page". `reload()` resets the cursor so the next load starts from page 1 again.
|
|
215
215
|
|
|
216
216
|
```ts title="threadListAdapter.ts (excerpt)"
|
|
217
217
|
async list({ after } = {}) {
|
|
@@ -271,7 +271,7 @@ A few invariants worth knowing when wiring a custom UI on top of `loadMore()`:
|
|
|
271
271
|
name: "list",
|
|
272
272
|
type: "(params?: { after?: string }) => Promise<{ threads: RemoteThreadMetadata[]; nextCursor?: string }>",
|
|
273
273
|
description:
|
|
274
|
-
"Hydrate threads on mount. Each thread must include status and remoteId; title, externalId, and custom are optional. Return a `nextCursor` to enable `aui.threads
|
|
274
|
+
"Hydrate threads on mount. Each thread must include status and remoteId; title, externalId, and custom are optional. Return a `nextCursor` to enable `aui.threads.loadMore()`; the runtime will pass it back as `params.after` on the next call.",
|
|
275
275
|
required: true,
|
|
276
276
|
},
|
|
277
277
|
{
|
|
@@ -291,7 +291,7 @@ A few invariants worth knowing when wiring a custom UI on top of `loadMore()`:
|
|
|
291
291
|
name: "updateCustom",
|
|
292
292
|
type: "(remoteId: string, custom: Record<string, unknown> | undefined) => Promise<void>",
|
|
293
293
|
description:
|
|
294
|
-
"Optional. Persist replacement custom metadata from `aui.threadListItem
|
|
294
|
+
"Optional. Persist replacement custom metadata from `aui.threadListItem.updateCustom(custom)`.",
|
|
295
295
|
},
|
|
296
296
|
{
|
|
297
297
|
name: "archive",
|
|
@@ -361,7 +361,7 @@ function ThreadListItemMeta() {
|
|
|
361
361
|
}
|
|
362
362
|
```
|
|
363
363
|
|
|
364
|
-
`custom` is preserved across `rename`, `archive`, `unarchive`, and `generateTitle`. To replace it from your UI, implement `RemoteThreadListAdapter.updateCustom` and call `aui.threadListItem
|
|
364
|
+
`custom` is preserved across `rename`, `archive`, `unarchive`, and `generateTitle`. To replace it from your UI, implement `RemoteThreadListAdapter.updateCustom` and call `aui.threadListItem.updateCustom(custom)`. The cloud adapter persists this through `cloud.threads.update(threadId, { metadata })`. If your adapter mutates thread metadata through a separate application path, return the updated values from `fetch()` or call `aui.threads.reload()` to re-run `list()`.
|
|
365
365
|
|
|
366
366
|
## ExternalStoreThreadListAdapter
|
|
367
367
|
|
|
@@ -536,8 +536,8 @@ function useResumeOnMount(threadId: string) {
|
|
|
536
536
|
r.json(),
|
|
537
537
|
);
|
|
538
538
|
if (status.isRunning) {
|
|
539
|
-
const parentId = aui.thread
|
|
540
|
-
aui.thread
|
|
539
|
+
const parentId = aui.thread.getState().messages.at(-1)?.id ?? null;
|
|
540
|
+
aui.thread.resumeRun({ parentId });
|
|
541
541
|
}
|
|
542
542
|
})();
|
|
543
543
|
}, [aui, threadId]);
|
|
@@ -19,14 +19,17 @@ If your backend exposes a richer state surface, consider [`AssistantTransport`](
|
|
|
19
19
|
|
|
20
20
|
## Wire protocols
|
|
21
21
|
|
|
22
|
-
`useDataStreamRuntime` accepts two wire formats
|
|
22
|
+
`useDataStreamRuntime` accepts two wire formats. When `protocol` is omitted, it
|
|
23
|
+
detects known Vercel AI response markers before falling back to UI message
|
|
24
|
+
stream for compatibility.
|
|
23
25
|
|
|
24
|
-
| Protocol |
|
|
26
|
+
| Protocol | Detection | Matching backend |
|
|
25
27
|
| --- | --- | --- |
|
|
26
|
-
| `"ui-message-stream"` |
|
|
27
|
-
| `"data-stream"` |
|
|
28
|
+
| `"ui-message-stream"` | `x-vercel-ai-ui-message-stream: v1`, otherwise fallback | AI SDK v5+'s `result.toUIMessageStreamResponse()` (SSE) |
|
|
29
|
+
| `"data-stream"` | `x-vercel-ai-data-stream: v1` | `createAssistantStreamResponse` from `assistant-stream`, or AI SDK v4's `toDataStreamResponse()` (Vercel data stream v1) |
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
Set `protocol` explicitly only for custom endpoints that do not preserve or
|
|
32
|
+
expose the response marker.
|
|
30
33
|
|
|
31
34
|
## Install
|
|
32
35
|
|
|
@@ -66,10 +69,7 @@ import { AssistantRuntimeProvider } from "@assistant-ui/react";
|
|
|
66
69
|
import { Thread } from "@/components/assistant-ui/thread";
|
|
67
70
|
|
|
68
71
|
export default function ChatPage() {
|
|
69
|
-
const runtime = useDataStreamRuntime({
|
|
70
|
-
api: "/api/chat",
|
|
71
|
-
protocol: "data-stream",
|
|
72
|
-
});
|
|
72
|
+
const runtime = useDataStreamRuntime({ api: "/api/chat" });
|
|
73
73
|
return (
|
|
74
74
|
<AssistantRuntimeProvider runtime={runtime}>
|
|
75
75
|
<Thread />
|
|
@@ -90,10 +90,7 @@ import { Thread } from "@/components/assistant-ui/thread";
|
|
|
90
90
|
const API_URL = process.env.EXPO_PUBLIC_API_URL ?? "http://localhost:3000";
|
|
91
91
|
|
|
92
92
|
export default function ChatPage() {
|
|
93
|
-
const runtime = useDataStreamRuntime({
|
|
94
|
-
api: `${API_URL}/api/chat`,
|
|
95
|
-
protocol: "data-stream",
|
|
96
|
-
});
|
|
93
|
+
const runtime = useDataStreamRuntime({ api: `${API_URL}/api/chat` });
|
|
97
94
|
return (
|
|
98
95
|
<AssistantRuntimeProvider runtime={runtime}>
|
|
99
96
|
<View style={{ flex: 1 }}>
|
|
@@ -116,7 +113,6 @@ import { Thread } from "./components/thread.js";
|
|
|
116
113
|
export function App() {
|
|
117
114
|
const runtime = useDataStreamRuntime({
|
|
118
115
|
api: "http://localhost:3000/api/chat",
|
|
119
|
-
protocol: "data-stream",
|
|
120
116
|
});
|
|
121
117
|
return (
|
|
122
118
|
<AssistantRuntimeProvider runtime={runtime}>
|
|
@@ -167,7 +163,6 @@ The request body includes `messages`, `tools`, `system` (if configured), and `th
|
|
|
167
163
|
```tsx
|
|
168
164
|
const runtime = useDataStreamRuntime({
|
|
169
165
|
api: "/api/chat",
|
|
170
|
-
protocol: "data-stream",
|
|
171
166
|
headers: { Authorization: `Bearer ${token}`, "X-Custom-Header": "value" },
|
|
172
167
|
credentials: "include",
|
|
173
168
|
});
|
|
@@ -178,7 +173,6 @@ Evaluate per-request:
|
|
|
178
173
|
```tsx
|
|
179
174
|
const runtime = useDataStreamRuntime({
|
|
180
175
|
api: "/api/chat",
|
|
181
|
-
protocol: "data-stream",
|
|
182
176
|
headers: async () => ({
|
|
183
177
|
Authorization: `Bearer ${await getAuthToken()}`,
|
|
184
178
|
}),
|
|
@@ -195,7 +189,6 @@ const runtime = useDataStreamRuntime({
|
|
|
195
189
|
```tsx
|
|
196
190
|
const runtime = useDataStreamRuntime({
|
|
197
191
|
api: "/api/chat",
|
|
198
|
-
protocol: "data-stream",
|
|
199
192
|
onResponse: (response) => console.log("status:", response.status),
|
|
200
193
|
onFinish: (message) => console.log("done:", message),
|
|
201
194
|
onError: (error) => console.error(error),
|
|
@@ -230,7 +223,6 @@ const myTools = {
|
|
|
230
223
|
|
|
231
224
|
const runtime = useDataStreamRuntime({
|
|
232
225
|
api: "/api/chat",
|
|
233
|
-
protocol: "data-stream",
|
|
234
226
|
body: { tools: toToolsJSONSchema(myTools) },
|
|
235
227
|
});
|
|
236
228
|
```
|
|
@@ -291,7 +283,6 @@ const runtime = useCloudRuntime({
|
|
|
291
283
|
```tsx
|
|
292
284
|
const runtime = useDataStreamRuntime({
|
|
293
285
|
api: "/api/chat",
|
|
294
|
-
protocol: "data-stream",
|
|
295
286
|
initialMessages: [
|
|
296
287
|
{ role: "user", content: [{ type: "text", text: "Hello" }] },
|
|
297
288
|
{ role: "assistant", content: [{ type: "text", text: "Hi!" }] },
|
|
@@ -741,7 +741,7 @@ useExternalStoreRuntime({
|
|
|
741
741
|
name: "isSendDisabled",
|
|
742
742
|
type: "boolean",
|
|
743
743
|
description:
|
|
744
|
-
"Blocks new-message sending while leaving the input usable. When true, the thread composer's canSend becomes false, the Send button is disabled, Enter and the steer hotkey are no-ops, and aui.composer
|
|
744
|
+
"Blocks new-message sending while leaving the input usable. When true, the thread composer's canSend becomes false, the Send button is disabled, Enter and the steer hotkey are no-ops, and aui.composer.send() short-circuits. Edit composers (saving message edits) ignore this flag. Use this to gate sending on external React state (e.g. while tools or auth are still loading).",
|
|
745
745
|
default: "false",
|
|
746
746
|
},
|
|
747
747
|
{
|
|
@@ -796,7 +796,7 @@ useExternalStoreRuntime({
|
|
|
796
796
|
name: "onResume",
|
|
797
797
|
type: "(config: ResumeRunConfig) => Promise<void>",
|
|
798
798
|
description:
|
|
799
|
-
"Handler for resuming an interrupted run (e.g. after a page reload mid-generation).",
|
|
799
|
+
"Handler for resuming an interrupted run (e.g. after a page reload mid-generation). For AI SDK reload-safe streaming, see the [Resumable Streams](/docs/guides/resumable-streams) guide.",
|
|
800
800
|
},
|
|
801
801
|
{
|
|
802
802
|
name: "onResumeToolCall",
|
|
@@ -595,7 +595,7 @@ async function* createCustomStream(): AsyncGenerator<ChatModelRunResult> {
|
|
|
595
595
|
};
|
|
596
596
|
}
|
|
597
597
|
|
|
598
|
-
aui.thread
|
|
598
|
+
aui.thread.resumeRun({
|
|
599
599
|
parentId: "message-id",
|
|
600
600
|
stream: createCustomStream,
|
|
601
601
|
});
|
|
@@ -617,8 +617,8 @@ function useStreamReconnect(threadId: string) {
|
|
|
617
617
|
r.json(),
|
|
618
618
|
);
|
|
619
619
|
if (status.isRunning) {
|
|
620
|
-
const parentId = aui.thread
|
|
621
|
-
aui.thread
|
|
620
|
+
const parentId = aui.thread.getState().messages.at(-1)?.id ?? null;
|
|
621
|
+
aui.thread.resumeRun({ parentId });
|
|
622
622
|
}
|
|
623
623
|
})();
|
|
624
624
|
}, [aui, threadId]);
|
|
@@ -698,6 +698,8 @@ const OpenAIAdapter: ChatModelAdapter = {
|
|
|
698
698
|
};
|
|
699
699
|
```
|
|
700
700
|
|
|
701
|
+
For local development on a ChatGPT Plus or Pro plan, the same client can run without a real API key by pointing `baseURL` at a local OAuth proxy and passing a placeholder `apiKey`; see [ChatGPT Subscription](/docs/guides/chatgpt-subscription#openai-compatible-proxy).
|
|
702
|
+
|
|
701
703
|
### Custom REST API
|
|
702
704
|
|
|
703
705
|
```tsx
|
|
@@ -56,7 +56,7 @@ export default function Home() {
|
|
|
56
56
|
- An Eve app mounted with `eve/next`.
|
|
57
57
|
- A model credential for the model configured in `agent/agent.ts`.
|
|
58
58
|
|
|
59
|
-
Eve's default gateway model ids route through the [Vercel AI Gateway](https://vercel.com/docs/ai-gateway). Use `AI_GATEWAY_API_KEY` or Vercel OIDC, or configure a direct provider `LanguageModel`.
|
|
59
|
+
Eve's default gateway model ids route through the [Vercel AI Gateway](https://vercel.com/docs/ai-gateway). Use `AI_GATEWAY_API_KEY` or Vercel OIDC, or configure a direct provider `LanguageModel`. For local development, a direct `LanguageModel` can also run on a ChatGPT Plus or Pro plan without any API key; see [ChatGPT Subscription](/docs/guides/chatgpt-subscription).
|
|
60
60
|
|
|
61
61
|
## Install
|
|
62
62
|
|
|
@@ -5,7 +5,7 @@ description: Use LangChain's useStream hook with a React chat UI through assista
|
|
|
5
5
|
|
|
6
6
|
import { LangGraphIcon } from "@/components/icons/langgraph";
|
|
7
7
|
|
|
8
|
-
`@assistant-ui/react-langchain` wraps [`useStream`](https://
|
|
8
|
+
`@assistant-ui/react-langchain` wraps [`useStream`](https://reference.langchain.com/javascript/langchain-react/use-stream) from `@langchain/react` and exposes it as an assistant-ui runtime. It targets the same backend as [`@assistant-ui/react-langgraph`](/docs/runtimes/langgraph/overview) (LangGraph Cloud) but at a higher level, delegating stream plumbing to the upstream hook.
|
|
9
9
|
|
|
10
10
|
## When to use it
|
|
11
11
|
|
|
@@ -32,7 +32,7 @@ Shared adapters (attachments, speech, feedback) work the same way described in [
|
|
|
32
32
|
|
|
33
33
|
## Requirements
|
|
34
34
|
|
|
35
|
-
- A LangGraph Cloud API server (locally via [LangGraph Studio](https://
|
|
35
|
+
- A LangGraph Cloud API server (locally via [LangGraph Studio](https://docs.langchain.com/langsmith/quick-start-studio) or hosted via [LangSmith](https://www.langchain.com/langsmith)).
|
|
36
36
|
- The graph state must include a `messages` key with LangChain-alike messages, or pass a custom `messagesKey`.
|
|
37
37
|
|
|
38
38
|
## Quickstart
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Agent state
|
|
3
|
+
description: Read and optimistically update graph state with useLangGraphState and useLangGraphSetState in LangGraph.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`useLangGraphState` mirrors the graph's values object that LangGraph streams to the client. It updates live while the agent runs (when the `values` stream mode is enabled). `useLangGraphSetState` lets you apply optimistic local updates that ride the next send.
|
|
7
|
+
|
|
8
|
+
## Enable the `values` stream mode
|
|
9
|
+
|
|
10
|
+
<Callout type="warn">
|
|
11
|
+
The default stream setup does **not** include the `values` stream mode. Without it, `useLangGraphState` never receives live graph state.
|
|
12
|
+
</Callout>
|
|
13
|
+
|
|
14
|
+
When you call `client.runs.stream` yourself, request `values` alongside the modes you already use:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
client.runs.stream(threadId, assistantId, {
|
|
18
|
+
input,
|
|
19
|
+
streamMode: ["messages", "updates", "custom", "values"],
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
If you use `unstable_createLangGraphStream`, its default stream modes do **not** include `values` either. Pass the option explicitly:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
const stream = unstable_createLangGraphStream({
|
|
27
|
+
client,
|
|
28
|
+
assistantId: ASSISTANT_ID,
|
|
29
|
+
streamMode: ["messages", "updates", "custom", "values"],
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Basic usage
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import {
|
|
37
|
+
useLangGraphState,
|
|
38
|
+
useLangGraphSetState,
|
|
39
|
+
} from "@assistant-ui/react-langgraph";
|
|
40
|
+
import { useAuiState } from "@assistant-ui/react";
|
|
41
|
+
|
|
42
|
+
type GraphState = {
|
|
43
|
+
messages: unknown[];
|
|
44
|
+
filters: { region: string; maxResults: number };
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const state = useLangGraphState<GraphState>();
|
|
48
|
+
// state: GraphState | undefined (latest agent state; updates live while the agent runs)
|
|
49
|
+
|
|
50
|
+
const setState = useLangGraphSetState<GraphState>();
|
|
51
|
+
// setState(next | (prev) => next) (optimistic local update; sent with the NEXT run)
|
|
52
|
+
|
|
53
|
+
const isRunning = useAuiState((s) => s.thread.isRunning);
|
|
54
|
+
// isRunning: boolean (whether the thread is currently running)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Example
|
|
58
|
+
|
|
59
|
+
Render graph state beside the chat and stage an optimistic filter update:
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
"use client";
|
|
63
|
+
|
|
64
|
+
import {
|
|
65
|
+
useLangGraphState,
|
|
66
|
+
useLangGraphSetState,
|
|
67
|
+
} from "@assistant-ui/react-langgraph";
|
|
68
|
+
import { useAuiState } from "@assistant-ui/react";
|
|
69
|
+
import { Thread } from "@/components/assistant-ui/thread";
|
|
70
|
+
|
|
71
|
+
type GraphState = {
|
|
72
|
+
filters: { region: string; maxResults: number };
|
|
73
|
+
lastQuery?: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export function CatalogAssistant() {
|
|
77
|
+
const state = useLangGraphState<GraphState>();
|
|
78
|
+
const setState = useLangGraphSetState<GraphState>();
|
|
79
|
+
const isRunning = useAuiState((s) => s.thread.isRunning);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div className="flex h-full">
|
|
83
|
+
<aside className="w-72 border-r p-4">
|
|
84
|
+
<h2>Graph state</h2>
|
|
85
|
+
{state ? (
|
|
86
|
+
<ul>
|
|
87
|
+
<li>Region: {state.filters.region}</li>
|
|
88
|
+
<li>Max results: {state.filters.maxResults}</li>
|
|
89
|
+
{state.lastQuery && <li>Last query: {state.lastQuery}</li>}
|
|
90
|
+
</ul>
|
|
91
|
+
) : (
|
|
92
|
+
<p>No graph state yet. Ensure streamMode includes "values".</p>
|
|
93
|
+
)}
|
|
94
|
+
<button
|
|
95
|
+
type="button"
|
|
96
|
+
disabled={isRunning}
|
|
97
|
+
onClick={() =>
|
|
98
|
+
setState((prev) => ({
|
|
99
|
+
...prev,
|
|
100
|
+
filters: {
|
|
101
|
+
region: "eu",
|
|
102
|
+
maxResults: prev?.filters.maxResults ?? 10,
|
|
103
|
+
},
|
|
104
|
+
}))
|
|
105
|
+
}
|
|
106
|
+
>
|
|
107
|
+
Prefer EU region
|
|
108
|
+
</button>
|
|
109
|
+
{isRunning && <p>Agent is running…</p>}
|
|
110
|
+
</aside>
|
|
111
|
+
<main className="flex-1">
|
|
112
|
+
<Thread />
|
|
113
|
+
</main>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The panel tracks the latest `values` events from the graph. The button updates the local overlay immediately; that update object is merged into the run `input` on the next send so LangGraph applies it through the graph's state reducers and input schema.
|
|
120
|
+
|
|
121
|
+
## How state is synced
|
|
122
|
+
|
|
123
|
+
LangGraph state is the graph's values object. When `streamMode` includes `"values"`, each `values` event updates the client-side snapshot that `useLangGraphState` exposes.
|
|
124
|
+
|
|
125
|
+
The setter from `useLangGraphSetState` overlays that snapshot locally. On the next send, the runtime merges the update object into the run `input`, so LangGraph reduces it through the graph's state reducers and input schema.
|
|
126
|
+
|
|
127
|
+
If you supply a custom `stream` callback, the staged update is available as `config.state`. Forward it into your run input yourself:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const runtime = useLangGraphRuntime({
|
|
131
|
+
stream: async (messages, { initialize, ...config }) => {
|
|
132
|
+
const { externalId } = await initialize();
|
|
133
|
+
if (!externalId) throw new Error("Thread not found");
|
|
134
|
+
|
|
135
|
+
const client = createClient();
|
|
136
|
+
return client.runs.stream(externalId, ASSISTANT_ID, {
|
|
137
|
+
input: {
|
|
138
|
+
...(config.state ?? {}),
|
|
139
|
+
messages,
|
|
140
|
+
},
|
|
141
|
+
streamMode: ["messages", "updates", "custom", "values"],
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The built-in path that uses the package helpers performs this merge for you. Custom `stream` callbacks must do it explicitly.
|
|
148
|
+
|
|
149
|
+
## Write-back timing
|
|
150
|
+
|
|
151
|
+
`useLangGraphSetState` is optimistic and local first. The value reaches the agent only when the next run starts. It is not a live channel into a run that is already in progress. Stage filters, preferences, or other graph fields that the next turn should apply through reducers; do not expect an in-flight run to observe mid-run setter calls.
|
|
152
|
+
|
|
153
|
+
## Relationship to other state
|
|
154
|
+
|
|
155
|
+
Keep the three state layers distinct:
|
|
156
|
+
|
|
157
|
+
- **Your app state** stays yours (React state, URL, a store). assistant-ui does not own it.
|
|
158
|
+
- **`useAuiState`** reads assistant-ui's client state (messages, composer, thread status).
|
|
159
|
+
- **`useLangGraphState` / `useLangGraphSetState`** mirror state the **agent** (your LangGraph graph) owns, synced over the wire via `values` events.
|
|
160
|
+
|
|
161
|
+
Use `useLangGraphState` and `useLangGraphSetState` for fields that live in the graph state schema. Use `useAuiState` for UI that depends on the chat thread itself. Use your own state for everything else.
|
|
162
|
+
|
|
163
|
+
## Next
|
|
164
|
+
|
|
165
|
+
<Cards>
|
|
166
|
+
<Card
|
|
167
|
+
title="Streaming"
|
|
168
|
+
description="Event handlers, message metadata, generative UI."
|
|
169
|
+
href="/docs/runtimes/langgraph/streaming"
|
|
170
|
+
/>
|
|
171
|
+
<Card
|
|
172
|
+
title="Quickstart"
|
|
173
|
+
description="From-template and manual setup paths."
|
|
174
|
+
href="/docs/runtimes/langgraph/quickstart"
|
|
175
|
+
/>
|
|
176
|
+
<Card
|
|
177
|
+
title="Generative UI"
|
|
178
|
+
description="Structured UI components emitted by your graph."
|
|
179
|
+
href="/docs/runtimes/langgraph/generative-ui"
|
|
180
|
+
/>
|
|
181
|
+
</Cards>
|
|
@@ -13,7 +13,7 @@ description: Build a chat UI for LangGraph agents in React with assistant-ui —
|
|
|
13
13
|
|
|
14
14
|
Pick the LangGraph runtime when:
|
|
15
15
|
|
|
16
|
-
- You have (or want) a LangGraph Cloud server, locally via [LangGraph Studio](https://
|
|
16
|
+
- You have (or want) a LangGraph Cloud server, locally via [LangGraph Studio](https://docs.langchain.com/langsmith/quick-start-studio) or hosted via [LangSmith](https://www.langchain.com/langsmith).
|
|
17
17
|
- Your graph state has a `messages` key with LangChain-alike messages.
|
|
18
18
|
- You want generative UI (`ui_message`), per-message metadata, subgraph events, or checkpoint-based message editing.
|
|
19
19
|
|
|
@@ -9,7 +9,9 @@ OpenCode-specific React hooks for interacting with the running session. All hook
|
|
|
9
9
|
|
|
10
10
|
## Permissions
|
|
11
11
|
|
|
12
|
-
OpenCode pauses tool execution to ask the user for permission (e.g. running shell commands, writing files). `
|
|
12
|
+
OpenCode pauses tool execution to ask the user for permission (e.g. running shell commands, writing files). Permissions linked to a tool call are projected into assistant-ui's standard tool approval contract, so the default `ToolFallback` renders working **Allow**, **Always allow**, and **Deny** actions without a custom permission component. The always option only appears when OpenCode offers patterns to persist.
|
|
13
|
+
|
|
14
|
+
`useOpenCodePermissions` remains available for custom tool renderers and permission requests that are not linked to a tool call. It returns the pending permission requests and a reply function:
|
|
13
15
|
|
|
14
16
|
```tsx
|
|
15
17
|
import { useOpenCodePermissions } from "@assistant-ui/react-opencode";
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: A2UI over AG-UI
|
|
3
|
+
description: Render A2UI surfaces from AG-UI activity snapshots as generative UI, using the community a2ui-surface convention.
|
|
4
|
+
platforms: ["react"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
[A2UI](https://a2ui.org/) is a declarative generative UI protocol: the agent streams surface operations (create a surface, upsert components, update a data model) and the host renders them from a pre-approved component catalog, with no code over the wire. The AG-UI ecosystem carries A2UI as `ACTIVITY_SNAPSHOT` events with `activityType: "a2ui-surface"` and the operations under `content.a2ui_operations`; this is the convention emitted by [`@ag-ui/a2ui-middleware`](https://github.com/ag-ui-protocol/ag-ui/tree/main/middlewares/a2ui-middleware).
|
|
8
|
+
|
|
9
|
+
`useAgUiRuntime` consumes these snapshots natively. Each surface becomes one tool-call part with `toolCallId` `a2ui:<surfaceId>` and `toolName` `"present"`, whose args are the converted generative UI spec, so surfaces render through the same path as the [`present` frontend tool](/docs/tools/generative-ui). Snapshots with any other `activityType` are ignored, and the MCP Apps activity path is unaffected.
|
|
10
|
+
|
|
11
|
+
## Wire contract
|
|
12
|
+
|
|
13
|
+
A backend paints or updates a surface by emitting an activity snapshot on the AG-UI stream:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"type": "ACTIVITY_SNAPSHOT",
|
|
18
|
+
"messageId": "a2ui-surface-call_1",
|
|
19
|
+
"activityType": "a2ui-surface",
|
|
20
|
+
"replace": true,
|
|
21
|
+
"content": {
|
|
22
|
+
"a2ui_operations": [
|
|
23
|
+
{ "version": "v0.9", "createSurface": { "surfaceId": "s1" } },
|
|
24
|
+
{
|
|
25
|
+
"version": "v0.9",
|
|
26
|
+
"updateComponents": {
|
|
27
|
+
"surfaceId": "s1",
|
|
28
|
+
"components": [
|
|
29
|
+
{ "id": "root", "component": "Card", "title": "Order", "children": ["total"] },
|
|
30
|
+
{ "id": "total", "component": "Text", "text": { "path": "/total" } }
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"version": "v0.9",
|
|
36
|
+
"updateDataModel": { "surfaceId": "s1", "path": "/", "contents": { "total": "$42" } }
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- A snapshot with `replace: true` (the schema default) rebuilds that `messageId`'s surface state from the operations it carries. `replace: false` means the snapshot is ignored when that `messageId` has already been seen, matching the AG-UI event spec. The middleware always emits `replace: true`.
|
|
44
|
+
- Surfaces are keyed by the `surfaceId` inside the operations, not by `messageId`. Multiple snapshots that share a `messageId` update their surfaces in place, and the synthesized part keeps its `toolCallId`, so the rendered surface updates without duplicating parts.
|
|
45
|
+
- Component trees use the A2UI adjacency-list model: nodes reference children by id, the root node has id `root`, and props of shape `{ "path": "/x/y" }` are JSON Pointer bindings resolved against the surface data model. Both v0.9 and v1.0 operation payloads are accepted, including the v1.0 inline `components` and `dataModel` on `createSurface`.
|
|
46
|
+
- Lifecycle snapshots that carry a `status` (such as `"building"`) but no `a2ui_operations` are tolerated and produce no part until operations arrive. A `deleteSurface` operation removes the surface's part.
|
|
47
|
+
- The `a2ui:` tool-call id prefix is reserved for synthesized surface parts: they are client-side render artifacts and are excluded from the history sent back to the agent, so genuine agent tool calls must not use ids starting with `a2ui:`.
|
|
48
|
+
|
|
49
|
+
## Quick start
|
|
50
|
+
|
|
51
|
+
Register the `present` frontend tool from `@assistant-ui/react-generative-ui`; incoming surfaces then render with the default vocabulary:
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import {
|
|
55
|
+
JSONGenerativeUI,
|
|
56
|
+
defaultGenerativeUILibrary,
|
|
57
|
+
} from "@assistant-ui/react-generative-ui";
|
|
58
|
+
|
|
59
|
+
const generative = new JSONGenerativeUI({
|
|
60
|
+
library: defaultGenerativeUILibrary,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const toolkit = {
|
|
64
|
+
present: generative.present({ display: "standalone" }),
|
|
65
|
+
};
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Pass the toolkit to your assistant as on the [Generative UI](/docs/tools/generative-ui) page; no other configuration is needed on `useAgUiRuntime`.
|
|
69
|
+
|
|
70
|
+
## Actions
|
|
71
|
+
|
|
72
|
+
`Button` nodes dispatch `$action` objects with type `"a2ui:action"` through the action registry. Wire the registry to `useAgUiSendA2uiAction` inside a component; the hook returns a stable function, so the toolkit can be memoized on it:
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import { useMemo } from "react";
|
|
76
|
+
import { useAgUiSendA2uiAction } from "@assistant-ui/react-ag-ui";
|
|
77
|
+
import {
|
|
78
|
+
JSONGenerativeUI,
|
|
79
|
+
createActionRegistry,
|
|
80
|
+
defaultGenerativeUILibrary,
|
|
81
|
+
} from "@assistant-ui/react-generative-ui";
|
|
82
|
+
|
|
83
|
+
function useA2uiToolkit() {
|
|
84
|
+
const sendA2uiAction = useAgUiSendA2uiAction();
|
|
85
|
+
return useMemo(() => {
|
|
86
|
+
const generative = new JSONGenerativeUI({
|
|
87
|
+
library: defaultGenerativeUILibrary,
|
|
88
|
+
actions: createActionRegistry({
|
|
89
|
+
"a2ui:action": ({ payload }) => sendA2uiAction(payload),
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
return { present: generative.present({ display: "standalone" }) };
|
|
93
|
+
}, [sendA2uiAction]);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Sending an action triggers a run with no new user message, and the agent receives it as `forwardedProps.a2uiAction.userAction`, the convention `@ag-ui/a2ui-middleware` consumes. The action rides exactly one run; the `type` field is stripped and a `timestamp` is added when absent.
|
|
98
|
+
|
|
99
|
+
The middleware turns the action into a synthetic `log_a2ui_event` tool-call pair that is never declared in `input.tools`; a backend that validates tool calls against the declared tool list will reject it.
|
|
100
|
+
|
|
101
|
+
## Component mapping
|
|
102
|
+
|
|
103
|
+
The converter maps the A2UI basic catalog onto the default generative UI vocabulary: `Text` becomes `Markdown` (`h1` to `h6` variants become `Header`, `caption` becomes `Caption`), `Column` becomes `Col`, `TextField` becomes `Input` (the control name is derived from the last segment of its binding path), `CheckBox` becomes `Checkbox`, and `Image`, `Row`, `Card`, `Divider`, `Button` map one to one. A node with template children expands into a `ListView` over the bound list. `Button` actions become `$action` objects with type `"a2ui:action"` carrying the action name, `surfaceId`, and `sourceComponentId`, which is how your action registry receives them. Unknown components and operations are skipped with a debug warning, and conversion is bounded (depth 32, 100 template items, 5000 nodes) so a malformed stream cannot hang the client.
|
|
104
|
+
|
|
105
|
+
## Limitations
|
|
106
|
+
|
|
107
|
+
- The upstream middleware currently emits v0.9 operation payloads; the renderer accepts v0.9 and v1.0 shapes.
|
|
@@ -13,6 +13,8 @@ For authoring tools, see [Defining Tools](/docs/tools/defining-tools). For MCP s
|
|
|
13
13
|
`@assistant-ui/react-ai-sdk` posts `{ messages, system, tools }` to your route. `tools` is the map of **frontend** tools the client serialized for this request (the model needs their schemas to call them, even though they run in the browser):
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
+
import type { FrontendTools } from "@assistant-ui/react-ai-sdk";
|
|
17
|
+
|
|
16
18
|
const {
|
|
17
19
|
messages,
|
|
18
20
|
system,
|
|
@@ -20,7 +22,7 @@ const {
|
|
|
20
22
|
}: {
|
|
21
23
|
messages: UIMessage[];
|
|
22
24
|
system?: string;
|
|
23
|
-
tools?:
|
|
25
|
+
tools?: FrontendTools;
|
|
24
26
|
} = await req.json();
|
|
25
27
|
```
|
|
26
28
|
|
|
@@ -136,9 +138,10 @@ To let the model see a frontend tool's result and continue, configure the runtim
|
|
|
136
138
|
import { lastAssistantMessageIsCompleteWithToolCalls } from "ai";
|
|
137
139
|
|
|
138
140
|
const runtime = useChatRuntime({
|
|
139
|
-
api: "/api/chat",
|
|
140
141
|
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
|
|
141
142
|
});
|
|
142
143
|
```
|
|
143
144
|
|
|
144
|
-
|
|
145
|
+
`useChatRuntime()` targets `/api/chat` by default. To point at a different endpoint or customize requests, see [Custom transport](/docs/runtimes/ai-sdk/v7#custom-transport).
|
|
146
|
+
|
|
147
|
+
For the full AI SDK v7 backend setup — history persistence, reasoning, server-side approvals — see the [AI SDK v7 guide](/docs/runtimes/ai-sdk/v7).
|