@ai-sdk/react 4.0.123 → 4.0.126

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.
package/dist/index.js CHANGED
@@ -1,1401 +1,1226 @@
1
- // src/use-object.ts
2
- import {
3
- isAbortError,
4
- resolve,
5
- normalizeHeaders,
6
- safeValidateTypes
7
- } from "@ai-sdk/provider-utils";
8
- import {
9
- asSchema,
10
- isDeepEqualData,
11
- parsePartialJson
12
- } from "ai";
13
- import { useCallback, useId, useRef, useState } from "react";
1
+ import { isAbortError, normalizeHeaders, resolve, safeValidateTypes, secureJsonParse } from "@ai-sdk/provider-utils";
2
+ import { AbstractChat, DefaultChatTransport, Experimental_AbstractRealtimeSession, asSchema, callCompletionApi, isDeepEqualData, parsePartialJson } from "ai";
3
+ import { useCallback, useEffect, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
14
4
  import useSWR from "swr";
15
- var getOriginalFetch = () => fetch;
16
- function useObject({
17
- api,
18
- id,
19
- schema,
20
- // required, in the future we will use it for validation
21
- initialValue,
22
- fetch: fetch2,
23
- onError,
24
- onFinish,
25
- headers,
26
- credentials
27
- }) {
28
- const hookId = useId();
29
- const completionId = id ?? hookId;
30
- const { data, mutate } = useSWR(
31
- [completionId, "object"],
32
- null,
33
- { fallbackData: { object: initialValue } }
34
- );
35
- const [error, setError] = useState(void 0);
36
- const [isLoading, setIsLoading] = useState(false);
37
- const abortControllerRef = useRef(null);
38
- const stop = useCallback(() => {
39
- try {
40
- abortControllerRef.current?.abort();
41
- } catch {
42
- } finally {
43
- setIsLoading(false);
44
- abortControllerRef.current = null;
45
- }
46
- }, []);
47
- const submit = async (input) => {
48
- const abortController = new AbortController();
49
- try {
50
- clearObject();
51
- setIsLoading(true);
52
- abortControllerRef.current = abortController;
53
- const resolvedHeaders = await resolve(headers);
54
- const actualFetch = fetch2 ?? getOriginalFetch();
55
- const response = await actualFetch(api, {
56
- method: "POST",
57
- headers: {
58
- "Content-Type": "application/json",
59
- ...normalizeHeaders(resolvedHeaders)
60
- },
61
- credentials,
62
- signal: abortController.signal,
63
- body: JSON.stringify(input)
64
- });
65
- if (!response.ok) {
66
- throw new Error(
67
- await response.text() || "Failed to fetch the response."
68
- );
69
- }
70
- if (response.body == null) {
71
- throw new Error("The response body is empty.");
72
- }
73
- let accumulatedText = "";
74
- let latestObject = void 0;
75
- await response.body.pipeThrough(new TextDecoderStream()).pipeTo(
76
- new WritableStream({
77
- async write(chunk) {
78
- accumulatedText += chunk;
79
- const { value } = await parsePartialJson(accumulatedText);
80
- const currentObject = value;
81
- if (!isDeepEqualData(latestObject, currentObject)) {
82
- latestObject = currentObject;
83
- mutate({ object: currentObject });
84
- }
85
- },
86
- async close() {
87
- if (abortControllerRef.current === abortController) {
88
- setIsLoading(false);
89
- }
90
- if (onFinish != null) {
91
- const validationResult = await safeValidateTypes({
92
- value: latestObject,
93
- schema: asSchema(schema)
94
- });
95
- await onFinish(
96
- validationResult.success ? { object: validationResult.value, error: void 0 } : { object: void 0, error: validationResult.error }
97
- );
98
- }
99
- if (abortControllerRef.current === abortController) {
100
- abortControllerRef.current = null;
101
- }
102
- }
103
- })
104
- );
105
- } catch (error2) {
106
- if (isAbortError(error2)) {
107
- return;
108
- }
109
- if (onError && error2 instanceof Error) {
110
- onError(error2);
111
- }
112
- if (abortControllerRef.current === abortController) {
113
- setIsLoading(false);
114
- abortControllerRef.current = null;
115
- setError(error2 instanceof Error ? error2 : new Error(String(error2)));
116
- }
117
- }
118
- };
119
- const clear = () => {
120
- stop();
121
- clearObject();
122
- };
123
- const clearObject = () => {
124
- setError(void 0);
125
- setIsLoading(false);
126
- mutate({ object: void 0 });
127
- };
128
- return {
129
- submit,
130
- object: data?.object,
131
- error,
132
- isLoading,
133
- stop,
134
- clear
135
- };
136
- }
137
-
138
- // src/use-chat.ts
139
- import {
140
- DefaultChatTransport
141
- } from "ai";
142
- import {
143
- useCallback as useCallback2,
144
- useEffect,
145
- useMemo,
146
- useRef as useRef2,
147
- useSyncExternalStore
148
- } from "react";
149
-
150
- // src/chat.react.ts
151
- import {
152
- AbstractChat
153
- } from "ai";
154
-
155
- // src/throttle.ts
156
5
  import throttleFunction from "throttleit";
6
+ import { isJSONObject } from "@ai-sdk/provider";
7
+ import { jsx } from "react/jsx-runtime";
8
+ //#region src/use-object.ts
9
+ const getOriginalFetch = () => fetch;
10
+ function useObject({ api, id, schema, initialValue, fetch, onError, onFinish, headers, credentials }) {
11
+ const hookId = useId();
12
+ const { data, mutate } = useSWR([id ?? hookId, "object"], null, { fallbackData: { object: initialValue } });
13
+ const [error, setError] = useState(void 0);
14
+ const [isLoading, setIsLoading] = useState(false);
15
+ const abortControllerRef = useRef(null);
16
+ const stop = useCallback(() => {
17
+ try {
18
+ abortControllerRef.current?.abort();
19
+ } catch {} finally {
20
+ setIsLoading(false);
21
+ abortControllerRef.current = null;
22
+ }
23
+ }, []);
24
+ const submit = async (input) => {
25
+ const abortController = new AbortController();
26
+ try {
27
+ clearObject();
28
+ setIsLoading(true);
29
+ abortControllerRef.current = abortController;
30
+ const resolvedHeaders = await resolve(headers);
31
+ const response = await (fetch ?? getOriginalFetch())(api, {
32
+ method: "POST",
33
+ headers: {
34
+ "Content-Type": "application/json",
35
+ ...normalizeHeaders(resolvedHeaders)
36
+ },
37
+ credentials,
38
+ signal: abortController.signal,
39
+ body: JSON.stringify(input)
40
+ });
41
+ if (!response.ok) throw new Error(await response.text() || "Failed to fetch the response.");
42
+ if (response.body == null) throw new Error("The response body is empty.");
43
+ let accumulatedText = "";
44
+ let latestObject = void 0;
45
+ await response.body.pipeThrough(new TextDecoderStream()).pipeTo(new WritableStream({
46
+ async write(chunk) {
47
+ accumulatedText += chunk;
48
+ const { value } = await parsePartialJson(accumulatedText);
49
+ const currentObject = value;
50
+ if (!isDeepEqualData(latestObject, currentObject)) {
51
+ latestObject = currentObject;
52
+ mutate({ object: currentObject });
53
+ }
54
+ },
55
+ async close() {
56
+ if (abortControllerRef.current === abortController) setIsLoading(false);
57
+ if (onFinish != null) {
58
+ const validationResult = await safeValidateTypes({
59
+ value: latestObject,
60
+ schema: asSchema(schema)
61
+ });
62
+ await onFinish(validationResult.success ? {
63
+ object: validationResult.value,
64
+ error: void 0
65
+ } : {
66
+ object: void 0,
67
+ error: validationResult.error
68
+ });
69
+ }
70
+ if (abortControllerRef.current === abortController) abortControllerRef.current = null;
71
+ }
72
+ }));
73
+ } catch (error) {
74
+ if (isAbortError(error)) return;
75
+ if (onError && error instanceof Error) onError(error);
76
+ if (abortControllerRef.current === abortController) {
77
+ setIsLoading(false);
78
+ abortControllerRef.current = null;
79
+ setError(error instanceof Error ? error : new Error(String(error)));
80
+ }
81
+ }
82
+ };
83
+ const clear = () => {
84
+ stop();
85
+ clearObject();
86
+ };
87
+ const clearObject = () => {
88
+ setError(void 0);
89
+ setIsLoading(false);
90
+ mutate({ object: void 0 });
91
+ };
92
+ return {
93
+ submit,
94
+ object: data?.object,
95
+ error,
96
+ isLoading,
97
+ stop,
98
+ clear
99
+ };
100
+ }
101
+ //#endregion
102
+ //#region src/throttle.ts
157
103
  function throttle(fn, waitMs) {
158
- return waitMs != null ? throttleFunction(fn, waitMs) : fn;
104
+ return waitMs != null ? throttleFunction(fn, waitMs) : fn;
159
105
  }
160
-
161
- // src/chat.react.ts
106
+ //#endregion
107
+ //#region src/chat.react.ts
162
108
  function cloneMetadata(metadata) {
163
- if (Array.isArray(metadata)) {
164
- return [...metadata];
165
- }
166
- if (metadata != null && typeof metadata === "object" && (Object.getPrototypeOf(metadata) === Object.prototype || Object.getPrototypeOf(metadata) === null)) {
167
- return { ...metadata };
168
- }
169
- return metadata;
109
+ if (Array.isArray(metadata)) return [...metadata];
110
+ if (metadata != null && typeof metadata === "object" && (Object.getPrototypeOf(metadata) === Object.prototype || Object.getPrototypeOf(metadata) === null)) return { ...metadata };
111
+ return metadata;
170
112
  }
171
113
  var ReactChatState = class {
172
- constructor(initialMessages = []) {
173
- this.#status = "ready";
174
- this.#error = void 0;
175
- this.#messagesCallbacks = /* @__PURE__ */ new Set();
176
- this.#statusCallbacks = /* @__PURE__ */ new Set();
177
- this.#errorCallbacks = /* @__PURE__ */ new Set();
178
- this.pushMessage = (message) => {
179
- this.#messages = this.#messages.concat(message);
180
- this.#callMessagesCallbacks();
181
- };
182
- this.popMessage = () => {
183
- this.#messages = this.#messages.slice(0, -1);
184
- this.#callMessagesCallbacks();
185
- };
186
- this.replaceMessage = (index, message) => {
187
- this.#messages = [
188
- ...this.#messages.slice(0, index),
189
- this.snapshot(message),
190
- ...this.#messages.slice(index + 1)
191
- ];
192
- this.#callMessagesCallbacks();
193
- };
194
- this.snapshot = (value) => {
195
- if (value == null || typeof value !== "object" || !("parts" in value) || !Array.isArray(value.parts)) {
196
- return value;
197
- }
198
- const message = value;
199
- const snapshot = {
200
- ...message,
201
- parts: message.parts.map((part) => ({ ...part }))
202
- };
203
- if ("metadata" in message) {
204
- snapshot.metadata = cloneMetadata(message.metadata);
205
- }
206
- return snapshot;
207
- };
208
- this["~registerMessagesCallback"] = (onChange, throttleWaitMs) => {
209
- const callback = throttleWaitMs ? throttle(onChange, throttleWaitMs) : onChange;
210
- this.#messagesCallbacks.add(callback);
211
- return () => {
212
- this.#messagesCallbacks.delete(callback);
213
- };
214
- };
215
- this["~registerStatusCallback"] = (onChange) => {
216
- this.#statusCallbacks.add(onChange);
217
- return () => {
218
- this.#statusCallbacks.delete(onChange);
219
- };
220
- };
221
- this["~registerErrorCallback"] = (onChange) => {
222
- this.#errorCallbacks.add(onChange);
223
- return () => {
224
- this.#errorCallbacks.delete(onChange);
225
- };
226
- };
227
- this.#callMessagesCallbacks = () => {
228
- this.#messagesCallbacks.forEach((callback) => callback());
229
- };
230
- this.#callStatusCallbacks = () => {
231
- this.#statusCallbacks.forEach((callback) => callback());
232
- };
233
- this.#callErrorCallbacks = () => {
234
- this.#errorCallbacks.forEach((callback) => callback());
235
- };
236
- this.#messages = initialMessages;
237
- }
238
- #messages;
239
- #status;
240
- #error;
241
- #messagesCallbacks;
242
- #statusCallbacks;
243
- #errorCallbacks;
244
- get status() {
245
- return this.#status;
246
- }
247
- set status(newStatus) {
248
- this.#status = newStatus;
249
- this.#callStatusCallbacks();
250
- }
251
- get error() {
252
- return this.#error;
253
- }
254
- set error(newError) {
255
- this.#error = newError;
256
- this.#callErrorCallbacks();
257
- }
258
- get messages() {
259
- return this.#messages;
260
- }
261
- set messages(newMessages) {
262
- this.#messages = [...newMessages];
263
- this.#callMessagesCallbacks();
264
- }
265
- #callMessagesCallbacks;
266
- #callStatusCallbacks;
267
- #callErrorCallbacks;
114
+ #messages;
115
+ #status;
116
+ #error;
117
+ #messagesCallbacks;
118
+ #statusCallbacks;
119
+ #errorCallbacks;
120
+ constructor(initialMessages = []) {
121
+ this.#status = "ready";
122
+ this.#error = void 0;
123
+ this.#messagesCallbacks = /* @__PURE__ */ new Set();
124
+ this.#statusCallbacks = /* @__PURE__ */ new Set();
125
+ this.#errorCallbacks = /* @__PURE__ */ new Set();
126
+ this.pushMessage = (message) => {
127
+ this.#messages = this.#messages.concat(message);
128
+ this.#callMessagesCallbacks();
129
+ };
130
+ this.popMessage = () => {
131
+ this.#messages = this.#messages.slice(0, -1);
132
+ this.#callMessagesCallbacks();
133
+ };
134
+ this.replaceMessage = (index, message) => {
135
+ this.#messages = [
136
+ ...this.#messages.slice(0, index),
137
+ this.snapshot(message),
138
+ ...this.#messages.slice(index + 1)
139
+ ];
140
+ this.#callMessagesCallbacks();
141
+ };
142
+ this.snapshot = (value) => {
143
+ if (value == null || typeof value !== "object" || !("parts" in value) || !Array.isArray(value.parts)) return value;
144
+ const message = value;
145
+ const snapshot = {
146
+ ...message,
147
+ parts: message.parts.map((part) => ({ ...part }))
148
+ };
149
+ if ("metadata" in message) snapshot.metadata = cloneMetadata(message.metadata);
150
+ return snapshot;
151
+ };
152
+ this["~registerMessagesCallback"] = (onChange, throttleWaitMs) => {
153
+ const callback = throttleWaitMs ? throttle(onChange, throttleWaitMs) : onChange;
154
+ this.#messagesCallbacks.add(callback);
155
+ return () => {
156
+ this.#messagesCallbacks.delete(callback);
157
+ };
158
+ };
159
+ this["~registerStatusCallback"] = (onChange) => {
160
+ this.#statusCallbacks.add(onChange);
161
+ return () => {
162
+ this.#statusCallbacks.delete(onChange);
163
+ };
164
+ };
165
+ this["~registerErrorCallback"] = (onChange) => {
166
+ this.#errorCallbacks.add(onChange);
167
+ return () => {
168
+ this.#errorCallbacks.delete(onChange);
169
+ };
170
+ };
171
+ this.#callMessagesCallbacks = () => {
172
+ this.#messagesCallbacks.forEach((callback) => callback());
173
+ };
174
+ this.#callStatusCallbacks = () => {
175
+ this.#statusCallbacks.forEach((callback) => callback());
176
+ };
177
+ this.#callErrorCallbacks = () => {
178
+ this.#errorCallbacks.forEach((callback) => callback());
179
+ };
180
+ this.#messages = initialMessages;
181
+ }
182
+ get status() {
183
+ return this.#status;
184
+ }
185
+ set status(newStatus) {
186
+ this.#status = newStatus;
187
+ this.#callStatusCallbacks();
188
+ }
189
+ get error() {
190
+ return this.#error;
191
+ }
192
+ set error(newError) {
193
+ this.#error = newError;
194
+ this.#callErrorCallbacks();
195
+ }
196
+ get messages() {
197
+ return this.#messages;
198
+ }
199
+ set messages(newMessages) {
200
+ this.#messages = [...newMessages];
201
+ this.#callMessagesCallbacks();
202
+ }
203
+ #callMessagesCallbacks;
204
+ #callStatusCallbacks;
205
+ #callErrorCallbacks;
268
206
  };
269
207
  var Chat = class extends AbstractChat {
270
- constructor({ messages, ...init }) {
271
- const state = new ReactChatState(messages);
272
- super({ ...init, state });
273
- this["~registerMessagesCallback"] = (onChange, throttleWaitMs) => this.#state["~registerMessagesCallback"](onChange, throttleWaitMs);
274
- this["~registerStatusCallback"] = (onChange) => this.#state["~registerStatusCallback"](onChange);
275
- this["~registerErrorCallback"] = (onChange) => this.#state["~registerErrorCallback"](onChange);
276
- this.#state = state;
277
- }
278
- #state;
208
+ #state;
209
+ constructor({ messages, ...init }) {
210
+ const state = new ReactChatState(messages);
211
+ super({
212
+ ...init,
213
+ state
214
+ });
215
+ this["~registerMessagesCallback"] = (onChange, throttleWaitMs) => this.#state["~registerMessagesCallback"](onChange, throttleWaitMs);
216
+ this["~registerStatusCallback"] = (onChange) => this.#state["~registerStatusCallback"](onChange);
217
+ this["~registerErrorCallback"] = (onChange) => this.#state["~registerErrorCallback"](onChange);
218
+ this.#state = state;
219
+ }
279
220
  };
280
-
281
- // src/use-chat.ts
282
- function useChat({
283
- throttle: throttle2,
284
- experimental_throttle,
285
- resume = false,
286
- ...options
287
- } = {}) {
288
- const throttleWaitMs = throttle2 ?? experimental_throttle;
289
- const latestRef = useRef2({});
290
- if (!("chat" in options)) {
291
- latestRef.current = {
292
- onToolCall: options.onToolCall,
293
- onData: options.onData,
294
- onFinish: options.onFinish,
295
- onError: options.onError,
296
- sendAutomaticallyWhen: options.sendAutomaticallyWhen,
297
- transport: options.transport
298
- };
299
- }
300
- let defaultTransport;
301
- const getTransport = () => latestRef.current.transport ?? (defaultTransport ??= new DefaultChatTransport());
302
- const chatOptions = {
303
- ...options,
304
- transport: {
305
- sendMessages: (sendOptions) => getTransport().sendMessages(sendOptions),
306
- reconnectToStream: (reconnectOptions) => getTransport().reconnectToStream(reconnectOptions)
307
- },
308
- onToolCall: (arg) => latestRef.current.onToolCall?.(arg),
309
- onData: (arg) => latestRef.current.onData?.(arg),
310
- onFinish: (arg) => latestRef.current.onFinish?.(arg),
311
- onError: (arg) => latestRef.current.onError?.(arg),
312
- sendAutomaticallyWhen: (arg) => latestRef.current.sendAutomaticallyWhen?.(arg) ?? false
313
- };
314
- const chatKey = "chat" in options ? options.chat : options.id;
315
- const { chat, isExternallyManaged } = useMemo(
316
- () => ({
317
- chat: "chat" in options ? options.chat : new Chat(chatOptions),
318
- isExternallyManaged: "chat" in options
319
- }),
320
- [chatKey]
321
- );
322
- useEffect(() => {
323
- if (isExternallyManaged) {
324
- return;
325
- }
326
- return () => {
327
- void chat.stop();
328
- };
329
- }, [chat, isExternallyManaged]);
330
- const messagesSnapshot = useMemo(() => ({ messages: chat.messages }), [chat]);
331
- const subscribeToMessages = useCallback2(
332
- (update) => {
333
- let isSubscribed = true;
334
- const updateMessages = () => {
335
- if (!isSubscribed) {
336
- return;
337
- }
338
- messagesSnapshot.messages = chat.messages;
339
- update();
340
- };
341
- const unsubscribe = chat["~registerMessagesCallback"](
342
- updateMessages,
343
- throttleWaitMs
344
- );
345
- messagesSnapshot.messages = chat.messages;
346
- return () => {
347
- isSubscribed = false;
348
- unsubscribe();
349
- };
350
- },
351
- [chat, messagesSnapshot, throttleWaitMs]
352
- );
353
- const getMessagesSnapshot = useCallback2(
354
- () => messagesSnapshot.messages,
355
- [messagesSnapshot]
356
- );
357
- const messages = useSyncExternalStore(
358
- subscribeToMessages,
359
- getMessagesSnapshot,
360
- getMessagesSnapshot
361
- );
362
- const subscribeToStatus = useCallback2(
363
- (update) => chat["~registerStatusCallback"](() => {
364
- if (chat.status === "ready" || chat.status === "error") {
365
- messagesSnapshot.messages = chat.messages;
366
- }
367
- update();
368
- }),
369
- [chat, messagesSnapshot]
370
- );
371
- const getStatusSnapshot = useCallback2(() => chat.status, [chat]);
372
- const status = useSyncExternalStore(
373
- subscribeToStatus,
374
- getStatusSnapshot,
375
- getStatusSnapshot
376
- );
377
- const error = useSyncExternalStore(
378
- chat["~registerErrorCallback"],
379
- () => chat.error,
380
- () => chat.error
381
- );
382
- const setMessages = useCallback2(
383
- (messagesParam) => {
384
- if (typeof messagesParam === "function") {
385
- messagesParam = messagesParam(chat.messages);
386
- }
387
- chat.messages = messagesParam;
388
- },
389
- [chat]
390
- );
391
- useEffect(() => {
392
- if (resume) {
393
- chat.resumeStream();
394
- }
395
- }, [resume, chat]);
396
- return {
397
- id: chat.id,
398
- messages,
399
- setMessages,
400
- sendMessage: chat.sendMessage,
401
- regenerate: chat.regenerate,
402
- clearError: chat.clearError,
403
- stop: chat.stop,
404
- error,
405
- resumeStream: chat.resumeStream,
406
- status,
407
- /**
408
- * @deprecated Use `addToolOutput` instead.
409
- */
410
- addToolResult: chat.addToolOutput,
411
- addToolOutput: chat.addToolOutput,
412
- addToolApprovalResponse: chat.addToolApprovalResponse
413
- };
221
+ //#endregion
222
+ //#region src/use-chat.ts
223
+ const automaticResumeRegistrations = /* @__PURE__ */ new WeakMap();
224
+ function registerAutomaticResume({ chat, registration }) {
225
+ let registrations = automaticResumeRegistrations.get(chat);
226
+ if (registrations == null) {
227
+ registrations = /* @__PURE__ */ new Set();
228
+ automaticResumeRegistrations.set(chat, registrations);
229
+ }
230
+ const shouldResume = registrations.size === 0;
231
+ registrations.add(registration);
232
+ if (shouldResume) chat.resumeStream();
233
+ return () => {
234
+ registrations.delete(registration);
235
+ if (registrations.size === 0) automaticResumeRegistrations.delete(chat);
236
+ };
414
237
  }
415
-
416
- // src/use-completion.ts
417
- import {
418
- callCompletionApi
419
- } from "ai";
420
- import { normalizeHeaders as normalizeHeaders2 } from "@ai-sdk/provider-utils";
421
- import { useCallback as useCallback3, useEffect as useEffect2, useId as useId2, useRef as useRef3, useState as useState2 } from "react";
422
- import useSWR2 from "swr";
423
- function useCompletion({
424
- api = "/api/completion",
425
- id,
426
- initialCompletion = "",
427
- initialInput = "",
428
- credentials,
429
- headers,
430
- body,
431
- streamProtocol = "data",
432
- fetch: fetch2,
433
- onFinish,
434
- onError,
435
- throttle: throttleWait,
436
- experimental_throttle
437
- } = {}) {
438
- const throttleWaitMs = throttleWait ?? experimental_throttle;
439
- const hookId = useId2();
440
- const completionId = id || hookId;
441
- const { data, mutate } = useSWR2([api, completionId], null, {
442
- fallbackData: initialCompletion
443
- });
444
- const { data: isLoading = false, mutate: mutateLoading } = useSWR2(
445
- [completionId, "loading"],
446
- null
447
- );
448
- const [error, setError] = useState2(void 0);
449
- const completion = data;
450
- const abortControllerRef = useRef3(null);
451
- const requestIdRef = useRef3(0);
452
- const extraMetadataRef = useRef3({
453
- credentials,
454
- headers,
455
- body
456
- });
457
- useEffect2(() => {
458
- extraMetadataRef.current = {
459
- credentials,
460
- headers,
461
- body
462
- };
463
- }, [credentials, headers, body]);
464
- const triggerRequest = useCallback3(
465
- async (prompt, options) => {
466
- const requestId = ++requestIdRef.current;
467
- return callCompletionApi({
468
- api,
469
- prompt,
470
- credentials: extraMetadataRef.current.credentials,
471
- headers: {
472
- ...normalizeHeaders2(extraMetadataRef.current.headers),
473
- ...normalizeHeaders2(options?.headers)
474
- },
475
- body: {
476
- ...extraMetadataRef.current.body,
477
- ...options?.body
478
- },
479
- streamProtocol,
480
- fetch: fetch2,
481
- // throttle streamed ui updates:
482
- setCompletion: throttle((completion2) => {
483
- if (requestIdRef.current === requestId) {
484
- mutate(completion2, false);
485
- }
486
- }, throttleWaitMs),
487
- setLoading: mutateLoading,
488
- setError,
489
- setAbortController: (controller) => {
490
- abortControllerRef.current = controller;
491
- },
492
- getAbortController: () => abortControllerRef.current,
493
- onFinish,
494
- onError
495
- });
496
- },
497
- [
498
- mutate,
499
- mutateLoading,
500
- api,
501
- extraMetadataRef,
502
- onFinish,
503
- onError,
504
- setError,
505
- streamProtocol,
506
- fetch2,
507
- throttleWaitMs
508
- ]
509
- );
510
- const stop = useCallback3(() => {
511
- abortControllerRef.current?.abort();
512
- }, []);
513
- const setCompletion = useCallback3(
514
- (completion2) => {
515
- mutate(completion2, false);
516
- },
517
- [mutate]
518
- );
519
- const complete = useCallback3(
520
- async (prompt, options) => {
521
- return triggerRequest(prompt, options);
522
- },
523
- [triggerRequest]
524
- );
525
- const [input, setInput] = useState2(initialInput);
526
- const handleSubmit = useCallback3(
527
- (event) => {
528
- event?.preventDefault?.();
529
- if (!input) {
530
- return;
531
- }
532
- const result = complete(input);
533
- setInput("");
534
- return result;
535
- },
536
- [input, complete, setInput]
537
- );
538
- const handleInputChange = useCallback3(
539
- (e) => {
540
- setInput(e.target.value);
541
- },
542
- [setInput]
543
- );
544
- return {
545
- completion,
546
- complete,
547
- error,
548
- setCompletion,
549
- stop,
550
- input,
551
- setInput,
552
- handleInputChange,
553
- handleSubmit,
554
- isLoading
555
- };
238
+ function useChat({ throttle, experimental_throttle, resume = false, ...options } = {}) {
239
+ const throttleWaitMs = throttle ?? experimental_throttle;
240
+ const automaticResumeRegistration = useRef({});
241
+ const latestRef = useRef({});
242
+ if (!("chat" in options)) latestRef.current = {
243
+ onToolCall: options.onToolCall,
244
+ onData: options.onData,
245
+ onFinish: options.onFinish,
246
+ onError: options.onError,
247
+ sendAutomaticallyWhen: options.sendAutomaticallyWhen,
248
+ transport: options.transport
249
+ };
250
+ let defaultTransport;
251
+ const getTransport = () => latestRef.current.transport ?? (defaultTransport ??= new DefaultChatTransport());
252
+ const chatOptions = {
253
+ ...options,
254
+ transport: {
255
+ sendMessages: (sendOptions) => getTransport().sendMessages(sendOptions),
256
+ reconnectToStream: (reconnectOptions) => getTransport().reconnectToStream(reconnectOptions)
257
+ },
258
+ onToolCall: (arg) => latestRef.current.onToolCall?.(arg),
259
+ onData: (arg) => latestRef.current.onData?.(arg),
260
+ onFinish: (arg) => latestRef.current.onFinish?.(arg),
261
+ onError: (arg) => latestRef.current.onError?.(arg),
262
+ sendAutomaticallyWhen: (arg) => latestRef.current.sendAutomaticallyWhen?.(arg) ?? false
263
+ };
264
+ const chatKey = "chat" in options ? options.chat : options.id;
265
+ const { chat, isExternallyManaged } = useMemo(() => ({
266
+ chat: "chat" in options ? options.chat : new Chat(chatOptions),
267
+ isExternallyManaged: "chat" in options
268
+ }), [chatKey]);
269
+ useEffect(() => {
270
+ if (isExternallyManaged) return;
271
+ return () => {
272
+ chat.stop();
273
+ };
274
+ }, [chat, isExternallyManaged]);
275
+ const messagesSnapshot = useMemo(() => ({ messages: chat.messages }), [chat]);
276
+ const subscribeToMessages = useCallback((update) => {
277
+ let isSubscribed = true;
278
+ const updateMessages = () => {
279
+ if (!isSubscribed) return;
280
+ messagesSnapshot.messages = chat.messages;
281
+ update();
282
+ };
283
+ const unsubscribe = chat["~registerMessagesCallback"](updateMessages, throttleWaitMs);
284
+ messagesSnapshot.messages = chat.messages;
285
+ return () => {
286
+ isSubscribed = false;
287
+ unsubscribe();
288
+ };
289
+ }, [
290
+ chat,
291
+ messagesSnapshot,
292
+ throttleWaitMs
293
+ ]);
294
+ const getMessagesSnapshot = useCallback(() => messagesSnapshot.messages, [messagesSnapshot]);
295
+ const messages = useSyncExternalStore(subscribeToMessages, getMessagesSnapshot, getMessagesSnapshot);
296
+ const subscribeToStatus = useCallback((update) => chat["~registerStatusCallback"](() => {
297
+ if (chat.status === "ready" || chat.status === "error") messagesSnapshot.messages = chat.messages;
298
+ update();
299
+ }), [chat, messagesSnapshot]);
300
+ const getStatusSnapshot = useCallback(() => chat.status, [chat]);
301
+ const status = useSyncExternalStore(subscribeToStatus, getStatusSnapshot, getStatusSnapshot);
302
+ const error = useSyncExternalStore(chat["~registerErrorCallback"], () => chat.error, () => chat.error);
303
+ const setMessages = useCallback((messagesParam) => {
304
+ if (typeof messagesParam === "function") messagesParam = messagesParam(chat.messages);
305
+ chat.messages = messagesParam;
306
+ }, [chat]);
307
+ useEffect(() => {
308
+ if (resume) return registerAutomaticResume({
309
+ chat,
310
+ registration: automaticResumeRegistration.current
311
+ });
312
+ }, [resume, chat]);
313
+ return {
314
+ id: chat.id,
315
+ messages,
316
+ setMessages,
317
+ sendMessage: chat.sendMessage,
318
+ regenerate: chat.regenerate,
319
+ clearError: chat.clearError,
320
+ stop: chat.stop,
321
+ error,
322
+ resumeStream: chat.resumeStream,
323
+ status,
324
+ /**
325
+ * @deprecated Use `addToolOutput` instead.
326
+ */
327
+ addToolResult: chat.addToolOutput,
328
+ addToolOutput: chat.addToolOutput,
329
+ addToolApprovalResponse: chat.addToolApprovalResponse
330
+ };
556
331
  }
557
-
558
- // src/use-realtime.ts
559
- import { secureJsonParse } from "@ai-sdk/provider-utils";
560
- import {
561
- Experimental_AbstractRealtimeSession as AbstractRealtimeSession
562
- } from "ai";
563
- import {
564
- useCallback as useCallback4,
565
- useEffect as useEffect3,
566
- useInsertionEffect,
567
- useLayoutEffect,
568
- useMemo as useMemo2,
569
- useRef as useRef4,
570
- useSyncExternalStore as useSyncExternalStore2
571
- } from "react";
572
- var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect3 : useLayoutEffect;
573
- var RealtimeStore = /* @__PURE__ */ (() => class RealtimeStore extends AbstractRealtimeSession {
574
- constructor() {
575
- super(...arguments);
576
- this.state = {
577
- status: "disconnected",
578
- messages: [],
579
- events: [],
580
- isCapturing: false,
581
- isPlaying: false
582
- };
583
- this.callbacks = {
584
- status: /* @__PURE__ */ new Set(),
585
- messages: /* @__PURE__ */ new Set(),
586
- events: /* @__PURE__ */ new Set(),
587
- isCapturing: /* @__PURE__ */ new Set(),
588
- isPlaying: /* @__PURE__ */ new Set(),
589
- session: /* @__PURE__ */ new Set()
590
- };
591
- }
592
- get status() {
593
- return this.state.status;
594
- }
595
- get messages() {
596
- return this.state.messages;
597
- }
598
- get events() {
599
- return this.state.events;
600
- }
601
- get isCapturing() {
602
- return this.state.isCapturing;
603
- }
604
- get isPlaying() {
605
- return this.state.isPlaying;
606
- }
607
- get session() {
608
- return this.state.session;
609
- }
610
- subscribe(key, onChange) {
611
- this.callbacks[key].add(onChange);
612
- return () => {
613
- this.callbacks[key].delete(onChange);
614
- };
615
- }
616
- setState(key, value) {
617
- this.state = { ...this.state, [key]: value };
618
- this.callbacks[key].forEach((callback) => callback());
619
- }
332
+ //#endregion
333
+ //#region src/use-completion.ts
334
+ function useCompletion({ api = "/api/completion", id, initialCompletion = "", initialInput = "", credentials, headers, body, streamProtocol = "data", fetch, onFinish, onError, throttle: throttleWait, experimental_throttle } = {}) {
335
+ const throttleWaitMs = throttleWait ?? experimental_throttle;
336
+ const hookId = useId();
337
+ const completionId = id || hookId;
338
+ const { data, mutate } = useSWR([api, completionId], null, { fallbackData: initialCompletion });
339
+ const { data: isLoading = false, mutate: mutateLoading } = useSWR([completionId, "loading"], null);
340
+ const [error, setError] = useState(void 0);
341
+ const completion = data;
342
+ const abortControllerRef = useRef(null);
343
+ const requestIdRef = useRef(0);
344
+ const extraMetadataRef = useRef({
345
+ credentials,
346
+ headers,
347
+ body
348
+ });
349
+ useEffect(() => {
350
+ extraMetadataRef.current = {
351
+ credentials,
352
+ headers,
353
+ body
354
+ };
355
+ }, [
356
+ credentials,
357
+ headers,
358
+ body
359
+ ]);
360
+ const triggerRequest = useCallback(async (prompt, options) => {
361
+ const requestId = ++requestIdRef.current;
362
+ return callCompletionApi({
363
+ api,
364
+ prompt,
365
+ credentials: extraMetadataRef.current.credentials,
366
+ headers: {
367
+ ...normalizeHeaders(extraMetadataRef.current.headers),
368
+ ...normalizeHeaders(options?.headers)
369
+ },
370
+ body: {
371
+ ...extraMetadataRef.current.body,
372
+ ...options?.body
373
+ },
374
+ streamProtocol,
375
+ fetch,
376
+ setCompletion: throttle((completion) => {
377
+ if (requestIdRef.current === requestId) mutate(completion, false);
378
+ }, throttleWaitMs),
379
+ setLoading: mutateLoading,
380
+ setError,
381
+ setAbortController: (controller) => {
382
+ abortControllerRef.current = controller;
383
+ },
384
+ getAbortController: () => abortControllerRef.current,
385
+ onFinish,
386
+ onError
387
+ });
388
+ }, [
389
+ mutate,
390
+ mutateLoading,
391
+ api,
392
+ extraMetadataRef,
393
+ onFinish,
394
+ onError,
395
+ setError,
396
+ streamProtocol,
397
+ fetch,
398
+ throttleWaitMs
399
+ ]);
400
+ const stop = useCallback(() => {
401
+ abortControllerRef.current?.abort();
402
+ }, []);
403
+ const setCompletion = useCallback((completion) => {
404
+ mutate(completion, false);
405
+ }, [mutate]);
406
+ const complete = useCallback(async (prompt, options) => {
407
+ return triggerRequest(prompt, options);
408
+ }, [triggerRequest]);
409
+ const [input, setInput] = useState(initialInput);
410
+ const handleSubmit = useCallback((event) => {
411
+ event?.preventDefault?.();
412
+ if (!input) return;
413
+ const result = complete(input);
414
+ setInput("");
415
+ return result;
416
+ }, [
417
+ input,
418
+ complete,
419
+ setInput
420
+ ]);
421
+ return {
422
+ completion,
423
+ complete,
424
+ error,
425
+ setCompletion,
426
+ stop,
427
+ input,
428
+ setInput,
429
+ handleInputChange: useCallback((e) => {
430
+ setInput(e.target.value);
431
+ }, [setInput]),
432
+ handleSubmit,
433
+ isLoading
434
+ };
435
+ }
436
+ //#endregion
437
+ //#region src/use-realtime.ts
438
+ const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
439
+ const RealtimeStore = /* @__PURE__ */ (() => class RealtimeStore extends Experimental_AbstractRealtimeSession {
440
+ constructor(..._args) {
441
+ super(..._args);
442
+ this.state = {
443
+ status: "disconnected",
444
+ messages: [],
445
+ events: [],
446
+ isCapturing: false,
447
+ isPlaying: false
448
+ };
449
+ this.callbacks = {
450
+ status: /* @__PURE__ */ new Set(),
451
+ messages: /* @__PURE__ */ new Set(),
452
+ events: /* @__PURE__ */ new Set(),
453
+ isCapturing: /* @__PURE__ */ new Set(),
454
+ isPlaying: /* @__PURE__ */ new Set(),
455
+ session: /* @__PURE__ */ new Set()
456
+ };
457
+ }
458
+ get status() {
459
+ return this.state.status;
460
+ }
461
+ get messages() {
462
+ return this.state.messages;
463
+ }
464
+ get events() {
465
+ return this.state.events;
466
+ }
467
+ get isCapturing() {
468
+ return this.state.isCapturing;
469
+ }
470
+ get isPlaying() {
471
+ return this.state.isPlaying;
472
+ }
473
+ get session() {
474
+ return this.state.session;
475
+ }
476
+ subscribe(key, onChange) {
477
+ this.callbacks[key].add(onChange);
478
+ return () => {
479
+ this.callbacks[key].delete(onChange);
480
+ };
481
+ }
482
+ setState(key, value) {
483
+ this.state = {
484
+ ...this.state,
485
+ [key]: value
486
+ };
487
+ this.callbacks[key].forEach((callback) => callback());
488
+ }
620
489
  })();
621
490
  function useRealtime(options) {
622
- const ownerRef = useRef4(null);
623
- const {
624
- model,
625
- api,
626
- startupTimeoutMs,
627
- closeTimeoutMs,
628
- rtcDisconnectTimeoutMs,
629
- sessionConfig,
630
- sampleRate,
631
- maxEvents,
632
- maxPlaybackBufferSeconds,
633
- onToolCall,
634
- onEvent,
635
- onError
636
- } = options;
637
- const { token, websocket, session: sessionEndpoint } = api;
638
- const protocols = JSON.stringify(api.protocols ?? []);
639
- const rt = useMemo2(() => {
640
- const store = new RealtimeStore({
641
- model,
642
- api: token != null ? { token } : sessionEndpoint != null ? { session: sessionEndpoint } : {
643
- websocket,
644
- protocols: secureJsonParse(protocols)
645
- },
646
- startupTimeoutMs,
647
- closeTimeoutMs,
648
- rtcDisconnectTimeoutMs,
649
- sessionConfig,
650
- sampleRate,
651
- maxEvents,
652
- maxPlaybackBufferSeconds,
653
- onEvent: (...args) => ownerRef.current?.store === store ? ownerRef.current.onEvent?.(...args) : void 0,
654
- onError: (...args) => ownerRef.current?.store === store ? ownerRef.current.onError?.(...args) : void 0
655
- });
656
- return store;
657
- }, [
658
- model,
659
- token,
660
- sessionEndpoint,
661
- websocket,
662
- protocols,
663
- startupTimeoutMs,
664
- closeTimeoutMs,
665
- rtcDisconnectTimeoutMs,
666
- sessionConfig,
667
- sampleRate,
668
- maxEvents,
669
- maxPlaybackBufferSeconds
670
- ]);
671
- useInsertionEffect(() => {
672
- ownerRef.current = { store: rt, onToolCall, onEvent, onError };
673
- rt.onToolCall = onToolCall == null ? void 0 : (...args) => ownerRef.current?.store === rt ? ownerRef.current.onToolCall?.(...args) : void 0;
674
- return () => {
675
- ownerRef.current = null;
676
- };
677
- });
678
- useIsomorphicLayoutEffect(() => {
679
- return () => rt.dispose();
680
- }, [rt]);
681
- const actions = useMemo2(() => {
682
- const current = () => {
683
- if (ownerRef.current == null)
684
- throw new Error("Realtime controls require a mounted hook");
685
- return ownerRef.current.store;
686
- };
687
- return {
688
- connect: async (options2) => {
689
- const store = current();
690
- return options2 == null ? store.connect() : store.connect(options2);
691
- },
692
- close: async (options2) => current().close(options2),
693
- resumePlayback: async () => current().resumePlayback(),
694
- resumeAudioCapture: async () => current().resumeAudioCapture(),
695
- disconnect: () => current().disconnect(),
696
- addToolOutput: (callId, result) => current().addToolOutput(callId, result),
697
- sendEvent: (event) => current().sendEvent(event),
698
- sendTextMessage: (text) => current().sendTextMessage(text),
699
- sendAudio: (audio) => current().sendAudio(audio),
700
- commitAudio: () => current().commitAudio(),
701
- clearAudioBuffer: () => current().clearAudioBuffer(),
702
- requestResponse: (options2) => current().requestResponse(options2),
703
- cancelResponse: () => current().cancelResponse(),
704
- startAudioCapture: (stream) => current().startAudioCapture(stream),
705
- stopAudioCapture: () => current().stopAudioCapture(),
706
- stopPlayback: () => current().stopPlayback()
707
- };
708
- }, []);
709
- const status = useSyncExternalStore2(
710
- useCallback4((cb) => rt.subscribe("status", cb), [rt]),
711
- () => rt.status,
712
- () => rt.status
713
- );
714
- const messages = useSyncExternalStore2(
715
- useCallback4((cb) => rt.subscribe("messages", cb), [rt]),
716
- () => rt.messages,
717
- () => rt.messages
718
- );
719
- const events = useSyncExternalStore2(
720
- useCallback4((cb) => rt.subscribe("events", cb), [rt]),
721
- () => rt.events,
722
- () => rt.events
723
- );
724
- const isCapturing = useSyncExternalStore2(
725
- useCallback4((cb) => rt.subscribe("isCapturing", cb), [rt]),
726
- () => rt.isCapturing,
727
- () => rt.isCapturing
728
- );
729
- const isPlaying = useSyncExternalStore2(
730
- useCallback4((cb) => rt.subscribe("isPlaying", cb), [rt]),
731
- () => rt.isPlaying,
732
- () => rt.isPlaying
733
- );
734
- const session = useSyncExternalStore2(
735
- useCallback4((cb) => rt.subscribe("session", cb), [rt]),
736
- () => rt.session,
737
- () => rt.session
738
- );
739
- return {
740
- status,
741
- messages,
742
- events,
743
- isCapturing,
744
- isPlaying,
745
- session,
746
- ...actions
747
- };
491
+ const ownerRef = useRef(null);
492
+ const { model, api, startupTimeoutMs, closeTimeoutMs, rtcDisconnectTimeoutMs, sessionConfig, sampleRate, maxEvents, maxPlaybackBufferSeconds, onToolCall, onEvent, onError } = options;
493
+ const { token, websocket, session: sessionEndpoint } = api;
494
+ const protocols = JSON.stringify(api.protocols ?? []);
495
+ const rt = useMemo(() => {
496
+ const store = new RealtimeStore({
497
+ model,
498
+ api: token != null ? { token } : sessionEndpoint != null ? { session: sessionEndpoint } : {
499
+ websocket,
500
+ protocols: secureJsonParse(protocols)
501
+ },
502
+ startupTimeoutMs,
503
+ closeTimeoutMs,
504
+ rtcDisconnectTimeoutMs,
505
+ sessionConfig,
506
+ sampleRate,
507
+ maxEvents,
508
+ maxPlaybackBufferSeconds,
509
+ onEvent: (...args) => ownerRef.current?.store === store ? ownerRef.current.onEvent?.(...args) : void 0,
510
+ onError: (...args) => ownerRef.current?.store === store ? ownerRef.current.onError?.(...args) : void 0
511
+ });
512
+ return store;
513
+ }, [
514
+ model,
515
+ token,
516
+ sessionEndpoint,
517
+ websocket,
518
+ protocols,
519
+ startupTimeoutMs,
520
+ closeTimeoutMs,
521
+ rtcDisconnectTimeoutMs,
522
+ sessionConfig,
523
+ sampleRate,
524
+ maxEvents,
525
+ maxPlaybackBufferSeconds
526
+ ]);
527
+ useInsertionEffect(() => {
528
+ ownerRef.current = {
529
+ store: rt,
530
+ onToolCall,
531
+ onEvent,
532
+ onError
533
+ };
534
+ rt.onToolCall = onToolCall == null ? void 0 : (...args) => ownerRef.current?.store === rt ? ownerRef.current.onToolCall?.(...args) : void 0;
535
+ return () => {
536
+ ownerRef.current = null;
537
+ };
538
+ });
539
+ useIsomorphicLayoutEffect(() => {
540
+ return () => rt.dispose();
541
+ }, [rt]);
542
+ const actions = useMemo(() => {
543
+ const current = () => {
544
+ if (ownerRef.current == null) throw new Error("Realtime controls require a mounted hook");
545
+ return ownerRef.current.store;
546
+ };
547
+ return {
548
+ connect: async (options) => {
549
+ const store = current();
550
+ return options == null ? store.connect() : store.connect(options);
551
+ },
552
+ close: async (options) => current().close(options),
553
+ resumePlayback: async () => current().resumePlayback(),
554
+ resumeAudioCapture: async () => current().resumeAudioCapture(),
555
+ disconnect: () => current().disconnect(),
556
+ addToolOutput: (callId, result) => current().addToolOutput(callId, result),
557
+ sendEvent: (event) => current().sendEvent(event),
558
+ sendTextMessage: (text) => current().sendTextMessage(text),
559
+ sendAudio: (audio) => current().sendAudio(audio),
560
+ commitAudio: () => current().commitAudio(),
561
+ clearAudioBuffer: () => current().clearAudioBuffer(),
562
+ requestResponse: (options) => current().requestResponse(options),
563
+ cancelResponse: () => current().cancelResponse(),
564
+ startAudioCapture: (stream) => current().startAudioCapture(stream),
565
+ stopAudioCapture: () => current().stopAudioCapture(),
566
+ stopPlayback: () => current().stopPlayback()
567
+ };
568
+ }, []);
569
+ return {
570
+ status: useSyncExternalStore(useCallback((cb) => rt.subscribe("status", cb), [rt]), () => rt.status, () => rt.status),
571
+ messages: useSyncExternalStore(useCallback((cb) => rt.subscribe("messages", cb), [rt]), () => rt.messages, () => rt.messages),
572
+ events: useSyncExternalStore(useCallback((cb) => rt.subscribe("events", cb), [rt]), () => rt.events, () => rt.events),
573
+ isCapturing: useSyncExternalStore(useCallback((cb) => rt.subscribe("isCapturing", cb), [rt]), () => rt.isCapturing, () => rt.isCapturing),
574
+ isPlaying: useSyncExternalStore(useCallback((cb) => rt.subscribe("isPlaying", cb), [rt]), () => rt.isPlaying, () => rt.isPlaying),
575
+ session: useSyncExternalStore(useCallback((cb) => rt.subscribe("session", cb), [rt]), () => rt.session, () => rt.session),
576
+ ...actions
577
+ };
748
578
  }
749
- var experimental_useRealtime = useRealtime;
750
-
751
- // src/mcp-apps/app-renderer.tsx
752
- import { useEffect as useEffect5, useState as useState3 } from "react";
753
-
754
- // src/mcp-apps/app-frame.tsx
755
- import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef5 } from "react";
756
-
757
- // src/mcp-apps/bridge.ts
758
- import { isJSONObject } from "@ai-sdk/provider";
759
- var MCP_APP_PROTOCOL_VERSION = "2026-01-26";
579
+ const experimental_useRealtime = useRealtime;
580
+ //#endregion
581
+ //#region src/mcp-apps/bridge.ts
582
+ const MCP_APP_PROTOCOL_VERSION = "2026-01-26";
583
+ /**
584
+ * Checks whether an iframe message looks like a JSON-RPC 2.0 message.
585
+ */
760
586
  function isJsonRpcMessage(value) {
761
- return value != null && typeof value === "object" && !Array.isArray(value) && "jsonrpc" in value && value.jsonrpc === "2.0";
587
+ return value != null && typeof value === "object" && !Array.isArray(value) && "jsonrpc" in value && value.jsonrpc === "2.0";
762
588
  }
589
+ /**
590
+ * Checks whether a JSON-RPC message expects a response.
591
+ */
763
592
  function isRequest(message) {
764
- return "method" in message && "id" in message;
593
+ return "method" in message && "id" in message;
765
594
  }
595
+ /**
596
+ * Checks whether a JSON-RPC message is a fire-and-forget notification.
597
+ */
766
598
  function isNotification(message) {
767
- return "method" in message && !("id" in message);
599
+ return "method" in message && !("id" in message);
768
600
  }
601
+ /**
602
+ * Normalizes unknown thrown values into an `Error`.
603
+ */
769
604
  function toError(error) {
770
- return error instanceof Error ? error : new Error(String(error));
605
+ return error instanceof Error ? error : new Error(String(error));
771
606
  }
607
+ /**
608
+ * Validates the params for app-initiated `tools/call` requests.
609
+ */
772
610
  function assertToolCallParams(params) {
773
- if (!isJSONObject(params) || typeof params.name !== "string") {
774
- throw new Error("Invalid tools/call params");
775
- }
776
- return {
777
- name: params.name,
778
- arguments: isJSONObject(params.arguments) ? params.arguments : void 0
779
- };
611
+ if (!isJSONObject(params) || typeof params.name !== "string") throw new Error("Invalid tools/call params");
612
+ return {
613
+ name: params.name,
614
+ arguments: isJSONObject(params.arguments) ? params.arguments : void 0
615
+ };
780
616
  }
617
+ /**
618
+ * Validates `resources/read` params and limits reads to `ui://` app resources.
619
+ */
781
620
  function assertResourceReadParams(params) {
782
- if (!isJSONObject(params) || typeof params.uri !== "string") {
783
- throw new Error("Invalid resources/read params");
784
- }
785
- if (!params.uri.startsWith("ui://")) {
786
- throw new Error(
787
- `resources/read is limited to ui:// resources: ${params.uri}`
788
- );
789
- }
790
- return { uri: params.uri };
621
+ if (!isJSONObject(params) || typeof params.uri !== "string") throw new Error("Invalid resources/read params");
622
+ if (!params.uri.startsWith("ui://")) throw new Error(`resources/read is limited to ui:// resources: ${params.uri}`);
623
+ return { uri: params.uri };
791
624
  }
625
+ /**
626
+ * Validates `ui/open-link` params and allows only `https:`/`http:`/`mailto:`
627
+ * URLs.
628
+ */
792
629
  function assertOpenLinkParams(params) {
793
- if (!isJSONObject(params) || typeof params.url !== "string") {
794
- throw new Error("Invalid ui/open-link params");
795
- }
796
- let scheme;
797
- try {
798
- scheme = new URL(params.url).protocol;
799
- } catch {
800
- throw new Error(`Invalid ui/open-link url: ${params.url}`);
801
- }
802
- if (scheme !== "https:" && scheme !== "http:" && scheme !== "mailto:") {
803
- throw new Error(`Disallowed ui/open-link scheme: ${scheme}`);
804
- }
805
- return { url: params.url };
630
+ if (!isJSONObject(params) || typeof params.url !== "string") throw new Error("Invalid ui/open-link params");
631
+ let scheme;
632
+ try {
633
+ scheme = new URL(params.url).protocol;
634
+ } catch {
635
+ throw new Error(`Invalid ui/open-link url: ${params.url}`);
636
+ }
637
+ if (scheme !== "https:" && scheme !== "http:" && scheme !== "mailto:") throw new Error(`Disallowed ui/open-link scheme: ${scheme}`);
638
+ return { url: params.url };
806
639
  }
640
+ /**
641
+ * Validates params for `ui/request-display-mode`.
642
+ */
807
643
  function assertDisplayModeParams(params) {
808
- if (!isJSONObject(params) || params.mode !== "inline" && params.mode !== "fullscreen" && params.mode !== "pip") {
809
- throw new Error("Invalid ui/request-display-mode params");
810
- }
811
- return { mode: params.mode };
644
+ if (!isJSONObject(params) || params.mode !== "inline" && params.mode !== "fullscreen" && params.mode !== "pip") throw new Error("Invalid ui/request-display-mode params");
645
+ return { mode: params.mode };
812
646
  }
647
+ /**
648
+ * Host-side JSON-RPC bridge for one MCP App iframe.
649
+ *
650
+ * It handles the MCP Apps initialization handshake, sends tool input/result
651
+ * notifications to the iframe, and proxies allowed iframe requests through
652
+ * host-provided callbacks.
653
+ *
654
+ * @example
655
+ * ```ts
656
+ * const bridge = new MCPAppBridge({
657
+ * targetWindow: iframe.contentWindow!,
658
+ * handlers: {
659
+ * allowedTools: ['refreshDashboardData'],
660
+ * callTool: params => client.callTool(params),
661
+ * },
662
+ * });
663
+ *
664
+ * window.addEventListener('message', event => bridge.handleMessage(event));
665
+ * bridge.sendToolInput({ topic: 'usage' });
666
+ * ```
667
+ */
813
668
  var MCPAppBridge = class {
814
- constructor({
815
- targetWindow,
816
- targetOrigin = "*",
817
- handlers = {},
818
- hostInfo = { name: "ai-sdk-react", version: "1.0.0" },
819
- hostContext = { displayMode: "inline" }
820
- }) {
821
- this.initialized = false;
822
- this.pendingNotifications = [];
823
- this.nextRequestId = 0;
824
- this.pendingResponses = /* @__PURE__ */ new Map();
825
- this.targetWindow = targetWindow;
826
- this.targetOrigin = targetOrigin;
827
- this.handlers = handlers;
828
- this.hostInfo = hostInfo;
829
- this.hostContext = hostContext;
830
- }
831
- /**
832
- * Replaces the callbacks used to serve iframe requests.
833
- */
834
- setHandlers(handlers) {
835
- this.handlers = handlers;
836
- }
837
- /**
838
- * Updates host context and notifies the iframe after initialization.
839
- *
840
- * @example
841
- * ```ts
842
- * bridge.setHostContext({ theme: 'dark', displayMode: 'inline' });
843
- * ```
844
- */
845
- setHostContext(hostContext) {
846
- this.hostContext = hostContext;
847
- this.sendNotification({
848
- method: "ui/notifications/host-context-changed",
849
- params: hostContext
850
- });
851
- }
852
- /**
853
- * Whether a `message` event came from the expected proxy window and origin.
854
- * The origin check is skipped only when `targetOrigin` is the `'*'` default.
855
- * Callers that intercept events before {@link handleMessage} share this check.
856
- */
857
- acceptsEvent(event) {
858
- return event.source === this.targetWindow && (this.targetOrigin === "*" || event.origin === this.targetOrigin);
859
- }
860
- /**
861
- * Processes one `message` event from the sandbox proxy iframe.
862
- */
863
- handleMessage(event) {
864
- if (!this.acceptsEvent(event) || !isJsonRpcMessage(event.data)) {
865
- return;
866
- }
867
- const message = event.data;
868
- if ("result" in message || "error" in message) {
869
- this.handleResponse(message);
870
- return;
871
- }
872
- if (isRequest(message)) {
873
- void this.handleRequest(message);
874
- return;
875
- }
876
- if (isNotification(message)) {
877
- this.handleNotification(message);
878
- }
879
- }
880
- /**
881
- * Sends app HTML and sandbox settings to the sandbox proxy.
882
- */
883
- sendSandboxResourceReady(params) {
884
- this.post({
885
- jsonrpc: "2.0",
886
- method: "ui/notifications/sandbox-resource-ready",
887
- params
888
- });
889
- }
890
- /**
891
- * Sends final tool arguments to the MCP App.
892
- */
893
- sendToolInput(input) {
894
- this.sendNotification({
895
- method: "ui/notifications/tool-input",
896
- params: { arguments: input }
897
- });
898
- }
899
- /**
900
- * Sends a completed MCP tool result to the MCP App.
901
- */
902
- sendToolResult(result) {
903
- this.sendNotification({
904
- method: "ui/notifications/tool-result",
905
- params: result
906
- });
907
- }
908
- /**
909
- * Notifies the MCP App that the related tool call was cancelled.
910
- */
911
- sendToolCancelled(reason) {
912
- this.sendNotification({
913
- method: "ui/notifications/tool-cancelled",
914
- params: reason != null ? { reason } : {}
915
- });
916
- }
917
- /**
918
- * Requests graceful teardown before the host removes the iframe.
919
- */
920
- teardownResource() {
921
- return this.request("ui/resource-teardown", {});
922
- }
923
- /**
924
- * Rejects pending bridge requests and clears queued notifications.
925
- */
926
- close() {
927
- for (const pending of this.pendingResponses.values()) {
928
- pending.reject(new Error("MCP App bridge closed"));
929
- }
930
- this.pendingResponses.clear();
931
- this.pendingNotifications = [];
932
- }
933
- /**
934
- * Resolves or rejects a host-initiated request when the iframe responds.
935
- */
936
- handleResponse(response) {
937
- const pending = this.pendingResponses.get(response.id);
938
- if (pending == null) {
939
- return;
940
- }
941
- this.pendingResponses.delete(response.id);
942
- if (response.error != null) {
943
- pending.reject(new Error(response.error.message));
944
- } else {
945
- pending.resolve(response.result);
946
- }
947
- }
948
- /**
949
- * Runs a handler for an iframe request and posts the JSON-RPC response.
950
- */
951
- async handleRequest(request) {
952
- try {
953
- const result = await this.getRequestResult(request);
954
- this.post({ jsonrpc: "2.0", id: request.id, result });
955
- } catch (error) {
956
- const normalizedError = toError(error);
957
- this.handlers.onError?.(normalizedError);
958
- this.post({
959
- jsonrpc: "2.0",
960
- id: request.id,
961
- error: { code: -32603, message: normalizedError.message }
962
- });
963
- }
964
- }
965
- /**
966
- * Maps supported iframe request methods to host callbacks.
967
- */
968
- async getRequestResult(request) {
969
- switch (request.method) {
970
- case "ui/initialize":
971
- return {
972
- protocolVersion: MCP_APP_PROTOCOL_VERSION,
973
- hostCapabilities: {
974
- ...this.handlers.callTool != null ? { serverTools: {} } : {},
975
- ...this.handlers.readResource != null ? { serverResources: {} } : {},
976
- ...this.handlers.onLog != null ? { logging: {} } : {}
977
- },
978
- hostInfo: this.hostInfo,
979
- hostContext: this.hostContext
980
- };
981
- case "tools/call": {
982
- if (this.handlers.callTool == null) {
983
- throw new Error("No tools/call handler configured");
984
- }
985
- const params = assertToolCallParams(request.params);
986
- if (this.handlers.allowedTools == null || !this.handlers.allowedTools.includes(params.name)) {
987
- throw new Error(`Tool is not app-visible: ${params.name}`);
988
- }
989
- return this.handlers.callTool(params);
990
- }
991
- case "resources/read":
992
- if (this.handlers.readResource == null) {
993
- throw new Error("No resources/read handler configured");
994
- }
995
- return this.handlers.readResource(
996
- assertResourceReadParams(request.params)
997
- );
998
- case "resources/list":
999
- if (this.handlers.listResources == null) {
1000
- throw new Error("No resources/list handler configured");
1001
- }
1002
- return this.handlers.listResources(request.params);
1003
- case "ui/open-link":
1004
- if (this.handlers.openLink == null) {
1005
- throw new Error("No ui/open-link handler configured");
1006
- }
1007
- return this.handlers.openLink(assertOpenLinkParams(request.params));
1008
- case "ui/message":
1009
- return this.handlers.sendMessage?.(request.params) ?? {};
1010
- case "ui/update-model-context":
1011
- return this.handlers.updateModelContext?.(request.params) ?? {};
1012
- case "ui/request-display-mode":
1013
- return this.handlers.requestDisplayMode?.(
1014
- assertDisplayModeParams(request.params)
1015
- ) ?? { mode: this.hostContext.displayMode ?? "inline" };
1016
- default:
1017
- throw new Error(`Unsupported MCP App method: ${request.method}`);
1018
- }
1019
- }
1020
- /**
1021
- * Handles iframe lifecycle and telemetry notifications.
1022
- */
1023
- handleNotification(notification) {
1024
- switch (notification.method) {
1025
- case "ui/notifications/initialized":
1026
- this.initialized = true;
1027
- this.flushNotifications();
1028
- this.handlers.onInitialized?.();
1029
- break;
1030
- case "ui/notifications/size-changed":
1031
- this.handlers.onSizeChange?.(
1032
- notification.params
1033
- );
1034
- break;
1035
- case "ui/notifications/request-teardown":
1036
- this.handlers.onRequestTeardown?.(notification.params);
1037
- break;
1038
- case "notifications/message":
1039
- this.handlers.onLog?.(notification.params);
1040
- break;
1041
- }
1042
- }
1043
- /**
1044
- * Sends a host-to-iframe notification, queueing it until app initialization.
1045
- */
1046
- sendNotification(notification) {
1047
- const message = { jsonrpc: "2.0", ...notification };
1048
- if (!this.initialized && !notification.method.includes("sandbox")) {
1049
- this.pendingNotifications.push(message);
1050
- return;
1051
- }
1052
- this.post(message);
1053
- }
1054
- /**
1055
- * Sends notifications that were queued before `ui/notifications/initialized`.
1056
- */
1057
- flushNotifications() {
1058
- const notifications = this.pendingNotifications;
1059
- this.pendingNotifications = [];
1060
- for (const notification of notifications) {
1061
- this.post(notification);
1062
- }
1063
- }
1064
- /**
1065
- * Sends a host-initiated JSON-RPC request to the iframe.
1066
- */
1067
- request(method, params) {
1068
- const id = this.nextRequestId++;
1069
- this.post({ jsonrpc: "2.0", id, method, params });
1070
- return new Promise((resolve2, reject) => {
1071
- this.pendingResponses.set(id, { resolve: resolve2, reject });
1072
- });
1073
- }
1074
- /**
1075
- * Posts a JSON-RPC message to the sandbox proxy iframe.
1076
- */
1077
- post(message) {
1078
- this.targetWindow.postMessage(message, this.targetOrigin);
1079
- }
669
+ constructor({ targetWindow, targetOrigin = "*", handlers = {}, hostInfo = {
670
+ name: "ai-sdk-react",
671
+ version: "1.0.0"
672
+ }, hostContext = { displayMode: "inline" } }) {
673
+ this.initialized = false;
674
+ this.pendingNotifications = [];
675
+ this.nextRequestId = 0;
676
+ this.pendingResponses = /* @__PURE__ */ new Map();
677
+ this.targetWindow = targetWindow;
678
+ this.targetOrigin = targetOrigin;
679
+ this.handlers = handlers;
680
+ this.hostInfo = hostInfo;
681
+ this.hostContext = hostContext;
682
+ }
683
+ /**
684
+ * Replaces the callbacks used to serve iframe requests.
685
+ */
686
+ setHandlers(handlers) {
687
+ this.handlers = handlers;
688
+ }
689
+ /**
690
+ * Updates host context and notifies the iframe after initialization.
691
+ *
692
+ * @example
693
+ * ```ts
694
+ * bridge.setHostContext({ theme: 'dark', displayMode: 'inline' });
695
+ * ```
696
+ */
697
+ setHostContext(hostContext) {
698
+ this.hostContext = hostContext;
699
+ this.sendNotification({
700
+ method: "ui/notifications/host-context-changed",
701
+ params: hostContext
702
+ });
703
+ }
704
+ /**
705
+ * Whether a `message` event came from the expected proxy window and origin.
706
+ * The origin check is skipped only when `targetOrigin` is the `'*'` default.
707
+ * Callers that intercept events before {@link handleMessage} share this check.
708
+ */
709
+ acceptsEvent(event) {
710
+ return event.source === this.targetWindow && (this.targetOrigin === "*" || event.origin === this.targetOrigin);
711
+ }
712
+ /**
713
+ * Processes one `message` event from the sandbox proxy iframe.
714
+ */
715
+ handleMessage(event) {
716
+ if (!this.acceptsEvent(event) || !isJsonRpcMessage(event.data)) return;
717
+ const message = event.data;
718
+ if ("result" in message || "error" in message) {
719
+ this.handleResponse(message);
720
+ return;
721
+ }
722
+ if (isRequest(message)) {
723
+ this.handleRequest(message);
724
+ return;
725
+ }
726
+ if (isNotification(message)) this.handleNotification(message);
727
+ }
728
+ /**
729
+ * Sends app HTML and sandbox settings to the sandbox proxy.
730
+ */
731
+ sendSandboxResourceReady(params) {
732
+ this.post({
733
+ jsonrpc: "2.0",
734
+ method: "ui/notifications/sandbox-resource-ready",
735
+ params
736
+ });
737
+ }
738
+ /**
739
+ * Sends final tool arguments to the MCP App.
740
+ */
741
+ sendToolInput(input) {
742
+ this.sendNotification({
743
+ method: "ui/notifications/tool-input",
744
+ params: { arguments: input }
745
+ });
746
+ }
747
+ /**
748
+ * Sends a completed MCP tool result to the MCP App.
749
+ */
750
+ sendToolResult(result) {
751
+ this.sendNotification({
752
+ method: "ui/notifications/tool-result",
753
+ params: result
754
+ });
755
+ }
756
+ /**
757
+ * Notifies the MCP App that the related tool call was cancelled.
758
+ */
759
+ sendToolCancelled(reason) {
760
+ this.sendNotification({
761
+ method: "ui/notifications/tool-cancelled",
762
+ params: reason != null ? { reason } : {}
763
+ });
764
+ }
765
+ /**
766
+ * Requests graceful teardown before the host removes the iframe.
767
+ */
768
+ teardownResource() {
769
+ return this.request("ui/resource-teardown", {});
770
+ }
771
+ /**
772
+ * Rejects pending bridge requests and clears queued notifications.
773
+ */
774
+ close() {
775
+ for (const pending of this.pendingResponses.values()) pending.reject(/* @__PURE__ */ new Error("MCP App bridge closed"));
776
+ this.pendingResponses.clear();
777
+ this.pendingNotifications = [];
778
+ }
779
+ /**
780
+ * Resolves or rejects a host-initiated request when the iframe responds.
781
+ */
782
+ handleResponse(response) {
783
+ const pending = this.pendingResponses.get(response.id);
784
+ if (pending == null) return;
785
+ this.pendingResponses.delete(response.id);
786
+ if (response.error != null) pending.reject(new Error(response.error.message));
787
+ else pending.resolve(response.result);
788
+ }
789
+ /**
790
+ * Runs a handler for an iframe request and posts the JSON-RPC response.
791
+ */
792
+ async handleRequest(request) {
793
+ try {
794
+ const result = await this.getRequestResult(request);
795
+ this.post({
796
+ jsonrpc: "2.0",
797
+ id: request.id,
798
+ result
799
+ });
800
+ } catch (error) {
801
+ const normalizedError = toError(error);
802
+ this.handlers.onError?.(normalizedError);
803
+ this.post({
804
+ jsonrpc: "2.0",
805
+ id: request.id,
806
+ error: {
807
+ code: -32603,
808
+ message: normalizedError.message
809
+ }
810
+ });
811
+ }
812
+ }
813
+ /**
814
+ * Maps supported iframe request methods to host callbacks.
815
+ */
816
+ async getRequestResult(request) {
817
+ switch (request.method) {
818
+ case "ui/initialize": return {
819
+ protocolVersion: MCP_APP_PROTOCOL_VERSION,
820
+ hostCapabilities: {
821
+ ...this.handlers.callTool != null ? { serverTools: {} } : {},
822
+ ...this.handlers.readResource != null ? { serverResources: {} } : {},
823
+ ...this.handlers.onLog != null ? { logging: {} } : {}
824
+ },
825
+ hostInfo: this.hostInfo,
826
+ hostContext: this.hostContext
827
+ };
828
+ case "tools/call": {
829
+ if (this.handlers.callTool == null) throw new Error("No tools/call handler configured");
830
+ const params = assertToolCallParams(request.params);
831
+ if (this.handlers.allowedTools == null || !this.handlers.allowedTools.includes(params.name)) throw new Error(`Tool is not app-visible: ${params.name}`);
832
+ return this.handlers.callTool(params);
833
+ }
834
+ case "resources/read":
835
+ if (this.handlers.readResource == null) throw new Error("No resources/read handler configured");
836
+ return this.handlers.readResource(assertResourceReadParams(request.params));
837
+ case "resources/list":
838
+ if (this.handlers.listResources == null) throw new Error("No resources/list handler configured");
839
+ return this.handlers.listResources(request.params);
840
+ case "ui/open-link":
841
+ if (this.handlers.openLink == null) throw new Error("No ui/open-link handler configured");
842
+ return this.handlers.openLink(assertOpenLinkParams(request.params));
843
+ case "ui/message": return this.handlers.sendMessage?.(request.params) ?? {};
844
+ case "ui/update-model-context": return this.handlers.updateModelContext?.(request.params) ?? {};
845
+ case "ui/request-display-mode": return this.handlers.requestDisplayMode?.(assertDisplayModeParams(request.params)) ?? { mode: this.hostContext.displayMode ?? "inline" };
846
+ default: throw new Error(`Unsupported MCP App method: ${request.method}`);
847
+ }
848
+ }
849
+ /**
850
+ * Handles iframe lifecycle and telemetry notifications.
851
+ */
852
+ handleNotification(notification) {
853
+ switch (notification.method) {
854
+ case "ui/notifications/initialized":
855
+ this.initialized = true;
856
+ this.flushNotifications();
857
+ this.handlers.onInitialized?.();
858
+ break;
859
+ case "ui/notifications/size-changed":
860
+ this.handlers.onSizeChange?.(notification.params);
861
+ break;
862
+ case "ui/notifications/request-teardown":
863
+ this.handlers.onRequestTeardown?.(notification.params);
864
+ break;
865
+ case "notifications/message": this.handlers.onLog?.(notification.params);
866
+ }
867
+ }
868
+ /**
869
+ * Sends a host-to-iframe notification, queueing it until app initialization.
870
+ */
871
+ sendNotification(notification) {
872
+ const message = {
873
+ jsonrpc: "2.0",
874
+ ...notification
875
+ };
876
+ if (!this.initialized && !notification.method.includes("sandbox")) {
877
+ this.pendingNotifications.push(message);
878
+ return;
879
+ }
880
+ this.post(message);
881
+ }
882
+ /**
883
+ * Sends notifications that were queued before `ui/notifications/initialized`.
884
+ */
885
+ flushNotifications() {
886
+ const notifications = this.pendingNotifications;
887
+ this.pendingNotifications = [];
888
+ for (const notification of notifications) this.post(notification);
889
+ }
890
+ /**
891
+ * Sends a host-initiated JSON-RPC request to the iframe.
892
+ */
893
+ request(method, params) {
894
+ const id = this.nextRequestId++;
895
+ this.post({
896
+ jsonrpc: "2.0",
897
+ id,
898
+ method,
899
+ params
900
+ });
901
+ return new Promise((resolve, reject) => {
902
+ this.pendingResponses.set(id, {
903
+ resolve,
904
+ reject
905
+ });
906
+ });
907
+ }
908
+ /**
909
+ * Posts a JSON-RPC message to the sandbox proxy iframe.
910
+ */
911
+ post(message) {
912
+ this.targetWindow.postMessage(message, this.targetOrigin);
913
+ }
1080
914
  };
1081
-
1082
- // src/mcp-apps/sandbox.ts
1083
- var ALLOWED_CSP_SCHEMES = /* @__PURE__ */ new Set(["https:", "wss:"]);
915
+ //#endregion
916
+ //#region src/mcp-apps/sandbox.ts
917
+ const ALLOWED_CSP_SCHEMES = /* @__PURE__ */ new Set(["https:", "wss:"]);
1084
918
  function sanitizeCSPSources(sources) {
1085
- const result = [];
1086
- for (const source of sources ?? []) {
1087
- if (typeof source !== "string") {
1088
- continue;
1089
- }
1090
- let origin;
1091
- try {
1092
- const url = new URL(source);
1093
- if (!ALLOWED_CSP_SCHEMES.has(url.protocol) || url.host.length === 0 || url.host === "*") {
1094
- continue;
1095
- }
1096
- origin = url.origin;
1097
- } catch {
1098
- continue;
1099
- }
1100
- if (/["'`\s;,]/.test(origin)) {
1101
- continue;
1102
- }
1103
- result.push(origin);
1104
- }
1105
- return result;
919
+ const result = [];
920
+ for (const source of sources ?? []) {
921
+ if (typeof source !== "string") continue;
922
+ let origin;
923
+ try {
924
+ const url = new URL(source);
925
+ if (!ALLOWED_CSP_SCHEMES.has(url.protocol) || url.host.length === 0 || url.host === "*") continue;
926
+ origin = url.origin;
927
+ } catch {
928
+ continue;
929
+ }
930
+ if (/["'`\s;,]/.test(origin)) continue;
931
+ result.push(origin);
932
+ }
933
+ return result;
1106
934
  }
1107
- var MCP_APP_DEFAULT_OUTER_SANDBOX = "allow-scripts allow-same-origin allow-forms";
1108
- var MCP_APP_DEFAULT_INNER_SANDBOX = "allow-scripts allow-forms";
935
+ /**
936
+ * Converts MCP App CSP metadata into a Content-Security-Policy string.
937
+ *
938
+ * The returned value is meant to be passed to a sandbox proxy, which can apply
939
+ * it to the inner iframe document.
940
+ *
941
+ * @example
942
+ * ```ts
943
+ * const csp = getMCPAppCSP({
944
+ * connectDomains: ['https://api.example.com'],
945
+ * resourceDomains: ['https://cdn.example.com'],
946
+ * });
947
+ * ```
948
+ */
1109
949
  function getMCPAppCSP(csp) {
1110
- if (csp == null) {
1111
- return void 0;
1112
- }
1113
- const connectSrc = ["'self'", ...sanitizeCSPSources(csp.connectDomains)];
1114
- const imgSrc = [
1115
- "'self'",
1116
- "data:",
1117
- ...sanitizeCSPSources(csp.resourceDomains)
1118
- ];
1119
- const frameSrc = ["'self'", ...sanitizeCSPSources(csp.frameDomains)];
1120
- return [
1121
- "default-src 'none'",
1122
- "base-uri 'none'",
1123
- "form-action 'none'",
1124
- "script-src 'unsafe-inline'",
1125
- "style-src 'unsafe-inline'",
1126
- `connect-src ${connectSrc.join(" ")}`,
1127
- `img-src ${imgSrc.join(" ")}`,
1128
- `font-src ${imgSrc.join(" ")}`,
1129
- `frame-src ${frameSrc.join(" ")}`
1130
- ].join("; ");
950
+ if (csp == null) return;
951
+ const connectSrc = ["'self'", ...sanitizeCSPSources(csp.connectDomains)];
952
+ const imgSrc = [
953
+ "'self'",
954
+ "data:",
955
+ ...sanitizeCSPSources(csp.resourceDomains)
956
+ ];
957
+ const frameSrc = ["'self'", ...sanitizeCSPSources(csp.frameDomains)];
958
+ return [
959
+ "default-src 'none'",
960
+ "base-uri 'none'",
961
+ "form-action 'none'",
962
+ "script-src 'unsafe-inline'",
963
+ "style-src 'unsafe-inline'",
964
+ `connect-src ${connectSrc.join(" ")}`,
965
+ `img-src ${imgSrc.join(" ")}`,
966
+ `font-src ${imgSrc.join(" ")}`,
967
+ `frame-src ${frameSrc.join(" ")}`
968
+ ].join("; ");
1131
969
  }
1132
- var MCP_APP_PERMISSION_FEATURES = {
1133
- camera: "camera",
1134
- microphone: "microphone",
1135
- geolocation: "geolocation",
1136
- clipboardWrite: "clipboard-write"
970
+ const MCP_APP_PERMISSION_FEATURES = {
971
+ camera: "camera",
972
+ microphone: "microphone",
973
+ geolocation: "geolocation",
974
+ clipboardWrite: "clipboard-write"
1137
975
  };
976
+ /**
977
+ * Converts MCP App permission metadata into an iframe `allow` attribute.
978
+ *
979
+ * Deny-by-default: a capability is granted only when it is both requested in
980
+ * `permissions` and present in the host `allowedPermissions` allowlist.
981
+ * Omitting the allowlist grants nothing, mirroring `allowedTools` in the bridge.
982
+ *
983
+ * @example
984
+ * ```ts
985
+ * const allow = getMCPAppAllowAttribute(
986
+ * { microphone: {}, camera: {} },
987
+ * ['microphone'],
988
+ * );
989
+ * // "microphone" — camera requested by server but not host-allowed
990
+ * ```
991
+ */
1138
992
  function getMCPAppAllowAttribute(permissions, allowedPermissions) {
1139
- if (permissions == null || allowedPermissions == null) {
1140
- return void 0;
1141
- }
1142
- const allow = allowedPermissions.filter((permission) => Boolean(permissions[permission])).map((permission) => MCP_APP_PERMISSION_FEATURES[permission]);
1143
- return allow.length > 0 ? allow.join("; ") : void 0;
993
+ if (permissions == null || allowedPermissions == null) return;
994
+ const allow = allowedPermissions.filter((permission) => Boolean(permissions[permission])).map((permission) => MCP_APP_PERMISSION_FEATURES[permission]);
995
+ return allow.length > 0 ? allow.join("; ") : void 0;
1144
996
  }
1145
-
1146
- // src/mcp-apps/utils.ts
1147
- import { isJSONObject as isJSONObject2 } from "@ai-sdk/provider";
997
+ //#endregion
998
+ //#region src/mcp-apps/utils.ts
999
+ /**
1000
+ * Extracts MCP App metadata from an AI SDK tool UI part.
1001
+ *
1002
+ * @example
1003
+ * ```ts
1004
+ * const app = getMCPAppFromToolPart({
1005
+ * type: 'dynamic-tool',
1006
+ * toolName: 'showDashboard',
1007
+ * toolCallId: 'call-1',
1008
+ * state: 'input-available',
1009
+ * input: { topic: 'usage' },
1010
+ * toolMetadata: {
1011
+ * app: {
1012
+ * resourceUri: 'ui://example/dashboard',
1013
+ * mimeType: 'text/html;profile=mcp-app',
1014
+ * },
1015
+ * },
1016
+ * });
1017
+ * // { resourceUri: 'ui://example/dashboard', mimeType: 'text/html;profile=mcp-app' }
1018
+ * ```
1019
+ */
1148
1020
  function getMCPAppFromToolPart(part) {
1149
- const rawAppMetadata = part.toolMetadata?.app;
1150
- const appMetadata = isJSONObject2(rawAppMetadata) ? rawAppMetadata : void 0;
1151
- if (appMetadata == null || appMetadata.mimeType !== "text/html;profile=mcp-app" || typeof appMetadata.resourceUri !== "string" || !appMetadata.resourceUri.startsWith("ui://") || appMetadata.visibility != null && (!Array.isArray(appMetadata.visibility) || appMetadata.visibility.some(
1152
- (value) => value !== "model" && value !== "app"
1153
- ))) {
1154
- return void 0;
1155
- }
1156
- return appMetadata;
1021
+ const rawAppMetadata = part.toolMetadata?.app;
1022
+ const appMetadata = isJSONObject(rawAppMetadata) ? rawAppMetadata : void 0;
1023
+ if (appMetadata == null || appMetadata.mimeType !== "text/html;profile=mcp-app" || typeof appMetadata.resourceUri !== "string" || !appMetadata.resourceUri.startsWith("ui://") || appMetadata.visibility != null && (!Array.isArray(appMetadata.visibility) || appMetadata.visibility.some((value) => value !== "model" && value !== "app"))) return;
1024
+ return appMetadata;
1157
1025
  }
1026
+ /**
1027
+ * Converts an AI SDK tool output into the MCP Apps tool-result shape.
1028
+ *
1029
+ * MCP Apps expect tool results to have `content` and optional
1030
+ * `structuredContent`. MCP tools already return that shape, but typed AI SDK
1031
+ * tools may return only structured data. This helper wraps structured-only
1032
+ * outputs so the iframe can receive a valid tool result notification.
1033
+ *
1034
+ * @example
1035
+ * ```ts
1036
+ * normalizeMCPAppToolResult({ cards: [] });
1037
+ * // { content: [], structuredContent: { cards: [] } }
1038
+ * ```
1039
+ */
1158
1040
  function normalizeMCPAppToolResult(output) {
1159
- if (output != null && typeof output === "object" && "content" in output) {
1160
- return output;
1161
- }
1162
- return {
1163
- content: [],
1164
- structuredContent: output
1165
- };
1041
+ if (output != null && typeof output === "object" && "content" in output) return output;
1042
+ return {
1043
+ content: [],
1044
+ structuredContent: output
1045
+ };
1166
1046
  }
1167
-
1168
- // src/mcp-apps/app-frame.tsx
1169
- import { jsx } from "react/jsx-runtime";
1047
+ //#endregion
1048
+ //#region src/mcp-apps/app-frame.tsx
1049
+ /**
1050
+ * Derives the concrete origin of the sandbox proxy from its URL, so outbound
1051
+ * postMessage targets a specific origin instead of `'*'` and inbound messages
1052
+ * can be origin-checked. The proxy must be served from a stable, concrete
1053
+ * origin (the default outer sandbox keeps `allow-same-origin`). Falls back to
1054
+ * the host origin on a malformed URL, never `'*'`.
1055
+ */
1170
1056
  function deriveTargetOrigin(url) {
1171
- const location = typeof window !== "undefined" ? window.location : void 0;
1172
- try {
1173
- return new URL(url, location?.href).origin;
1174
- } catch {
1175
- return location?.origin ?? "null";
1176
- }
1057
+ const location = typeof window !== "undefined" ? window.location : void 0;
1058
+ try {
1059
+ return new URL(url, location?.href).origin;
1060
+ } catch {
1061
+ return location?.origin ?? "null";
1062
+ }
1177
1063
  }
1178
- function sendToolState({
1179
- bridge,
1180
- input,
1181
- output
1182
- }) {
1183
- if (bridge == null) {
1184
- return;
1185
- }
1186
- if (input !== void 0) {
1187
- bridge.sendToolInput(input);
1188
- }
1189
- if (output !== void 0) {
1190
- bridge.sendToolResult(normalizeMCPAppToolResult(output));
1191
- }
1064
+ function sendToolState({ bridge, input, output }) {
1065
+ if (bridge == null) return;
1066
+ if (input !== void 0) bridge.sendToolInput(input);
1067
+ if (output !== void 0) bridge.sendToolResult(normalizeMCPAppToolResult(output));
1192
1068
  }
1193
- function MCPAppFrame({
1194
- app,
1195
- resource,
1196
- input,
1197
- output,
1198
- sandbox,
1199
- handlers,
1200
- hostInfo,
1201
- hostContext
1202
- }) {
1203
- const iframeRef = useRef5(null);
1204
- const bridgeRef = useRef5(void 0);
1205
- const inputRef = useRef5(input);
1206
- const outputRef = useRef5(output);
1207
- const hostContextRef = useRef5(hostContext);
1208
- const initializedRef = useRef5(false);
1209
- inputRef.current = input;
1210
- outputRef.current = output;
1211
- hostContextRef.current = hostContext;
1212
- const sandboxUrl = String(sandbox.url);
1213
- const targetOrigin = sandbox.targetOrigin ?? deriveTargetOrigin(sandboxUrl);
1214
- const resourceCSP = getMCPAppCSP(resource.meta?.csp);
1215
- const resourceAllow = getMCPAppAllowAttribute(
1216
- resource.meta?.permissions,
1217
- sandbox.allowedPermissions
1218
- );
1219
- const innerSandbox = sandbox.innerSandbox ?? MCP_APP_DEFAULT_INNER_SANDBOX;
1220
- const bridgeHandlers = useMemo3(
1221
- () => ({
1222
- ...handlers,
1223
- onInitialized: () => {
1224
- initializedRef.current = true;
1225
- handlers?.onInitialized?.();
1226
- sendToolState({
1227
- bridge: bridgeRef.current,
1228
- input: inputRef.current,
1229
- output: outputRef.current
1230
- });
1231
- }
1232
- }),
1233
- [handlers]
1234
- );
1235
- const bridgeHandlersRef = useRef5(bridgeHandlers);
1236
- bridgeHandlersRef.current = bridgeHandlers;
1237
- useEffect4(() => {
1238
- const iframe = iframeRef.current;
1239
- const targetWindow = iframe?.contentWindow;
1240
- if (targetWindow == null) {
1241
- return;
1242
- }
1243
- initializedRef.current = false;
1244
- const bridge = new MCPAppBridge({
1245
- targetWindow,
1246
- targetOrigin,
1247
- handlers: bridgeHandlersRef.current,
1248
- hostInfo,
1249
- hostContext: hostContextRef.current
1250
- });
1251
- bridgeRef.current = bridge;
1252
- const onMessage = (event) => {
1253
- if (!bridge.acceptsEvent(event)) {
1254
- return;
1255
- }
1256
- if (event.data?.jsonrpc === "2.0" && event.data.method === "ui/notifications/sandbox-proxy-ready") {
1257
- bridge.sendSandboxResourceReady({
1258
- html: resource.html,
1259
- csp: resourceCSP,
1260
- sandbox: innerSandbox,
1261
- allow: resourceAllow
1262
- });
1263
- return;
1264
- }
1265
- bridge.handleMessage(event);
1266
- };
1267
- window.addEventListener("message", onMessage);
1268
- return () => {
1269
- initializedRef.current = false;
1270
- window.removeEventListener("message", onMessage);
1271
- void bridge.teardownResource().catch(() => {
1272
- });
1273
- bridge.close();
1274
- bridgeRef.current = void 0;
1275
- };
1276
- }, [
1277
- hostInfo,
1278
- innerSandbox,
1279
- resource.html,
1280
- resourceAllow,
1281
- resourceCSP,
1282
- sandboxUrl,
1283
- targetOrigin
1284
- ]);
1285
- useEffect4(() => {
1286
- bridgeRef.current?.setHandlers(bridgeHandlers);
1287
- }, [bridgeHandlers]);
1288
- useEffect4(() => {
1289
- if (hostContext != null) {
1290
- bridgeRef.current?.setHostContext(hostContext);
1291
- }
1292
- }, [hostContext]);
1293
- useEffect4(() => {
1294
- if (initializedRef.current && input !== void 0) {
1295
- bridgeRef.current?.sendToolInput(input);
1296
- }
1297
- }, [input]);
1298
- useEffect4(() => {
1299
- if (initializedRef.current && output !== void 0) {
1300
- bridgeRef.current?.sendToolResult(normalizeMCPAppToolResult(output));
1301
- }
1302
- }, [output]);
1303
- return /* @__PURE__ */ jsx(
1304
- "iframe",
1305
- {
1306
- ref: iframeRef,
1307
- title: "MCP App",
1308
- "aria-label": sandbox.title ?? app.resourceUri,
1309
- src: sandboxUrl,
1310
- className: sandbox.className,
1311
- style: sandbox.style,
1312
- allow: resourceAllow,
1313
- sandbox: sandbox.outerSandbox ?? MCP_APP_DEFAULT_OUTER_SANDBOX
1314
- }
1315
- );
1069
+ function MCPAppFrame({ app, resource, input, output, sandbox, handlers, hostInfo, hostContext }) {
1070
+ const iframeRef = useRef(null);
1071
+ const bridgeRef = useRef(void 0);
1072
+ const inputRef = useRef(input);
1073
+ const outputRef = useRef(output);
1074
+ const hostContextRef = useRef(hostContext);
1075
+ const initializedRef = useRef(false);
1076
+ inputRef.current = input;
1077
+ outputRef.current = output;
1078
+ hostContextRef.current = hostContext;
1079
+ const sandboxUrl = String(sandbox.url);
1080
+ const targetOrigin = sandbox.targetOrigin ?? deriveTargetOrigin(sandboxUrl);
1081
+ const resourceCSP = getMCPAppCSP(resource.meta?.csp);
1082
+ const resourceAllow = getMCPAppAllowAttribute(resource.meta?.permissions, sandbox.allowedPermissions);
1083
+ const innerSandbox = sandbox.innerSandbox ?? "allow-scripts allow-forms";
1084
+ const bridgeHandlers = useMemo(() => ({
1085
+ ...handlers,
1086
+ onInitialized: () => {
1087
+ initializedRef.current = true;
1088
+ handlers?.onInitialized?.();
1089
+ sendToolState({
1090
+ bridge: bridgeRef.current,
1091
+ input: inputRef.current,
1092
+ output: outputRef.current
1093
+ });
1094
+ }
1095
+ }), [handlers]);
1096
+ const bridgeHandlersRef = useRef(bridgeHandlers);
1097
+ bridgeHandlersRef.current = bridgeHandlers;
1098
+ useEffect(() => {
1099
+ const targetWindow = iframeRef.current?.contentWindow;
1100
+ if (targetWindow == null) return;
1101
+ initializedRef.current = false;
1102
+ const bridge = new MCPAppBridge({
1103
+ targetWindow,
1104
+ targetOrigin,
1105
+ handlers: bridgeHandlersRef.current,
1106
+ hostInfo,
1107
+ hostContext: hostContextRef.current
1108
+ });
1109
+ bridgeRef.current = bridge;
1110
+ const onMessage = (event) => {
1111
+ if (!bridge.acceptsEvent(event)) return;
1112
+ if (event.data?.jsonrpc === "2.0" && event.data.method === "ui/notifications/sandbox-proxy-ready") {
1113
+ bridge.sendSandboxResourceReady({
1114
+ html: resource.html,
1115
+ csp: resourceCSP,
1116
+ sandbox: innerSandbox,
1117
+ allow: resourceAllow
1118
+ });
1119
+ return;
1120
+ }
1121
+ bridge.handleMessage(event);
1122
+ };
1123
+ window.addEventListener("message", onMessage);
1124
+ return () => {
1125
+ initializedRef.current = false;
1126
+ window.removeEventListener("message", onMessage);
1127
+ bridge.teardownResource().catch(() => {});
1128
+ bridge.close();
1129
+ bridgeRef.current = void 0;
1130
+ };
1131
+ }, [
1132
+ hostInfo,
1133
+ innerSandbox,
1134
+ resource.html,
1135
+ resourceAllow,
1136
+ resourceCSP,
1137
+ sandboxUrl,
1138
+ targetOrigin
1139
+ ]);
1140
+ useEffect(() => {
1141
+ bridgeRef.current?.setHandlers(bridgeHandlers);
1142
+ }, [bridgeHandlers]);
1143
+ useEffect(() => {
1144
+ if (hostContext != null) bridgeRef.current?.setHostContext(hostContext);
1145
+ }, [hostContext]);
1146
+ useEffect(() => {
1147
+ if (initializedRef.current && input !== void 0) bridgeRef.current?.sendToolInput(input);
1148
+ }, [input]);
1149
+ useEffect(() => {
1150
+ if (initializedRef.current && output !== void 0) bridgeRef.current?.sendToolResult(normalizeMCPAppToolResult(output));
1151
+ }, [output]);
1152
+ return /* @__PURE__ */ jsx("iframe", {
1153
+ ref: iframeRef,
1154
+ title: "MCP App",
1155
+ "aria-label": sandbox.title ?? app.resourceUri,
1156
+ src: sandboxUrl,
1157
+ className: sandbox.className,
1158
+ style: sandbox.style,
1159
+ allow: resourceAllow,
1160
+ sandbox: sandbox.outerSandbox ?? "allow-scripts allow-same-origin allow-forms"
1161
+ });
1316
1162
  }
1317
-
1318
- // src/mcp-apps/app-renderer.tsx
1319
- import { jsx as jsx2 } from "react/jsx-runtime";
1163
+ //#endregion
1164
+ //#region src/mcp-apps/app-renderer.tsx
1320
1165
  function getToolPartOutput(part) {
1321
- return part.state === "output-available" ? part.output : void 0;
1166
+ return part.state === "output-available" ? part.output : void 0;
1322
1167
  }
1323
1168
  function getToolPartInput(part) {
1324
- return part.state === "input-available" || part.state === "output-available" ? part.input : void 0;
1169
+ return part.state === "input-available" || part.state === "output-available" ? part.input : void 0;
1325
1170
  }
1326
- function MCPAppRenderer({
1327
- part,
1328
- sandbox,
1329
- resource: resourceProp,
1330
- loadResource,
1331
- handlers,
1332
- hostInfo,
1333
- hostContext,
1334
- fallback = null
1335
- }) {
1336
- const app = getMCPAppFromToolPart(part);
1337
- const [cachedApp, setCachedApp] = useState3();
1338
- const [loadedResource, setLoadedResource] = useState3();
1339
- useEffect5(() => {
1340
- if (app != null) {
1341
- setCachedApp(
1342
- (previous) => previous?.resourceUri === app.resourceUri ? previous : app
1343
- );
1344
- }
1345
- }, [app?.resourceUri]);
1346
- const appForRender = app ?? cachedApp;
1347
- useEffect5(() => {
1348
- if (appForRender == null || resourceProp != null || loadResource == null) {
1349
- return;
1350
- }
1351
- let cancelled = false;
1352
- const resourceUri = appForRender.resourceUri;
1353
- loadResource(appForRender).then((resource2) => {
1354
- if (!cancelled) {
1355
- setLoadedResource({ resourceUri, resource: resource2 });
1356
- }
1357
- }).catch((error2) => {
1358
- if (!cancelled) {
1359
- setLoadedResource({
1360
- resourceUri,
1361
- error: error2 instanceof Error ? error2 : new Error(String(error2))
1362
- });
1363
- }
1364
- });
1365
- return () => {
1366
- cancelled = true;
1367
- };
1368
- }, [appForRender?.resourceUri, loadResource, resourceProp]);
1369
- const loadedResourceForApp = loadedResource?.resourceUri === appForRender?.resourceUri ? loadedResource : void 0;
1370
- const resource = resourceProp ?? loadedResourceForApp?.resource;
1371
- const error = resourceProp == null ? loadedResourceForApp?.error : void 0;
1372
- if (appForRender == null || error != null || resource == null) {
1373
- return fallback;
1374
- }
1375
- return /* @__PURE__ */ jsx2(
1376
- MCPAppFrame,
1377
- {
1378
- app: appForRender,
1379
- resource,
1380
- input: getToolPartInput(part),
1381
- output: getToolPartOutput(part),
1382
- sandbox,
1383
- handlers,
1384
- hostInfo,
1385
- hostContext
1386
- }
1387
- );
1171
+ function MCPAppRenderer({ part, sandbox, resource: resourceProp, loadResource, handlers, hostInfo, hostContext, fallback = null }) {
1172
+ const app = getMCPAppFromToolPart(part);
1173
+ const [cachedApp, setCachedApp] = useState();
1174
+ const [loadedResource, setLoadedResource] = useState();
1175
+ useEffect(() => {
1176
+ if (app != null) setCachedApp((previous) => previous?.resourceUri === app.resourceUri ? previous : app);
1177
+ }, [app?.resourceUri]);
1178
+ const appForRender = app ?? cachedApp;
1179
+ useEffect(() => {
1180
+ if (appForRender == null || resourceProp != null || loadResource == null) return;
1181
+ let cancelled = false;
1182
+ const resourceUri = appForRender.resourceUri;
1183
+ loadResource(appForRender).then((resource) => {
1184
+ if (!cancelled) setLoadedResource({
1185
+ resourceUri,
1186
+ resource
1187
+ });
1188
+ }).catch((error) => {
1189
+ if (!cancelled) setLoadedResource({
1190
+ resourceUri,
1191
+ error: error instanceof Error ? error : new Error(String(error))
1192
+ });
1193
+ });
1194
+ return () => {
1195
+ cancelled = true;
1196
+ };
1197
+ }, [
1198
+ appForRender?.resourceUri,
1199
+ loadResource,
1200
+ resourceProp
1201
+ ]);
1202
+ const loadedResourceForApp = loadedResource?.resourceUri === appForRender?.resourceUri ? loadedResource : void 0;
1203
+ const resource = resourceProp ?? loadedResourceForApp?.resource;
1204
+ const error = resourceProp == null ? loadedResourceForApp?.error : void 0;
1205
+ if (appForRender == null || error != null || resource == null) return fallback;
1206
+ return /* @__PURE__ */ jsx(MCPAppFrame, {
1207
+ app: appForRender,
1208
+ resource,
1209
+ input: getToolPartInput(part),
1210
+ output: getToolPartOutput(part),
1211
+ sandbox,
1212
+ handlers,
1213
+ hostInfo,
1214
+ hostContext
1215
+ });
1388
1216
  }
1217
+ //#endregion
1218
+ //#region src/index.ts
1219
+ /**
1220
+ * @deprecated Use `useObject` instead.
1221
+ */
1222
+ const experimental_useObject = useObject;
1223
+ //#endregion
1224
+ export { Chat, MCPAppRenderer as experimental_MCPAppRenderer, experimental_useObject, experimental_useRealtime, useChat, useCompletion, useObject };
1389
1225
 
1390
- // src/index.ts
1391
- var experimental_useObject = useObject;
1392
- export {
1393
- Chat,
1394
- MCPAppRenderer as experimental_MCPAppRenderer,
1395
- experimental_useObject,
1396
- experimental_useRealtime,
1397
- useChat,
1398
- useCompletion,
1399
- useObject
1400
- };
1401
1226
  //# sourceMappingURL=index.js.map