@assistant-ui/mcp-docs-server 0.1.34 → 0.1.35

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.
Files changed (64) hide show
  1. package/.docs/organized/code-examples/waterfall.md +4 -4
  2. package/.docs/organized/code-examples/with-a2a.md +5 -5
  3. package/.docs/organized/code-examples/with-ag-ui.md +6 -6
  4. package/.docs/organized/code-examples/with-ai-sdk-v6.md +7 -7
  5. package/.docs/organized/code-examples/with-artifacts.md +7 -7
  6. package/.docs/organized/code-examples/with-assistant-transport.md +5 -5
  7. package/.docs/organized/code-examples/with-browser-extension.md +5 -5
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +8 -8
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +7 -7
  10. package/.docs/organized/code-examples/with-cloud.md +7 -7
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +7 -7
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +9 -9
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +9 -9
  14. package/.docs/organized/code-examples/with-expo.md +43 -17
  15. package/.docs/organized/code-examples/with-external-store.md +5 -5
  16. package/.docs/organized/code-examples/with-ffmpeg.md +7 -7
  17. package/.docs/organized/code-examples/with-generative-ui.md +32 -308
  18. package/.docs/organized/code-examples/with-google-adk.md +5 -5
  19. package/.docs/organized/code-examples/with-heat-graph.md +4 -4
  20. package/.docs/organized/code-examples/with-image-generation.md +7 -7
  21. package/.docs/organized/code-examples/with-interactables.md +7 -7
  22. package/.docs/organized/code-examples/with-langchain.md +7 -7
  23. package/.docs/organized/code-examples/with-langgraph.md +6 -6
  24. package/.docs/organized/code-examples/with-livekit.md +9 -9
  25. package/.docs/organized/code-examples/with-mcp.md +7 -7
  26. package/.docs/organized/code-examples/with-opencode.md +106 -580
  27. package/.docs/organized/code-examples/with-pi.md +2044 -0
  28. package/.docs/organized/code-examples/with-react-hook-form.md +8 -8
  29. package/.docs/organized/code-examples/with-react-ink-web.md +691 -0
  30. package/.docs/organized/code-examples/with-react-ink.md +28 -16
  31. package/.docs/organized/code-examples/with-react-router.md +5 -5
  32. package/.docs/organized/code-examples/with-resumable-stream.md +8 -8
  33. package/.docs/organized/code-examples/with-store.md +14 -10
  34. package/.docs/organized/code-examples/with-tanstack.md +18 -4
  35. package/.docs/organized/code-examples/with-tap-runtime.md +5 -5
  36. package/.docs/organized/code-examples/with-virtualized-thread.md +656 -0
  37. package/.docs/raw/docs/(docs)/architecture.mdx +13 -12
  38. package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +44 -1
  39. package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -1
  40. package/.docs/raw/docs/(reference)/api-reference/tools/rendering.mdx +2 -23
  41. package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +22 -0
  42. package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +0 -7
  43. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +20 -21
  44. package/.docs/raw/docs/guides/chain-of-thought.mdx +1 -1
  45. package/.docs/raw/docs/guides/index.mdx +3 -0
  46. package/.docs/raw/docs/guides/input-history.mdx +55 -0
  47. package/.docs/raw/docs/guides/virtualization.mdx +63 -0
  48. package/.docs/raw/docs/ink/hooks.mdx +2 -2
  49. package/.docs/raw/docs/react-native/hooks.mdx +1 -1
  50. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +1 -3
  51. package/.docs/raw/docs/runtimes/concepts/stability.mdx +1 -1
  52. package/.docs/raw/docs/runtimes/custom/data-stream.mdx +1 -1
  53. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +108 -4
  54. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +6 -6
  55. package/.docs/raw/docs/tools/tool-ui.mdx +37 -1
  56. package/.docs/raw/docs/ui/dot-matrix.mdx +133 -0
  57. package/.docs/raw/docs/ui/model-selector.mdx +219 -52
  58. package/.docs/raw/docs/ui/number-roll.mdx +154 -0
  59. package/.docs/raw/docs/ui/reasoning.mdx +3 -3
  60. package/.docs/raw/docs/ui/streamdown.mdx +2 -0
  61. package/.docs/raw/docs/ui/syntax-highlighting.mdx +1 -1
  62. package/.docs/raw/docs/ui/thread.mdx +52 -0
  63. package/.docs/raw/docs/ui/tool-fallback.mdx +2 -2
  64. package/package.json +3 -3
@@ -3,7 +3,7 @@ title: Primitive Hooks
3
3
  description: Primitive hooks for reading scoped assistant-ui runtime state, viewport behavior, timing, and message part data inside React components.
4
4
  ---
5
5
 
6
- import { useAssistantRuntime, useAttachmentRuntime, useCloudThreadListAdapter, useComposerRuntime, useEditComposerAttachmentRuntime, useMessageAttachmentRuntime, useMessagePartData, useMessagePartRuntime, useMessageRuntime, useScrollLock, useThreadComposerAttachmentRuntime, useThreadListItemRuntime, useThreadRuntime, useThreadViewportAutoScroll } from "@/generated/typeDocs";
6
+ import { unstable_useMessageStallDetection, useAssistantRuntime, useAttachmentRuntime, useCloudThreadListAdapter, useComposerRuntime, useEditComposerAttachmentRuntime, useMessageAttachmentRuntime, useMessagePartData, useMessagePartRuntime, useMessageRuntime, useScrollLock, useThreadComposerAttachmentRuntime, useThreadListItemRuntime, useThreadRuntime, useThreadViewportAutoScroll } from "@/generated/typeDocs";
7
7
 
8
8
  {/* AUTO-GENERATED PAGE by scripts/generate-api-reference.mts */}
9
9
  {/* Do not edit manually. */}
@@ -637,4 +637,47 @@ const useThreadViewport: { (): ThreadViewportState; <TSelected>(selector: (state
637
637
  ```ts
638
638
  const useThreadViewportStore: { (): ReadonlyStore<ThreadViewportState>; (options: { optional: true; }): ReadonlyStore<ThreadViewportState> | null; };
639
639
  ```
640
+
641
+ ### unstable_useComposerInputHistory
642
+
643
+ <Callout type="warn">
644
+ <strong>Deprecated.</strong> Under active development and might change without notice.
645
+ </Callout>
646
+
647
+ Terminal-style input history for the thread composer: ArrowUp on an
648
+ empty draft recalls previously sent user messages (newest first),
649
+ ArrowDown steps back toward the newest and finally restores the draft
650
+ that was being typed when browsing started.
651
+
652
+ Recall only triggers when the caret is on the first/last line with no
653
+ selection, so multi-line editing keeps native arrow behavior. The
654
+ handler yields to an open mention/slash popover, to IME composition,
655
+ to modifier keys, and to consumer handlers that already called
656
+ `preventDefault`. It is inert on edit composers.
657
+
658
+ ```tsx
659
+ const history = unstable_useComposerInputHistory();
660
+ <ComposerPrimitive.Input {...history} />
661
+ ```
662
+
663
+ ```ts
664
+ function unstable_useComposerInputHistory(): Unstable_ComposerInputHistory;
665
+ ```
666
+
667
+ ### unstable_useMessageStallDetection
668
+
669
+ <Callout type="warn">
670
+ <strong>Deprecated.</strong> Under active development and might change without notice.
671
+ </Callout>
672
+
673
+ Detects mid-run output stalls on the current message: while the message is
674
+ running, watches a fingerprint of its content (part count plus text,
675
+ argument, and result sizes) and reports a stall once the fingerprint stops
676
+ changing for `thresholdMs`. Useful for re-surfacing a "still working"
677
+ indicator during tool think-time or provider stalls, after the first
678
+ tokens have already streamed.
679
+
680
+ Must be used inside a message scope.
681
+
682
+ <ParametersTable {...unstable_useMessageStallDetection} />
640
683
  {/* api-reference:end */}
@@ -29,7 +29,7 @@ const mergeModelContexts: (configSet: Set<ModelContextProvider>) => ModelContext
29
29
  ### ModelContextClient
30
30
 
31
31
  ```ts
32
- const ModelContextClient: () => ResourceElement<ClientOutput<"modelContext">, undefined>;
32
+ const ModelContextClient: Resource<ClientOutput<"modelContext">, []>;
33
33
  ```
34
34
 
35
35
  ### ModelContextProvider
@@ -3,7 +3,7 @@ title: Tool Rendering
3
3
  description: Register React renderers for assistant-ui tool calls, tool results, and model data parts.
4
4
  ---
5
5
 
6
- import { McpAppRenderer, McpAppsRemoteHost, getMcpAppFromToolPart, useAssistantDataUI, useAssistantToolUI } from "@/generated/typeDocs";
6
+ import { DataRenderers, McpAppRenderer, McpAppsRemoteHost, getMcpAppFromToolPart, useAssistantDataUI, useAssistantToolUI } from "@/generated/typeDocs";
7
7
 
8
8
  {/* AUTO-GENERATED PAGE by scripts/generate-api-reference.mts */}
9
9
  {/* Do not edit manually. */}
@@ -16,15 +16,7 @@ import { McpAppRenderer, McpAppsRemoteHost, getMcpAppFromToolPart, useAssistantD
16
16
 
17
17
  ### DataRenderers
18
18
 
19
- Registers renderers for `data` message parts.
20
-
21
- Data renderers are looked up by the part's `name` field. Use this resource
22
- directly for a renderer scope, or prefer [useAssistantDataUI](/docs/api-reference/tools/rendering#useassistantdataui) /
23
- [makeAssistantDataUI](/docs/api-reference/tools/rendering#makeassistantdataui) when registering from React components.
24
-
25
- ```ts
26
- const DataRenderers: () => ResourceElement<ClientOutput<"dataRenderers">, undefined>;
27
- ```
19
+ <ParametersTable {...DataRenderers} />
28
20
 
29
21
  ### getMcpAppFromToolPart
30
22
 
@@ -84,23 +76,10 @@ const makeAssistantToolUI: <TArgs, TResult>(tool: AssistantToolUIProps<TArgs, TR
84
76
 
85
77
  ### McpAppRenderer
86
78
 
87
- Creates a tool-call renderer for MCP Apps embedded in assistant messages.
88
-
89
- Compose this into the `Tools` resource through its `mcpApp` option. When a
90
- tool-call part carries `mcp.app` metadata for a `ui://` resource, the
91
- renderer loads that resource from the configured host and displays it in a
92
- sandboxed frame.
93
-
94
79
  <ParametersTable {...McpAppRenderer} />
95
80
 
96
81
  ### McpAppsRemoteHost
97
82
 
98
- Creates the default HTTP host for MCP App widgets.
99
-
100
- The host POSTs widget requests to the configured route as `{ method,
101
- params }`, using the method names expected by the assistant-ui MCP Apps
102
- guide.
103
-
104
83
  <ParametersTable {...McpAppsRemoteHost} />
105
84
 
106
85
  ### useAssistantDataUI
@@ -39,4 +39,26 @@ function WeatherToolUI({
39
39
  ```ts
40
40
  const useToolArgsStatus: <TArgs extends Record<string, unknown> = Record<string, unknown>>() => ToolArgsStatus<TArgs>;
41
41
  ```
42
+
43
+ ### useToolCallElapsed
44
+
45
+ Hook that returns the elapsed wall-clock time of the current tool call in
46
+ milliseconds, ticking once per second while the call runs.
47
+
48
+ Reads `part.timing`. Returns `undefined` when the part is not a tool call,
49
+ carries no timing, ended without a recorded completion (the duration is
50
+ unknown), or when no message part scope is available (so kit components
51
+ stay renderable standalone, e.g. in docs previews).
52
+
53
+ ```tsx
54
+ function ToolDuration() {
55
+ const elapsedMs = useToolCallElapsed();
56
+ if (elapsedMs === undefined) return null;
57
+ return <span>{(elapsedMs / 1000).toFixed(1)}s</span>;
58
+ }
59
+ ```
60
+
61
+ ```ts
62
+ const useToolCallElapsed: () => number;
63
+ ```
42
64
  {/* api-reference:end */}
@@ -80,13 +80,6 @@ type Toolkit = Record<string, ToolDefinition<any, any>>;
80
80
 
81
81
  ### Tools
82
82
 
83
- Registers tools with model context and installs tool-call renderers.
84
-
85
- Mount this resource near an assistant subtree when you want to expose a
86
- group of tools declaratively. Tool definitions are registered with model
87
- context, while each tool renderer is registered with the tools scope for
88
- message rendering.
89
-
90
83
  <ParametersTable {...Tools} />
91
84
 
92
85
  ### defineMcpToolkit
@@ -3,7 +3,7 @@ title: Utilities
3
3
  description: Miscellaneous @assistant-ui/react utilities for custom rendering, composition, and advanced assistant UI behavior.
4
4
  ---
5
5
 
6
- import { AssistantCloud, ChainOfThoughtClient, DevToolsHooks, InMemoryThreadList, SingleThreadList } from "@/generated/typeDocs";
6
+ import { AssistantCloud, ChainOfThoughtClient, DevToolsHooks, InMemoryThreadList, Interactables, SingleThreadList, Suggestions, useSmooth } from "@/generated/typeDocs";
7
7
 
8
8
  {/* AUTO-GENERATED PAGE by scripts/generate-api-reference.mts */}
9
9
  {/* Do not edit manually. */}
@@ -38,35 +38,34 @@ const createMessageQueue: (driver: MessageQueueDriver) => MessageQueueController
38
38
 
39
39
  ### Interactables
40
40
 
41
- ```ts
42
- const Interactables: () => ResourceElement<ClientOutput<"interactables">, undefined>;
43
- ```
41
+ <ParametersTable {...Interactables} />
44
42
 
45
43
  ### SingleThreadList
46
44
 
47
- A minimal threads scope that wraps a single thread.
48
- Automatically provided by ExternalThread when no threads scope exists.
49
- Mounts the provided thread resource element.
50
-
51
45
  <ParametersTable {...SingleThreadList} />
52
46
 
53
47
  ### Suggestions
54
48
 
55
- ```ts
56
- const Suggestions: {
57
- (): ResourceElement<
58
- ClientOutput<"suggestions">,
59
- undefined
60
- >;
61
- (
62
- suggestions: SuggestionConfig[],
63
- ): ResourceElement<
64
- ClientOutput<"suggestions">,
65
- SuggestionConfig[]
66
- >;
67
- };
49
+ <ParametersTable {...Suggestions} />
50
+
51
+ ### useSmooth
52
+
53
+ Animates streamed message part text with a typewriter-style reveal.
54
+
55
+ Takes the current part state and a `smooth` argument: `false` disables,
56
+ `true` uses the default rate, and a SmoothOptions object tunes
57
+ the reveal. Returns the part state with `text` replaced by the revealed
58
+ prefix and `status` reporting `running` until the reveal catches up.
59
+
60
+ ```tsx
61
+ const { text, status } = useSmooth(useMessagePartText(), {
62
+ drainMs: 500,
63
+ maxCharsPerFrame: 30,
64
+ });
68
65
  ```
69
66
 
67
+ <ParametersTable {...useSmooth} />
68
+
70
69
  ### unstable_defaultDirectiveFormatter
71
70
 
72
71
  Default directive formatter using the `:type[label]{name=id}` syntax.
@@ -60,7 +60,7 @@ const AssistantMessage: FC = () => {
60
60
  case "group-reasoning": {
61
61
  const running = part.status.type === "running";
62
62
  return (
63
- <ReasoningRoot defaultOpen={running}>
63
+ <ReasoningRoot streaming={running}>
64
64
  <ReasoningTrigger active={running} />
65
65
  <ReasoningContent aria-busy={running}>
66
66
  <ReasoningText>{children}</ReasoningText>
@@ -68,6 +68,9 @@ Render specialized message content.
68
68
  <Card title="LaTeX" href="/docs/guides/latex">
69
69
  Render math via React Markdown or Streamdown, with streaming-safe escape rules.
70
70
  </Card>
71
+ <Card title="Thread Virtualization" href="/docs/guides/virtualization">
72
+ Render very long threads with @tanstack/react-virtual and per-index message rendering.
73
+ </Card>
71
74
  </Cards>
72
75
 
73
76
  ## Audio
@@ -0,0 +1,55 @@
1
+ ---
2
+ title: Input History
3
+ description: Terminal-style ArrowUp/ArrowDown recall of previously sent messages in the assistant-ui React composer.
4
+ platforms: ["react"]
5
+ ---
6
+
7
+ Input history lets users press ArrowUp in an empty composer to recall previously sent messages, newest first, like a shell prompt. ArrowDown steps back toward the newest entry and finally restores the draft that was being typed when browsing started.
8
+
9
+ <Callout type="warn">
10
+ This API is marked unstable and may change without notice.
11
+ </Callout>
12
+
13
+ ## Usage
14
+
15
+ Spread the hook's bundle onto `ComposerPrimitive.Input`:
16
+
17
+ ```tsx
18
+ import {
19
+ ComposerPrimitive,
20
+ unstable_useComposerInputHistory,
21
+ } from "@assistant-ui/react";
22
+
23
+ const Composer = () => {
24
+ const history = unstable_useComposerInputHistory();
25
+
26
+ return (
27
+ <ComposerPrimitive.Root>
28
+ <ComposerPrimitive.Input {...history} />
29
+ <ComposerPrimitive.Send />
30
+ </ComposerPrimitive.Root>
31
+ );
32
+ };
33
+ ```
34
+
35
+ The history ring is derived live from the current thread's user messages (trimmed, with adjacent duplicates collapsed), so it needs no persistence or configuration.
36
+
37
+ ## Behavior
38
+
39
+ - ArrowUp starts recall only when the composer is empty (whitespace-only drafts count as empty and are restored on the way back down).
40
+ - While a recalled multi-line message is shown, arrows move the caret line by line; recall only steps when the caret is on the first line (ArrowUp) or last line (ArrowDown).
41
+ - An open mention or slash-command popover keeps owning the arrow keys; the hook yields whenever a trigger popover is active.
42
+ - IME composition, modifier keys, text selections, and events a preceding handler already `preventDefault`ed are left untouched.
43
+ - Switching threads or sending a message resets the browse position.
44
+ - The hook is inert on edit composers.
45
+
46
+ To interleave your own ArrowUp handling ahead of history (for example, stepping through queued messages), compose your handler before the hook's and call `preventDefault` when you consume the key:
47
+
48
+ ```tsx
49
+ <ComposerPrimitive.Input
50
+ onKeyDown={(e) => {
51
+ myHandler(e);
52
+ history.onKeyDown(e);
53
+ }}
54
+ />
55
+ ```
@@ -0,0 +1,63 @@
1
+ ---
2
+ title: Thread Virtualization
3
+ description: Render very long threads with @tanstack/react-virtual and ThreadPrimitive.MessageByIndex.
4
+ platforms: ["react"]
5
+ ---
6
+
7
+ Virtualization mounts only the messages near the viewport and represents the rest as empty space, so a thread with thousands of messages scrolls like one with twenty. assistant-ui does not ship a virtualized thread component; this guide shows the supported composition, extracted from a production consumer and available as a runnable example: [`examples/with-virtualized-thread`](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-virtualized-thread).
8
+
9
+ ## Do you need this?
10
+
11
+ Probably not. The default kit already renders message bodies with `content-visibility: auto` and `contain-intrinsic-size`, which skips paint work for off-screen messages, and `ThreadPrimitive.Viewport` handles auto-scroll. That covers typical threads. Reach for virtualization when React mount and update cost itself becomes the bottleneck: threads with hundreds to thousands of messages, or very heavy per-message content, where typing latency degrades because every message stays mounted.
12
+
13
+ ## Rendering messages by index
14
+
15
+ `ThreadPrimitive.MessageByIndex` renders a single message at a given index and memoizes on the index plus per-field `components` identity. Pass a module-level components object so the memo holds and a streaming delta only re-renders the message it belongs to:
16
+
17
+ ```tsx
18
+ const MESSAGE_COMPONENTS = { UserMessage, AssistantMessage };
19
+
20
+ <ThreadPrimitive.MessageByIndex index={index} components={MESSAGE_COMPONENTS} />;
21
+ ```
22
+
23
+ ## Grouping into turns
24
+
25
+ Virtualizing per user turn (a user message plus the responses that follow it) gives the virtualizer stable, meaningfully sized items. Subscribe to a single signature string so the selector returns a primitive and the turn array only rebuilds when membership actually changes:
26
+
27
+ ```tsx
28
+ const signature = useAuiState((s) =>
29
+ s.thread.messages.map((m, i) => `${i}:${m.role}:${m.id}`).join("\n"),
30
+ );
31
+ const turns = useMemo(() => buildTurns(signature), [signature]);
32
+ ```
33
+
34
+ ## Padding spacers, not absolute positioning
35
+
36
+ Render the virtual items in normal document flow inside a spacer div whose `paddingTop`/`paddingBottom` represent the unmounted regions. Items remain regular flow children, so message CSS (including `position: sticky` patterns and the kit styling) keeps working, and `virtualizer.measureElement` records real heights as items mount:
37
+
38
+ ```tsx
39
+ const items = virtualizer.getVirtualItems();
40
+ const paddingTop = items[0]?.start ?? 0;
41
+ const paddingBottom = Math.max(
42
+ 0,
43
+ virtualizer.getTotalSize() - (items.at(-1)?.end ?? 0),
44
+ );
45
+ ```
46
+
47
+ ## Owning the scroll element
48
+
49
+ The composition owns its scroll container instead of using `ThreadPrimitive.Viewport`: the built-in auto-scroll assumes every message is mounted, and its resize-driven re-pin can fight the virtualizer's measurement adjustments. Three pieces replace it:
50
+
51
+ 1. **Auto-follow.** A ResizeObserver on the content wrapper re-pins the scroller to the bottom while a sticky flag is armed. The flag disarms when the user scrolls up (detected as `scrollTop` decreasing while `scrollHeight` and `clientHeight` are stable, the same heuristic the built-in viewport uses, plus wheel-up and touchmove) and re-arms when the user returns to the bottom.
52
+ 2. **Measurement guard.** While pinned at the bottom, the virtualizer's own scroll adjustments on item re-measurement are suppressed via a custom `scrollToFn`; without this the two scroll writers fight and the view rubber-bands during streaming.
53
+ 3. **Run-start jump.** A `useLayoutEffect` observes `s.thread.isRunning` flipping to true and jumps to the bottom before paint, so a just-sent message never flashes below the fold. The `thread.runStart` event is deprecated; deriving the transition from state is the supported path.
54
+
55
+ The production consumer this is extracted from disables streaming auto-follow entirely as a product choice; the example keeps following because it matches `ThreadPrimitive.Viewport`'s default behavior. Both are valid, and the disarm guard makes either safe.
56
+
57
+ ## Try it
58
+
59
+ ```sh
60
+ npx assistant-ui@latest create my-app
61
+ ```
62
+
63
+ Then copy `app/VirtualizedThread.tsx`, `app/MyRuntimeProvider.tsx`, and `app/seed-messages.ts` from [the example](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-virtualized-thread), or clone the repo and run `pnpm --filter with-virtualized-thread dev`.
@@ -62,7 +62,7 @@ useAuiEvent("thread.runStart", (payload) => {
62
62
 
63
63
  ### useNotification
64
64
 
65
- Ring the terminal bell and emit an OSC desktop notification when the assistant finishes a run, stops with an error, or pauses for human approval.
65
+ Ring the terminal bell and emit an OSC desktop notification when the assistant finishes a run, stops with an error, or pauses for user input.
66
66
 
67
67
  ```tsx
68
68
  import { useNotification } from "@assistant-ui/react-ink";
@@ -130,7 +130,7 @@ const runtime = useLocalRuntime(chatModel, {
130
130
  | `adapters.feedback` | `FeedbackAdapter` | Adapter for message feedback (thumbs up/down) |
131
131
  | `adapters.suggestion` | `SuggestionAdapter` | Adapter for suggested prompts |
132
132
  | `cloud` | `AssistantCloud` | Cloud instance for thread persistence via `@assistant-ui/cloud` |
133
- | `unstable_humanToolNames` | `string[]` | Tool names that trigger a run interruption to wait for human/external approval |
133
+ | `unstable_humanToolNames` | `string[]` | Tool names that pause the run until a result is added via `addResult` |
134
134
 
135
135
  ### useRemoteThreadListRuntime
136
136
 
@@ -80,7 +80,7 @@ const runtime = useLocalRuntime(chatModel, {
80
80
  | `maxSteps` | `number` | Maximum tool call steps per run |
81
81
  | `cloud` | `AssistantCloud` | Optional cloud instance for persistence |
82
82
  | `adapters` | `object` | Optional adapter overrides (see below) |
83
- | `unstable_humanToolNames` | `string[]` | Tool names that pause the run for human approval |
83
+ | `unstable_humanToolNames` | `string[]` | Tool names that pause the run until a result is added via `addResult` |
84
84
 
85
85
  The `adapters` option accepts the following fields (all optional):
86
86
 
@@ -66,9 +66,7 @@ messages are gone on the next page load.
66
66
  `showThinking: false`, so imported reasoning messages are dropped at conversion
67
67
  time, the same way a live run never stores them.
68
68
 
69
- `fromAgUiMessages` reconstructs the text, reasoning, and tool calls of each
70
- message. Non-text message content such as images and files is not restored, so a
71
- backend that persists multimodal messages loads only their text on reload.
69
+ `fromAgUiMessages` reconstructs the text, reasoning, and tool calls of each message. Multimodal user input (`image`, `audio`, `video`, and `document` parts, as well as legacy `binary` parts) is restored as attachments on the user message, so a backend that persists multimodal messages shows them again on reload and re-sends them on the next run. Legacy `binary` parts that only reference a file id are not restored.
72
70
 
73
71
  ## Thread list (experimental)
74
72
 
@@ -30,7 +30,7 @@ A non-exhaustive list of `unstable_` exports surfaced in the runtime docs.
30
30
  | API | Package | Notes |
31
31
  | --- | --- | --- |
32
32
  | `unstable_createMessageConverter` | `@assistant-ui/react` | Message-format converter used by AssistantTransport and DataStream. |
33
- | `unstable_humanToolNames` | `@assistant-ui/react` | Tool names that pause for human approval. Only available on LocalRuntime; not supported in DataStream. |
33
+ | `unstable_humanToolNames` | `@assistant-ui/react` | Tool names that pause the run until a result is added via `addResult`. Only available on LocalRuntime; not supported in DataStream. |
34
34
  | `unstable_threadListAdapter` | `@assistant-ui/react-langgraph` | LangGraph thread-list adapter slot on `useLangGraphRuntime`. |
35
35
  | `unstable_createLangGraphStream` | `@assistant-ui/react-langgraph` | End-to-end cancellation primitive. |
36
36
  | `unstable_Provider` | Various adapters | Thread-scoped provider on `RemoteThreadListAdapter`. Must render children synchronously. |
@@ -206,7 +206,7 @@ const runtime = useDataStreamRuntime({
206
206
  ## Tool integration
207
207
 
208
208
  <Callout type="warn">
209
- Human-in-the-loop tools (`unstable_humanToolNames`, `human()` interrupts) are not supported in the data stream runtime. Use [`LocalRuntime`](/docs/runtimes/custom/local-runtime) directly if you need approval flows.
209
+ Human-in-the-loop tools (`unstable_humanToolNames`, `human()` interrupts) are not supported in the data stream runtime. Use [`LocalRuntime`](/docs/runtimes/custom/local-runtime#human-in-the-loop-tools) directly if you need them.
210
210
  </Callout>
211
211
 
212
212
  ### Frontend tools
@@ -457,18 +457,122 @@ function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
457
457
 
458
458
  See the [tools guide](/docs/tools/defining-tools) for advanced patterns.
459
459
 
460
- ### Human-in-the-loop approval
460
+ ### Human-in-the-loop tools
461
461
 
462
- Require user confirmation before specific tools execute:
462
+ Tools listed in `unstable_humanToolNames` are not executed by code. The run pauses on the tool call and the user supplies the result through the tool UI:
463
463
 
464
464
  ```ts
465
465
  const runtime = useLocalRuntime(MyModelAdapter, {
466
- unstable_humanToolNames: ["delete_file", "send_email"],
466
+ unstable_humanToolNames: ["send_email"],
467
+ });
468
+ ```
469
+
470
+ The pause is driven by the message status your adapter returns; `LocalRuntime` never sets it for you. When the model requests a human tool call, end the run with `status: { type: "requires-action", reason: "tool-calls" }`. Without that status, the runtime marks the message complete and nothing waits. Only return it while a listed tool call is missing its result: unresolved tool calls that are not listed do not hold the run, so the runtime would invoke your adapter again immediately.
471
+
472
+ ```tsx
473
+ const MyModelAdapter: ChatModelAdapter = {
474
+ async run({ messages, abortSignal, unstable_getMessage }) {
475
+ const toolResults = unstable_getMessage().content.flatMap((part) =>
476
+ part.type === "tool-call" && part.result !== undefined
477
+ ? [{ toolCallId: part.toolCallId, result: part.result }]
478
+ : [],
479
+ );
480
+
481
+ const result = await fetch("<YOUR_API_ENDPOINT>", {
482
+ method: "POST",
483
+ headers: { "Content-Type": "application/json" },
484
+ body: JSON.stringify({ messages, toolResults }),
485
+ signal: abortSignal,
486
+ });
487
+ const data = await result.json();
488
+
489
+ if (data.toolCall) {
490
+ return {
491
+ content: [
492
+ {
493
+ type: "tool-call",
494
+ toolCallId: data.toolCall.id,
495
+ toolName: data.toolCall.name,
496
+ args: data.toolCall.args,
497
+ argsText: JSON.stringify(data.toolCall.args),
498
+ },
499
+ ],
500
+ status: { type: "requires-action", reason: "tool-calls" },
501
+ };
502
+ }
503
+
504
+ return { content: [{ type: "text", text: data.text }] };
505
+ },
506
+ };
507
+ ```
508
+
509
+ The full loop:
510
+
511
+ 1. **The run pauses.** While a listed tool call has no result, the runtime stops invoking your adapter. The unresolved tool call part reports `status.type === "requires-action"` to its renderer.
512
+ 2. **The user responds.** The tool UI completes the call with `addResult(...)`. The stock [`ToolFallback`](/docs/ui/tool-fallback) component handles this out of the box: in the requires-action state it shows Allow and Deny buttons that record the decision as the tool result.
513
+ 3. **The run resumes.** Once every listed tool call has a result, the runtime invokes your adapter again. The resumed call receives the same `messages` array as before (it ends at the user message; the in-progress assistant message is not part of it), so read the recorded results from `unstable_getMessage().content` as shown above. Content returned by the resumed call is appended to the same assistant message.
514
+
515
+ For a custom confirmation UI, register a human tool whose `render` completes the call with `addResult`. The shape of the result payload is yours to define; the adapter receives it verbatim and translates it for your backend:
516
+
517
+ ```tsx
518
+ const toolkit = defineToolkit({
519
+ send_email: {
520
+ type: "human",
521
+ description: "Send an email after the user confirms",
522
+ parameters: z.object({ to: z.string(), subject: z.string() }),
523
+ render: ({ args, result, addResult }) => {
524
+ if (result) {
525
+ return <p>{result.approved ? "Sent" : `Cancelled: ${result.reason}`}</p>;
526
+ }
527
+ return (
528
+ <div>
529
+ <p>
530
+ Send "{args.subject}" to {args.to}?
531
+ </p>
532
+ <button onClick={() => addResult({ approved: true })}>Allow</button>
533
+ <button
534
+ onClick={() =>
535
+ addResult({ approved: false, reason: "User declined" })
536
+ }
537
+ >
538
+ Deny
539
+ </button>
540
+ </div>
541
+ );
542
+ },
543
+ },
467
544
  });
468
545
  ```
469
546
 
470
547
  `unstable_humanToolNames` is unstable; see [stability](/docs/runtimes/concepts/stability).
471
548
 
549
+ ### Approval gates
550
+
551
+ The [server-side approval gate](/docs/tools/tool-ui#server-side-approval-gates) is also supported on `LocalRuntime`, for actions your backend executes after the user authorizes them. Where a human tool asks the user to supply the tool result, an approval gate asks the user to allow or block an action the adapter performs. Emit `approval: { id }` on the tool call part and end the run with the same `requires-action` status:
552
+
553
+ ```tsx
554
+ return {
555
+ content: [
556
+ {
557
+ type: "tool-call",
558
+ toolCallId: data.toolCall.id,
559
+ toolName: data.toolCall.name,
560
+ args: data.toolCall.args,
561
+ argsText: JSON.stringify(data.toolCall.args),
562
+ approval: { id: data.toolCall.id },
563
+ },
564
+ ],
565
+ status: { type: "requires-action", reason: "tool-calls" },
566
+ };
567
+ ```
568
+
569
+ A tool call with a pending approval pauses the run, whether or not the tool is listed in `unstable_humanToolNames`. Always emit gates in the pending state (`approval: { id }` with no `approved` field); a part that arrives already decided is treated as resolved and the runtime invokes the adapter again immediately. The stock [`ToolFallback`](/docs/ui/tool-fallback) Allow and Deny buttons, or a custom renderer calling `respondToApproval({ approved, reason? })`, record the decision:
570
+
571
+ - **Deny** sets `approval.approved: false` and synthesizes an error result (`{ error: reason || "Tool approval denied" }` with `isError: true`), so the model sees the denial.
572
+ - **Allow** sets `approval.approved: true` and leaves the result empty; performing the action is your adapter's job.
573
+
574
+ Once every pending approval on the message is decided and every listed human tool has a result, the runtime invokes your adapter again. Read the decisions from `unstable_getMessage().content`, perform the approved actions, and return the follow-up response. A tool call that carries an approval is owned by the gate: it does not additionally require a result, even when its name is listed in `unstable_humanToolNames`.
575
+
472
576
  ## Resuming a run
473
577
 
474
578
  `resumeRun` reconnects to an in-progress assistant run. Useful for page refresh, network reconnect, tab backgrounding, or thread switching when the backend is still generating.
@@ -743,7 +847,7 @@ const CustomAPIAdapter: ChatModelAdapter = {
743
847
  name: "unstable_humanToolNames",
744
848
  type: "string[]",
745
849
  description:
746
- "Tool names that require human approval before execution (unstable).",
850
+ "Tool names that pause the run until the user supplies a result via addResult (unstable).",
747
851
  },
748
852
  ]}
749
853
  />
@@ -28,13 +28,13 @@ assistant-ui ships React adapter packages for these. Pick the matching card and
28
28
  icon={<VercelIcon width={20} height={20} />}
29
29
  title="Vercel AI SDK"
30
30
  description="useChat hook, streaming, tools, attachments, multi-step. v6 current; v5 / v4 legacy."
31
- href="/docs/runtimes/ai-sdk"
31
+ href="/docs/runtimes/ai-sdk/overview"
32
32
  />
33
33
  <Card
34
34
  icon={<LangGraphIcon width={20} height={20} className="text-[#1C3C3C] dark:text-[#5b9595]" />}
35
35
  title="LangGraph"
36
36
  description="Direct integration with @langchain/langgraph-sdk. Subgraph events, UI messages, message metadata."
37
- href="/docs/runtimes/langgraph"
37
+ href="/docs/runtimes/langgraph/overview"
38
38
  />
39
39
  <Card
40
40
  icon={<LangChainIcon width={20} height={20} className="text-[#7FC8FF]" />}
@@ -46,7 +46,7 @@ assistant-ui ships React adapter packages for these. Pick the matching card and
46
46
  icon={<AdkIcon width={20} height={20} />}
47
47
  title="Google ADK"
48
48
  description="ADK JS or Python agents. Tool confirmations, auth flows, multi-agent, code execution."
49
- href="/docs/runtimes/google-adk"
49
+ href="/docs/runtimes/google-adk/overview"
50
50
  />
51
51
  <Card
52
52
  icon={<A2AIcon width={20} height={20} />}
@@ -58,14 +58,14 @@ assistant-ui ships React adapter packages for these. Pick the matching card and
58
58
  icon={<AguiIcon width={20} height={20} />}
59
59
  title="AG-UI Protocol"
60
60
  description="AG-UI agents (CopilotKit, custom servers). Streaming text, thinking, tool calls, state snapshots."
61
- href="/docs/runtimes/ag-ui"
61
+ href="/docs/runtimes/ag-ui/overview"
62
62
  />
63
63
  <PlatformOnly platforms={["react"]}>
64
64
  <Card
65
65
  icon={<OpenCodeIcon width={20} height={20} />}
66
66
  title="OpenCode"
67
67
  description="OpenCode coding-agent server. Permission flows, questions, fork / revert. Experimental."
68
- href="/docs/runtimes/opencode"
68
+ href="/docs/runtimes/opencode/overview"
69
69
  />
70
70
  </PlatformOnly>
71
71
  </Cards>
@@ -104,7 +104,7 @@ If you do not know your framework yet, or your backend is custom, pick by what y
104
104
  | Backend that already speaks the data stream protocol | [DataStream](/docs/runtimes/custom/data-stream) |
105
105
  | Stream full agent state snapshots (not just messages) | [AssistantTransport](/docs/runtimes/custom/assistant-transport) |
106
106
 
107
- If none of the framework adapters fits, start at [custom backend](/docs/runtimes/custom).
107
+ If none of the framework adapters fits, start at [custom backend](/docs/runtimes/custom/overview).
108
108
 
109
109
  ## Shared concepts
110
110