@assistant-ui/mcp-docs-server 0.1.39 → 0.2.1

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 (185) hide show
  1. package/.docs/organized/code-examples/waterfall.md +17 -18
  2. package/.docs/organized/code-examples/with-a2a.md +20 -15
  3. package/.docs/organized/code-examples/with-ag-ui.md +19 -17
  4. package/.docs/organized/code-examples/with-ai-sdk-v7.md +17 -16
  5. package/.docs/organized/code-examples/with-artifacts.md +471 -145
  6. package/.docs/organized/code-examples/with-assistant-transport.md +23 -31
  7. package/.docs/organized/code-examples/with-browser-extension.md +21 -14
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +22 -20
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +13 -14
  10. package/.docs/organized/code-examples/with-cloud.md +22 -17
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +17 -16
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +17 -17
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +21 -19
  14. package/.docs/organized/code-examples/with-eve.md +70 -16
  15. package/.docs/organized/code-examples/with-expo.md +32 -51
  16. package/.docs/organized/code-examples/with-external-store.md +20 -15
  17. package/.docs/organized/code-examples/with-ffmpeg.md +24 -18
  18. package/.docs/organized/code-examples/with-generative-ui.md +20 -21
  19. package/.docs/organized/code-examples/with-google-adk.md +21 -16
  20. package/.docs/organized/code-examples/with-heat-graph.md +10 -11
  21. package/.docs/organized/code-examples/with-image-generation.md +13 -14
  22. package/.docs/organized/code-examples/with-interactables.md +17 -16
  23. package/.docs/organized/code-examples/with-langchain.md +13 -14
  24. package/.docs/organized/code-examples/with-langgraph.md +23 -17
  25. package/.docs/organized/code-examples/with-livekit.md +17 -17
  26. package/.docs/organized/code-examples/with-mcp.md +36 -32
  27. package/.docs/organized/code-examples/with-nuxt.md +2492 -0
  28. package/.docs/organized/code-examples/with-opencode.md +27 -21
  29. package/.docs/organized/code-examples/with-pi.md +69 -67
  30. package/.docs/organized/code-examples/with-react-hook-form.md +18 -17
  31. package/.docs/organized/code-examples/with-react-ink-web.md +10 -11
  32. package/.docs/organized/code-examples/with-react-ink.md +7 -7
  33. package/.docs/organized/code-examples/with-react-router.md +23 -17
  34. package/.docs/organized/code-examples/with-resumable-stream.md +15 -16
  35. package/.docs/organized/code-examples/with-store.md +31 -20
  36. package/.docs/organized/code-examples/with-tanstack.md +22 -16
  37. package/.docs/organized/code-examples/with-tap-runtime.md +19 -19
  38. package/.docs/organized/code-examples/with-virtualized-thread.md +12 -13
  39. package/.docs/organized/code-examples/with-vue.md +408 -0
  40. package/.docs/raw/docs/(docs)/cli.mdx +3 -1
  41. package/.docs/raw/docs/(docs)/devtools.mdx +7 -2
  42. package/.docs/raw/docs/(docs)/index.mdx +9 -76
  43. package/.docs/raw/docs/(docs)/installation.mdx +4 -18
  44. package/.docs/raw/docs/(reference)/api-reference/context-providers/assistant-runtime-provider.mdx +24 -4
  45. package/.docs/raw/docs/(reference)/api-reference/generative-ui/a2ui.mdx +40 -0
  46. package/.docs/raw/docs/(reference)/api-reference/generative-ui/index.mdx +3 -0
  47. package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +19 -420
  48. package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +4 -1
  49. package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +8 -0
  50. package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +21 -0
  51. package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -9
  52. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +1 -18
  53. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +24 -1
  54. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +1 -1
  55. package/.docs/raw/docs/cloud/ai-sdk.mdx +2 -2
  56. package/.docs/raw/docs/cloud/langgraph.mdx +1 -1
  57. package/.docs/raw/docs/copilots/model-context.mdx +5 -4
  58. package/.docs/raw/docs/copilots/motivation.mdx +5 -5
  59. package/.docs/raw/docs/guides/attachments.mdx +3 -3
  60. package/.docs/raw/docs/guides/branching.mdx +2 -2
  61. package/.docs/raw/docs/guides/chain-of-thought.mdx +1 -1
  62. package/.docs/raw/docs/guides/context-api.mdx +89 -111
  63. package/.docs/raw/docs/guides/editing.mdx +5 -5
  64. package/.docs/raw/docs/guides/electron.mdx +369 -0
  65. package/.docs/raw/docs/guides/index.mdx +10 -0
  66. package/.docs/raw/docs/guides/quoting.mdx +3 -3
  67. package/.docs/raw/docs/guides/resumable-stream-deployment.mdx +2 -2
  68. package/.docs/raw/docs/guides/resumable-streams.mdx +74 -3
  69. package/.docs/raw/docs/guides/suggestions.mdx +9 -9
  70. package/.docs/raw/docs/ink/hooks.mdx +12 -7
  71. package/.docs/raw/docs/ink/primitives.mdx +18 -10
  72. package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +16 -0
  73. package/.docs/raw/docs/integrations/auth/better-auth.mdx +3 -3
  74. package/.docs/raw/docs/integrations/auth/clerk.mdx +3 -3
  75. package/.docs/raw/docs/integrations/auth/next-auth.mdx +4 -4
  76. package/.docs/raw/docs/integrations/frameworks/cloudflare-agents/overview.mdx +1 -1
  77. package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +2 -2
  78. package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +1 -1
  79. package/.docs/raw/docs/integrations/gateways/index.mdx +2 -2
  80. package/.docs/raw/docs/integrations/observability/helicone.mdx +2 -2
  81. package/.docs/raw/docs/integrations/observability/langfuse.mdx +1 -1
  82. package/.docs/raw/docs/integrations/observability/langsmith.mdx +3 -3
  83. package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +36 -9
  84. package/.docs/raw/docs/migrations/toolkit-tools.mdx +15 -13
  85. package/.docs/raw/docs/migrations/v0-15.mdx +236 -0
  86. package/.docs/raw/docs/primitives/attachment.mdx +2 -2
  87. package/.docs/raw/docs/primitives/composer.mdx +3 -3
  88. package/.docs/raw/docs/primitives/message.mdx +33 -1
  89. package/.docs/raw/docs/primitives/suggestion.mdx +1 -1
  90. package/.docs/raw/docs/primitives/thread-list.mdx +2 -2
  91. package/.docs/raw/docs/react-native/hooks.mdx +17 -7
  92. package/.docs/raw/docs/react-native/index.mdx +3 -3
  93. package/.docs/raw/docs/react-native/primitives.mdx +41 -7
  94. package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +3 -3
  95. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +5 -5
  96. package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +5 -5
  97. package/.docs/raw/docs/runtimes/ai-sdk/v6-legacy.mdx +13 -14
  98. package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +11 -12
  99. package/.docs/raw/docs/runtimes/concepts/threads.mdx +50 -11
  100. package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +20 -2
  101. package/.docs/raw/docs/runtimes/custom/external-store.mdx +31 -2
  102. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +39 -12
  103. package/.docs/raw/docs/runtimes/eve/overview.mdx +51 -0
  104. package/.docs/raw/docs/runtimes/eve/quickstart.mdx +51 -2
  105. package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +9 -12
  106. package/.docs/raw/docs/runtimes/langchain.mdx +2 -2
  107. package/.docs/raw/docs/runtimes/langgraph/overview.mdx +1 -1
  108. package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +1 -1
  109. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +7 -7
  110. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +4 -4
  111. package/.docs/raw/docs/runtimes/opencode/hooks.mdx +4 -1
  112. package/.docs/raw/docs/runtimes/opencode/overview.mdx +10 -0
  113. package/.docs/raw/docs/tools/a2ui.mdx +107 -0
  114. package/.docs/raw/docs/tools/backend.mdx +2 -2
  115. package/.docs/raw/docs/tools/defining-tools.mdx +9 -7
  116. package/.docs/raw/docs/tools/dynamic-tools.mdx +6 -4
  117. package/.docs/raw/docs/tools/interactables-legacy.mdx +28 -15
  118. package/.docs/raw/docs/tools/interactables.mdx +27 -16
  119. package/.docs/raw/docs/tools/mcp-apps.mdx +96 -8
  120. package/.docs/raw/docs/tools/mcp.mdx +9 -7
  121. package/.docs/raw/docs/tools/tool-ui.mdx +26 -22
  122. package/.docs/raw/docs/tools/user-managed-mcp.mdx +64 -14
  123. package/.docs/raw/docs/ui/file.mdx +6 -1
  124. package/.docs/raw/docs/ui/follow-up-suggestions.mdx +2 -0
  125. package/.docs/raw/docs/ui/mcp-config.mdx +8 -3
  126. package/.docs/raw/docs/ui/model-selector.mdx +9 -9
  127. package/.docs/raw/docs/ui/part-grouping.mdx +1 -5
  128. package/.docs/raw/docs/ui/reasoning.mdx +1 -1
  129. package/.docs/raw/docs/ui/thread.mdx +24 -5
  130. package/.docs/raw/docs/utilities/react-o11y.mdx +7 -9
  131. package/dist/constants.js +2 -2
  132. package/dist/constants.js.map +1 -1
  133. package/dist/index.d.ts +1 -1
  134. package/dist/index.js +2 -2
  135. package/dist/index.js.map +1 -1
  136. package/dist/prepare-docs/code-examples.js.map +1 -1
  137. package/dist/prepare-docs/prepare.d.ts +1 -1
  138. package/dist/prepare-docs/prepare.js.map +1 -1
  139. package/dist/stdio.d.ts +1 -1
  140. package/dist/tools/docs.d.ts +6 -10
  141. package/dist/tools/docs.d.ts.map +1 -1
  142. package/dist/tools/docs.js +6 -4
  143. package/dist/tools/docs.js.map +1 -1
  144. package/dist/tools/examples.d.ts +4 -8
  145. package/dist/tools/examples.d.ts.map +1 -1
  146. package/dist/tools/examples.js +4 -3
  147. package/dist/tools/examples.js.map +1 -1
  148. package/dist/tools/resources.d.ts +1 -1
  149. package/dist/tools/resources.d.ts.map +1 -1
  150. package/dist/tools/resources.js +3 -2
  151. package/dist/tools/resources.js.map +1 -1
  152. package/dist/tools/search.d.ts +4 -10
  153. package/dist/tools/search.d.ts.map +1 -1
  154. package/dist/tools/search.js +2 -2
  155. package/dist/tools/search.js.map +1 -1
  156. package/dist/tools/tests/mcp-test-client.d.ts +15 -0
  157. package/dist/tools/tests/mcp-test-client.d.ts.map +1 -0
  158. package/dist/tools/tests/mcp-test-client.js +68 -0
  159. package/dist/tools/tests/mcp-test-client.js.map +1 -0
  160. package/dist/tools/tests/test-setup.d.ts.map +1 -1
  161. package/dist/tools/tests/test-setup.js +2 -1
  162. package/dist/tools/tests/test-setup.js.map +1 -1
  163. package/dist/tools/xulux-templates.d.ts +9 -23
  164. package/dist/tools/xulux-templates.d.ts.map +1 -1
  165. package/dist/tools/xulux-templates.js +8 -6
  166. package/dist/tools/xulux-templates.js.map +1 -1
  167. package/dist/utils/logger.d.ts.map +1 -1
  168. package/dist/utils/mdx.js +2 -1
  169. package/dist/utils/mdx.js.map +1 -1
  170. package/dist/utils/security.js.map +1 -1
  171. package/dist/xulux/catalog-client.js +1 -1
  172. package/dist/xulux/catalog-client.js.map +1 -1
  173. package/package.json +6 -5
  174. package/src/index.ts +2 -2
  175. package/src/tools/docs.ts +2 -2
  176. package/src/tools/examples.ts +2 -2
  177. package/src/tools/resources.ts +1 -4
  178. package/src/tools/search.ts +2 -2
  179. package/src/tools/tests/completions.test.ts +40 -26
  180. package/src/tools/tests/docs.test.ts +2 -2
  181. package/src/tools/tests/integration.test.ts +3 -4
  182. package/src/tools/tests/mcp-protocol.test.ts +160 -175
  183. package/src/tools/tests/mcp-test-client.ts +111 -0
  184. package/src/tools/tests/resources.test.ts +97 -66
  185. package/src/tools/xulux-templates.ts +4 -4
@@ -4,45 +4,43 @@
4
4
 
5
5
  ```typescript
6
6
  import { openai } from "@ai-sdk/openai";
7
- import {
8
- streamText,
9
- convertToModelMessages,
10
- tool,
11
- stepCountIs,
12
- zodSchema,
13
- } from "ai";
7
+ import { convertToModelMessages, stepCountIs, streamText } from "ai";
14
8
  import type { UIMessage } from "ai";
15
- import { z } from "zod";
9
+ import {
10
+ AISDKToolkit,
11
+ unstable_injectInteractableContext,
12
+ type AISDKToolkitToolsOptions,
13
+ } from "@assistant-ui/react-ai-sdk";
14
+ import toolkit from "../../toolkit";
16
15
 
17
16
  export const maxDuration = 30;
18
17
 
18
+ const aiToolkit = new AISDKToolkit({ toolkit });
19
+
20
+ type FrontendToolDefs = NonNullable<AISDKToolkitToolsOptions["frontend"]>;
21
+
19
22
  export async function POST(req: Request) {
20
- const { messages }: { messages: UIMessage[] } = await req.json();
23
+ const {
24
+ messages,
25
+ tools: clientTools,
26
+ }: {
27
+ messages: UIMessage[];
28
+ tools?: FrontendToolDefs;
29
+ } = await req.json();
30
+
31
+ const tools = await aiToolkit.tools({
32
+ ...(clientTools && { frontend: clientTools }),
33
+ });
21
34
 
22
35
  const result = streamText({
23
36
  model: openai("gpt-5.4-nano"),
24
37
  system:
25
- "You are a helpful assistant that can generate HTML code. When the user asks you to create any visual content, UI, or webpage, use the render_html tool to render it in the browser. Always use the render_html tool for HTML output rather than showing code in a code block.",
26
- messages: await convertToModelMessages(messages),
38
+ "You create functional HTML artifacts. When the user requests a webpage, interface, visualization, or other visual content, call the artifact tool with a short title and a complete HTML document. When an artifact already exists and the user requests a change, call update_artifact with its id and only the fields that changed. Do not put artifact HTML in a code block.",
39
+ messages: await convertToModelMessages(
40
+ unstable_injectInteractableContext(messages),
41
+ ),
27
42
  stopWhen: stepCountIs(10),
28
- tools: {
29
- render_html: tool({
30
- description:
31
- "Render HTML code in the user's browser. Use this whenever the user asks for HTML, a webpage, a UI component, or any visual content.",
32
- inputSchema: zodSchema(
33
- z.object({
34
- code: z
35
- .string()
36
- .describe(
37
- "The complete HTML code to render, including inline CSS and JavaScript if needed.",
38
- ),
39
- }),
40
- ),
41
- execute: async () => {
42
- return { success: true };
43
- },
44
- }),
45
- },
43
+ tools,
46
44
  });
47
45
 
48
46
  return result.toUIMessageStreamResponse();
@@ -50,6 +48,391 @@ export async function POST(req: Request) {
50
48
 
51
49
  ```
52
50
 
51
+ ## app/artifact-state.ts
52
+
53
+ ```typescript
54
+ import { z } from "zod";
55
+
56
+ export const artifactSchema = z.object({
57
+ title: z.string().describe("A short title for the artifact."),
58
+ code: z
59
+ .string()
60
+ .describe(
61
+ "A complete HTML document with inline CSS and JavaScript when needed.",
62
+ ),
63
+ });
64
+
65
+ export type ArtifactState = z.infer<typeof artifactSchema>;
66
+
67
+ export const emptyArtifact: ArtifactState = {
68
+ title: "Untitled artifact",
69
+ code: "",
70
+ };
71
+
72
+ export const artifactDescription =
73
+ "Create an interactive HTML artifact when the user asks for a webpage, interface, visualization, or other visual content. Provide a short title and a complete HTML document.";
74
+
75
+ ```
76
+
77
+ ## app/artifact-surface.tsx
78
+
79
+ ```tsx
80
+ "use client";
81
+
82
+ import {
83
+ createContext,
84
+ useContext,
85
+ useEffect,
86
+ useMemo,
87
+ useRef,
88
+ useState,
89
+ type ReactNode,
90
+ } from "react";
91
+ import {
92
+ unstable_useInteractable,
93
+ unstable_useInteractableVersions,
94
+ useAuiState,
95
+ type Unstable_InteractableToolRenderProps,
96
+ } from "@assistant-ui/react";
97
+ import {
98
+ ArrowLeftIcon,
99
+ CheckIcon,
100
+ Code2Icon,
101
+ EyeIcon,
102
+ FileCode2Icon,
103
+ HistoryIcon,
104
+ Loader2Icon,
105
+ PanelRightOpenIcon,
106
+ SparklesIcon,
107
+ XIcon,
108
+ } from "lucide-react";
109
+ import {
110
+ artifactDescription,
111
+ artifactSchema,
112
+ emptyArtifact,
113
+ type ArtifactState,
114
+ } from "./artifact-state";
115
+
116
+ type ArtifactSurfaceTarget = {
117
+ id: string;
118
+ name: "artifact";
119
+ messageId: string;
120
+ };
121
+
122
+ type ArtifactSurfaceContextValue = {
123
+ active: ArtifactSurfaceTarget | null;
124
+ open: (target: ArtifactSurfaceTarget) => void;
125
+ close: () => void;
126
+ returnToOrigin: () => void;
127
+ };
128
+
129
+ const ArtifactSurfaceContext =
130
+ createContext<ArtifactSurfaceContextValue | null>(null);
131
+
132
+ export function ArtifactSurfaceProvider({ children }: { children: ReactNode }) {
133
+ const [active, setActive] = useState<ArtifactSurfaceTarget | null>(null);
134
+
135
+ const value = useMemo<ArtifactSurfaceContextValue>(
136
+ () => ({
137
+ active,
138
+ open: setActive,
139
+ close: () => setActive(null),
140
+ returnToOrigin: () => {
141
+ const messageId = active?.messageId;
142
+ setActive(null);
143
+ if (!messageId) return;
144
+ requestAnimationFrame(() => {
145
+ const message = Array.from(
146
+ document.querySelectorAll<HTMLElement>("[data-message-id]"),
147
+ ).find((element) => element.dataset.messageId === messageId);
148
+ message?.scrollIntoView({ behavior: "smooth", block: "center" });
149
+ });
150
+ },
151
+ }),
152
+ [active],
153
+ );
154
+
155
+ return (
156
+ <ArtifactSurfaceContext.Provider value={value}>
157
+ {children}
158
+ </ArtifactSurfaceContext.Provider>
159
+ );
160
+ }
161
+
162
+ export function useArtifactSurface() {
163
+ const value = useContext(ArtifactSurfaceContext);
164
+ if (!value) {
165
+ throw new Error(
166
+ "useArtifactSurface must be used within ArtifactSurfaceProvider",
167
+ );
168
+ }
169
+ return value;
170
+ }
171
+
172
+ export function ArtifactTrigger({
173
+ state,
174
+ version,
175
+ id,
176
+ streaming,
177
+ }: Unstable_InteractableToolRenderProps<ArtifactState>) {
178
+ const messageId = useAuiState((s) => s.message.id);
179
+ const { active, open } = useArtifactSurface();
180
+ const displayState = version && !version.isLatest ? version.state : state;
181
+ const title = displayState.title || "Untitled artifact";
182
+ const lineCount = displayState.code
183
+ ? displayState.code.split(/\r?\n/).length
184
+ : 0;
185
+ const isOpen = active?.id === id;
186
+
187
+ return (
188
+ <div className="bg-card my-3 overflow-hidden rounded-xl border shadow-[0_10px_30px_-22px_rgba(24,24,27,0.5)]">
189
+ <div className="bg-muted text-muted-foreground flex items-center gap-2 border-b px-3 py-2 font-mono text-[10px] tracking-[0.16em] uppercase">
190
+ <FileCode2Icon className="size-3.5" />
191
+ HTML artifact
192
+ {streaming ? (
193
+ <span className="ml-auto inline-flex items-center gap-1.5 text-amber-700">
194
+ <Loader2Icon className="size-3 animate-spin" />
195
+ Writing
196
+ </span>
197
+ ) : (
198
+ <span className="ml-auto inline-flex items-center gap-1.5 text-emerald-700">
199
+ <CheckIcon className="size-3" />
200
+ Ready
201
+ </span>
202
+ )}
203
+ </div>
204
+ <div className="flex items-center gap-4 p-3">
205
+ <div className="min-w-0 flex-1">
206
+ <p className="truncate text-sm font-semibold">{title}</p>
207
+ <p className="text-muted-foreground mt-0.5 text-xs">
208
+ {lineCount > 0 ? `${lineCount} lines` : "Preparing document"}
209
+ {version && !version.isLatest ? " · historical version" : ""}
210
+ </p>
211
+ </div>
212
+ <button
213
+ type="button"
214
+ data-testid="artifact-trigger"
215
+ disabled={streaming}
216
+ onClick={() =>
217
+ open({
218
+ id,
219
+ name: "artifact",
220
+ messageId,
221
+ })
222
+ }
223
+ className="bg-primary text-primary-foreground hover:bg-primary/90 inline-flex h-9 shrink-0 items-center gap-2 rounded-lg px-3 text-xs font-medium transition-colors disabled:cursor-wait disabled:opacity-50"
224
+ >
225
+ <PanelRightOpenIcon className="size-3.5" />
226
+ {isOpen ? "In workbench" : "Open workbench"}
227
+ </button>
228
+ </div>
229
+ </div>
230
+ );
231
+ }
232
+
233
+ export function ArtifactSurface() {
234
+ const { active } = useArtifactSurface();
235
+ if (!active) return null;
236
+ return <ArtifactWorkbench key={active.id} target={active} />;
237
+ }
238
+
239
+ function ArtifactWorkbench({ target }: { target: ArtifactSurfaceTarget }) {
240
+ const [state, { setState }] = unstable_useInteractable("artifact", {
241
+ id: target.id,
242
+ description: artifactDescription,
243
+ stateSchema: artifactSchema,
244
+ initialState: emptyArtifact,
245
+ });
246
+ const versions = unstable_useInteractableVersions<ArtifactState>(
247
+ target.id,
248
+ target.name,
249
+ );
250
+ const { close, returnToOrigin } = useArtifactSurface();
251
+ const [view, setView] = useState<"preview" | "code">("preview");
252
+ const [historyOpen, setHistoryOpen] = useState(false);
253
+ const historyRef = useRef<HTMLDivElement>(null);
254
+
255
+ useEffect(() => {
256
+ if (!historyOpen) return;
257
+ const dismissPointer = (event: PointerEvent) => {
258
+ if (!historyRef.current?.contains(event.target as Node)) {
259
+ setHistoryOpen(false);
260
+ }
261
+ };
262
+ const dismissKey = (event: KeyboardEvent) => {
263
+ if (event.key === "Escape") setHistoryOpen(false);
264
+ };
265
+ const dismissBlur = () => setHistoryOpen(false);
266
+ document.addEventListener("pointerdown", dismissPointer);
267
+ document.addEventListener("keydown", dismissKey);
268
+ window.addEventListener("blur", dismissBlur);
269
+ return () => {
270
+ document.removeEventListener("pointerdown", dismissPointer);
271
+ document.removeEventListener("keydown", dismissKey);
272
+ window.removeEventListener("blur", dismissBlur);
273
+ };
274
+ }, [historyOpen]);
275
+
276
+ return (
277
+ <aside
278
+ data-testid="artifact-surface"
279
+ className="bg-muted fixed inset-0 z-30 flex min-w-0 flex-1 flex-col md:static md:z-0 md:border-l"
280
+ >
281
+ <header className="border-border bg-background/90 flex h-14 shrink-0 items-center gap-3 border-b px-3 backdrop-blur md:px-5">
282
+ <div className="flex min-w-0 items-center gap-2">
283
+ <span className="grid size-7 shrink-0 place-items-center rounded-lg bg-amber-300 text-zinc-950 shadow-[inset_0_0_0_1px_rgba(24,24,27,0.12)]">
284
+ <SparklesIcon className="size-3.5" />
285
+ </span>
286
+ <div className="min-w-0">
287
+ <p className="truncate text-sm font-semibold">
288
+ {state.title || "Untitled artifact"}
289
+ </p>
290
+ <p className="text-muted-foreground font-mono text-[9px] tracking-[0.14em] uppercase">
291
+ Opened from conversation
292
+ </p>
293
+ </div>
294
+ </div>
295
+
296
+ <div className="ml-auto flex items-center gap-1.5">
297
+ <div className="relative" ref={historyRef}>
298
+ <button
299
+ type="button"
300
+ data-testid="artifact-version-select"
301
+ aria-label="Artifact version history"
302
+ aria-expanded={historyOpen}
303
+ onClick={() => setHistoryOpen((open) => !open)}
304
+ className="border-border bg-card text-foreground hover:bg-muted inline-flex h-8 items-center gap-1.5 rounded-md border px-2.5 text-xs font-medium"
305
+ >
306
+ <HistoryIcon className="size-3.5" />
307
+ {versions.length} {versions.length === 1 ? "version" : "versions"}
308
+ </button>
309
+ {historyOpen && (
310
+ <div className="border-border bg-card absolute top-9 right-0 z-10 flex w-44 flex-col overflow-hidden rounded-md border py-1 shadow-lg">
311
+ {versions.map((version, index) => (
312
+ <button
313
+ key={`${version.origin}-${index}`}
314
+ type="button"
315
+ onClick={() => {
316
+ version.restore();
317
+ setHistoryOpen(false);
318
+ }}
319
+ className="text-foreground hover:bg-muted flex items-center justify-between px-3 py-1.5 text-left text-xs"
320
+ >
321
+ <span>
322
+ v{index + 1} ·{" "}
323
+ {version.origin === "user-edit" ? "you" : "assistant"}
324
+ </span>
325
+ {index === versions.length - 1 && (
326
+ <span className="text-muted-foreground font-mono text-[9px] uppercase">
327
+ latest
328
+ </span>
329
+ )}
330
+ </button>
331
+ ))}
332
+ </div>
333
+ )}
334
+ </div>
335
+ <button
336
+ type="button"
337
+ data-testid="artifact-return"
338
+ aria-label="Return to conversation"
339
+ onClick={returnToOrigin}
340
+ className="border-border bg-card text-foreground hover:bg-muted inline-flex h-8 items-center gap-1.5 rounded-md border px-2.5 text-xs font-medium"
341
+ >
342
+ <ArrowLeftIcon className="size-3.5" />
343
+ <span className="hidden sm:inline">Conversation</span>
344
+ </button>
345
+ <button
346
+ type="button"
347
+ data-testid="artifact-close"
348
+ onClick={close}
349
+ className="text-muted-foreground hover:bg-muted hover:text-foreground grid size-8 place-items-center rounded-md"
350
+ aria-label="Close artifact"
351
+ >
352
+ <XIcon className="size-4" />
353
+ </button>
354
+ </div>
355
+ </header>
356
+
357
+ <div className="flex min-h-0 flex-1 flex-col p-2 md:p-4">
358
+ <div className="border-border bg-card flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl border shadow-[0_24px_70px_-44px_rgba(24,24,27,0.55)]">
359
+ <div className="bg-muted/80 flex shrink-0 items-center gap-1 border-b p-1.5">
360
+ <button
361
+ type="button"
362
+ aria-pressed={view === "preview"}
363
+ onClick={() => setView("preview")}
364
+ className={`inline-flex h-8 items-center gap-2 rounded-md px-3 text-xs font-medium transition-colors ${
365
+ view === "preview"
366
+ ? "bg-card text-foreground ring-border shadow-sm ring-1"
367
+ : "text-muted-foreground hover:text-foreground"
368
+ }`}
369
+ >
370
+ <EyeIcon className="size-3.5" />
371
+ Preview
372
+ </button>
373
+ <button
374
+ type="button"
375
+ aria-pressed={view === "code"}
376
+ onClick={() => setView("code")}
377
+ className={`inline-flex h-8 items-center gap-2 rounded-md px-3 text-xs font-medium transition-colors ${
378
+ view === "code"
379
+ ? "bg-card text-foreground ring-border shadow-sm ring-1"
380
+ : "text-muted-foreground hover:text-foreground"
381
+ }`}
382
+ >
383
+ <Code2Icon className="size-3.5" />
384
+ Source
385
+ </button>
386
+ <span className="text-muted-foreground ml-auto pr-2 font-mono text-[9px] tracking-[0.14em] uppercase">
387
+ Live document
388
+ </span>
389
+ </div>
390
+
391
+ {view === "preview" ? (
392
+ <iframe
393
+ data-testid="artifact-preview"
394
+ title={`Preview of ${state.title || "artifact"}`}
395
+ srcDoc={state.code}
396
+ sandbox="allow-scripts allow-forms"
397
+ className="min-h-0 flex-1 bg-white"
398
+ />
399
+ ) : (
400
+ <div className="flex min-h-0 flex-1 flex-col">
401
+ <input
402
+ aria-label="Artifact title"
403
+ value={state.title}
404
+ onChange={(event) =>
405
+ setState((previous) => ({
406
+ ...previous,
407
+ title: event.target.value,
408
+ }))
409
+ }
410
+ className="placeholder:text-muted-foreground h-11 shrink-0 border-b px-4 text-sm font-semibold outline-none"
411
+ placeholder="Artifact title"
412
+ />
413
+ <textarea
414
+ data-testid="artifact-code-editor"
415
+ aria-label="Artifact HTML source"
416
+ value={state.code}
417
+ onChange={(event) =>
418
+ setState((previous) => ({
419
+ ...previous,
420
+ code: event.target.value,
421
+ }))
422
+ }
423
+ spellCheck={false}
424
+ className="min-h-0 flex-1 resize-none bg-zinc-950 p-4 font-mono text-[13px] leading-6 text-zinc-100 outline-none selection:bg-amber-300 selection:text-zinc-950"
425
+ />
426
+ </div>
427
+ )}
428
+ </div>
429
+ </div>
430
+ </aside>
431
+ );
432
+ }
433
+
434
+ ```
435
+
53
436
  ## app/globals.css
54
437
 
55
438
  ```css
@@ -211,90 +594,46 @@ export default function RootLayout({
211
594
  import { Thread } from "@/components/assistant-ui/thread";
212
595
  import {
213
596
  AssistantRuntimeProvider,
214
- Tools,
215
- useAui,
216
- useAuiState,
217
597
  AuiProvider,
598
+ AuiConfig,
218
599
  Suggestions,
600
+ Tools,
601
+ unstable_Interactables,
602
+ useAui,
219
603
  } from "@assistant-ui/react";
220
604
  import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
221
- import type { ToolCallMessagePart } from "@assistant-ui/react";
222
- import { CodeIcon, EyeIcon } from "lucide-react";
223
- import { useState } from "react";
605
+ import { lastAssistantMessageIsCompleteWithToolCalls } from "ai";
606
+ import {
607
+ ArtifactSurface,
608
+ ArtifactSurfaceProvider,
609
+ useArtifactSurface,
610
+ } from "./artifact-surface";
224
611
  import toolkit from "./toolkit";
225
612
 
226
- function ArtifactsView() {
227
- const [tab, setTab] = useState<"source" | "preview">("source");
228
-
229
- const lastToolCall = useAuiState((s) => {
230
- const messages = s.thread.messages;
231
- return messages
232
- .flatMap((m) =>
233
- m.content.filter(
234
- (c): c is ToolCallMessagePart =>
235
- c.type === "tool-call" && c.toolName === "render_html",
236
- ),
237
- )
238
- .at(-1);
239
- });
240
-
241
- const code = lastToolCall?.args.code as string | undefined;
242
- const isComplete = lastToolCall?.result !== undefined;
243
-
244
- if (!code) return null;
613
+ function ArtifactExperience() {
614
+ const { active } = useArtifactSurface();
245
615
 
246
616
  return (
247
- <div className="flex flex-grow basis-full justify-stretch p-3">
248
- <div className="flex h-full w-full flex-col overflow-hidden rounded-lg border">
249
- <div className="flex border-b">
250
- <button
251
- type="button"
252
- onClick={() => setTab("source")}
253
- className={`inline-flex flex-1 items-center justify-center gap-2 px-4 py-2.5 text-sm font-medium transition-colors ${
254
- tab === "source"
255
- ? "bg-background text-foreground"
256
- : "text-muted-foreground hover:text-foreground"
257
- }`}
258
- >
259
- <CodeIcon className="size-4" />
260
- Source Code
261
- </button>
262
- <button
263
- type="button"
264
- onClick={() => isComplete && setTab("preview")}
265
- disabled={!isComplete}
266
- className={`inline-flex flex-1 items-center justify-center gap-2 px-4 py-2.5 text-sm font-medium transition-colors ${
267
- !isComplete
268
- ? "cursor-not-allowed opacity-50"
269
- : tab === "preview"
270
- ? "bg-background text-foreground"
271
- : "text-muted-foreground hover:text-foreground"
272
- }`}
273
- >
274
- <EyeIcon className="size-4" />
275
- Preview
276
- </button>
277
- </div>
278
- {tab === "source" || !isComplete ? (
279
- <div className="h-full overflow-y-auto px-4 py-2 font-mono text-sm break-words whitespace-pre-line">
280
- {code}
281
- </div>
282
- ) : (
283
- <div className="flex h-full flex-grow px-4 py-2">
284
- <iframe
285
- className="h-full w-full"
286
- title="Artifact Preview"
287
- srcDoc={code}
288
- />
289
- </div>
290
- )}
291
- </div>
292
- </div>
617
+ <main className="bg-background flex h-full min-h-0 overflow-hidden">
618
+ <section
619
+ className={
620
+ active
621
+ ? "bg-background hidden min-w-0 md:block md:w-[min(44vw,42rem)] md:shrink-0"
622
+ : "bg-background min-w-0 flex-1"
623
+ }
624
+ >
625
+ <Thread />
626
+ </section>
627
+ <ArtifactSurface />
628
+ </main>
293
629
  );
294
630
  }
295
631
 
296
- function ThreadWithSuggestions() {
297
- const aui = useAui({
632
+ function ArtifactScopes() {
633
+ const aui = useAui();
634
+ const config = AuiConfig({
635
+ tools: Tools({ toolkit }),
636
+ unstable_interactables: unstable_Interactables(),
298
637
  suggestions: Suggestions([
299
638
  {
300
639
  title: "Build a landing page",
@@ -310,27 +649,24 @@ function ThreadWithSuggestions() {
310
649
  },
311
650
  ]),
312
651
  });
652
+
313
653
  return (
314
- <AuiProvider value={aui}>
315
- <Thread />
654
+ <AuiProvider extends={aui} config={config}>
655
+ <ArtifactSurfaceProvider>
656
+ <ArtifactExperience />
657
+ </ArtifactSurfaceProvider>
316
658
  </AuiProvider>
317
659
  );
318
660
  }
319
661
 
320
662
  export default function Home() {
321
- const runtime = useChatRuntime();
322
- const aui = useAui({
323
- tools: Tools({ toolkit }),
663
+ const runtime = useChatRuntime({
664
+ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
324
665
  });
325
666
 
326
667
  return (
327
- <AssistantRuntimeProvider aui={aui} runtime={runtime}>
328
- <main className="flex h-full justify-stretch">
329
- <div className="flex-grow basis-full">
330
- <ThreadWithSuggestions />
331
- </div>
332
- <ArtifactsView />
333
- </main>
668
+ <AssistantRuntimeProvider runtime={runtime}>
669
+ <ArtifactScopes />
334
670
  </AssistantRuntimeProvider>
335
671
  );
336
672
  }
@@ -342,25 +678,16 @@ export default function Home() {
342
678
  ```tsx
343
679
  "use generative";
344
680
 
345
- import { defineToolkit, externalTool } from "@assistant-ui/react";
346
- import { TerminalIcon } from "lucide-react";
347
- import { z } from "zod";
681
+ import { defineToolkit, unstable_interactableTool } from "@assistant-ui/react";
682
+ import { ArtifactTrigger } from "./artifact-surface";
683
+ import { artifactDescription, artifactSchema } from "./artifact-state";
348
684
 
349
685
  export default defineToolkit({
350
- render_html: {
351
- parameters: z.object({
352
- code: z.string(),
353
- }),
354
- execute: externalTool(),
355
- render: () => {
356
- return (
357
- <div className="bg-primary text-primary-foreground my-2 inline-flex items-center gap-2 rounded-full border px-4 py-2">
358
- <TerminalIcon className="size-4" />
359
- render_html(&#123; code: &quot;...&quot; &#125;)
360
- </div>
361
- );
362
- },
363
- },
686
+ artifact: unstable_interactableTool({
687
+ description: artifactDescription,
688
+ stateSchema: artifactSchema,
689
+ render: (props) => <ArtifactTrigger {...props} />,
690
+ }),
364
691
  });
365
692
 
366
693
  ```
@@ -426,33 +753,32 @@ export default withAui(nextConfig);
426
753
  "start": "next start"
427
754
  },
428
755
  "dependencies": {
429
- "@ai-sdk/openai": "^4.0.14",
756
+ "@ai-sdk/openai": "^4.0.34",
430
757
  "@assistant-ui/react": "workspace:*",
431
758
  "@assistant-ui/react-ai-sdk": "workspace:*",
432
759
  "@assistant-ui/react-markdown": "workspace:*",
433
760
  "@assistant-ui/ui": "workspace:*",
434
- "ai": "^7.0.28",
761
+ "ai": "^7.0.56",
435
762
  "class-variance-authority": "^0.7.1",
436
763
  "clsx": "^2.1.1",
437
- "lucide-react": "^1.24.0",
438
- "next": "^16.2.10",
439
- "react": "^19.2.7",
440
- "react-dom": "^19.2.7",
764
+ "lucide-react": "^1.29.0",
765
+ "next": "^16.3.0",
766
+ "react": "^19.2.8",
767
+ "react-dom": "^19.2.8",
441
768
  "tailwind-merge": "^3.6.0",
442
769
  "zod": "^4.4.3"
443
770
  },
444
771
  "devDependencies": {
445
772
  "@assistant-ui/next": "workspace:*",
446
773
  "@assistant-ui/x-buildutils": "workspace:*",
447
- "@tailwindcss/postcss": "^4.3.2",
448
- "@types/node": "^26.1.1",
449
- "@types/react": "^19.2.17",
450
- "@types/react-dom": "^19.2.3",
451
- "postcss": "^8.5.19",
452
- "tailwindcss": "^4.3.2",
774
+ "@tailwindcss/postcss": "^4.3.3",
775
+ "@types/node": "^26.1.2",
776
+ "@types/react": "^19.2.18",
777
+ "@types/react-dom": "^19.2.4",
778
+ "postcss": "^8.5.26",
779
+ "tailwindcss": "^4.3.3",
453
780
  "tw-animate-css": "^1.4.0",
454
- "typescript": "npm:@typescript/typescript6@^6.0.2",
455
- "typescript-7": "npm:typescript@^7.0.2"
781
+ "typescript": "^7.0.2"
456
782
  }
457
783
  }
458
784