@assistant-ui/mcp-docs-server 0.1.24 → 0.1.26

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 (151) hide show
  1. package/.docs/organized/code-examples/waterfall.md +8 -6
  2. package/.docs/organized/code-examples/with-a2a.md +676 -0
  3. package/.docs/organized/code-examples/with-ag-ui.md +10 -11
  4. package/.docs/organized/code-examples/with-ai-sdk-v6.md +31 -19
  5. package/.docs/organized/code-examples/with-artifacts.md +8 -8
  6. package/.docs/organized/code-examples/with-assistant-transport.md +6 -6
  7. package/.docs/organized/code-examples/with-chain-of-thought.md +37 -29
  8. package/.docs/organized/code-examples/with-cloud-standalone.md +14 -11
  9. package/.docs/organized/code-examples/with-cloud.md +8 -8
  10. package/.docs/organized/code-examples/with-custom-thread-list.md +10 -10
  11. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +11 -11
  12. package/.docs/organized/code-examples/with-expo.md +571 -520
  13. package/.docs/organized/code-examples/with-external-store.md +6 -6
  14. package/.docs/organized/code-examples/with-ffmpeg.md +8 -8
  15. package/.docs/organized/code-examples/with-google-adk.md +353 -0
  16. package/.docs/organized/code-examples/with-heat-graph.md +304 -0
  17. package/.docs/organized/code-examples/with-interactables.md +778 -0
  18. package/.docs/organized/code-examples/with-langgraph.md +28 -26
  19. package/.docs/organized/code-examples/with-parent-id-grouping.md +7 -7
  20. package/.docs/organized/code-examples/with-react-hook-form.md +9 -9
  21. package/.docs/organized/code-examples/with-react-ink.md +265 -0
  22. package/.docs/organized/code-examples/with-react-router.md +12 -12
  23. package/.docs/organized/code-examples/with-store.md +33 -22
  24. package/.docs/organized/code-examples/with-tanstack.md +10 -10
  25. package/.docs/organized/code-examples/with-tap-runtime.md +12 -10
  26. package/.docs/raw/blog/2025-01-31-changelog/index.mdx +1 -1
  27. package/.docs/raw/blog/2026-03-launch-week/index.mdx +258 -0
  28. package/.docs/raw/docs/(docs)/architecture.mdx +1 -1
  29. package/.docs/raw/docs/(docs)/cli.mdx +74 -9
  30. package/.docs/raw/docs/(docs)/copilots/make-assistant-tool-ui.mdx +8 -3
  31. package/.docs/raw/docs/(docs)/copilots/make-assistant-tool.mdx +5 -1
  32. package/.docs/raw/docs/(docs)/copilots/{make-assistant-readable.mdx → make-assistant-visible.mdx} +14 -5
  33. package/.docs/raw/docs/(docs)/copilots/model-context.mdx +11 -11
  34. package/.docs/raw/docs/(docs)/copilots/motivation.mdx +2 -2
  35. package/.docs/raw/docs/(docs)/devtools.mdx +3 -2
  36. package/.docs/raw/docs/(docs)/guides/attachments.mdx +74 -15
  37. package/.docs/raw/docs/(docs)/guides/branching.mdx +11 -6
  38. package/.docs/raw/docs/(docs)/guides/chain-of-thought.mdx +18 -16
  39. package/.docs/raw/docs/(docs)/guides/context-api.mdx +81 -43
  40. package/.docs/raw/docs/(docs)/guides/dictation.mdx +5 -5
  41. package/.docs/raw/docs/(docs)/guides/editing.mdx +16 -7
  42. package/.docs/raw/docs/(docs)/guides/interactables.mdx +292 -0
  43. package/.docs/raw/docs/(docs)/guides/latex.mdx +3 -0
  44. package/.docs/raw/docs/(docs)/guides/message-timing.mdx +5 -4
  45. package/.docs/raw/docs/(docs)/guides/multi-agent.mdx +174 -0
  46. package/.docs/raw/docs/(docs)/guides/quoting.mdx +55 -206
  47. package/.docs/raw/docs/(docs)/guides/speech.mdx +1 -4
  48. package/.docs/raw/docs/(docs)/guides/suggestions.mdx +9 -15
  49. package/.docs/raw/docs/(docs)/guides/tool-ui.mdx +17 -7
  50. package/.docs/raw/docs/(docs)/guides/tools.mdx +24 -9
  51. package/.docs/raw/docs/(docs)/index.mdx +3 -3
  52. package/.docs/raw/docs/(docs)/installation.mdx +69 -46
  53. package/.docs/raw/docs/(reference)/api-reference/context-providers/text-message-part-provider.mdx +20 -6
  54. package/.docs/raw/docs/(reference)/api-reference/integrations/react-data-stream.mdx +24 -4
  55. package/.docs/raw/docs/(reference)/api-reference/integrations/react-hook-form.mdx +1 -1
  56. package/.docs/raw/docs/(reference)/api-reference/integrations/vercel-ai-sdk.mdx +20 -19
  57. package/.docs/raw/docs/(reference)/api-reference/overview.mdx +28 -53
  58. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar.mdx +4 -4
  59. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-modal.mdx +7 -1
  60. package/.docs/raw/docs/(reference)/api-reference/primitives/attachment.mdx +20 -14
  61. package/.docs/raw/docs/(reference)/api-reference/primitives/branch-picker.mdx +1 -1
  62. package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +226 -44
  63. package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +52 -40
  64. package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +343 -23
  65. package/.docs/raw/docs/(reference)/api-reference/primitives/suggestion.mdx +4 -6
  66. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list-item.mdx +4 -2
  67. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list.mdx +3 -5
  68. package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +169 -22
  69. package/.docs/raw/docs/(reference)/api-reference/runtimes/assistant-runtime.mdx +14 -4
  70. package/.docs/raw/docs/(reference)/api-reference/runtimes/attachment-runtime.mdx +15 -26
  71. package/.docs/raw/docs/(reference)/api-reference/runtimes/composer-runtime.mdx +39 -21
  72. package/.docs/raw/docs/(reference)/api-reference/runtimes/message-part-runtime.mdx +33 -9
  73. package/.docs/raw/docs/(reference)/api-reference/runtimes/message-runtime.mdx +48 -21
  74. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-item-runtime.mdx +36 -7
  75. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-runtime.mdx +30 -10
  76. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-runtime.mdx +12 -10
  77. package/.docs/raw/docs/(reference)/migrations/deprecation-policy.mdx +1 -1
  78. package/.docs/raw/docs/(reference)/migrations/react-langgraph-v0-7.mdx +9 -4
  79. package/.docs/raw/docs/(reference)/migrations/v0-11.mdx +7 -5
  80. package/.docs/raw/docs/(reference)/migrations/v0-12.mdx +9 -7
  81. package/.docs/raw/docs/(reference)/migrations/v0-14.mdx +159 -0
  82. package/.docs/raw/docs/(reference)/react-compatibility.mdx +5 -134
  83. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +90 -6
  84. package/.docs/raw/docs/cloud/ai-sdk.mdx +95 -5
  85. package/.docs/raw/docs/cloud/langgraph.mdx +13 -3
  86. package/.docs/raw/docs/ink/adapters.mdx +41 -0
  87. package/.docs/raw/docs/ink/custom-backend.mdx +203 -0
  88. package/.docs/raw/docs/ink/hooks.mdx +448 -0
  89. package/.docs/raw/docs/ink/index.mdx +239 -0
  90. package/.docs/raw/docs/ink/migration.mdx +140 -0
  91. package/.docs/raw/docs/ink/primitives.mdx +840 -0
  92. package/.docs/raw/docs/primitives/action-bar.mdx +351 -0
  93. package/.docs/raw/docs/primitives/assistant-modal.mdx +215 -0
  94. package/.docs/raw/docs/primitives/attachment.mdx +216 -0
  95. package/.docs/raw/docs/primitives/branch-picker.mdx +221 -0
  96. package/.docs/raw/docs/primitives/chain-of-thought.mdx +311 -0
  97. package/.docs/raw/docs/primitives/composer.mdx +526 -0
  98. package/.docs/raw/docs/primitives/error.mdx +141 -0
  99. package/.docs/raw/docs/primitives/index.mdx +98 -0
  100. package/.docs/raw/docs/primitives/message.mdx +524 -0
  101. package/.docs/raw/docs/primitives/selection-toolbar.mdx +165 -0
  102. package/.docs/raw/docs/primitives/suggestion.mdx +242 -0
  103. package/.docs/raw/docs/primitives/thread-list.mdx +404 -0
  104. package/.docs/raw/docs/primitives/thread.mdx +482 -0
  105. package/.docs/raw/docs/react-native/adapters.mdx +63 -87
  106. package/.docs/raw/docs/react-native/custom-backend.mdx +11 -14
  107. package/.docs/raw/docs/react-native/hooks.mdx +214 -232
  108. package/.docs/raw/docs/react-native/index.mdx +118 -159
  109. package/.docs/raw/docs/react-native/migration.mdx +144 -0
  110. package/.docs/raw/docs/react-native/primitives.mdx +431 -302
  111. package/.docs/raw/docs/runtimes/a2a/index.mdx +294 -0
  112. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +9 -9
  113. package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +14 -3
  114. package/.docs/raw/docs/runtimes/assistant-transport.mdx +59 -25
  115. package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +13 -6
  116. package/.docs/raw/docs/runtimes/custom/external-store.mdx +138 -38
  117. package/.docs/raw/docs/runtimes/custom/local.mdx +184 -42
  118. package/.docs/raw/docs/runtimes/data-stream.mdx +92 -19
  119. package/.docs/raw/docs/runtimes/google-adk/index.mdx +624 -0
  120. package/.docs/raw/docs/runtimes/helicone.mdx +6 -6
  121. package/.docs/raw/docs/runtimes/langgraph/index.mdx +38 -27
  122. package/.docs/raw/docs/runtimes/langgraph/tutorial/introduction.mdx +1 -1
  123. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +15 -20
  124. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +7 -11
  125. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +8 -11
  126. package/.docs/raw/docs/runtimes/langserve.mdx +6 -7
  127. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +18 -3
  128. package/.docs/raw/docs/ui/file.mdx +5 -4
  129. package/.docs/raw/docs/ui/image.mdx +5 -4
  130. package/.docs/raw/docs/ui/markdown.mdx +3 -1
  131. package/.docs/raw/docs/ui/mention.mdx +168 -0
  132. package/.docs/raw/docs/ui/model-selector.mdx +8 -8
  133. package/.docs/raw/docs/ui/part-grouping.mdx +7 -10
  134. package/.docs/raw/docs/ui/quote.mdx +210 -0
  135. package/.docs/raw/docs/ui/reasoning.mdx +12 -11
  136. package/.docs/raw/docs/ui/sources.mdx +88 -17
  137. package/.docs/raw/docs/ui/streamdown.mdx +16 -7
  138. package/.docs/raw/docs/ui/thread-list.mdx +11 -13
  139. package/.docs/raw/docs/ui/thread.mdx +28 -33
  140. package/.docs/raw/docs/ui/tool-fallback.mdx +5 -6
  141. package/.docs/raw/docs/ui/tool-group.mdx +9 -8
  142. package/.docs/raw/docs/utilities/heat-graph.mdx +236 -0
  143. package/.docs/raw/docs/utilities/tw-shimmer.mdx +211 -0
  144. package/package.json +5 -5
  145. package/.docs/raw/docs/(reference)/legacy/styled/assistant-modal.mdx +0 -77
  146. package/.docs/raw/docs/(reference)/legacy/styled/decomposition.mdx +0 -635
  147. package/.docs/raw/docs/(reference)/legacy/styled/markdown.mdx +0 -77
  148. package/.docs/raw/docs/(reference)/legacy/styled/scrollbar.mdx +0 -72
  149. package/.docs/raw/docs/(reference)/legacy/styled/thread-width.mdx +0 -22
  150. package/.docs/raw/docs/(reference)/legacy/styled/thread.mdx +0 -77
  151. /package/.docs/raw/docs/cloud/{overview.mdx → index.mdx} +0 -0
@@ -26,7 +26,7 @@ The `useChatRuntime` hook from `@assistant-ui/react-ai-sdk` wraps AI SDK's `useC
26
26
  3. Generates a conversation title after the assistant's first response
27
27
  4. Loads historical messages when switching threads via `<ThreadList />`
28
28
 
29
- You provide the AI SDK endpoint (`api: "/api/chat"`) and the cloud configuration—everything else is handled.
29
+ You provide the cloud configuration—everything else is handled. The default `AssistantChatTransport` automatically sends requests to `/api/chat`.
30
30
 
31
31
  ## Prerequisites
32
32
 
@@ -84,16 +84,21 @@ Create a client-side AssistantCloud instance and integrate it with your AI SDK r
84
84
  ```tsx title="app/chat/page.tsx"
85
85
  "use client";
86
86
 
87
+ import { useMemo } from "react";
87
88
  import { AssistantCloud, AssistantRuntimeProvider } from "@assistant-ui/react";
88
89
  import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
89
90
  import { ThreadList } from "@/components/assistant-ui/thread-list";
90
91
  import { Thread } from "@/components/assistant-ui/thread";
91
92
 
92
93
  export default function ChatPage() {
93
- const cloud = new AssistantCloud({
94
- baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
95
- anonymous: true, // Creates browser-session based user ID
96
- });
94
+ const cloud = useMemo(
95
+ () =>
96
+ new AssistantCloud({
97
+ baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
98
+ anonymous: true, // Creates browser-session based user ID
99
+ }),
100
+ [],
101
+ );
97
102
 
98
103
  const runtime = useChatRuntime({
99
104
  cloud,
@@ -114,6 +119,74 @@ export default function ChatPage() {
114
119
 
115
120
  </Steps>
116
121
 
122
+ ## `useChatRuntime` Options
123
+
124
+ <ParametersTable
125
+ parameters={[
126
+ {
127
+ name: "cloud",
128
+ type: "AssistantCloud",
129
+ description:
130
+ "Optional AssistantCloud instance for chat persistence and thread management.",
131
+ },
132
+ {
133
+ name: "adapters",
134
+ type: "RuntimeAdapters",
135
+ description:
136
+ "Optional runtime adapters to extend or override built-in functionality.",
137
+ children: [
138
+ {
139
+ type: "RuntimeAdapters",
140
+ parameters: [
141
+ {
142
+ name: "attachments",
143
+ type: "AttachmentAdapter",
144
+ description:
145
+ "Custom attachment adapter for file uploads. Defaults to the Vercel AI SDK attachment adapter.",
146
+ },
147
+ {
148
+ name: "speech",
149
+ type: "SpeechSynthesisAdapter",
150
+ description:
151
+ "Adapter for text-to-speech functionality.",
152
+ },
153
+ {
154
+ name: "dictation",
155
+ type: "DictationAdapter",
156
+ description:
157
+ "Adapter for speech-to-text dictation input.",
158
+ },
159
+ {
160
+ name: "feedback",
161
+ type: "FeedbackAdapter",
162
+ description:
163
+ "Adapter for collecting user feedback on messages.",
164
+ },
165
+ {
166
+ name: "history",
167
+ type: "ThreadHistoryAdapter",
168
+ description:
169
+ "Adapter for loading and saving thread history. Used to restore previous messages when switching threads.",
170
+ },
171
+ ],
172
+ },
173
+ ],
174
+ },
175
+ {
176
+ name: "toCreateMessage",
177
+ type: "(message: AppendMessage) => CreateUIMessage",
178
+ description:
179
+ "Optional custom function to convert an assistant-ui AppendMessage into an AI SDK CreateUIMessage before sending. Use this to customize how outgoing messages are formatted, for example to add custom metadata or transform content parts.",
180
+ },
181
+ {
182
+ name: "transport",
183
+ type: "ChatTransport",
184
+ description:
185
+ "Custom transport implementation. Defaults to AssistantChatTransport which sends requests to '/api/chat'.",
186
+ },
187
+ ]}
188
+ />
189
+
117
190
  ## Telemetry
118
191
 
119
192
  The `useChatRuntime` hook captures full run telemetry including timing data. This integrates with the assistant-ui runtime to provide:
@@ -134,10 +207,13 @@ To capture model and usage data, add the `messageMetadata` callback to your AI S
134
207
 
135
208
  ```tsx title="app/api/chat/route.ts"
136
209
  import { streamText } from "ai";
210
+ import { openai } from "@ai-sdk/openai";
137
211
 
138
212
  export async function POST(req: Request) {
213
+ const { messages } = await req.json();
214
+
139
215
  const result = streamText({
140
- model: openai("gpt-5-mini"),
216
+ model: openai("gpt-4o-mini"),
141
217
  messages,
142
218
  });
143
219
 
@@ -168,6 +244,7 @@ Use the `beforeReport` hook to add custom metadata or filter reports:
168
244
  ```tsx
169
245
  const cloud = new AssistantCloud({
170
246
  baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
247
+ anonymous: true,
171
248
  telemetry: {
172
249
  beforeReport: (report) => ({
173
250
  ...report,
@@ -179,11 +256,18 @@ const cloud = new AssistantCloud({
179
256
 
180
257
  Return `null` from `beforeReport` to skip reporting a specific run. To disable telemetry entirely, pass `telemetry: false`.
181
258
 
259
+ ### Sub-Agent Model Tracking
260
+
261
+ When tool calls delegate to a different model (e.g., the main run uses GPT but a tool invokes Gemini), you can track the delegated model's usage. Pass sampling call data through `messageMetadata.samplingCalls` in your API route, and the telemetry reporter will automatically include it in the report.
262
+
263
+ See the [AI SDK Telemetry guide](/docs/cloud/ai-sdk#sub-agent-model-tracking) for the full setup with `createSamplingCollector` and `wrapSamplingHandler`.
264
+
182
265
  ## Authentication
183
266
 
184
267
  The example above uses anonymous mode (browser session-based user ID) via the env var. For production apps with user accounts, pass an explicit cloud instance:
185
268
 
186
269
  ```tsx
270
+ import { useMemo } from "react";
187
271
  import { useAuth } from "@clerk/nextjs";
188
272
  import { AssistantCloud } from "@assistant-ui/react";
189
273
  import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
@@ -49,7 +49,7 @@ NEXT_PUBLIC_ASSISTANT_BASE_URL=https://proj-[YOUR-ID].assistant-api.com
49
49
 
50
50
  ### Install Dependencies
51
51
 
52
- <InstallCommand npm={["@assistant-ui/cloud-ai-sdk", "assistant-cloud", "@ai-sdk/react", "ai"]} />
52
+ <InstallCommand npm={["@assistant-ui/cloud-ai-sdk", "@ai-sdk/react", "ai"]} />
53
53
 
54
54
  </Step>
55
55
 
@@ -151,7 +151,7 @@ const chat = useCloudChat({ threads: myThreads });
151
151
  | `options.threads` | `UseThreadsResult` | External thread management from `useThreads()`. Use when you need thread operations in a separate component or custom thread options like `includeArchived` |
152
152
  | `options.onSyncError` | `(error: Error) => void` | Callback invoked when a sync error occurs |
153
153
 
154
- All other [AI SDK `useChat` options](https://sdk.vercel.ai/docs/reference/ai-sdk-ui/use-chat) are also accepted.
154
+ A subset of [AI SDK `useChat` options](https://sdk.vercel.ai/docs/reference/ai-sdk-ui/use-chat) are also accepted (those defined on `ChatInit`). Some options available on `useChat` such as `experimental_throttle` and `resume` are not supported.
155
155
 
156
156
  **Returns:** `UseCloudChatResult`
157
157
 
@@ -169,12 +169,15 @@ Plus all other properties from AI SDK's [`UseChatHelpers`](https://sdk.vercel.ai
169
169
 
170
170
  | Value | Type | Description |
171
171
  |-------|------|-------------|
172
+ | `threads.cloud` | `AssistantCloud` | The cloud instance used for thread operations |
172
173
  | `threads.threads` | `CloudThread[]` | Active threads sorted by recency |
173
174
  | `threads.threadId` | `string \| null` | Current thread ID (`null` for a new unsaved chat) |
174
175
  | `threads.selectThread` | `(id: string \| null) => void` | Switch threads or pass `null` for a new chat |
175
176
  | `threads.isLoading` | `boolean` | `true` during initial load or refresh |
176
177
  | `threads.error` | `Error \| null` | Last error, if any |
177
178
  | `threads.refresh` | `() => Promise<boolean>` | Re-fetch the thread list |
179
+ | `threads.get` | `(id: string) => Promise<CloudThread \| null>` | Fetch a single thread by ID |
180
+ | `threads.create` | `(options?: \{ externalId?: string \}) => Promise<CloudThread \| null>` | Create a new thread |
178
181
  | `threads.delete` | `(id: string) => Promise<boolean>` | Delete a thread |
179
182
  | `threads.rename` | `(id: string, title: string) => Promise<boolean>` | Rename a thread |
180
183
  | `threads.archive` | `(id: string) => Promise<boolean>` | Archive a thread |
@@ -206,22 +209,27 @@ The `useCloudChat` hook automatically reports run telemetry to Assistant Cloud a
206
209
 
207
210
  **Automatically captured:**
208
211
  - `status` — `"completed"` or `"incomplete"` based on response content
209
- - `tool_calls` — Tool invocations with name, arguments, results, and source (MCP, frontend, or backend)
212
+ - `tool_calls` — Tool invocations with name, arguments, and results. MCP tool calls are explicitly tagged with `tool_source: "mcp"`
210
213
  - `total_steps` — Number of reasoning/tool steps in the response
211
214
  - `output_text` — Full response text (truncated at 50K characters)
212
215
 
213
216
  **Requires route configuration:**
214
217
  - `model_id` — The model used for the response
215
218
  - `input_tokens` / `output_tokens` — Token usage statistics
219
+ - `reasoning_tokens` — Tokens used for chain-of-thought reasoning (e.g. o1/o3 models)
220
+ - `cached_input_tokens` — Input tokens served from the provider's prompt cache
216
221
 
217
222
  To capture model and usage data, configure the `messageMetadata` callback in your AI SDK route:
218
223
 
219
224
  ```tsx title="app/api/chat/route.ts"
220
225
  import { streamText } from "ai";
226
+ import { openai } from "@ai-sdk/openai";
221
227
 
222
228
  export async function POST(req: Request) {
229
+ const { messages } = await req.json();
230
+
223
231
  const result = streamText({
224
- model: openai("gpt-5-mini"),
232
+ model: openai("gpt-4o-mini"),
225
233
  messages,
226
234
  });
227
235
 
@@ -244,7 +252,7 @@ export async function POST(req: Request) {
244
252
  ```
245
253
 
246
254
  <Callout type="info">
247
- The standalone hook does not capture `duration_ms`, per-step breakdowns (`steps`), custom `metadata` pass-through, or `"error"` status. These require the full runtime integration available via [`useChatRuntime`](/docs/cloud/ai-sdk-assistant-ui).
255
+ The standalone hook captures message metadata when it is JSON-serializable, but it does not capture `duration_ms`, per-step breakdowns (`steps`), or `"error"` status. Those require the full runtime integration available via [`useChatRuntime`](/docs/cloud/ai-sdk-assistant-ui).
248
256
  </Callout>
249
257
 
250
258
  ### Customizing Reports
@@ -254,6 +262,7 @@ Use the `beforeReport` hook to enrich or filter telemetry:
254
262
  ```tsx
255
263
  const cloud = new AssistantCloud({
256
264
  baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
265
+ anonymous: true,
257
266
  telemetry: {
258
267
  beforeReport: (report) => ({
259
268
  ...report,
@@ -265,11 +274,92 @@ const cloud = new AssistantCloud({
265
274
 
266
275
  Return `null` from `beforeReport` to skip reporting a specific run. To disable telemetry entirely, pass `telemetry: false`.
267
276
 
277
+ ### Sub-Agent Model Tracking
278
+
279
+ In multi-agent setups where tool calls delegate to a different model (e.g., the main run uses GPT but a tool invokes Gemini), you can track the delegated model's usage by passing sampling call data through `messageMetadata`.
280
+
281
+ **Step 1: Collect sampling data on the server**
282
+
283
+ Use `createSamplingCollector` and `wrapSamplingHandler` from `assistant-cloud` to capture LLM calls made during tool execution:
284
+
285
+ ```ts title="app/api/chat/route.ts"
286
+ import { streamText } from "ai";
287
+ import { openai } from "@ai-sdk/openai";
288
+ import {
289
+ createSamplingCollector,
290
+ wrapSamplingHandler,
291
+ } from "assistant-cloud";
292
+
293
+ export async function POST(req: Request) {
294
+ const { messages } = await req.json();
295
+
296
+ // Collect sub-agent sampling calls per tool call
297
+ const samplingCalls: Record<string, SamplingCallData[]> = {};
298
+
299
+ const result = streamText({
300
+ model: openai("gpt-4o"),
301
+ messages,
302
+ tools: {
303
+ delegate_to_gemini: tool({
304
+ parameters: z.object({ task: z.string() }),
305
+ execute: async ({ task }, { toolCallId }) => {
306
+ const collector = createSamplingCollector();
307
+ // Your sub-agent logic that calls another model
308
+ const result = await runSubAgent(task, {
309
+ onSamplingCall: collector.collect,
310
+ });
311
+ samplingCalls[toolCallId] = collector.getCalls();
312
+ return result;
313
+ },
314
+ }),
315
+ },
316
+ });
317
+
318
+ return result.toUIMessageStreamResponse({
319
+ messageMetadata: ({ part }) => {
320
+ if (part.type === "finish") {
321
+ return {
322
+ usage: part.totalUsage,
323
+ samplingCalls, // attach collected sampling data
324
+ };
325
+ }
326
+ if (part.type === "finish-step") {
327
+ return { modelId: part.response.modelId };
328
+ }
329
+ return undefined;
330
+ },
331
+ });
332
+ }
333
+ ```
334
+
335
+ **Step 2: That's it.** The telemetry reporter automatically reads `samplingCalls` from message metadata and attaches the data to matching tool calls in the report. The Cloud dashboard will show each delegated model in the model distribution chart with its own token and cost breakdown.
336
+
337
+ <Callout type="info">
338
+ For MCP tools that use the sampling protocol, `wrapSamplingHandler` can wrap the MCP client's sampling handler directly to capture all nested LLM calls transparently.
339
+ </Callout>
340
+
341
+ <Callout type="tip">
342
+ **On older versions** that don't yet read `samplingCalls` from metadata, use `beforeReport` to inject the data manually:
343
+
344
+ ```ts
345
+ telemetry: {
346
+ beforeReport: (report) => ({
347
+ ...report,
348
+ tool_calls: report.tool_calls?.map((tc) => ({
349
+ ...tc,
350
+ sampling_calls: samplingCalls[tc.tool_call_id],
351
+ })),
352
+ }),
353
+ }
354
+ ```
355
+ </Callout>
356
+
268
357
  ## Authentication
269
358
 
270
359
  The example above uses anonymous mode (browser session-based user ID) via the env var. For production apps with user accounts, pass an explicit cloud instance:
271
360
 
272
361
  ```tsx
362
+ import { useMemo } from "react";
273
363
  import { useAuth } from "@clerk/nextjs";
274
364
  import { AssistantCloud } from "assistant-cloud";
275
365
  import { useCloudChat } from "@assistant-ui/cloud-ai-sdk";
@@ -74,7 +74,7 @@ import {
74
74
  AssistantRuntimeProvider,
75
75
  } from "@assistant-ui/react";
76
76
  import { useLangGraphRuntime } from "@assistant-ui/react-langgraph";
77
- import { createThread, getThreadState, sendMessage } from "@/lib/chatApi";
77
+ import { createThread, deleteThread, getThreadState, sendMessage } from "@/lib/chatApi";
78
78
  import { LangChainMessage } from "@assistant-ui/react-langgraph";
79
79
  import { useMemo } from "react";
80
80
 
@@ -114,6 +114,9 @@ export function MyRuntimeProvider({
114
114
  (state.values as { messages?: LangChainMessage[] }).messages ?? [],
115
115
  };
116
116
  },
117
+ delete: async (externalId) => {
118
+ await deleteThread(externalId);
119
+ },
117
120
  });
118
121
 
119
122
  return (
@@ -136,7 +139,7 @@ import {
136
139
  AssistantRuntimeProvider,
137
140
  } from "@assistant-ui/react";
138
141
  import { useLangGraphRuntime } from "@assistant-ui/react-langgraph";
139
- import { createThread, getThreadState, sendMessage } from "@/lib/chatApi";
142
+ import { createThread, deleteThread, getThreadState, sendMessage } from "@/lib/chatApi";
140
143
  import { LangChainMessage } from "@assistant-ui/react-langgraph";
141
144
  import { useAuth } from "@clerk/nextjs";
142
145
  import { useMemo } from "react";
@@ -179,6 +182,9 @@ export function MyRuntimeProvider({
179
182
  (state.values as { messages?: LangChainMessage[] }).messages ?? [],
180
183
  };
181
184
  },
185
+ delete: async (externalId) => {
186
+ await deleteThread(externalId);
187
+ },
182
188
  });
183
189
 
184
190
  return (
@@ -199,7 +205,11 @@ export function MyRuntimeProvider({
199
205
  </Tabs>
200
206
 
201
207
  <Callout type="info">
202
- The `useLangGraphRuntime` hook now directly accepts `cloud`, `create`, and `load` parameters for simplified thread management. The runtime handles thread lifecycle internally.
208
+ The `useLangGraphRuntime` hook accepts `cloud`, `create`, `load`, and `delete` parameters for simplified thread management. The runtime handles the thread lifecycle internally.
209
+
210
+ - **`create`**: Called when creating a new thread. Returns `{ externalId }` with your backend's thread ID.
211
+ - **`load`**: Called when switching to an existing thread. Returns the thread's messages (and optionally interrupts).
212
+ - **`delete`**: Called when deleting a thread. Receives the thread's `externalId`. When provided, users can delete threads from the thread list UI.
203
213
  </Callout>
204
214
 
205
215
  </Step>
@@ -0,0 +1,41 @@
1
+ ---
2
+ title: Adapters
3
+ description: Title generation adapters for React Ink.
4
+ ---
5
+
6
+ Adapters customize runtime behavior. They can be passed as options to `useLocalRuntime` or `useRemoteThreadListRuntime`.
7
+
8
+ ## RemoteThreadListAdapter
9
+
10
+ Title generation is configured via the `generateTitle` method on `RemoteThreadListAdapter`. See the [Custom Backend](/docs/ink/custom-backend) page for a full example.
11
+
12
+ ```tsx
13
+ import type { RemoteThreadListAdapter } from "@assistant-ui/react-ink";
14
+ import { createAssistantStream } from "assistant-stream";
15
+
16
+ const myAdapter: RemoteThreadListAdapter = {
17
+ // ... other methods ...
18
+
19
+ async generateTitle(remoteId, unstable_messages) {
20
+ return createAssistantStream(async (controller) => {
21
+ const res = await fetch(`/api/threads/${remoteId}/title`, {
22
+ method: "POST",
23
+ headers: { "Content-Type": "application/json" },
24
+ body: JSON.stringify({ messages: unstable_messages }),
25
+ });
26
+ const { title } = await res.json();
27
+ controller.appendText(title);
28
+ });
29
+ },
30
+ };
31
+ ```
32
+
33
+ ## Which option to choose?
34
+
35
+ | | ChatModelAdapter + `useLocalRuntime` | RemoteThreadListAdapter + `useRemoteThreadListRuntime` |
36
+ |---|---|---|
37
+ | **Thread storage** | In-memory | Your backend |
38
+ | **Message storage** | In-memory | In-memory (can add history adapter for server-side) |
39
+ | **Cross-session persistence** | No | Yes |
40
+ | **Setup complexity** | Minimal | Moderate |
41
+ | **Best for** | CLI tools, demos, prototypes | Production apps with persistence |
@@ -0,0 +1,203 @@
1
+ ---
2
+ title: Custom Backend
3
+ description: Connect your terminal app to your own backend API.
4
+ ---
5
+
6
+ By default, `useLocalRuntime` manages threads and messages in memory. You can connect to your own backend in two ways depending on your needs.
7
+
8
+ ## Option 1: ChatModelAdapter only
9
+
10
+ The simplest approach — keep thread management local, but send messages to your backend for inference.
11
+
12
+ ```tsx title="adapters/my-chat-adapter.ts"
13
+ import type { ChatModelAdapter } from "@assistant-ui/react-ink";
14
+
15
+ export const myChatAdapter: ChatModelAdapter = {
16
+ async *run({ messages, abortSignal }) {
17
+ const response = await fetch("https://my-api.com/chat", {
18
+ method: "POST",
19
+ headers: { "Content-Type": "application/json" },
20
+ body: JSON.stringify({ messages }),
21
+ signal: abortSignal,
22
+ });
23
+
24
+ const reader = response.body?.getReader();
25
+ if (!reader) throw new Error("No response body");
26
+
27
+ const decoder = new TextDecoder();
28
+ let fullText = "";
29
+
30
+ while (true) {
31
+ const { done, value } = await reader.read();
32
+ if (done) break;
33
+
34
+ const chunk = decoder.decode(value, { stream: true });
35
+ fullText += chunk;
36
+ yield { content: [{ type: "text", text: fullText }] };
37
+ }
38
+ },
39
+ };
40
+ ```
41
+
42
+ ```tsx title="app.tsx"
43
+ import { useLocalRuntime, AssistantRuntimeProvider } from "@assistant-ui/react-ink";
44
+ import { myChatAdapter } from "./adapters/my-chat-adapter.js";
45
+
46
+ export function App() {
47
+ const runtime = useLocalRuntime(myChatAdapter);
48
+ return (
49
+ <AssistantRuntimeProvider runtime={runtime}>
50
+ {/* your chat UI */}
51
+ </AssistantRuntimeProvider>
52
+ );
53
+ }
54
+ ```
55
+
56
+ This gives you:
57
+
58
+ - Streaming chat responses from your API
59
+ - In-memory thread list (lost on process exit)
60
+ - Multi-thread support
61
+
62
+ ## Option 2: Full backend thread management
63
+
64
+ When you want your backend to own thread state (e.g. for persistence across sessions, team sharing, or server-side history), implement a `RemoteThreadListAdapter`.
65
+
66
+ <Steps>
67
+ <Step>
68
+
69
+ ### Implement the adapter
70
+
71
+ ```tsx title="adapters/my-thread-list-adapter.ts"
72
+ import type { RemoteThreadListAdapter } from "@assistant-ui/react-ink";
73
+ import { createAssistantStream } from "assistant-stream";
74
+
75
+ const API_BASE = "https://my-api.com";
76
+
77
+ export const myThreadListAdapter: RemoteThreadListAdapter = {
78
+ async list() {
79
+ const res = await fetch(`${API_BASE}/threads`);
80
+ const threads = await res.json();
81
+ return {
82
+ threads: threads.map((t: any) => ({
83
+ remoteId: t.id,
84
+ status: t.archived ? "archived" : "regular",
85
+ title: t.title,
86
+ })),
87
+ };
88
+ },
89
+
90
+ async initialize(localId) {
91
+ const res = await fetch(`${API_BASE}/threads`, {
92
+ method: "POST",
93
+ headers: { "Content-Type": "application/json" },
94
+ body: JSON.stringify({ localId }),
95
+ });
96
+ const { id } = await res.json();
97
+ return { remoteId: id, externalId: undefined };
98
+ },
99
+
100
+ async rename(remoteId, title) {
101
+ await fetch(`${API_BASE}/threads/${remoteId}`, {
102
+ method: "PATCH",
103
+ headers: { "Content-Type": "application/json" },
104
+ body: JSON.stringify({ title }),
105
+ });
106
+ },
107
+
108
+ async archive(remoteId) {
109
+ await fetch(`${API_BASE}/threads/${remoteId}/archive`, {
110
+ method: "POST",
111
+ });
112
+ },
113
+
114
+ async unarchive(remoteId) {
115
+ await fetch(`${API_BASE}/threads/${remoteId}/unarchive`, {
116
+ method: "POST",
117
+ });
118
+ },
119
+
120
+ async delete(remoteId) {
121
+ await fetch(`${API_BASE}/threads/${remoteId}`, { method: "DELETE" });
122
+ },
123
+
124
+ async fetch(remoteId) {
125
+ const res = await fetch(`${API_BASE}/threads/${remoteId}`);
126
+ const t = await res.json();
127
+ return {
128
+ remoteId: t.id,
129
+ status: t.archived ? "archived" : "regular",
130
+ title: t.title,
131
+ };
132
+ },
133
+
134
+ async generateTitle(remoteId, unstable_messages) {
135
+ return createAssistantStream(async (controller) => {
136
+ const res = await fetch(`${API_BASE}/threads/${remoteId}/title`, {
137
+ method: "POST",
138
+ headers: { "Content-Type": "application/json" },
139
+ body: JSON.stringify({ messages: unstable_messages }),
140
+ });
141
+ const { title } = await res.json();
142
+ controller.appendText(title);
143
+ });
144
+ },
145
+ };
146
+ ```
147
+
148
+ </Step>
149
+ <Step>
150
+
151
+ ### Compose the runtime
152
+
153
+ ```tsx title="app.tsx"
154
+ import {
155
+ useLocalRuntime,
156
+ useRemoteThreadListRuntime,
157
+ AssistantRuntimeProvider,
158
+ } from "@assistant-ui/react-ink";
159
+ import { myChatAdapter } from "./adapters/my-chat-adapter.js";
160
+ import { myThreadListAdapter } from "./adapters/my-thread-list-adapter.js";
161
+
162
+ function useAppRuntime() {
163
+ return useRemoteThreadListRuntime({
164
+ runtimeHook: () => useLocalRuntime(myChatAdapter),
165
+ adapter: myThreadListAdapter,
166
+ });
167
+ }
168
+
169
+ export function App() {
170
+ const runtime = useAppRuntime();
171
+ return (
172
+ <AssistantRuntimeProvider runtime={runtime}>
173
+ {/* your chat UI */}
174
+ </AssistantRuntimeProvider>
175
+ );
176
+ }
177
+ ```
178
+
179
+ </Step>
180
+ </Steps>
181
+
182
+ ## Adapter methods
183
+
184
+ | Method | Description |
185
+ |--------|-------------|
186
+ | `list()` | Return all threads on mount |
187
+ | `initialize(localId)` | Create a thread server-side, return `{ remoteId }` |
188
+ | `rename(remoteId, title)` | Persist title changes |
189
+ | `archive(remoteId)` | Mark thread as archived |
190
+ | `unarchive(remoteId)` | Restore archived thread |
191
+ | `delete(remoteId)` | Permanently remove thread |
192
+ | `fetch(remoteId)` | Fetch single thread metadata |
193
+ | `generateTitle(remoteId, unstable_messages)` | Return an `AssistantStream` with the generated title |
194
+
195
+ ## Which option to choose?
196
+
197
+ | | Option 1: ChatModelAdapter | Option 2: RemoteThreadListAdapter |
198
+ |---|---|---|
199
+ | **Thread storage** | In-memory (process lifetime) | Your backend |
200
+ | **Message storage** | In-memory | On-device (can add history adapter for server-side) |
201
+ | **Cross-session persistence** | No | Yes |
202
+ | **Setup complexity** | Minimal | Moderate |
203
+ | **Best for** | CLI tools, demos, prototypes | Production apps with persistence |