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

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 (127) 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 +73 -57
  7. package/.docs/organized/code-examples/with-browser-extension.md +7 -7
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +44 -93
  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-eve.md +343 -0
  15. package/.docs/organized/code-examples/with-expo.md +943 -940
  16. package/.docs/organized/code-examples/with-external-store.md +5 -5
  17. package/.docs/organized/code-examples/with-ffmpeg.md +8 -11
  18. package/.docs/organized/code-examples/with-generative-ui.md +33 -309
  19. package/.docs/organized/code-examples/with-google-adk.md +5 -5
  20. package/.docs/organized/code-examples/with-heat-graph.md +4 -4
  21. package/.docs/organized/code-examples/with-image-generation.md +7 -7
  22. package/.docs/organized/code-examples/with-interactables.md +169 -341
  23. package/.docs/organized/code-examples/with-langchain.md +7 -7
  24. package/.docs/organized/code-examples/with-langgraph.md +23 -160
  25. package/.docs/organized/code-examples/with-livekit.md +10 -10
  26. package/.docs/organized/code-examples/with-mcp.md +7 -7
  27. package/.docs/organized/code-examples/with-opencode.md +106 -580
  28. package/.docs/organized/code-examples/with-pi.md +2046 -0
  29. package/.docs/organized/code-examples/with-react-hook-form.md +16 -9
  30. package/.docs/organized/code-examples/with-react-ink-web.md +691 -0
  31. package/.docs/organized/code-examples/with-react-ink.md +29 -17
  32. package/.docs/organized/code-examples/with-react-router.md +12 -12
  33. package/.docs/organized/code-examples/with-resumable-stream.md +8 -8
  34. package/.docs/organized/code-examples/with-store.md +14 -10
  35. package/.docs/organized/code-examples/with-tanstack.md +21 -7
  36. package/.docs/organized/code-examples/with-tap-runtime.md +5 -5
  37. package/.docs/organized/code-examples/with-virtualized-thread.md +676 -0
  38. package/.docs/raw/docs/(docs)/architecture.mdx +13 -12
  39. package/.docs/raw/docs/(docs)/cli.mdx +4 -2
  40. package/.docs/raw/docs/(docs)/devtools.mdx +25 -2
  41. package/.docs/raw/docs/(docs)/index.mdx +5 -2
  42. package/.docs/raw/docs/(docs)/installation.mdx +5 -2
  43. package/.docs/raw/docs/(reference)/api-reference/external-store/message-conversion.mdx +16 -16
  44. package/.docs/raw/docs/(reference)/api-reference/hooks/composer-triggers.mdx +73 -42
  45. package/.docs/raw/docs/(reference)/api-reference/hooks/model-context.mdx +3 -20
  46. package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +234 -123
  47. package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +51 -0
  48. package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +4 -1
  49. package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +70 -38
  50. package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -1
  51. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar.mdx +30 -0
  52. package/.docs/raw/docs/(reference)/api-reference/primitives/attachment.mdx +7 -0
  53. package/.docs/raw/docs/(reference)/api-reference/primitives/branch-picker.mdx +26 -0
  54. package/.docs/raw/docs/(reference)/api-reference/primitives/chain-of-thought.mdx +15 -0
  55. package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +103 -0
  56. package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +16 -0
  57. package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +56 -0
  58. package/.docs/raw/docs/(reference)/api-reference/primitives/queue-item.mdx +12 -0
  59. package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +12 -0
  60. package/.docs/raw/docs/(reference)/api-reference/primitives/suggestion.mdx +14 -0
  61. package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +57 -1
  62. package/.docs/raw/docs/(reference)/api-reference/tools/index.mdx +6 -0
  63. package/.docs/raw/docs/(reference)/api-reference/tools/interactables-legacy.mdx +55 -0
  64. package/.docs/raw/docs/(reference)/api-reference/tools/interactables.mdx +151 -0
  65. package/.docs/raw/docs/(reference)/api-reference/tools/rendering.mdx +17 -38
  66. package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +22 -0
  67. package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +20 -27
  68. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +40 -25
  69. package/.docs/raw/docs/guides/chain-of-thought.mdx +1 -1
  70. package/.docs/raw/docs/guides/headless-composer-input.mdx +113 -0
  71. package/.docs/raw/docs/guides/index.mdx +3 -0
  72. package/.docs/raw/docs/guides/input-history.mdx +55 -0
  73. package/.docs/raw/docs/guides/latex.mdx +28 -22
  74. package/.docs/raw/docs/guides/mentions.mdx +32 -7
  75. package/.docs/raw/docs/guides/slash-commands.mdx +3 -6
  76. package/.docs/raw/docs/guides/speech.mdx +5 -7
  77. package/.docs/raw/docs/guides/virtualization.mdx +133 -0
  78. package/.docs/raw/docs/guides/voice.mdx +3 -2
  79. package/.docs/raw/docs/ink/hooks.mdx +2 -2
  80. package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +4 -12
  81. package/.docs/raw/docs/integrations/auth/better-auth.mdx +7 -10
  82. package/.docs/raw/docs/integrations/auth/clerk.mdx +3 -9
  83. package/.docs/raw/docs/integrations/auth/next-auth.mdx +3 -8
  84. package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +8 -5
  85. package/.docs/raw/docs/integrations/index.mdx +5 -12
  86. package/.docs/raw/docs/integrations/observability/helicone.mdx +3 -4
  87. package/.docs/raw/docs/integrations/observability/langfuse.mdx +3 -5
  88. package/.docs/raw/docs/integrations/observability/langsmith.mdx +3 -2
  89. package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +4 -3
  90. package/.docs/raw/docs/primitives/composer.mdx +8 -0
  91. package/.docs/raw/docs/primitives/thread.mdx +24 -0
  92. package/.docs/raw/docs/react-native/hooks.mdx +1 -1
  93. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +22 -3
  94. package/.docs/raw/docs/runtimes/concepts/stability.mdx +1 -1
  95. package/.docs/raw/docs/runtimes/custom/data-stream.mdx +1 -1
  96. package/.docs/raw/docs/runtimes/custom/external-store.mdx +54 -0
  97. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +108 -4
  98. package/.docs/raw/docs/runtimes/eve/overview.mdx +100 -0
  99. package/.docs/raw/docs/runtimes/eve/quickstart.mdx +143 -0
  100. package/.docs/raw/docs/runtimes/langchain.mdx +386 -18
  101. package/.docs/raw/docs/runtimes/langgraph/quickstart.mdx +1 -1
  102. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +1 -1
  103. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +12 -6
  104. package/.docs/raw/docs/tools/defining-tools.mdx +4 -0
  105. package/.docs/raw/docs/tools/interactables-legacy.mdx +410 -0
  106. package/.docs/raw/docs/tools/interactables.mdx +892 -223
  107. package/.docs/raw/docs/tools/mcp.mdx +4 -4
  108. package/.docs/raw/docs/tools/multi-agent.mdx +5 -0
  109. package/.docs/raw/docs/tools/tool-ui.mdx +37 -1
  110. package/.docs/raw/docs/ui/composer-trigger-popover.mdx +2 -0
  111. package/.docs/raw/docs/ui/dot-matrix.mdx +133 -0
  112. package/.docs/raw/docs/ui/file.mdx +1 -1
  113. package/.docs/raw/docs/ui/model-selector.mdx +219 -52
  114. package/.docs/raw/docs/ui/number-roll.mdx +154 -0
  115. package/.docs/raw/docs/ui/part-grouping.mdx +38 -0
  116. package/.docs/raw/docs/ui/reasoning.mdx +3 -3
  117. package/.docs/raw/docs/ui/streamdown.mdx +2 -0
  118. package/.docs/raw/docs/ui/syntax-highlighting.mdx +1 -1
  119. package/.docs/raw/docs/ui/thread.mdx +52 -0
  120. package/.docs/raw/docs/ui/tool-fallback.mdx +2 -2
  121. package/.docs/raw/docs/utilities/react-o11y.mdx +92 -6
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +18 -2
  124. package/dist/index.js.map +1 -1
  125. package/package.json +4 -4
  126. package/src/index.ts +14 -6
  127. package/src/tools/tests/mcp-protocol.test.ts +9 -0
@@ -15,18 +15,11 @@ Integrations are wiring guides for using third-party services with assistant-ui,
15
15
 
16
16
  ## Where integrations slot in
17
17
 
18
- ```
19
- client ──► your API route ──► LLM provider
20
- │ ▲
21
- │ │
22
- agent │ │ observability
23
- frameworks │ │ proxies
24
- (e.g. │ │ (e.g. Helicone,
25
- Mastra) │ │ Langfuse)
26
- ▼ │
27
- run on the server, │
28
- then forward calls ──────┘
29
- to the provider
18
+ ```mermaid
19
+ flowchart LR
20
+ client["client"] --> route["your API route"] --> provider["LLM provider"]
21
+ route -->|"agent frameworks (e.g. Mastra)"| server["run on the server,<br/>then forward calls"]
22
+ server -->|"observability proxies (e.g. Helicone, Langfuse)"| provider
30
23
  ```
31
24
 
32
25
  Integrations live on the server. **Agent frameworks** like Mastra take over the API route. **Gateways** swap the upstream provider URL. **Observability** logs or traces every call. **Auth** gates the route and scopes per-user data. **Persistence** and **attachments** are adapter recipes for storing chat data outside the default in-memory path.
@@ -11,10 +11,9 @@ Helicone is independent of which assistant-ui runtime you use. It slots in at th
11
11
 
12
12
  ## How it works
13
13
 
14
- ```
15
- your server ──► Helicone proxy ──► OpenAI / Anthropic / etc.
16
- │
17
- └─ logs request, response, tokens, cost
14
+ ```mermaid
15
+ flowchart LR
16
+ server["your server"] --> proxy["Helicone proxy<br/>(logs request, response, tokens, cost)"] --> provider["OpenAI / Anthropic / etc."]
18
17
  ```
19
18
 
20
19
  Calls pass through Helicone's edge before reaching the upstream provider. The proxy is transparent: response shape and streaming behavior are unchanged, you just gain a dashboard of every call.
@@ -13,11 +13,9 @@ Pick Langfuse when you want to see the agent's full call tree inside a single tu
13
13
 
14
14
  ## How it works
15
15
 
16
- ```
17
- your route ──► AI SDK streamText (with experimental_telemetry)
18
- │
19
- ▼
20
- OpenTelemetry SDK ──► LangfuseSpanProcessor ──► Langfuse
16
+ ```mermaid
17
+ flowchart LR
18
+ route["your route"] --> stream["AI SDK streamText<br/>(experimental_telemetry)"] --> otel["OpenTelemetry SDK"] --> proc["LangfuseSpanProcessor"] --> langfuse["Langfuse"]
21
19
  ```
22
20
 
23
21
  Langfuse subscribes to OpenTelemetry spans the AI SDK already emits when telemetry is enabled. No proxy, no wrapping; the SDK ships spans and Langfuse renders them.
@@ -15,8 +15,9 @@ This page covers the **AI SDK** path. If you use [`@assistant-ui/react-langgraph
15
15
 
16
16
  LangSmith provides a wrapper around the `ai` namespace. You call `wrapAISDK(ai)`, get back the same exports (`generateText`, `streamText`, `generateObject`, `streamObject`), and use those in place of the originals. Every call is then traced.
17
17
 
18
- ```
19
- your route ──► wrapped streamText ──► LangSmith client ──► LangSmith
18
+ ```mermaid
19
+ flowchart LR
20
+ route["your route"] --> stream["wrapped streamText"] --> client["LangSmith client"] --> langsmith["LangSmith"]
20
21
  ```
21
22
 
22
23
  ## Setup
@@ -18,9 +18,10 @@ If you're rolling your own auth, replace `auth()` calls with whatever your stack
18
18
 
19
19
  ## How it works
20
20
 
21
- ```
22
- client ──► /api/threads/* (RemoteThreadListAdapter) ──► threads table
23
- /api/messages/* (ThreadHistoryAdapter) ──► messages table
21
+ ```mermaid
22
+ flowchart LR
23
+ client["client"] --> threads["/api/threads/*<br/>(RemoteThreadListAdapter)"] --> tt["threads table"]
24
+ client --> messages["/api/messages/*<br/>(ThreadHistoryAdapter)"] --> mt["messages table"]
24
25
  ```
25
26
 
26
27
  Two adapters, two tables:
@@ -103,6 +103,14 @@ import { ComposerPrimitive } from "@assistant-ui/react";
103
103
 
104
104
  The primitive's behavior (keyboard handling, disabled state, form submission) is merged onto your element. Your styles, your component, primitive wiring.
105
105
 
106
+ <Callout type="info">
107
+ Own the input DOM entirely, such as a `contentEditable` surface or editor
108
+ library that cannot be expressed through `asChild` or `render`? See
109
+ [Headless Composer Input](/docs/guides/headless-composer-input) for the
110
+ unstable hook that supplies composer text and send gating without
111
+ `ComposerPrimitive.Input`.
112
+ </Callout>
113
+
106
114
  ### Unstable Trigger Popovers
107
115
 
108
116
  Composer includes an unstable **trigger popover** system for character-triggered popovers (e.g. `@` for mentions, `/` for slash commands). Multiple triggers coexist under a single `TriggerPopoverRoot`.
@@ -319,6 +319,30 @@ Renders a single message at a specific index in the thread.
319
319
 
320
320
  <PrimitivesTypeTable type="ThreadPrimitiveMessageByIndexProps" parameters={ThreadPrimitiveDocs.MessageByIndex.props} />
321
321
 
322
+ ### Unstable_MessageById
323
+
324
+ Renders a single message by id with the same `components` surface as
325
+ `MessageByIndex`. Pair it with `unstable_useThreadMessageIds` for virtualized or
326
+ custom message lists that should stay attached to messages across reordering and
327
+ windowing. Unknown ids render `null`.
328
+
329
+ ```tsx
330
+ const messageIds = unstable_useThreadMessageIds();
331
+
332
+ messageIds.map((messageId) => (
333
+ <ThreadPrimitive.Unstable_MessageById
334
+ key={messageId}
335
+ messageId={messageId}
336
+ components={{ Message: MyMessage }}
337
+ />
338
+ ));
339
+ ```
340
+
341
+ <Callout type="warn">
342
+ `unstable_useThreadMessageIds` and `ThreadPrimitive.Unstable_MessageById` are
343
+ experimental and may change in any release.
344
+ </Callout>
345
+
322
346
  ### ScrollToBottom
323
347
 
324
348
  Scrolls the viewport to the bottom. Automatically disabled when already at the bottom. Renders a `<button>` element unless `asChild` is set.
@@ -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,9 @@ 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.
70
+
71
+ An assistant message whose tool call has no matching tool result is reconstructed with `requires-action` status, the same status the runtime derives for a pending tool call, so a reloaded human-in-the-loop call (for example an `ask_user` tool) is actionable rather than stuck. This matches how every other external-store runtime surfaces a pending tool call on reload. The AG-UI wire snapshot carries no run outcome, so a tool call that a successful run intentionally left without a result is also surfaced as actionable.
72
72
 
73
73
  ## Thread list (experimental)
74
74
 
@@ -133,6 +133,25 @@ await runtime.unstable_submitInterruptResponses(
133
133
  );
134
134
  ```
135
135
 
136
+ ### Steering away from an interrupt
137
+
138
+ When the user ignores the interrupt UI and just sends a new message, use the `useAgUiSteerAway` hook. Every open interrupt defaults to `status: "cancelled"`, the new message is appended, and the run resumes with `resume: ResumeEntry[]` on the wire. With no pending interrupts it behaves like a normal append.
139
+
140
+ ```tsx
141
+ const steerAway = useAgUiSteerAway();
142
+
143
+ // the user typed a new message instead of answering the interrupt
144
+ await steerAway("actually, let's do something else");
145
+ ```
146
+
147
+ The message accepts a plain string or a partial `AppendMessage` (the parent defaults to the current head, which is the interrupted assistant message). Pass `responses` to override the status of specific interrupts; the rest still default to cancelled.
148
+
149
+ ```tsx
150
+ await steerAway("continue without the file", [
151
+ { interruptId: "tool-1", status: "resolved", payload: { approved: true } },
152
+ ]);
153
+ ```
154
+
136
155
  ## Supported events
137
156
 
138
157
  The runtime parses the AG-UI event stream and maps each event type to assistant-ui state.
@@ -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
@@ -299,6 +299,54 @@ runtime.thread.import(repo);
299
299
 
300
300
  Each message must have an explicit `id` and `parentId`; messages with the same `parentId` create branches. Parents must appear before children in the array.
301
301
 
302
+ ### Exporting a snapshot
303
+
304
+ `thread.import()` has a counterpart, `thread.export()`, which captures the current thread (including its full branch tree) as a serializable `ExportedMessageRepository`. Use it to persist a conversation and re-import it later:
305
+
306
+ ```tsx
307
+ // capture the current thread as a serializable snapshot
308
+ const repo = runtime.thread.export();
309
+ await saveToBackend(JSON.stringify(repo));
310
+
311
+ // later, restore it into a runtime
312
+ runtime.thread.import(repo);
313
+ ```
314
+
315
+ The exported shape round-trips through `thread.import()` directly, so the same value is both your persistence format and what you load back.
316
+
317
+ ### Persisting branch selection
318
+
319
+ If you store the full branch tree outside assistant-ui, persist the selected branch head too and pass it back as `messageRepository.headId`. `setMessages` still performs the branch switch; `unstable_onBranchChange` is an additional signal that fires after an explicit `switchToBranch` action, such as a BranchPicker click.
320
+
321
+ ```tsx
322
+ const runtime = useExternalStoreRuntime({
323
+ messageRepository: {
324
+ messages: storedMessages,
325
+ headId: selectedHeadId,
326
+ },
327
+ setMessages: (messages) => {
328
+ setVisibleMessages(messages);
329
+ },
330
+ unstable_onBranchChange: ({ headId, visibleMessageIds }) => {
331
+ saveSelectedBranch({
332
+ headId,
333
+ visibleMessageIds,
334
+ });
335
+ },
336
+ onNew,
337
+ });
338
+ ```
339
+
340
+ `headId` is the canonical persisted head of the visible branch. Optimistic or transient message ids are not surfaced there. `visibleMessageIds` is the currently visible path in order, which can include an optimistic leaf while `headId` points to its persisted ancestor.
341
+
342
+ The callback only fires for explicit branch switches, and consecutive switches that resolve to the same canonical head are de-duped. It does not fire on adapter resync, `messageRepository` reset, append, edit/regenerate, content-only updates, or while the thread is running.
343
+
344
+ <Callout type="warn">
345
+ `unstable_onBranchChange` is under active development and may change without
346
+ notice. It complements `setMessages`; it does not enable branch switching by
347
+ itself.
348
+ </Callout>
349
+
302
350
  ## Tool calling
303
351
 
304
352
  Handle tool results by updating the matching tool-call entry:
@@ -717,6 +765,12 @@ useExternalStoreRuntime({
717
765
  type: "(messages: readonly T[]) => void",
718
766
  description: "Update messages (required for branch switching).",
719
767
  },
768
+ {
769
+ name: "unstable_onBranchChange",
770
+ type: "(event: ExternalStoreBranchChange) => void",
771
+ description:
772
+ "Called after an explicit branch switch with the canonical persisted head id and visible message path. Complements setMessages and is unstable.",
773
+ },
720
774
  {
721
775
  name: "onEdit",
722
776
  type: "(message: AppendMessage) => Promise<void>",
@@ -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
  />
@@ -0,0 +1,100 @@
1
+ ---
2
+ title: Eve Runtime
3
+ description: Connect an Eve agent to assistant-ui with useEveAgentRuntime, eve/next, durable sessions, streaming messages, and human-in-the-loop approvals.
4
+ ---
5
+
6
+ import { VercelIcon } from "@/components/icons/vercel";
7
+
8
+ `@assistant-ui/eve` integrates assistant-ui with [Eve](https://eve.dev/), Vercel's filesystem-first framework for durable agents. It wraps Eve's `useEveAgent` hook and exposes it as an assistant-ui `ExternalStoreRuntime`, so Eve owns the session stream while assistant-ui renders messages, reasoning, dynamic tool calls, and approval requests.
9
+
10
+ ## When to use it
11
+
12
+ Pick the Eve runtime when:
13
+
14
+ - You want your agent implementation to live under `agent/` and be served by `eve/next`.
15
+ - You want Eve sessions, continuation tokens, NDJSON streaming, and local development tooling.
16
+ - You want assistant-ui to render Eve messages and human-in-the-loop tool approvals without writing a custom runtime adapter.
17
+
18
+ ## Architecture
19
+
20
+ The Next.js app mounts Eve with `withEve()` and assistant-ui's registry transform with `withAui()`:
21
+
22
+ ```ts title="next.config.ts"
23
+ import { withAui } from "@assistant-ui/next";
24
+ import type { NextConfig } from "next";
25
+ import { withEve } from "eve/next";
26
+
27
+ const nextConfig: NextConfig = {};
28
+
29
+ export default withEve(withAui(nextConfig));
30
+ ```
31
+
32
+ On the client, `useEveAgentRuntime()` calls Eve's React hook and converts Eve message parts into assistant-ui thread messages:
33
+
34
+ ```tsx title="app/page.tsx"
35
+ "use client";
36
+
37
+ import { Thread } from "@/components/assistant-ui/thread";
38
+ import { useEveAgentRuntime } from "@assistant-ui/eve";
39
+ import { AssistantRuntimeProvider } from "@assistant-ui/react";
40
+
41
+ export default function Home() {
42
+ const runtime = useEveAgentRuntime();
43
+
44
+ return (
45
+ <AssistantRuntimeProvider runtime={runtime}>
46
+ <Thread />
47
+ </AssistantRuntimeProvider>
48
+ );
49
+ }
50
+ ```
51
+
52
+ ## Requirements
53
+
54
+ - Node.js 24 or higher.
55
+ - React 18 or 19.
56
+ - An Eve app mounted with `eve/next`.
57
+ - A model credential for the model configured in `agent/agent.ts`.
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`.
60
+
61
+ ## Install
62
+
63
+ <PlatformTabs>
64
+ <Tab value="React">
65
+
66
+ <InstallCommand npm={["@assistant-ui/react", "@assistant-ui/eve", "eve"]} />
67
+
68
+ </Tab>
69
+ <Tab value="React Native">
70
+
71
+ Eve's browser hook talks to the Eve HTTP channel. There is not a React Native Eve runtime package yet.
72
+
73
+ </Tab>
74
+ <Tab value="React Ink">
75
+
76
+ Use Eve's terminal UI directly for command-line agent sessions. There is not an Ink Eve runtime package yet.
77
+
78
+ </Tab>
79
+ </PlatformTabs>
80
+
81
+ ## Auth note
82
+
83
+ Eve's built-in `eve` channel accepts localhost during development and trusted Vercel OIDC callers. It does not automatically admit browser users in production. Before deploying a public app, add `agent/channels/eve.ts` and wire the channel to your application auth.
84
+
85
+ ## Next
86
+
87
+ <Cards>
88
+ <Card
89
+ icon={<VercelIcon width={20} height={20} />}
90
+ title="Quickstart"
91
+ description="Scaffold the Eve template or add Eve to an existing assistant-ui app."
92
+ href="/docs/runtimes/eve/quickstart"
93
+ />
94
+ <Card
95
+ icon={<VercelIcon width={20} height={20} />}
96
+ title="Eve channel"
97
+ description="Routes, auth, session creation, and stream events in the default Eve HTTP channel."
98
+ href="https://eve.dev/docs/channels/eve"
99
+ />
100
+ </Cards>
@@ -0,0 +1,143 @@
1
+ ---
2
+ title: Quickstart
3
+ description: From-template and manual setup paths to a working Eve agent chat in assistant-ui.
4
+ ---
5
+
6
+ Two paths to a running Eve-powered assistant-ui app. The template is fastest; the manual path is what you adapt when integrating into an existing Next.js project.
7
+
8
+ ## From the template
9
+
10
+ <PlatformTabs>
11
+ <Tab value="React">
12
+
13
+ ```sh
14
+ npx create-assistant-ui@latest -t eve my-app
15
+ cd my-app
16
+ ```
17
+
18
+ Set a model credential:
19
+
20
+ ```sh title=".env.local"
21
+ AI_GATEWAY_API_KEY=your-api-key
22
+ ```
23
+
24
+ ```sh
25
+ npm run dev
26
+ ```
27
+
28
+ Open http://localhost:3000 and send a message. The template includes:
29
+
30
+ - `agent/agent.ts` for Eve runtime config.
31
+ - `agent/instructions.md` for the always-on system prompt.
32
+ - `next.config.ts` with `withEve(withAui(nextConfig))`.
33
+ - `app/page.tsx` with `useEveAgentRuntime()`.
34
+
35
+ </Tab>
36
+ <Tab value="React Native">
37
+
38
+ There is not a React Native Eve template yet.
39
+
40
+ </Tab>
41
+ <Tab value="React Ink">
42
+
43
+ Use Eve's terminal UI directly for command-line agent sessions.
44
+
45
+ </Tab>
46
+ </PlatformTabs>
47
+
48
+ ## Manual setup in an existing app
49
+
50
+ <Steps>
51
+ <Step>
52
+
53
+ ### Install dependencies
54
+
55
+ <InstallCommand npm={["@assistant-ui/react", "@assistant-ui/eve", "eve"]} />
56
+
57
+ </Step>
58
+ <Step>
59
+
60
+ ### Mount Eve in Next.js
61
+
62
+ ```ts title="next.config.ts"
63
+ import { withAui } from "@assistant-ui/next";
64
+ import type { NextConfig } from "next";
65
+ import { withEve } from "eve/next";
66
+
67
+ const nextConfig: NextConfig = {};
68
+
69
+ export default withEve(withAui(nextConfig));
70
+ ```
71
+
72
+ </Step>
73
+ <Step>
74
+
75
+ ### Add an Eve agent
76
+
77
+ ```ts title="agent/agent.ts"
78
+ import { defineAgent } from "eve";
79
+
80
+ export default defineAgent({
81
+ model: "anthropic/claude-sonnet-4.6",
82
+ });
83
+ ```
84
+
85
+ ```md title="agent/instructions.md"
86
+ You are a concise assistant. Use tools when they are available.
87
+ ```
88
+
89
+ </Step>
90
+ <Step>
91
+
92
+ ### Create the runtime
93
+
94
+ ```tsx title="app/page.tsx"
95
+ "use client";
96
+
97
+ import { Thread } from "@/components/assistant-ui/thread";
98
+ import { useEveAgentRuntime } from "@assistant-ui/eve";
99
+ import { AssistantRuntimeProvider } from "@assistant-ui/react";
100
+
101
+ export default function Home() {
102
+ const runtime = useEveAgentRuntime();
103
+
104
+ return (
105
+ <AssistantRuntimeProvider runtime={runtime}>
106
+ <Thread />
107
+ </AssistantRuntimeProvider>
108
+ );
109
+ }
110
+ ```
111
+
112
+ </Step>
113
+ </Steps>
114
+
115
+ ## Production auth
116
+
117
+ The default Eve channel is convenient for local development. For production browser users, define `agent/channels/eve.ts` and replace the default auth policy with your app's auth.
118
+
119
+ ```ts title="agent/channels/eve.ts"
120
+ import { localDev, vercelOidc } from "eve/channels/auth";
121
+ import { eveChannel } from "eve/channels/eve";
122
+
123
+ export default eveChannel({
124
+ auth: [localDev(), vercelOidc()],
125
+ });
126
+ ```
127
+
128
+ That example keeps the development defaults. Swap in your Clerk, Auth.js, OIDC, or JWT verification before going live.
129
+
130
+ ## Next
131
+
132
+ <Cards>
133
+ <Card
134
+ title="Eve overview"
135
+ description="Architecture, requirements, and runtime behavior."
136
+ href="/docs/runtimes/eve/overview"
137
+ />
138
+ <Card
139
+ title="API reference"
140
+ description="useEveAgentRuntime and message conversion helpers."
141
+ href="/docs/api-reference/integrations/eve"
142
+ />
143
+ </Cards>