@assistant-ui/ai-sdk 0.0.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 (167) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -0
  3. package/dist/adapters/aiSDKFormatAdapter.d.ts +8 -0
  4. package/dist/adapters/aiSDKFormatAdapter.d.ts.map +1 -0
  5. package/dist/adapters/aiSDKFormatAdapter.js +26 -0
  6. package/dist/adapters/aiSDKFormatAdapter.js.map +1 -0
  7. package/dist/adapters/vercelAttachmentAdapter.d.ts +6 -0
  8. package/dist/adapters/vercelAttachmentAdapter.d.ts.map +1 -0
  9. package/dist/adapters/vercelAttachmentAdapter.js +37 -0
  10. package/dist/adapters/vercelAttachmentAdapter.js.map +1 -0
  11. package/dist/aiSDKExtras.d.ts +11 -0
  12. package/dist/aiSDKExtras.d.ts.map +1 -0
  13. package/dist/aiSDKExtras.js +7 -0
  14. package/dist/aiSDKExtras.js.map +1 -0
  15. package/dist/converters/convertMessage.d.ts +28 -0
  16. package/dist/converters/convertMessage.d.ts.map +1 -0
  17. package/dist/converters/convertMessage.js +249 -0
  18. package/dist/converters/convertMessage.js.map +1 -0
  19. package/dist/converters/modelContentEnvelope.d.ts +16 -0
  20. package/dist/converters/modelContentEnvelope.d.ts.map +1 -0
  21. package/dist/converters/modelContentEnvelope.js +22 -0
  22. package/dist/converters/modelContentEnvelope.js.map +1 -0
  23. package/dist/converters/toCreateMessage.d.ts +7 -0
  24. package/dist/converters/toCreateMessage.d.ts.map +1 -0
  25. package/dist/converters/toCreateMessage.js +58 -0
  26. package/dist/converters/toCreateMessage.js.map +1 -0
  27. package/dist/converters/toolOutputConversion.d.ts +29 -0
  28. package/dist/converters/toolOutputConversion.d.ts.map +1 -0
  29. package/dist/converters/toolOutputConversion.js +31 -0
  30. package/dist/converters/toolOutputConversion.js.map +1 -0
  31. package/dist/hooks.d.ts +19 -0
  32. package/dist/hooks.d.ts.map +1 -0
  33. package/dist/hooks.js +20 -0
  34. package/dist/hooks.js.map +1 -0
  35. package/dist/index.d.ts +14 -0
  36. package/dist/index.js +13 -0
  37. package/dist/index.native.d.ts +12 -0
  38. package/dist/index.native.js +11 -0
  39. package/dist/model-context/injectInteractableContext.d.ts +37 -0
  40. package/dist/model-context/injectInteractableContext.d.ts.map +1 -0
  41. package/dist/model-context/injectInteractableContext.js +52 -0
  42. package/dist/model-context/injectInteractableContext.js.map +1 -0
  43. package/dist/model-context/injectQuoteContext.d.ts +27 -0
  44. package/dist/model-context/injectQuoteContext.d.ts.map +1 -0
  45. package/dist/model-context/injectQuoteContext.js +51 -0
  46. package/dist/model-context/injectQuoteContext.js.map +1 -0
  47. package/dist/runtime/AISDKChat.d.ts +16 -0
  48. package/dist/runtime/AISDKChat.d.ts.map +1 -0
  49. package/dist/runtime/AISDKChat.js +69 -0
  50. package/dist/runtime/AISDKChat.js.map +1 -0
  51. package/dist/runtime/AISDKThreads.d.ts +45 -0
  52. package/dist/runtime/AISDKThreads.d.ts.map +1 -0
  53. package/dist/runtime/AISDKThreads.js +203 -0
  54. package/dist/runtime/AISDKThreads.js.map +1 -0
  55. package/dist/runtime/useAISDKRuntime.d.ts +49 -0
  56. package/dist/runtime/useAISDKRuntime.d.ts.map +1 -0
  57. package/dist/runtime/useAISDKRuntime.js +266 -0
  58. package/dist/runtime/useAISDKRuntime.js.map +1 -0
  59. package/dist/runtime/useChatRuntime.d.ts +13 -0
  60. package/dist/runtime/useChatRuntime.d.ts.map +1 -0
  61. package/dist/runtime/useChatRuntime.js +52 -0
  62. package/dist/runtime/useChatRuntime.js.map +1 -0
  63. package/dist/runtime/useChatThread.d.ts +77 -0
  64. package/dist/runtime/useChatThread.d.ts.map +1 -0
  65. package/dist/runtime/useChatThread.js +134 -0
  66. package/dist/runtime/useChatThread.js.map +1 -0
  67. package/dist/runtime/useExternalHistory.d.ts +11 -0
  68. package/dist/runtime/useExternalHistory.d.ts.map +1 -0
  69. package/dist/runtime/useExternalHistory.js +278 -0
  70. package/dist/runtime/useExternalHistory.js.map +1 -0
  71. package/dist/runtime/useResourceCleanup.d.ts +5 -0
  72. package/dist/runtime/useResourceCleanup.d.ts.map +1 -0
  73. package/dist/runtime/useResourceCleanup.js +47 -0
  74. package/dist/runtime/useResourceCleanup.js.map +1 -0
  75. package/dist/runtime/useStreamingTiming.d.ts +15 -0
  76. package/dist/runtime/useStreamingTiming.d.ts.map +1 -0
  77. package/dist/runtime/useStreamingTiming.js +38 -0
  78. package/dist/runtime/useStreamingTiming.js.map +1 -0
  79. package/dist/tools/frontendTools.d.ts +33 -0
  80. package/dist/tools/frontendTools.d.ts.map +1 -0
  81. package/dist/tools/frontendTools.js +33 -0
  82. package/dist/tools/frontendTools.js.map +1 -0
  83. package/dist/tools/generativeTools.d.ts +73 -0
  84. package/dist/tools/generativeTools.d.ts.map +1 -0
  85. package/dist/tools/generativeTools.js +245 -0
  86. package/dist/tools/generativeTools.js.map +1 -0
  87. package/dist/tools/mcp-stdio.node.d.ts +2 -0
  88. package/dist/tools/mcp-stdio.node.js +2 -0
  89. package/dist/tools/mcp-stdio.unsupported.d.ts +6 -0
  90. package/dist/tools/mcp-stdio.unsupported.d.ts.map +1 -0
  91. package/dist/tools/mcp-stdio.unsupported.js +11 -0
  92. package/dist/tools/mcp-stdio.unsupported.js.map +1 -0
  93. package/dist/transport/AssistantChatTransport.d.ts +23 -0
  94. package/dist/transport/AssistantChatTransport.d.ts.map +1 -0
  95. package/dist/transport/AssistantChatTransport.js +134 -0
  96. package/dist/transport/AssistantChatTransport.js.map +1 -0
  97. package/dist/transport/resumable.d.ts +38 -0
  98. package/dist/transport/resumable.d.ts.map +1 -0
  99. package/dist/transport/resumable.js +113 -0
  100. package/dist/transport/resumable.js.map +1 -0
  101. package/dist/usage.d.ts +19 -0
  102. package/dist/usage.d.ts.map +1 -0
  103. package/dist/usage.js +127 -0
  104. package/dist/usage.js.map +1 -0
  105. package/dist/utils/getVercelAIMessages.d.ts +7 -0
  106. package/dist/utils/getVercelAIMessages.d.ts.map +1 -0
  107. package/dist/utils/getVercelAIMessages.js +9 -0
  108. package/dist/utils/getVercelAIMessages.js.map +1 -0
  109. package/dist/utils/sliceMessagesUntil.d.ts +6 -0
  110. package/dist/utils/sliceMessagesUntil.d.ts.map +1 -0
  111. package/dist/utils/sliceMessagesUntil.js +12 -0
  112. package/dist/utils/sliceMessagesUntil.js.map +1 -0
  113. package/package.json +100 -0
  114. package/src/__tests__/standalone.e2e.ts +59 -0
  115. package/src/adapters/aiSDKFormatAdapter.ts +41 -0
  116. package/src/adapters/vercelAttachmentAdapter.test.ts +29 -0
  117. package/src/adapters/vercelAttachmentAdapter.ts +36 -0
  118. package/src/aiSDKExtras.ts +11 -0
  119. package/src/converters/convertMessage.test.ts +1130 -0
  120. package/src/converters/convertMessage.ts +457 -0
  121. package/src/converters/modelContentEnvelope.ts +41 -0
  122. package/src/converters/toCreateMessage.test.ts +596 -0
  123. package/src/converters/toCreateMessage.ts +99 -0
  124. package/src/converters/toolOutputConversion.ts +26 -0
  125. package/src/hooks.ts +23 -0
  126. package/src/index.native.ts +21 -0
  127. package/src/index.ts +29 -0
  128. package/src/model-context/injectInteractableContext.test.ts +167 -0
  129. package/src/model-context/injectInteractableContext.ts +61 -0
  130. package/src/model-context/injectQuoteContext.test.ts +33 -0
  131. package/src/model-context/injectQuoteContext.ts +62 -0
  132. package/src/runtime/AISDKChat.integration.test.tsx +47 -0
  133. package/src/runtime/AISDKChat.react-compiler.test.ts +49 -0
  134. package/src/runtime/AISDKChat.test.ts +193 -0
  135. package/src/runtime/AISDKChat.ts +47 -0
  136. package/src/runtime/AISDKThreads.cloud.test.ts +136 -0
  137. package/src/runtime/AISDKThreads.test.ts +548 -0
  138. package/src/runtime/AISDKThreads.ts +223 -0
  139. package/src/runtime/__tests__/controlled-transport.ts +43 -0
  140. package/src/runtime/useAISDKRuntime.approval.test.tsx +69 -0
  141. package/src/runtime/useAISDKRuntime.test.ts +1021 -0
  142. package/src/runtime/useAISDKRuntime.tool-output.test.tsx +113 -0
  143. package/src/runtime/useAISDKRuntime.ts +524 -0
  144. package/src/runtime/useChatOnTap.test.ts +58 -0
  145. package/src/runtime/useChatRuntime.integration.test.tsx +67 -0
  146. package/src/runtime/useChatRuntime.test.ts +522 -0
  147. package/src/runtime/useChatRuntime.ts +49 -0
  148. package/src/runtime/useChatThread.ts +286 -0
  149. package/src/runtime/useExternalHistory.test.ts +1289 -0
  150. package/src/runtime/useExternalHistory.ts +435 -0
  151. package/src/runtime/useResourceCleanup.ts +32 -0
  152. package/src/runtime/useStreamingTiming.ts +68 -0
  153. package/src/tools/frontendTools.test.ts +205 -0
  154. package/src/tools/frontendTools.ts +83 -0
  155. package/src/tools/generativeTools.test.ts +785 -0
  156. package/src/tools/generativeTools.ts +470 -0
  157. package/src/tools/mcp-stdio.node.ts +1 -0
  158. package/src/tools/mcp-stdio.unsupported.ts +12 -0
  159. package/src/transport/AssistantChatTransport.test.ts +300 -0
  160. package/src/transport/AssistantChatTransport.ts +204 -0
  161. package/src/transport/resumable.test.ts +262 -0
  162. package/src/transport/resumable.ts +161 -0
  163. package/src/usage.test.ts +186 -0
  164. package/src/usage.ts +174 -0
  165. package/src/utils/getVercelAIMessages.ts +11 -0
  166. package/src/utils/sliceMessagesUntil.test.ts +55 -0
  167. package/src/utils/sliceMessagesUntil.ts +20 -0
@@ -0,0 +1,470 @@
1
+ import { jsonSchema, type ToolSet } from "ai";
2
+ import type { MCPClient, MCPClientConfig } from "@ai-sdk/mcp";
3
+ import { createMCPClient } from "@ai-sdk/mcp";
4
+ import { Experimental_StdioMCPTransport } from "#mcp-stdio";
5
+ import {
6
+ toJSONSchema,
7
+ type Tool,
8
+ type McpServerConfig,
9
+ type ToolModelOutputFunction,
10
+ } from "assistant-stream";
11
+ import type {
12
+ McpToolkitToolConfig,
13
+ Toolkit,
14
+ ToolkitDefinition,
15
+ } from "@assistant-ui/core/react";
16
+ import { frontendTools, type FrontendTools } from "./frontendTools";
17
+ import {
18
+ toAISDKContent,
19
+ toAISDKDefaultOutput,
20
+ } from "../converters/toolOutputConversion";
21
+ import {
22
+ unwrapModelContentEnvelope,
23
+ type ModelContentEnvelope,
24
+ } from "../converters/modelContentEnvelope";
25
+
26
+ const EMPTY_SCHEMA = { type: "object" as const, properties: {} };
27
+
28
+ const humanNotSupported = (): never => {
29
+ throw new Error(
30
+ "`human()` is not available during server-side tool execution.",
31
+ );
32
+ };
33
+
34
+ // AI SDK leaves `abortSignal` optional; assistant-ui's execute requires one.
35
+ const neverAbort = new AbortController().signal;
36
+
37
+ type MCPConnectionTimeoutPhase = "connecting" | "listing tools";
38
+
39
+ class MCPConnectionTimeoutError extends Error {}
40
+
41
+ const createMcpConnectionTimeoutError = (
42
+ name: string,
43
+ phase: MCPConnectionTimeoutPhase,
44
+ timeoutMs: number,
45
+ ) =>
46
+ new MCPConnectionTimeoutError(
47
+ `MCP toolkit entry "${name}" timed out while ${phase} after ${timeoutMs}ms.`,
48
+ );
49
+
50
+ const withMcpConnectionTimeout = async <T>(
51
+ promise: Promise<T>,
52
+ options: {
53
+ name: string;
54
+ config: McpServerConfig;
55
+ phase: MCPConnectionTimeoutPhase;
56
+ startedAt: number;
57
+ },
58
+ ): Promise<T> => {
59
+ const timeoutMs = options.config.connectionTimeout;
60
+ if (timeoutMs === undefined) return await promise;
61
+ const remainingMs = timeoutMs - (Date.now() - options.startedAt);
62
+ const timeoutError = () =>
63
+ createMcpConnectionTimeoutError(options.name, options.phase, timeoutMs);
64
+ if (remainingMs <= 0) throw timeoutError();
65
+
66
+ let timeout: ReturnType<typeof setTimeout> | undefined;
67
+ try {
68
+ return await Promise.race([
69
+ promise,
70
+ new Promise<never>((_, reject) => {
71
+ timeout = setTimeout(() => reject(timeoutError()), remainingMs);
72
+ }),
73
+ ]);
74
+ } finally {
75
+ if (timeout !== undefined) clearTimeout(timeout);
76
+ }
77
+ };
78
+
79
+ const parametersToInputSchema = (parameters: Tool["parameters"] | undefined) =>
80
+ jsonSchema(parameters ? toJSONSchema(parameters) : EMPTY_SCHEMA);
81
+
82
+ /**
83
+ * @deprecated Options for the deprecated {@link generativeTools}. Use
84
+ * {@link AISDKToolkit} with {@link AISDKToolkitOptions} /
85
+ * {@link AISDKToolkitToolsOptions} instead.
86
+ */
87
+ export interface GenerativeToolsOptions {
88
+ /**
89
+ * The server build of a generative toolkit (schema + server `execute`). Typed
90
+ * as the canonical {@link Toolkit} so callers don't need to cast; the server
91
+ * build carries `execute`, recovered internally as {@link ToolkitDefinition}.
92
+ */
93
+ toolkit: Toolkit;
94
+ /**
95
+ * Tools uploaded by the frontend (the request body's `tools`). Merged in
96
+ * alongside the `toolkit`; a server `execute` from `toolkit` takes precedence
97
+ * over an uploaded entry of the same name.
98
+ */
99
+ frontendTools?: FrontendTools;
100
+ }
101
+
102
+ export type AISDKToolkitOptions = {
103
+ toolkit: Toolkit;
104
+ };
105
+
106
+ export type AISDKToolkitToolsOptions = {
107
+ /**
108
+ * Tools uploaded by the frontend request body.
109
+ */
110
+ frontend?: FrontendTools;
111
+ };
112
+
113
+ /**
114
+ * Builds an AI SDK `ToolSet` for server-side use with `streamText` /
115
+ * `generateText` from a generative `toolkit` and the frontend-uploaded tools.
116
+ *
117
+ * Each toolkit tool's `execute` runs on the server. Pair this with the
118
+ * `"use generative"` compiler: import the toolkit in a server route (where it
119
+ * resolves to the server build — schema + `execute`, with `render` stripped) and
120
+ * pass it here. Tools without an `execute` are still exposed to the model but
121
+ * left for the client to fulfill. `frontendTools` lets the client contribute
122
+ * tools that aren't in the static toolkit.
123
+ *
124
+ * @deprecated Use {@link AISDKToolkit} instead:
125
+ * `new AISDKToolkit({ toolkit }).tools({ frontend })`. It is a strict superset
126
+ * (it also opens MCP server connections), so it replaces `generativeTools`
127
+ * everywhere. The `frontendTools` option is named `frontend` on `.tools()`, and
128
+ * `.tools()` is async. `generativeTools` will be removed in a future version.
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * // Define once at module scope so any MCP connections pool across requests.
133
+ * const aiToolkit = new AISDKToolkit({ toolkit: docsToolkit });
134
+ *
135
+ * // In your route handler:
136
+ * const { tools } = await req.json();
137
+ * streamText({
138
+ * model,
139
+ * messages,
140
+ * tools: await aiToolkit.tools({ frontend: tools }),
141
+ * });
142
+ * ```
143
+ */
144
+ export const generativeTools = (options: GenerativeToolsOptions): ToolSet => {
145
+ assertNoMcpToolkitTools(options.toolkit);
146
+ return {
147
+ ...(options.frontendTools ? frontendTools(options.frontendTools) : {}),
148
+ // `toolkit` last so its server-side `execute` wins over an uploaded entry of
149
+ // the same name. The cast recovers the declaration shape — the server build
150
+ // carries `execute`, which the canonical `Toolkit` type erases.
151
+ ...toProviderToolSet(options.toolkit),
152
+ ...toServerToolSet(options.toolkit as ToolkitDefinition),
153
+ };
154
+ };
155
+
156
+ export class AISDKToolkit {
157
+ readonly #toolkit: Toolkit;
158
+ readonly #mcpClients = new Map<string, Promise<MCPClient>>();
159
+
160
+ constructor(options: AISDKToolkitOptions) {
161
+ this.#toolkit = options.toolkit;
162
+ }
163
+
164
+ async tools(options: AISDKToolkitToolsOptions = {}): Promise<ToolSet> {
165
+ const frontendToolSet = options.frontend
166
+ ? frontendTools(options.frontend)
167
+ : {};
168
+ const mcpToolSet = await this.#mcpTools();
169
+ const providerToolSet = toProviderToolSet(this.#toolkit);
170
+ const serverToolSet = toServerToolSet(this.#toolkit as ToolkitDefinition);
171
+
172
+ assertNoMcpToolNameCollisions(mcpToolSet, [
173
+ { source: "frontend", tools: frontendToolSet },
174
+ { source: "provider", tools: providerToolSet },
175
+ { source: "toolkit", tools: serverToolSet },
176
+ ]);
177
+
178
+ return {
179
+ ...frontendToolSet,
180
+ ...mcpToolSet.tools,
181
+ ...providerToolSet,
182
+ ...serverToolSet,
183
+ };
184
+ }
185
+
186
+ async close(): Promise<void> {
187
+ const clientEntries = [...this.#mcpClients.entries()];
188
+ const clientNames = clientEntries.map(([name]) => name);
189
+ this.#mcpClients.clear();
190
+ const clientResults = await Promise.allSettled(
191
+ clientEntries.map(([, clientPromise]) => clientPromise),
192
+ );
193
+ const clients = clientResults.flatMap((result, index) =>
194
+ result.status === "fulfilled"
195
+ ? [[clientNames[index]!, result.value] as const]
196
+ : [],
197
+ );
198
+ const closeResults = await Promise.allSettled(
199
+ clients.map(([, client]) => client.close()),
200
+ );
201
+ const errors = [
202
+ ...clientResults.flatMap((result, index) =>
203
+ result.status === "rejected"
204
+ ? [toMcpToolkitError(clientNames[index]!, "connect", result.reason)]
205
+ : [],
206
+ ),
207
+ ...closeResults.flatMap((result, index) =>
208
+ result.status === "rejected"
209
+ ? [toMcpToolkitError(clients[index]![0], "close", result.reason)]
210
+ : [],
211
+ ),
212
+ ];
213
+ if (errors.length === 1) throw errors[0];
214
+ if (errors.length > 1) {
215
+ throw new AggregateError(
216
+ errors,
217
+ "Failed to close one or more MCP clients",
218
+ );
219
+ }
220
+ }
221
+
222
+ async #mcpTools(): Promise<McpToolSet> {
223
+ const toolSets = await Promise.all(
224
+ Object.entries(this.#toolkit)
225
+ .filter((entry): entry is [string, McpToolkitTool] =>
226
+ isMcpToolkitTool(entry[1]),
227
+ )
228
+ .map(async ([name, tool]) => {
229
+ const startedAt = Date.now();
230
+ const client = await this.#mcpClient(
231
+ name,
232
+ tool.server,
233
+ startedAt,
234
+ ).catch((error: unknown) => {
235
+ if (error instanceof MCPConnectionTimeoutError) throw error;
236
+ throw toMcpToolkitError(name, "connect", error);
237
+ });
238
+ try {
239
+ const tools = await withMcpConnectionTimeout(client.tools(), {
240
+ name,
241
+ config: tool.server,
242
+ phase: "listing tools",
243
+ startedAt,
244
+ });
245
+ return [name, tool, tools] as const;
246
+ } catch (error) {
247
+ if (error instanceof MCPConnectionTimeoutError) {
248
+ this.#mcpClients.delete(name);
249
+ void client.close().catch(() => {});
250
+ throw error;
251
+ }
252
+ throw toMcpToolkitError(name, "list tools", error);
253
+ }
254
+ }),
255
+ );
256
+
257
+ const tools: ToolSet = {};
258
+ const toolSources = new Map<string, string>();
259
+ for (const [serverName, mcpTool, toolSet] of toolSets) {
260
+ for (const [toolName, tool] of Object.entries(toolSet)) {
261
+ if (isDisabledMcpTool(mcpTool.tools?.[toolName])) continue;
262
+ const exposedName = `${mcpTool.prefix ?? ""}${toolName}`;
263
+ const existingServerName = toolSources.get(exposedName);
264
+ if (existingServerName) {
265
+ throw new Error(
266
+ `MCP tool name collision: "${exposedName}" is exposed by both "${existingServerName}" and "${serverName}". Rename one of the toolkit entries or expose distinct MCP tool names.`,
267
+ );
268
+ }
269
+ toolSources.set(exposedName, serverName);
270
+ tools[exposedName] = tool as ToolSet[string];
271
+ }
272
+ }
273
+ return { tools, sources: toolSources };
274
+ }
275
+
276
+ #mcpClient(
277
+ name: string,
278
+ config: McpServerConfig,
279
+ startedAt: number,
280
+ ): Promise<MCPClient> {
281
+ const existing = this.#mcpClients.get(name);
282
+ if (existing) return existing;
283
+ const createPromise = createMCPClient(toMCPClientConfig(config));
284
+ let next: Promise<MCPClient>;
285
+ next = withMcpConnectionTimeout(createPromise, {
286
+ name,
287
+ config,
288
+ phase: "connecting",
289
+ startedAt,
290
+ }).catch((error) => {
291
+ if (this.#mcpClients.get(name) === next) {
292
+ this.#mcpClients.delete(name);
293
+ }
294
+ if (error instanceof MCPConnectionTimeoutError) {
295
+ void createPromise.then((client) => client.close()).catch(() => {});
296
+ }
297
+ throw error;
298
+ });
299
+ this.#mcpClients.set(name, next);
300
+ return next;
301
+ }
302
+ }
303
+
304
+ const toMCPClientConfig = (config: McpServerConfig): MCPClientConfig => {
305
+ if (config.type === "stdio") {
306
+ return {
307
+ transport: new Experimental_StdioMCPTransport({
308
+ command: config.command,
309
+ ...(config.args && { args: [...config.args] }),
310
+ ...(config.env && { env: config.env }),
311
+ ...(config.cwd && { cwd: config.cwd }),
312
+ }),
313
+ };
314
+ }
315
+
316
+ return {
317
+ transport: {
318
+ type: config.type,
319
+ url: config.url,
320
+ ...(config.headers && { headers: config.headers }),
321
+ ...(config.redirect && { redirect: config.redirect }),
322
+ },
323
+ };
324
+ };
325
+
326
+ type ToolkitTool = Toolkit[string];
327
+
328
+ type McpToolkitTool = ToolkitTool & {
329
+ type: "mcp";
330
+ server: McpServerConfig;
331
+ prefix?: string | undefined;
332
+ tools?: Record<string, McpToolkitToolConfig> | undefined;
333
+ };
334
+
335
+ type McpToolSet = {
336
+ tools: ToolSet;
337
+ sources: Map<string, string>;
338
+ };
339
+
340
+ const assertNoMcpToolNameCollisions = (
341
+ mcp: McpToolSet,
342
+ toolSets: readonly { source: string; tools: ToolSet }[],
343
+ ): void => {
344
+ for (const [toolName, serverName] of mcp.sources) {
345
+ for (const { source, tools } of toolSets) {
346
+ if (!Object.prototype.hasOwnProperty.call(tools, toolName)) continue;
347
+ throw new Error(
348
+ `MCP tool "${toolName}" from "${serverName}" conflicts with ${source} tool "${toolName}". Rename one of the tools so each model-visible tool name is unique.`,
349
+ );
350
+ }
351
+ }
352
+ };
353
+
354
+ const isMcpToolkitTool = (tool: ToolkitTool): tool is McpToolkitTool =>
355
+ tool.type === "mcp" && !tool.disabled;
356
+
357
+ const getErrorMessage = (error: unknown): string =>
358
+ error instanceof Error ? error.message || error.name : String(error);
359
+
360
+ const toMcpToolkitError = (
361
+ entryName: string,
362
+ action: "connect" | "list tools" | "close",
363
+ error: unknown,
364
+ ): Error => {
365
+ return new Error(
366
+ `MCP toolkit entry "${entryName}" failed to ${action}: ${getErrorMessage(error)}`,
367
+ { cause: error },
368
+ );
369
+ };
370
+
371
+ const isDisabledMcpTool = (config: McpToolkitToolConfig | undefined): boolean =>
372
+ config?.disabled === true;
373
+
374
+ const assertNoMcpToolkitTools = (toolkit: Toolkit): void => {
375
+ const mcpToolName = Object.entries(toolkit).find(([, tool]) =>
376
+ isMcpToolkitTool(tool),
377
+ )?.[0];
378
+ if (!mcpToolName) return;
379
+
380
+ throw new Error(
381
+ `MCP toolkit entry "${mcpToolName}" requires AISDKToolkit. Use new AISDKToolkit({ toolkit }).tools(...) instead of generativeTools(...).`,
382
+ );
383
+ };
384
+
385
+ type AISDKToModelOutputOptions<TArgs, TResult> = Omit<
386
+ Parameters<ToolModelOutputFunction<TArgs, TResult>>[0],
387
+ "output"
388
+ > & {
389
+ output: TResult | ModelContentEnvelope<TResult>;
390
+ };
391
+
392
+ const toAISDKToModelOutput =
393
+ <TArgs, TResult>(toModelOutput?: ToolModelOutputFunction<TArgs, TResult>) =>
394
+ async (options: AISDKToModelOutputOptions<TArgs, TResult>) => {
395
+ const { result, modelContent } = unwrapModelContentEnvelope(options.output);
396
+
397
+ if (modelContent !== undefined) {
398
+ return toAISDKContent(modelContent);
399
+ }
400
+
401
+ if (!toModelOutput) {
402
+ return toAISDKDefaultOutput(result);
403
+ }
404
+
405
+ const parts = await toModelOutput({
406
+ ...options,
407
+ output: result,
408
+ });
409
+ return toAISDKContent(parts);
410
+ };
411
+
412
+ const toServerToolSet = (toolkit: ToolkitDefinition): ToolSet =>
413
+ Object.fromEntries(
414
+ Object.entries(toolkit)
415
+ .filter(
416
+ ([, t]) => t.type !== "mcp" && t.type !== "provider" && !t.disabled,
417
+ )
418
+ .map(([name, t]) => {
419
+ const execute = t.execute;
420
+ return [
421
+ name,
422
+ {
423
+ ...(t.description !== undefined && { description: t.description }),
424
+ inputSchema: parametersToInputSchema(t.parameters),
425
+ toModelOutput: toAISDKToModelOutput(t.toModelOutput),
426
+ ...(t.providerOptions && { providerOptions: t.providerOptions }),
427
+ ...(execute && {
428
+ execute: (
429
+ args: unknown,
430
+ callOptions: { toolCallId: string; abortSignal?: AbortSignal },
431
+ ) =>
432
+ execute(args as never, {
433
+ toolCallId: callOptions.toolCallId,
434
+ abortSignal: callOptions.abortSignal ?? neverAbort,
435
+ human: humanNotSupported,
436
+ }),
437
+ }),
438
+ },
439
+ ];
440
+ }),
441
+ ) as ToolSet;
442
+
443
+ const toProviderToolSet = (toolkit: Toolkit): ToolSet =>
444
+ Object.fromEntries(
445
+ Object.entries(toolkit)
446
+ .filter((entry): entry is [string, ProviderToolkitTool] =>
447
+ isProviderToolkitTool(entry[1]),
448
+ )
449
+ .map(([name, t]) => [
450
+ name,
451
+ {
452
+ type: "provider",
453
+ id: t.providerId,
454
+ args: t.args,
455
+ ...(t.parameters && {
456
+ inputSchema: parametersToInputSchema(t.parameters),
457
+ }),
458
+ ...(t.providerOptions && { providerOptions: t.providerOptions }),
459
+ ...(t.supportsDeferredResults !== undefined && {
460
+ supportsDeferredResults: t.supportsDeferredResults,
461
+ }),
462
+ },
463
+ ]),
464
+ ) as ToolSet;
465
+
466
+ type ProviderToolkitTool = Extract<Toolkit[string], { type: "provider" }>;
467
+
468
+ const isProviderToolkitTool = (
469
+ tool: Toolkit[string],
470
+ ): tool is ProviderToolkitTool => tool.type === "provider" && !tool.disabled;
@@ -0,0 +1 @@
1
+ export { Experimental_StdioMCPTransport } from "@ai-sdk/mcp/mcp-stdio";
@@ -0,0 +1,12 @@
1
+ import type { Experimental_StdioMCPTransport as NodeStdioMCPTransport } from "@ai-sdk/mcp/mcp-stdio";
2
+
3
+ class UnsupportedStdioMCPTransport {
4
+ constructor() {
5
+ throw new Error(
6
+ "stdio MCP transport requires a runtime that can spawn a subprocess, such as Node, Bun, or Deno (with --allow-run). Use an HTTP or SSE MCP server config in browser, React Native, edge, or worker runtimes.",
7
+ );
8
+ }
9
+ }
10
+
11
+ export const Experimental_StdioMCPTransport =
12
+ UnsupportedStdioMCPTransport as unknown as typeof NodeStdioMCPTransport;