@assistant-ui/mcp-docs-server 0.2.1 → 0.2.2
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 +1 -1
- package/.docs/organized/code-examples/with-a2a.md +2 -2
- package/.docs/organized/code-examples/with-ag-ui.md +3 -3
- package/.docs/organized/code-examples/with-ai-sdk-v7.md +5 -5
- package/.docs/organized/code-examples/with-artifacts.md +5 -5
- package/.docs/organized/code-examples/with-assistant-transport.md +3 -3
- package/.docs/organized/code-examples/with-browser-extension.md +4 -4
- package/.docs/organized/code-examples/with-chain-of-thought.md +5 -5
- package/.docs/organized/code-examples/with-cloud-standalone.md +5 -5
- package/.docs/organized/code-examples/with-cloud.md +5 -5
- package/.docs/organized/code-examples/with-custom-thread-list.md +6 -6
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +7 -7
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +8 -8
- package/.docs/organized/code-examples/with-eve.md +3 -3
- package/.docs/organized/code-examples/with-expo.md +13 -19
- package/.docs/organized/code-examples/with-external-store.md +3 -3
- package/.docs/organized/code-examples/with-ffmpeg.md +5 -5
- package/.docs/organized/code-examples/with-generative-ui.md +259 -23
- package/.docs/organized/code-examples/with-google-adk.md +2 -2
- package/.docs/organized/code-examples/with-heat-graph.md +1 -1
- package/.docs/organized/code-examples/with-image-generation.md +4 -4
- package/.docs/organized/code-examples/with-interactables.md +5 -5
- package/.docs/organized/code-examples/with-langchain.md +7 -7
- package/.docs/organized/code-examples/with-langgraph.md +4 -4
- package/.docs/organized/code-examples/with-livekit.md +7 -7
- package/.docs/organized/code-examples/with-mcp.md +6 -6
- package/.docs/organized/code-examples/with-nuxt.md +500 -564
- package/.docs/organized/code-examples/with-opencode.md +2 -2
- package/.docs/organized/code-examples/with-openui.md +449 -0
- package/.docs/organized/code-examples/with-pi.md +2 -2
- package/.docs/organized/code-examples/with-react-hook-form.md +7 -7
- package/.docs/organized/code-examples/with-react-ink-web.md +4 -4
- package/.docs/organized/code-examples/with-react-ink.md +2 -2
- package/.docs/organized/code-examples/with-react-router.md +4 -4
- package/.docs/organized/code-examples/with-resumable-stream.md +7 -7
- package/.docs/organized/code-examples/with-store.md +1 -1
- package/.docs/organized/code-examples/with-svelte.md +415 -0
- package/.docs/organized/code-examples/with-sveltekit.md +1061 -0
- package/.docs/organized/code-examples/with-tanstack.md +5 -5
- package/.docs/organized/code-examples/with-tap-runtime.md +2 -2
- package/.docs/organized/code-examples/with-virtualized-thread.md +2 -2
- package/.docs/organized/code-examples/with-vue.md +1 -1
- package/.docs/raw/docs/(docs)/cli.mdx +6 -1
- package/.docs/raw/docs/(docs)/installation.mdx +2 -2
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/slack.mdx +5 -1
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/teams.mdx +9 -2
- package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +2 -2
- package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +39 -1
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +29 -4
- package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +1 -1
- package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +29 -1
- package/.docs/raw/docs/(reference)/api-reference/voice/session.mdx +1 -1
- package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +1 -1
- package/.docs/raw/docs/cloud/ai-sdk.mdx +2 -2
- package/.docs/raw/docs/cloud/index.mdx +1 -1
- package/.docs/raw/docs/guides/attachments.mdx +2 -2
- package/.docs/raw/docs/guides/context-api.mdx +15 -17
- package/.docs/raw/docs/guides/dictation.mdx +1 -1
- package/.docs/raw/docs/guides/mentions.mdx +2 -0
- package/.docs/raw/docs/guides/suggestions.mdx +6 -3
- package/.docs/raw/docs/ink/primitives.mdx +1 -1
- package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +146 -128
- package/.docs/raw/docs/migrations/v0-15.mdx +34 -0
- package/.docs/raw/docs/primitives/suggestion.mdx +3 -1
- package/.docs/raw/docs/primitives/thread.mdx +1 -1
- package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +35 -5
- package/.docs/raw/docs/runtimes/claude-managed-agents.mdx +118 -0
- package/.docs/raw/docs/runtimes/concepts/stability.mdx +2 -1
- package/.docs/raw/docs/runtimes/concepts/threads.mdx +68 -28
- package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +1 -1
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +6 -2
- package/.docs/raw/docs/runtimes/langchain.mdx +1 -1
- package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +4 -0
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +8 -1
- package/.docs/raw/docs/tools/defining-tools.mdx +19 -0
- package/.docs/raw/docs/tools/generative-ui-primitive.mdx +180 -0
- package/.docs/raw/docs/tools/generative-ui-slack.mdx +167 -0
- package/.docs/raw/docs/tools/generative-ui-teams.mdx +160 -0
- package/.docs/raw/docs/tools/generative-ui.mdx +224 -211
- package/.docs/raw/docs/tools/index.mdx +2 -1
- package/.docs/raw/docs/tools/interactables.mdx +5 -4
- package/.docs/raw/docs/tools/openui.mdx +175 -0
- package/.docs/raw/docs/tools/tool-ui.mdx +1 -2
- package/.docs/raw/docs/tools/user-managed-mcp.mdx +5 -1
- package/.docs/raw/docs/ui/attachment.mdx +27 -0
- package/.docs/raw/docs/ui/file.mdx +1 -1
- package/.docs/raw/docs/ui/image.mdx +1 -1
- package/package.json +4 -4
- package/.docs/raw/docs/tools/interactables-legacy.mdx +0 -423
|
@@ -37,10 +37,11 @@ const cloud = new AssistantCloud({
|
|
|
37
37
|
const runtime = useLocalRuntime(modelAdapter, { cloud });
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Framework adapters take `cloud` directly
|
|
40
|
+
Framework adapters take `cloud` directly. `AISDKThreads({ cloud })` is the store-entry list for `AuiConfig` hosts. It mounts only the visible thread, so a switch cancels an in-flight run and that exchange is not persisted.
|
|
41
41
|
|
|
42
42
|
```tsx
|
|
43
43
|
const runtime = useChatRuntime({ cloud });
|
|
44
|
+
const threads = AISDKThreads({ cloud });
|
|
44
45
|
const runtime = useLangGraphRuntime({ cloud /* stream, load, ... */ });
|
|
45
46
|
const runtime = useAdkRuntime({ cloud, stream });
|
|
46
47
|
```
|
|
@@ -130,44 +131,68 @@ export function MyProvider({ children }: { children: React.ReactNode }) {
|
|
|
130
131
|
}
|
|
131
132
|
```
|
|
132
133
|
|
|
133
|
-
### Persisting messages
|
|
134
|
+
### Persisting messages
|
|
134
135
|
|
|
135
|
-
`RemoteThreadListAdapter` only manages thread metadata.
|
|
136
|
+
`RemoteThreadListAdapter` only manages thread metadata. Per-thread history and attachments are a separate seam with two faces:
|
|
137
|
+
|
|
138
|
+
- `unstable_useAdapters` is a hook. The `RemoteThreadList` store entry calls it inside the client tree, so any `createAssistantClient` host gets the same adapters as a React hook host. `useRemoteThreadListRuntime` also calls it when `unstable_Provider` is omitted.
|
|
139
|
+
- `unstable_Provider` is a React component. `useRemoteThreadListRuntime` renders it when present. The store entry ignores it.
|
|
140
|
+
|
|
141
|
+
On the store entry, wrap the thread factory with `withKey` so the thread remounts on a switch. History adapters load once per mount. An unkeyed factory keeps one instance, and the next thread's messages never appear.
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { withKey } from "@assistant-ui/tap";
|
|
145
|
+
import { RemoteThreadList } from "@assistant-ui/react";
|
|
146
|
+
|
|
147
|
+
threads: RemoteThreadList({
|
|
148
|
+
adapter,
|
|
149
|
+
thread: (id) => withKey(id, MyThread({ threadId: id })),
|
|
150
|
+
}),
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Share one hook between both faces:
|
|
136
154
|
|
|
137
155
|
```tsx
|
|
138
156
|
import {
|
|
139
157
|
RuntimeAdapterProvider,
|
|
140
158
|
useAui,
|
|
159
|
+
type RemoteThreadListAdapter,
|
|
141
160
|
type ThreadHistoryAdapter,
|
|
142
161
|
} from "@assistant-ui/react";
|
|
143
162
|
import { useMemo } from "react";
|
|
144
163
|
|
|
164
|
+
function useThreadListAdapters() {
|
|
165
|
+
const aui = useAui();
|
|
166
|
+
const history = useMemo<ThreadHistoryAdapter>(
|
|
167
|
+
() => ({
|
|
168
|
+
async load() {
|
|
169
|
+
const { remoteId } = aui.threadListItem.getState();
|
|
170
|
+
if (!remoteId) return { messages: [] };
|
|
171
|
+
const rows = await fetch(
|
|
172
|
+
`/api/threads/${remoteId}/messages`,
|
|
173
|
+
).then((r) => r.json());
|
|
174
|
+
return { messages: rows.map(toThreadMessage) };
|
|
175
|
+
},
|
|
176
|
+
async append({ message, parentId }) {
|
|
177
|
+
const { remoteId } = await aui.threadListItem.initialize();
|
|
178
|
+
await fetch(`/api/threads/${remoteId}/messages`, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
body: JSON.stringify({ message, parentId }),
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
}),
|
|
184
|
+
[aui],
|
|
185
|
+
);
|
|
186
|
+
return useMemo(() => ({ history }), [history]);
|
|
187
|
+
}
|
|
188
|
+
|
|
145
189
|
const adapterWithHistory: RemoteThreadListAdapter = {
|
|
146
190
|
// ...metadata methods above...
|
|
191
|
+
unstable_useAdapters: useThreadListAdapters,
|
|
147
192
|
unstable_Provider({ children }) {
|
|
148
|
-
const
|
|
149
|
-
const history = useMemo<ThreadHistoryAdapter>(
|
|
150
|
-
() => ({
|
|
151
|
-
async load() {
|
|
152
|
-
const { remoteId } = aui.threadListItem.getState();
|
|
153
|
-
if (!remoteId) return { messages: [] };
|
|
154
|
-
const rows = await fetch(
|
|
155
|
-
`/api/threads/${remoteId}/messages`,
|
|
156
|
-
).then((r) => r.json());
|
|
157
|
-
return { messages: rows.map(toThreadMessage) };
|
|
158
|
-
},
|
|
159
|
-
async append({ message, parentId }) {
|
|
160
|
-
const { remoteId } = await aui.threadListItem.initialize();
|
|
161
|
-
await fetch(`/api/threads/${remoteId}/messages`, {
|
|
162
|
-
method: "POST",
|
|
163
|
-
body: JSON.stringify({ message, parentId }),
|
|
164
|
-
});
|
|
165
|
-
},
|
|
166
|
-
}),
|
|
167
|
-
[aui],
|
|
168
|
-
);
|
|
193
|
+
const adapters = useThreadListAdapters();
|
|
169
194
|
return (
|
|
170
|
-
<RuntimeAdapterProvider adapters={
|
|
195
|
+
<RuntimeAdapterProvider adapters={adapters}>
|
|
171
196
|
{children}
|
|
172
197
|
</RuntimeAdapterProvider>
|
|
173
198
|
);
|
|
@@ -192,6 +217,15 @@ async append({ message, parentId }) {
|
|
|
192
217
|
|
|
193
218
|
`initialize()` is safe to call multiple times. It always resolves to the same `remoteId` for the active thread.
|
|
194
219
|
|
|
220
|
+
The same rule applies to a custom external store's dispatch. The runtime does not hold `onNew` or `onEdit` until the thread record exists (that would keep the user's message off screen for the whole roundtrip), so a handler that talks to a backend keyed by the remote identity must await `initialize()` itself:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
onNew: async (message) => {
|
|
224
|
+
const { remoteId } = await aui.threadListItem.initialize();
|
|
225
|
+
await sendToBackend(remoteId, message);
|
|
226
|
+
},
|
|
227
|
+
```
|
|
228
|
+
|
|
195
229
|
### Reloading after async authentication
|
|
196
230
|
|
|
197
231
|
If your adapter depends on a user that resolves asynchronously (oidc, `next-auth`, `better-auth`), the initial `list()` may run before the user is available. Call `aui.threads.reload()` after auth completes:
|
|
@@ -230,13 +264,13 @@ A thread that has not been sent yet is left alone because it holds no remote sta
|
|
|
230
264
|
|
|
231
265
|
What happens to a run in progress depends on the path. The remount path drops the runtime that was rendering the run; whether the run itself stops is up to that hook's unmount cleanup, which core cannot enforce. On the in-place path the runtime that declared the capability decides, since core does not stop the run for it. Either way this belongs on an event rather than a short timer: drive it from a state change like the one above, or skip the call while `useAuiState((s) => s.thread.isRunning)` is true.
|
|
232
266
|
|
|
233
|
-
How the refetch happens depends on the runtime, in one of three ways. When it declares the capability, the thread runtime is reused: composer drafts survive, existing messages stay rendered while the fresh state loads, and the returned promise settles with the refetch, rejecting if it fails. A remote thread list without the capability remounts the runtime hook instead, which re-runs `load()` at the cost of discarding unsent composer input, and resolves once the new runtime attaches. The single and in-memory thread lists
|
|
267
|
+
How the refetch happens depends on the runtime, in one of three ways. When it declares the capability, the thread runtime is reused: composer drafts survive, existing messages stay rendered while the fresh state loads, and the returned promise settles with the refetch, rejecting if it fails. A remote thread list without the capability remounts the runtime hook instead, which re-runs `load()` at the cost of discarding unsent composer input, and resolves once the new runtime attaches. The single and in-memory thread lists have no hook to remount: they take the in-place path when their tap `ExternalThread` was given `onRefetchThread`, and resolve without doing anything when it was not.
|
|
234
268
|
|
|
235
269
|
`useAuiState((s) => s.thread.capabilities.refetchThread)` reports which of those you would get, in place or not. It is not a signal for whether to offer a refresh at all: it is false on the remount path, where the call still does the work, and false again where the call does nothing.
|
|
236
270
|
|
|
237
271
|
Both the LangGraph and Google ADK adapters register the in-place refetch capability when their runtime hook receives a `load` function; without one they fall back to the remount path. Other remote adapters take the remount path unless they provide the capability themselves.
|
|
238
272
|
|
|
239
|
-
For an external store runtime, declare it with `onRefetchThread`, which is unrelated to `onReload` (that one re-generates an assistant message):
|
|
273
|
+
For an external store runtime, declare it with `onRefetchThread`, which is unrelated to `onReload` (that one re-generates an assistant message); the tap `ExternalThread` accepts the same prop:
|
|
240
274
|
|
|
241
275
|
```ts
|
|
242
276
|
useExternalStoreRuntime({
|
|
@@ -367,7 +401,13 @@ A few invariants worth knowing when wiring a custom UI on top of `loadMore()`:
|
|
|
367
401
|
name: "unstable_Provider",
|
|
368
402
|
type: "RemoteThreadListProviderComponent",
|
|
369
403
|
description:
|
|
370
|
-
"Optional wrapper rendered around each active thread. Inject thread-scoped adapters
|
|
404
|
+
"Optional React wrapper rendered around each active thread by useRemoteThreadListRuntime when present. Inject thread-scoped adapters here. Omit it to let that host use unstable_useAdapters.",
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
name: "unstable_useAdapters",
|
|
408
|
+
type: "() => RuntimeAdapters | null | undefined",
|
|
409
|
+
description:
|
|
410
|
+
"Optional hook called by the RemoteThreadList store entry, and by useRemoteThreadListRuntime when unstable_Provider is omitted. Per-thread history requires the thread factory to be keyed with withKey.",
|
|
371
411
|
},
|
|
372
412
|
]}
|
|
373
413
|
/>
|
|
@@ -308,7 +308,7 @@ aui-state:[{"type":"set","path":["status"],"value":"completed"}]
|
|
|
308
308
|
prepareSendCommandsRequest?: (body: SendCommandsRequestBody) => Record<string, unknown> | Promise<Record<string, unknown>>,
|
|
309
309
|
capabilities?: { edit?: boolean },
|
|
310
310
|
adapters?: { attachments?: AttachmentAdapter; history?: ThreadHistoryAdapter },
|
|
311
|
-
onResponse?: (response: Response) => void
|
|
311
|
+
onResponse?: (response: Response) => void | Promise<void>,
|
|
312
312
|
onFinish?: () => void,
|
|
313
313
|
onError?: (error: Error, params: { commands: AssistantTransportCommand[]; updateState: (updater: (state: T) => T) => void }) => void | Promise<void>,
|
|
314
314
|
onCancel?: (params: { commands: AssistantTransportCommand[]; updateState: (updater: (state: T) => T) => void; error?: Error }) => void
|
|
@@ -421,13 +421,13 @@ useEffect(() => {
|
|
|
421
421
|
}, [isRunning, queue]);
|
|
422
422
|
```
|
|
423
423
|
|
|
424
|
-
|
|
424
|
+
With a `createMessageQueue` adapter, cancelling pauses the queue for you: the runtime tells the queue before your `onCancel` runs, so the cancelled run's settle keeps the pending items instead of dispatching the next one, and the next send resumes draining. Call `queue.clear()` in `onCancel` instead if you want a cancel to drop them. A hand-rolled adapter has no such channel, so it owns its cancel policy the same way it owns the rest. Edit and reload stay host-owned: call `queue.clear()` in your `onEdit` and `onReload` handlers so stale items do not drain onto the new branch.
|
|
425
425
|
|
|
426
426
|
```tsx
|
|
427
427
|
const runtime = useExternalStoreRuntime({
|
|
428
428
|
// ...
|
|
429
429
|
onCancel: async () => {
|
|
430
|
-
|
|
430
|
+
// the runtime already paused the queue; clear() here to drop the items
|
|
431
431
|
await cancelRun();
|
|
432
432
|
},
|
|
433
433
|
onEdit: async (message) => {
|
|
@@ -447,6 +447,10 @@ const runtime = useExternalStoreRuntime({
|
|
|
447
447
|
|
|
448
448
|
## Integration examples
|
|
449
449
|
|
|
450
|
+
<Callout type="info">
|
|
451
|
+
**Real-world example:** the [Claude Managed Agents guide](/docs/runtimes/claude-managed-agents) maps Anthropic-hosted agent sessions onto this runtime, with Anthropic's official [quickstart](https://github.com/anthropics/claude-quickstarts/tree/main/managed-agents/assistant-ui) as the runnable reference.
|
|
452
|
+
</Callout>
|
|
453
|
+
|
|
450
454
|
### Redux
|
|
451
455
|
|
|
452
456
|
```tsx title="app/chatSlice.ts"
|
|
@@ -262,7 +262,7 @@ Follow the [Ink setup](/docs/ink).
|
|
|
262
262
|
|
|
263
263
|
### Forwarding per-run config
|
|
264
264
|
|
|
265
|
-
When a message is sent, its `runConfig.custom` (for example a selected mode or model) is forwarded on the underlying `useStream().submit` call as `config.configurable`. Read it in the graph from `config["configurable"]`; on LangGraph v1 the same values are also reachable through the Runtime `context`, which `config.configurable` is aliased to. This lets per-run app config reach the graph without extra wiring.
|
|
265
|
+
When a message is sent, its `runConfig.custom` (for example a selected mode or model) is forwarded on the underlying `useStream().submit` call as `config.configurable`. Automatic tool-result resumes and the interrupt helpers (`useLangChainRespond`, `useLangChainRespondAll`, and `useLangChainSubmit(null, { command })`) reuse that same recorded `configurable` unless the caller passes `config`. Raw `useLangChainSubmit` / `useLangChainSend` calls that start a new run do not inherit it. The recording is session-scoped and does not survive a reload. Read it in the graph from `config["configurable"]`; on LangGraph v1 the same values are also reachable through the Runtime `context`, which `config.configurable` is aliased to. This lets per-run app config reach the graph without extra wiring.
|
|
266
266
|
|
|
267
267
|
## Reading custom state keys
|
|
268
268
|
|
|
@@ -119,6 +119,10 @@ const runtime = useLangGraphRuntime({
|
|
|
119
119
|
|
|
120
120
|
Render the pending messages with [`ComposerPrimitive.Queue`](/docs/api-reference/primitives/composer) and [`QueueItemPrimitive`](/docs/api-reference/primitives/queue-item).
|
|
121
121
|
|
|
122
|
+
## Per-run config
|
|
123
|
+
|
|
124
|
+
When a message is sent, its `runConfig` is forwarded to `stream` and, if you use `unstable_createLangGraphStream`, posted as the LangGraph SDK run `config`. Automatic frontend tool-result resumes and `useLangGraphSendCommand` reuse the `runConfig` of the run that produced the pending tool call or interrupt. An explicit `runConfig` on `useLangGraphSend` still wins. A thread refetch that still carries the interrupt keeps that owner. History loaded without local ownership stays unconfigured on resume; the recording is session-scoped and does not survive a reload.
|
|
125
|
+
|
|
122
126
|
## Next
|
|
123
127
|
|
|
124
128
|
<Cards>
|
|
@@ -6,6 +6,7 @@ description: Decision guide for choosing the right runtime, by framework or by f
|
|
|
6
6
|
import { A2AIcon } from "@/components/icons/a2a";
|
|
7
7
|
import { AdkIcon } from "@/components/icons/adk";
|
|
8
8
|
import { AguiIcon } from "@/components/icons/agui";
|
|
9
|
+
import { ClaudeIcon } from "@/components/icons/claude";
|
|
9
10
|
import { CloudflareIcon } from "@/components/icons/cloudflare";
|
|
10
11
|
import { LangChainIcon } from "@/components/icons/langchain";
|
|
11
12
|
import { LangGraphIcon } from "@/components/icons/langgraph";
|
|
@@ -80,7 +81,7 @@ assistant-ui ships React adapter packages for these. Pick the matching card and
|
|
|
80
81
|
|
|
81
82
|
### Integration guides
|
|
82
83
|
|
|
83
|
-
For frameworks without a dedicated adapter, these wiring guides route through one of the
|
|
84
|
+
For frameworks without a dedicated adapter, these wiring guides route through an existing runtime: an adapter above, or one of the core runtimes.
|
|
84
85
|
|
|
85
86
|
<Cards>
|
|
86
87
|
<Card
|
|
@@ -95,6 +96,12 @@ For frameworks without a dedicated adapter, these wiring guides route through on
|
|
|
95
96
|
description="TypeScript agent framework. Wired through the Vercel AI SDK runtime."
|
|
96
97
|
href="/docs/integrations/frameworks/mastra/overview"
|
|
97
98
|
/>
|
|
99
|
+
<Card
|
|
100
|
+
icon={<ClaudeIcon width={20} height={20} className="text-[#D97757]" />}
|
|
101
|
+
title="Claude Managed Agents"
|
|
102
|
+
description="Anthropic-hosted agent sessions. Wired through the external store runtime: event-log replay, approval gates, sessions as threads."
|
|
103
|
+
href="/docs/runtimes/claude-managed-agents"
|
|
104
|
+
/>
|
|
98
105
|
</Cards>
|
|
99
106
|
|
|
100
107
|
</PlatformOnly>
|
|
@@ -194,6 +194,25 @@ The compiler also enforces, at build time:
|
|
|
194
194
|
- a **frontend** tool declares a `render` or `renderText`;
|
|
195
195
|
- a **human** tool declares a `render`.
|
|
196
196
|
|
|
197
|
+
### Running without your own backend
|
|
198
|
+
|
|
199
|
+
The client build skips uploading frontend/human schemas because it assumes your backend imported the same file's server build and already knows them. When no server of yours does — for example, cloud-hosted runs — that assumption breaks and the model never learns about those tools. Compile with the `backendless` option so the client keeps every schema uploadable, including the `present`/`prompt_user` schema of a `JSONGenerativeUI` component library:
|
|
200
|
+
|
|
201
|
+
```ts title="next.config.ts"
|
|
202
|
+
export default withAui({ ...yourConfig, aui: { backendless: true } });
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```ts title="vite.config.ts"
|
|
206
|
+
plugins: [aui({ backendless: true })];
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```js title="metro.config.js"
|
|
210
|
+
module.exports = withAui({
|
|
211
|
+
...getDefaultConfig(__dirname),
|
|
212
|
+
aui: { backendless: true },
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
197
216
|
## Tool kinds
|
|
198
217
|
|
|
199
218
|
### Backend tools
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Generative UI primitive
|
|
3
|
+
description: Render agent-described React UI from a JSON spec with a consumer-provided component allowlist, using the MessagePrimitive.GenerativeUI primitive.
|
|
4
|
+
platforms: ["react"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<Callout type="info">
|
|
8
|
+
This page is for a **backend that already emits `generative-ui` message parts**. If you are starting fresh and want the model to compose an interface, use the [`present` tool](/docs/tools/generative-ui) instead: it ships a component vocabulary, generates the model-facing schema for you, and renders inside the stock `Thread` without extra wiring. The two are not versions of one API and their specs are not interchangeable; see [Spec shape](#spec-shape).
|
|
9
|
+
</Callout>
|
|
10
|
+
|
|
11
|
+
`MessagePrimitive.GenerativeUI` renders UI described by the agent at runtime as a JSON spec. Instead of hard-coding a component per tool, the agent emits a `generative-ui` message part containing a tree of components by name. assistant-ui resolves each name against a **consumer-provided allowlist** and renders the result. The producer is your backend rather than a model tool call: you decide what goes into the part and prompt your agent to emit it.
|
|
12
|
+
|
|
13
|
+
The allowlist controls **which** components the agent may render: any name not in it throws a typed `GenerativeUIRenderError` (no implicit fallback). It does not constrain the props passed to those components; see [Security](#security).
|
|
14
|
+
|
|
15
|
+
<Callout type="warn">
|
|
16
|
+
The default shadcn `Thread` does **not** render `generative-ui` parts. You must wire the primitive explicitly, see [Opt-in wiring](#opt-in-wiring).
|
|
17
|
+
</Callout>
|
|
18
|
+
|
|
19
|
+
## When not to use the primitive
|
|
20
|
+
|
|
21
|
+
- **A vocabulary the model composes freely** → the [`present` tool](/docs/tools/generative-ui)
|
|
22
|
+
- **User input and two-way interaction** → [Tool UI](/docs/tools/tool-ui) or [Interactables](/docs/tools/interactables)
|
|
23
|
+
- **LangGraph `push_ui_message`** → [LangGraph data UI](/docs/runtimes/langgraph/generative-ui)
|
|
24
|
+
- **Untrusted HTML or third-party widgets** → [MCP Apps](/docs/tools/mcp-apps) (sandboxed frames)
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
### 1. Define your component allowlist
|
|
29
|
+
|
|
30
|
+
```tsx title="components/gui.tsx"
|
|
31
|
+
const Card = ({ title, children }) => (
|
|
32
|
+
<div className="rounded-xl border bg-card p-4 shadow-sm">
|
|
33
|
+
<div className="text-base font-semibold">{title}</div>
|
|
34
|
+
<div className="mt-2">{children}</div>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const Button = ({ label }) => (
|
|
39
|
+
<button className="rounded-md bg-primary px-3 py-1.5 text-primary-foreground">
|
|
40
|
+
{label}
|
|
41
|
+
</button>
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
export const componentsAllowlist = { Card, Button };
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Wire the primitive into your message renderer
|
|
48
|
+
|
|
49
|
+
See [Opt-in wiring](#opt-in-wiring) for all three integration patterns.
|
|
50
|
+
|
|
51
|
+
### 3. Have the agent emit UI
|
|
52
|
+
|
|
53
|
+
**ExternalStore / manual messages** attach a native part:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
{
|
|
57
|
+
type: "generative-ui",
|
|
58
|
+
spec: {
|
|
59
|
+
root: {
|
|
60
|
+
component: "Card",
|
|
61
|
+
props: { title: "Welcome" },
|
|
62
|
+
children: [
|
|
63
|
+
{ component: "Button", props: { label: "Get started" } },
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**AI SDK (`useChatRuntime`)** maps tool results to `tool-call` parts, not `generative-ui` parts. Use the [AI SDK interim bridge](#pattern-3--ai-sdk-interim-bridge).
|
|
71
|
+
|
|
72
|
+
Live routes in [`examples/with-generative-ui`](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-generative-ui): static primitive (`/primitive`), GUI chat bridge (`/gui-chat`).
|
|
73
|
+
|
|
74
|
+
## Opt-in wiring
|
|
75
|
+
|
|
76
|
+
The stock `@assistant-ui/ui` `Thread` switch returns `null` for unknown part types, including `generative-ui`. Add one of these patterns in **your** assistant message renderer.
|
|
77
|
+
|
|
78
|
+
### Pattern 1 — `MessagePrimitive.Parts`
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<MessagePrimitive.Parts
|
|
82
|
+
components={{
|
|
83
|
+
generativeUI: {
|
|
84
|
+
components: componentsAllowlist,
|
|
85
|
+
Fallback: UnknownComponentFallback,
|
|
86
|
+
},
|
|
87
|
+
}}
|
|
88
|
+
/>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Pattern 2 — `GroupedParts` case (shadcn Thread fork)
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
case "generative-ui":
|
|
95
|
+
return (
|
|
96
|
+
<MessagePrimitive.GenerativeUI
|
|
97
|
+
components={componentsAllowlist}
|
|
98
|
+
Fallback={UnknownComponentFallback}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Also exclude `render_gui` from tool-group chrome in `groupBy` if you use the AI SDK bridge (return `null` for that tool name).
|
|
104
|
+
|
|
105
|
+
### Pattern 3 — AI SDK interim bridge
|
|
106
|
+
|
|
107
|
+
When using `useChatRuntime`, map a dedicated tool result to the renderer:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
case "tool-call":
|
|
111
|
+
if (part.toolName === "render_gui") {
|
|
112
|
+
const spec = parseRenderGuiResult(part.result);
|
|
113
|
+
if (spec) {
|
|
114
|
+
return (
|
|
115
|
+
<MessagePrimitive.GenerativeUI
|
|
116
|
+
spec={spec}
|
|
117
|
+
components={componentsAllowlist}
|
|
118
|
+
Fallback={UnknownComponentFallback}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return part.toolUI ?? <ToolFallback {...part} />;
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The message store still holds a `tool-call` on this path, not a `generative-ui` part. See `examples/with-generative-ui/app/gui-chat` for a working reference.
|
|
127
|
+
|
|
128
|
+
Bare strings act as inline text leaves.
|
|
129
|
+
|
|
130
|
+
## Spec shape
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
type GenerativeUINode =
|
|
134
|
+
| string
|
|
135
|
+
| {
|
|
136
|
+
component: string; // resolved against the allowlist
|
|
137
|
+
props?: Record<string, unknown>;
|
|
138
|
+
children?: GenerativeUINode[];
|
|
139
|
+
key?: string; // optional stable React key
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
type GenerativeUISpec = {
|
|
143
|
+
root: GenerativeUINode | GenerativeUINode[];
|
|
144
|
+
};
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The spec is plain JSON, easy for any agent to emit and easy to validate on the server before delivery.
|
|
148
|
+
|
|
149
|
+
This is a different shape from the flat `$type` tree the [`present` tool](/docs/tools/generative-ui) uses. The two are not interchangeable, and the Slack, Teams, and A2UI converters only accept the `$type` form.
|
|
150
|
+
|
|
151
|
+
## Streaming
|
|
152
|
+
|
|
153
|
+
When a message contains native `generative-ui` parts whose `spec` updates incrementally (for example via ExternalStore), the primitive renders progressively as nodes and props arrive.
|
|
154
|
+
|
|
155
|
+
The AI SDK `render_gui` tool path returns the full spec at **tool completion**, not incrementally during the tool execute step. For args streaming during generation, use [Tool UI](/docs/tools/tool-ui) instead.
|
|
156
|
+
|
|
157
|
+
## Security
|
|
158
|
+
|
|
159
|
+
The allowlist is the boundary on **which** components render: a spec can only instantiate components you put in the registry, with no `eval` and no dynamic import (names are looked up in the registry and nothing else). An unknown name throws `GenerativeUIRenderError` or invokes your `Fallback`.
|
|
160
|
+
|
|
161
|
+
It does **not** constrain the `props` the agent supplies. Spec props are spread directly onto your allowlisted components, so treat every allowlisted component as receiving untrusted input: never forward agent-supplied props into `dangerouslySetInnerHTML`, validate or reject `href` and `src` values (for example block `javascript:` URLs), and avoid passing spec props anywhere they become executable. The safest allowlisted components accept only primitive, display-oriented props.
|
|
162
|
+
|
|
163
|
+
## Error handling
|
|
164
|
+
|
|
165
|
+
Unknown component names throw `GenerativeUIRenderError` with a typed `componentName` field. Catch it with a React error boundary, or pass a `Fallback` component to opt into a soft-fail UX:
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
<MessagePrimitive.GenerativeUI
|
|
169
|
+
components={componentsAllowlist}
|
|
170
|
+
Fallback={({ component }) => (
|
|
171
|
+
<span className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">
|
|
172
|
+
unknown component: {component}
|
|
173
|
+
</span>
|
|
174
|
+
)}
|
|
175
|
+
/>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Composing with other primitives
|
|
179
|
+
|
|
180
|
+
`generative-ui` is a regular `MessagePart` type, so it composes cleanly with `MessagePrimitive.Parts`, `MessagePrimitive.PartByIndex`, and `MessagePrimitive.GroupedParts`. Render it alongside text, tool calls, and reasoning in the same message.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Generative UI on Slack
|
|
3
|
+
description: Convert a generative UI tree into Slack Block Kit, post it, and decode the block_actions payload Slack sends back into your action handlers.
|
|
4
|
+
platforms: ["react"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The `$type` tree your assistant renders in the browser is plain JSON, so it does not have to stay in the browser. `@assistant-ui/react-generative-ui/slack` converts the same tree into [Block Kit](https://docs.slack.dev/reference/block-kit/blocks) JSON, decodes the interactions Slack posts back, and parses Block Kit payloads into the tree.
|
|
8
|
+
|
|
9
|
+
The subpath is React-free, so a server action, queue worker, or webhook handler imports it without pulling React into the bundle.
|
|
10
|
+
|
|
11
|
+
## Posting a tree
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { WebClient } from "@slack/web-api";
|
|
15
|
+
import { toSlackBlocks } from "@assistant-ui/react-generative-ui/slack";
|
|
16
|
+
|
|
17
|
+
const slack = new WebClient(process.env.SLACK_BOT_TOKEN);
|
|
18
|
+
|
|
19
|
+
const { blocks, warnings } = toSlackBlocks({
|
|
20
|
+
$type: "Card",
|
|
21
|
+
title: "Order #48213",
|
|
22
|
+
children: [{ $type: "Text", value: "Shipped, arriving Thursday." }],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
await slack.chat.postMessage({ channel: "#orders", blocks });
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`toSlackBlocks(node, options?)` returns `{ blocks, warnings }` and never throws: an input it cannot convert at all comes back as empty `blocks` plus one warning rather than an exception. Pass `{ surface: "modal" }` to target a modal instead of a message; the surface changes the block budget and unlocks the native `alert` block.
|
|
29
|
+
|
|
30
|
+
## Warnings
|
|
31
|
+
|
|
32
|
+
Conversion is total. Every downgrade is reported rather than thrown, so one unsupported node never costs you the whole message:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
type SlackConversionWarning = {
|
|
36
|
+
code: "clamped" | "dropped" | "fallback";
|
|
37
|
+
component: string; // the IR component name, or "Root" for whole-payload issues
|
|
38
|
+
detail: string;
|
|
39
|
+
};
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`clamped` means content was truncated to fit a Slack limit, `dropped` means a node or one of its props was discarded and may have left a placeholder note behind (an oversized button payload is dropped while the button itself is kept, a `Chart` becomes an omission note), and `fallback` means the node rendered through a different construct than requested. Warnings arrive in traversal order and are not deduplicated. Logging them in development is the fastest way to see why a composition looks different on Slack than in the browser.
|
|
43
|
+
|
|
44
|
+
## Component mapping
|
|
45
|
+
|
|
46
|
+
| Component | Slack output | Fidelity |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `Header` | `header` block | Only `text` survives; `size` is dropped silently, since Slack's `header` block has no size |
|
|
49
|
+
| `Text` | `section` block with `mrkdwn` text | Only `value` survives; `size`, `weight`, and `color` are dropped silently |
|
|
50
|
+
| `Markdown` | `markdown` block | Downgrades to a `section` once the payload's markdown budget is spent |
|
|
51
|
+
| `Caption`, `Badge` | `context` block with one element | The two become identical output and cannot be told apart coming back |
|
|
52
|
+
| `Image` | `image` block | Only `src` and `alt` survive; `size` and `round` are dropped silently |
|
|
53
|
+
| `Divider` | `divider` block | `flush` is dropped silently |
|
|
54
|
+
| `Fact` | Merged into one `section`'s `fields`, as `*label*` then value | Consecutive facts merge; every 10 fields start a new section |
|
|
55
|
+
| `Table` | `data_table` block, first row as header | Cells become `raw_number` or `raw_text`; rows are padded to a uniform width; a column without a string label keeps its position with an empty header and warns |
|
|
56
|
+
| `Card` | Native `card` block, or a header plus inline blocks | See [Cards](#cards) |
|
|
57
|
+
| `Carousel` | `carousel` block of `card` elements | A non-card child that would have rendered is dropped with a warning; a card that cannot map cleanly is reshaped to title and body, and separately reports the images, tables, charts, and controls that reshape loses |
|
|
58
|
+
| `Alert` | Native `alert` block on a modal; a `context` plus `section` pair on a message | Slack supports `alert` [only in modals](https://docs.slack.dev/reference/block-kit/blocks/alert-block) |
|
|
59
|
+
| `ListView` | One `section` per item, with `divider` blocks between them | Item children collapse into concatenated text; a non-item child that would have rendered is dropped with a warning |
|
|
60
|
+
| `ListViewItem` | `section`, plus an "Open" button accessory when it carries an action | |
|
|
61
|
+
| `Button` | `button` element inside an `actions` block | `primary` and `danger` styles survive; other styles are dropped |
|
|
62
|
+
| `Select` | `static_select` element | An option without a string label and value is dropped with a warning |
|
|
63
|
+
| `RadioGroup` | `radio_buttons` element | An option without a string label and value is dropped with a warning |
|
|
64
|
+
| `Checkbox` | `checkboxes` element with a single option | |
|
|
65
|
+
| `DatePicker` | `datepicker` element | `min` and `max` are dropped; a non-`YYYY-MM-DD` value is dropped with a warning |
|
|
66
|
+
| `Input` | Its own `input` block | Not grouped into an `actions` block |
|
|
67
|
+
| `Form` | Children inline, then a "Submit" button | Slack has no form container |
|
|
68
|
+
| `Row` | One `context` block when every child is a `Badge` or `Caption`, otherwise flattened | Horizontal layout is lost in the flattened case |
|
|
69
|
+
| `Col`, `Box` | Flattened into the sibling block stream | Slack has no nesting container |
|
|
70
|
+
| `Chart` | Replaced by a note block | Always warns |
|
|
71
|
+
| `Spacer`, `Icon` | Dropped | Silently, since neither has a Slack equivalent |
|
|
72
|
+
|
|
73
|
+
An unknown component and a bare string child are both handled: the unknown one is dropped with a warning, and the string becomes a `section`.
|
|
74
|
+
|
|
75
|
+
Presentation props the converter does not map are dropped silently, without a warning, because the node itself still renders. That covers `Box`'s `width`, `height`, `radius`, and `background`; `Card`'s `padding` and `background`; `gap` on `Row`, `Col`, and `Form`, `align` on `Row` and `Col`, and `justify` on `Row`; `Badge`'s `variant`; `Carousel`'s `label`; and `Button`'s `block`. `Card`'s `asForm` and `Button`'s `submit` are dropped for a different reason: Slack has no client-side form model to submit into, so a submit button and a click button both convert to the same `button` element carrying the node's `$action`, and a card marked `asForm` converts exactly like one that is not.
|
|
76
|
+
|
|
77
|
+
### Cards
|
|
78
|
+
|
|
79
|
+
`Card` takes the native `card` block only when its children fit that block's fixed fields, because Slack's `card` carries `hero_image`, `title`, `body`, `subtext`, and up to three action buttons rather than nesting arbitrary blocks. The converter fills those from the first `Image`, the first `Text` or `Markdown`, and the first `Caption`.
|
|
80
|
+
|
|
81
|
+
Anything else in the card, including a second image, a `Fact`, or a loose `Button`, makes the card fall back to a header plus the children rendered inline plus an actions block, and reports a `fallback` warning. A card carrying none of an image, title, body, or actions is dropped outright.
|
|
82
|
+
|
|
83
|
+
Inside a `Carousel` that fallback is unavailable, so an over-full card is reshaped to title and body text instead, with a `fallback` warning. Text carried in a `text`, `value`, `label`, `title`, or `description` prop survives the reshape at any depth, which covers a `Caption`'s text and a `Button`'s label. An image, a `Table`, a `Chart`, and any control do not, since none of them is text, and those are reported separately as `dropped` so the two facts stay distinguishable. A control here is a `Button`, `Select`, `DatePicker`, `Checkbox`, `RadioGroup`, `Input`, or `Form`, plus a `ListViewItem` or a nested `Card` footer that carries an action. An `$action` on a layout node such as `Box`, `Col`, or `Row` does not count, because those render no control on the clean path either.
|
|
84
|
+
|
|
85
|
+
## Limits
|
|
86
|
+
|
|
87
|
+
The converter clamps to Slack's published budgets rather than letting the API reject the payload. The ones you are most likely to hit:
|
|
88
|
+
|
|
89
|
+
| Budget | Value | Behavior when exceeded |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| Blocks per message | 50 (100 in a modal) | Extra blocks are dropped and a note block reports the count |
|
|
92
|
+
| Section text | 3,000 characters | Truncated |
|
|
93
|
+
| Section fields | 10 per section, 2,000 characters each | Chunked into further sections; text truncated |
|
|
94
|
+
| Actions elements | 25 per block | Chunked into further actions blocks |
|
|
95
|
+
| Select options | 100 | Truncated |
|
|
96
|
+
| Radio options | 10 | Truncated |
|
|
97
|
+
| Button label | 75 characters | Truncated |
|
|
98
|
+
| Button action payload | 2,000 characters | Dropped entirely, not truncated, so a partial payload never round-trips |
|
|
99
|
+
| Card title | 150 characters, body and subtext 200 | Truncated |
|
|
100
|
+
| Carousel cards | 10 | Truncated; a carousel with no renderable card is dropped |
|
|
101
|
+
| Table | 200 data rows, 20 columns, 20,000 characters across all tables in one payload | Rows truncated; a table whose header alone busts the budget is dropped |
|
|
102
|
+
| Markdown | 12,000 characters across the payload | Every markdown block from that point on becomes a `section` |
|
|
103
|
+
|
|
104
|
+
Traversal itself is bounded too: 200 children per level, 5,000 nodes per call, and 32 levels of element nesting, each reported as a `Root` warning. These bounds exist because the tree arrives from a model.
|
|
105
|
+
|
|
106
|
+
## Actions
|
|
107
|
+
|
|
108
|
+
### Outbound
|
|
109
|
+
|
|
110
|
+
A node's `$action` is split across two Block Kit fields. `$action.type` becomes the element's `action_id`, and the remaining keys are JSON-serialized into the element's `value`.
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"$type": "Button",
|
|
115
|
+
"label": "Approve",
|
|
116
|
+
"$action": { "type": "approve_order", "orderId": "48213" }
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
becomes a button with `action_id: "approve_order"` and `value: "{\"orderId\":\"48213\"}"`.
|
|
121
|
+
|
|
122
|
+
<Callout type="warn">
|
|
123
|
+
Only buttons carry `value`. `Select`, `Input`, `DatePicker`, `Checkbox`, and `RadioGroup` emit `action_id` alone, so any extra keys on their `$action` are dropped. Keep those controls' actions to a bare `type`, or put the payload on a button that submits alongside them.
|
|
124
|
+
</Callout>
|
|
125
|
+
|
|
126
|
+
### Inbound
|
|
127
|
+
|
|
128
|
+
Slack posts interactions to your request URL as a [`block_actions` payload](https://docs.slack.dev/reference/interaction-payloads/block_actions-payload). `decodeBlockAction` takes one entry from its `actions` array and rebuilds the action your tree dispatched, with the user's runtime selection under `$input`:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { decodeBlockAction } from "@assistant-ui/react-generative-ui/slack";
|
|
132
|
+
|
|
133
|
+
const action = decodeBlockAction(payload.actions[0]);
|
|
134
|
+
// { type: "approve_order", orderId: "48213", $input: "…" }
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
It returns `undefined` for anything without a usable `action_id`, and never throws. `type` always comes from `action_id`, so a payload key named `type` cannot override it, and a `$input` key smuggled into the serialized payload is always stripped.
|
|
138
|
+
|
|
139
|
+
What lands in `$input` depends on the element:
|
|
140
|
+
|
|
141
|
+
| Element | `$input` |
|
|
142
|
+
|---|---|
|
|
143
|
+
| `static_select`, `radio_buttons` | The selected option's value, as a string |
|
|
144
|
+
| `datepicker` | The selected date, as `YYYY-MM-DD` |
|
|
145
|
+
| `checkboxes` | An array of selected values, empty when nothing is checked |
|
|
146
|
+
| `plain_text_input` | The typed text |
|
|
147
|
+
| `button` | The raw `value` string, when it is not a serialized object |
|
|
148
|
+
|
|
149
|
+
### Reading a tree back
|
|
150
|
+
|
|
151
|
+
`fromSlackBlocks` is the inverse direction, mapping a Block Kit payload into vocabulary nodes and returning `{ nodes, warnings }`. It accepts a bare array or a `{ blocks }` wrapper.
|
|
152
|
+
|
|
153
|
+
The round trip is faithful on the plain building blocks (text, images, facts, controls, tables, simple cards) and documented-lossy elsewhere: context elements all return as `Caption`, so the `Badge` distinction is gone, button styles beyond `primary` and `danger` are dropped, an alert's title and description come back merged into the description, and card layouts flatten to the fields the `card` block carries. Card footers are re-derived from button style, with the primary-styled button becoming `confirm`; when style cannot decide it, position does, and that emits a `fallback` warning.
|
|
154
|
+
|
|
155
|
+
## Before interactions work
|
|
156
|
+
|
|
157
|
+
Converting and posting a tree needs only a bot token with `chat:write`. Making its buttons do anything additionally needs, on the Slack app side:
|
|
158
|
+
|
|
159
|
+
- **Interactivity enabled with a request URL**, under Interactivity & Shortcuts. Slack posts every `block_actions` payload there.
|
|
160
|
+
- **An acknowledgement within 3 seconds.** Return HTTP 200 first and do the work afterwards; the payload's `response_url` accepts up to five follow-up posts within 30 minutes if you need to update or replace the message.
|
|
161
|
+
- **[Request signature verification](https://docs.slack.dev/authentication/verifying-requests-from-slack)** on that endpoint, using your signing secret.
|
|
162
|
+
|
|
163
|
+
Receiving the webhook, verifying it, and routing the decoded action to your handler stay your application's responsibility. The converter only speaks JSON in and JSON out.
|
|
164
|
+
|
|
165
|
+
## Reference
|
|
166
|
+
|
|
167
|
+
The generated per-export reference, including every type in the subpath, is at [Slack Block Kit](/docs/api-reference/generative-ui/slack). For the Teams equivalent of this page, see [Generative UI on Microsoft Teams](/docs/tools/generative-ui-teams).
|