UNPKG

@ai-sdk/react

Version:

[React](https://react.dev/) UI components for the [AI SDK](https://ai-sdk.dev/docs):

1,265 lines (1,249 loc) • 39.4 kB
var __accessCheck = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; var __privateGet = (obj, member, getter) => { __accessCheck(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; var __privateAdd = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); setter ? setter.call(obj, value) : member.set(obj, value); return value; }; // src/use-object.ts import { isAbortError, resolve, normalizeHeaders, safeValidateTypes } from "@ai-sdk/provider-utils"; import { asSchema, isDeepEqualData, parsePartialJson } from "ai"; import { useCallback, useId, useRef, useState } from "react"; import useSWR from "swr"; var getOriginalFetch = () => fetch; function useObject({ api, id, schema, // required, in the future we will use it for validation initialValue, fetch: fetch2, onError, onFinish, headers, credentials }) { const hookId = useId(); const completionId = id != null ? id : hookId; const { data, mutate } = useSWR( [api, completionId], null, { fallbackData: initialValue } ); const [error, setError] = useState(void 0); const [isLoading, setIsLoading] = useState(false); const abortControllerRef = useRef(null); const stop = useCallback(() => { var _a; try { (_a = abortControllerRef.current) == null ? void 0 : _a.abort(); } catch (e) { } finally { setIsLoading(false); abortControllerRef.current = null; } }, []); const submit = async (input) => { var _a; try { clearObject(); setIsLoading(true); const abortController = new AbortController(); abortControllerRef.current = abortController; const resolvedHeaders = await resolve(headers); const actualFetch = fetch2 != null ? fetch2 : getOriginalFetch(); const response = await actualFetch(api, { method: "POST", headers: { "Content-Type": "application/json", ...normalizeHeaders(resolvedHeaders) }, credentials, signal: abortController.signal, body: JSON.stringify(input) }); if (!response.ok) { throw new Error( (_a = await response.text()) != null ? _a : "Failed to fetch the response." ); } if (response.body == null) { throw new Error("The response body is empty."); } let accumulatedText = ""; let latestObject = void 0; await response.body.pipeThrough(new TextDecoderStream()).pipeTo( new WritableStream({ async write(chunk) { accumulatedText += chunk; const { value } = await parsePartialJson(accumulatedText); const currentObject = value; if (!isDeepEqualData(latestObject, currentObject)) { latestObject = currentObject; mutate(currentObject); } }, async close() { setIsLoading(false); abortControllerRef.current = null; if (onFinish != null) { const validationResult = await safeValidateTypes({ value: latestObject, schema: asSchema(schema) }); onFinish( validationResult.success ? { object: validationResult.value, error: void 0 } : { object: void 0, error: validationResult.error } ); } } }) ); } catch (error2) { if (isAbortError(error2)) { return; } if (onError && error2 instanceof Error) { onError(error2); } setIsLoading(false); setError(error2 instanceof Error ? error2 : new Error(String(error2))); } }; const clear = () => { stop(); clearObject(); }; const clearObject = () => { setError(void 0); setIsLoading(false); mutate(void 0); }; return { submit, object: data, error, isLoading, stop, clear }; } // src/use-chat.ts import { DefaultChatTransport } from "ai"; import { useCallback as useCallback2, useEffect, useRef as useRef2, useSyncExternalStore } from "react"; // src/chat.react.ts import { AbstractChat } from "ai"; // src/throttle.ts import throttleFunction from "throttleit"; function throttle(fn, waitMs) { return waitMs != null ? throttleFunction(fn, waitMs) : fn; } // src/chat.react.ts var _messages, _status, _error, _messagesCallbacks, _statusCallbacks, _errorCallbacks, _callMessagesCallbacks, _callStatusCallbacks, _callErrorCallbacks; var ReactChatState = class { constructor(initialMessages = []) { __privateAdd(this, _messages, void 0); __privateAdd(this, _status, "ready"); __privateAdd(this, _error, void 0); __privateAdd(this, _messagesCallbacks, /* @__PURE__ */ new Set()); __privateAdd(this, _statusCallbacks, /* @__PURE__ */ new Set()); __privateAdd(this, _errorCallbacks, /* @__PURE__ */ new Set()); this.pushMessage = (message) => { __privateSet(this, _messages, __privateGet(this, _messages).concat(message)); __privateGet(this, _callMessagesCallbacks).call(this); }; this.popMessage = () => { __privateSet(this, _messages, __privateGet(this, _messages).slice(0, -1)); __privateGet(this, _callMessagesCallbacks).call(this); }; this.replaceMessage = (index, message) => { __privateSet(this, _messages, [ ...__privateGet(this, _messages).slice(0, index), // We deep clone the message here to ensure the new React Compiler (currently in RC) detects deeply nested parts/metadata changes: this.snapshot(message), ...__privateGet(this, _messages).slice(index + 1) ]); __privateGet(this, _callMessagesCallbacks).call(this); }; this.snapshot = (value) => structuredClone(value); this["~registerMessagesCallback"] = (onChange, throttleWaitMs) => { const callback = throttleWaitMs ? throttle(onChange, throttleWaitMs) : onChange; __privateGet(this, _messagesCallbacks).add(callback); return () => { __privateGet(this, _messagesCallbacks).delete(callback); }; }; this["~registerStatusCallback"] = (onChange) => { __privateGet(this, _statusCallbacks).add(onChange); return () => { __privateGet(this, _statusCallbacks).delete(onChange); }; }; this["~registerErrorCallback"] = (onChange) => { __privateGet(this, _errorCallbacks).add(onChange); return () => { __privateGet(this, _errorCallbacks).delete(onChange); }; }; __privateAdd(this, _callMessagesCallbacks, () => { __privateGet(this, _messagesCallbacks).forEach((callback) => callback()); }); __privateAdd(this, _callStatusCallbacks, () => { __privateGet(this, _statusCallbacks).forEach((callback) => callback()); }); __privateAdd(this, _callErrorCallbacks, () => { __privateGet(this, _errorCallbacks).forEach((callback) => callback()); }); __privateSet(this, _messages, initialMessages); } get status() { return __privateGet(this, _status); } set status(newStatus) { __privateSet(this, _status, newStatus); __privateGet(this, _callStatusCallbacks).call(this); } get error() { return __privateGet(this, _error); } set error(newError) { __privateSet(this, _error, newError); __privateGet(this, _callErrorCallbacks).call(this); } get messages() { return __privateGet(this, _messages); } set messages(newMessages) { __privateSet(this, _messages, [...newMessages]); __privateGet(this, _callMessagesCallbacks).call(this); } }; _messages = new WeakMap(); _status = new WeakMap(); _error = new WeakMap(); _messagesCallbacks = new WeakMap(); _statusCallbacks = new WeakMap(); _errorCallbacks = new WeakMap(); _callMessagesCallbacks = new WeakMap(); _callStatusCallbacks = new WeakMap(); _callErrorCallbacks = new WeakMap(); var _state; var Chat = class extends AbstractChat { constructor({ messages, ...init }) { const state = new ReactChatState(messages); super({ ...init, state }); __privateAdd(this, _state, void 0); this["~registerMessagesCallback"] = (onChange, throttleWaitMs) => __privateGet(this, _state)["~registerMessagesCallback"](onChange, throttleWaitMs); this["~registerStatusCallback"] = (onChange) => __privateGet(this, _state)["~registerStatusCallback"](onChange); this["~registerErrorCallback"] = (onChange) => __privateGet(this, _state)["~registerErrorCallback"](onChange); __privateSet(this, _state, state); } }; _state = new WeakMap(); // src/use-chat.ts function useChat({ throttle: throttle2, experimental_throttle, resume = false, ...options } = {}) { const throttleWaitMs = throttle2 != null ? throttle2 : experimental_throttle; const latestRef = useRef2({}); if (!("chat" in options)) { latestRef.current = { onToolCall: options.onToolCall, onData: options.onData, onFinish: options.onFinish, onError: options.onError, sendAutomaticallyWhen: options.sendAutomaticallyWhen, transport: options.transport }; } let defaultTransport; const getTransport = () => { var _a; return (_a = latestRef.current.transport) != null ? _a : defaultTransport != null ? defaultTransport : defaultTransport = new DefaultChatTransport(); }; const chatOptions = { ...options, transport: { sendMessages: (sendOptions) => getTransport().sendMessages(sendOptions), reconnectToStream: (reconnectOptions) => getTransport().reconnectToStream(reconnectOptions) }, onToolCall: (arg) => { var _a, _b; return (_b = (_a = latestRef.current).onToolCall) == null ? void 0 : _b.call(_a, arg); }, onData: (arg) => { var _a, _b; return (_b = (_a = latestRef.current).onData) == null ? void 0 : _b.call(_a, arg); }, onFinish: (arg) => { var _a, _b; return (_b = (_a = latestRef.current).onFinish) == null ? void 0 : _b.call(_a, arg); }, onError: (arg) => { var _a, _b; return (_b = (_a = latestRef.current).onError) == null ? void 0 : _b.call(_a, arg); }, sendAutomaticallyWhen: (arg) => { var _a, _b, _c; return (_c = (_b = (_a = latestRef.current).sendAutomaticallyWhen) == null ? void 0 : _b.call(_a, arg)) != null ? _c : false; } }; const chatRef = useRef2( "chat" in options ? options.chat : new Chat(chatOptions) ); const shouldRecreateChat = "chat" in options && options.chat !== chatRef.current || "id" in options && options.id != null && chatRef.current.id !== options.id; if (shouldRecreateChat) { chatRef.current = "chat" in options ? options.chat : new Chat(chatOptions); } const subscribeToMessages = useCallback2( (update) => chatRef.current["~registerMessagesCallback"](update, throttleWaitMs), // `chatRef.current.id` is required to trigger re-subscription when the chat ID changes // eslint-disable-next-line react-hooks/exhaustive-deps [throttleWaitMs, chatRef.current.id] ); const messages = useSyncExternalStore( subscribeToMessages, () => chatRef.current.messages, () => chatRef.current.messages ); const status = useSyncExternalStore( chatRef.current["~registerStatusCallback"], () => chatRef.current.status, () => chatRef.current.status ); const error = useSyncExternalStore( chatRef.current["~registerErrorCallback"], () => chatRef.current.error, () => chatRef.current.error ); const setMessages = useCallback2( (messagesParam) => { if (typeof messagesParam === "function") { messagesParam = messagesParam(chatRef.current.messages); } chatRef.current.messages = messagesParam; }, [chatRef] ); useEffect(() => { if (resume) { chatRef.current.resumeStream(); } }, [resume, chatRef]); return { id: chatRef.current.id, messages, setMessages, sendMessage: chatRef.current.sendMessage, regenerate: chatRef.current.regenerate, clearError: chatRef.current.clearError, stop: chatRef.current.stop, error, resumeStream: chatRef.current.resumeStream, status, /** * @deprecated Use `addToolOutput` instead. */ addToolResult: chatRef.current.addToolOutput, addToolOutput: chatRef.current.addToolOutput, addToolApprovalResponse: chatRef.current.addToolApprovalResponse }; } // src/use-completion.ts import { callCompletionApi } from "ai"; import { useCallback as useCallback3, useEffect as useEffect2, useId as useId2, useRef as useRef3, useState as useState2 } from "react"; import useSWR2 from "swr"; function useCompletion({ api = "/api/completion", id, initialCompletion = "", initialInput = "", credentials, headers, body, streamProtocol = "data", fetch: fetch2, onFinish, onError, throttle: throttleWait, experimental_throttle } = {}) { const throttleWaitMs = throttleWait != null ? throttleWait : experimental_throttle; const hookId = useId2(); const completionId = id || hookId; const { data, mutate } = useSWR2([api, completionId], null, { fallbackData: initialCompletion }); const { data: isLoading = false, mutate: mutateLoading } = useSWR2( [completionId, "loading"], null ); const [error, setError] = useState2(void 0); const completion = data; const [abortController, setAbortController] = useState2(null); const extraMetadataRef = useRef3({ credentials, headers, body }); useEffect2(() => { extraMetadataRef.current = { credentials, headers, body }; }, [credentials, headers, body]); const triggerRequest = useCallback3( async (prompt, options) => callCompletionApi({ api, prompt, credentials: extraMetadataRef.current.credentials, headers: { ...extraMetadataRef.current.headers, ...options == null ? void 0 : options.headers }, body: { ...extraMetadataRef.current.body, ...options == null ? void 0 : options.body }, streamProtocol, fetch: fetch2, // throttle streamed ui updates: setCompletion: throttle( (completion2) => mutate(completion2, false), throttleWaitMs ), setLoading: mutateLoading, setError, setAbortController, onFinish, onError }), [ mutate, mutateLoading, api, extraMetadataRef, setAbortController, onFinish, onError, setError, streamProtocol, fetch2, throttleWaitMs ] ); const stop = useCallback3(() => { if (abortController) { abortController.abort(); setAbortController(null); } }, [abortController]); const setCompletion = useCallback3( (completion2) => { mutate(completion2, false); }, [mutate] ); const complete = useCallback3( async (prompt, options) => { return triggerRequest(prompt, options); }, [triggerRequest] ); const [input, setInput] = useState2(initialInput); const handleSubmit = useCallback3( (event) => { var _a; (_a = event == null ? void 0 : event.preventDefault) == null ? void 0 : _a.call(event); return input ? complete(input) : void 0; }, [input, complete] ); const handleInputChange = useCallback3( (e) => { setInput(e.target.value); }, [setInput] ); return { completion, complete, error, setCompletion, stop, input, setInput, handleInputChange, handleSubmit, isLoading }; } // src/use-realtime.ts import { Experimental_AbstractRealtimeSession as AbstractRealtimeSession } from "ai"; import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef4, useSyncExternalStore as useSyncExternalStore2 } from "react"; function getRealtimeStoreKey(options) { return { model: options.model, token: options.api.token, sessionConfig: options.sessionConfig, sampleRate: options.sampleRate, maxEvents: options.maxEvents }; } function shouldCreateRealtimeStore(currentKey, nextOptions) { return currentKey.model !== nextOptions.model || currentKey.token !== nextOptions.api.token || currentKey.sessionConfig !== nextOptions.sessionConfig || currentKey.sampleRate !== nextOptions.sampleRate || currentKey.maxEvents !== nextOptions.maxEvents; } var RealtimeStore = class extends AbstractRealtimeSession { constructor() { super(...arguments); this.state = { status: "disconnected", messages: [], events: [], isCapturing: false, isPlaying: false }; this.callbacks = { status: /* @__PURE__ */ new Set(), messages: /* @__PURE__ */ new Set(), events: /* @__PURE__ */ new Set(), isCapturing: /* @__PURE__ */ new Set(), isPlaying: /* @__PURE__ */ new Set() }; } get status() { return this.state.status; } get messages() { return this.state.messages; } get events() { return this.state.events; } get isCapturing() { return this.state.isCapturing; } get isPlaying() { return this.state.isPlaying; } subscribe(key, onChange) { this.callbacks[key].add(onChange); return () => { this.callbacks[key].delete(onChange); }; } setState(key, value) { this.state = { ...this.state, [key]: value }; this.callbacks[key].forEach((callback) => callback()); } pushMessage(message) { this.state = { ...this.state, messages: [...this.state.messages, message] }; this.callbacks.messages.forEach((callback) => callback()); } updateMessages(updater) { this.state = { ...this.state, messages: updater(this.state.messages) }; this.callbacks.messages.forEach((callback) => callback()); } pushEvent(event) { const nextEvents = [...this.state.events, event]; this.state = { ...this.state, events: nextEvents.length > this.maxEvents ? nextEvents.slice(-this.maxEvents) : nextEvents }; this.callbacks.events.forEach((callback) => callback()); } }; function useRealtime(options) { const callbacksRef = useRef4({ onToolCall: options.onToolCall, onEvent: options.onEvent, onError: options.onError }); callbacksRef.current = { onToolCall: options.onToolCall, onEvent: options.onEvent, onError: options.onError }; const realtimeRef = useRef4(null); let realtimeEntry = realtimeRef.current; if (realtimeEntry == null || shouldCreateRealtimeStore(realtimeEntry.key, options)) { realtimeEntry = { store: new RealtimeStore({ ...options, onToolCall: (...args) => { var _a, _b; return (_b = (_a = callbacksRef.current).onToolCall) == null ? void 0 : _b.call(_a, ...args); }, onEvent: (...args) => { var _a, _b; return (_b = (_a = callbacksRef.current).onEvent) == null ? void 0 : _b.call(_a, ...args); }, onError: (...args) => { var _a, _b; return (_b = (_a = callbacksRef.current).onError) == null ? void 0 : _b.call(_a, ...args); } }), key: getRealtimeStoreKey(options) }; realtimeRef.current = realtimeEntry; } else { realtimeEntry.key = getRealtimeStoreKey(options); } const rt = realtimeEntry.store; const status = useSyncExternalStore2( useCallback4((cb) => rt.subscribe("status", cb), [rt]), () => rt.status, () => rt.status ); const messages = useSyncExternalStore2( useCallback4((cb) => rt.subscribe("messages", cb), [rt]), () => rt.messages, () => rt.messages ); const events = useSyncExternalStore2( useCallback4((cb) => rt.subscribe("events", cb), [rt]), () => rt.events, () => rt.events ); const isCapturing = useSyncExternalStore2( useCallback4((cb) => rt.subscribe("isCapturing", cb), [rt]), () => rt.isCapturing, () => rt.isCapturing ); const isPlaying = useSyncExternalStore2( useCallback4((cb) => rt.subscribe("isPlaying", cb), [rt]), () => rt.isPlaying, () => rt.isPlaying ); useEffect3(() => { return () => rt.dispose(); }, [rt]); return { status, messages, events, isCapturing, isPlaying, connect: rt.connect.bind(rt), disconnect: rt.disconnect.bind(rt), addToolOutput: rt.addToolOutput.bind(rt), sendEvent: rt.sendEvent.bind(rt), sendTextMessage: rt.sendTextMessage.bind(rt), sendAudio: rt.sendAudio.bind(rt), commitAudio: rt.commitAudio.bind(rt), clearAudioBuffer: rt.clearAudioBuffer.bind(rt), requestResponse: rt.requestResponse.bind(rt), cancelResponse: rt.cancelResponse.bind(rt), startAudioCapture: rt.startAudioCapture.bind(rt), stopAudioCapture: rt.stopAudioCapture.bind(rt), stopPlayback: rt.stopPlayback.bind(rt) }; } var experimental_useRealtime = useRealtime; // src/mcp-apps/app-renderer.tsx import { useEffect as useEffect5, useState as useState3 } from "react"; // src/mcp-apps/app-frame.tsx import { useEffect as useEffect4, useMemo, useRef as useRef5 } from "react"; // src/mcp-apps/bridge.ts import { isJSONObject } from "@ai-sdk/provider"; var MCP_APP_PROTOCOL_VERSION = "2026-01-26"; function isJsonRpcMessage(value) { return value != null && typeof value === "object" && !Array.isArray(value) && "jsonrpc" in value && value.jsonrpc === "2.0"; } function isRequest(message) { return "method" in message && "id" in message; } function isNotification(message) { return "method" in message && !("id" in message); } function toError(error) { return error instanceof Error ? error : new Error(String(error)); } function assertToolCallParams(params) { if (!isJSONObject(params) || typeof params.name !== "string") { throw new Error("Invalid tools/call params"); } return { name: params.name, arguments: isJSONObject(params.arguments) ? params.arguments : void 0 }; } var MCPAppBridge = class { constructor({ targetWindow, targetOrigin = "*", handlers = {}, hostInfo = { name: "ai-sdk-react", version: "1.0.0" }, hostContext = { displayMode: "inline" } }) { this.initialized = false; this.pendingNotifications = []; this.nextRequestId = 0; this.pendingResponses = /* @__PURE__ */ new Map(); this.targetWindow = targetWindow; this.targetOrigin = targetOrigin; this.handlers = handlers; this.hostInfo = hostInfo; this.hostContext = hostContext; } /** * Replaces the callbacks used to serve iframe requests. */ setHandlers(handlers) { this.handlers = handlers; } /** * Updates host context and notifies the iframe after initialization. * * @example * ```ts * bridge.setHostContext({ theme: 'dark', displayMode: 'inline' }); * ``` */ setHostContext(hostContext) { this.hostContext = hostContext; this.sendNotification({ method: "ui/notifications/host-context-changed", params: hostContext }); } /** * Processes one `message` event from the sandbox proxy iframe. */ handleMessage(event) { if (event.source !== this.targetWindow || !isJsonRpcMessage(event.data)) { return; } const message = event.data; if ("result" in message || "error" in message) { this.handleResponse(message); return; } if (isRequest(message)) { void this.handleRequest(message); return; } if (isNotification(message)) { this.handleNotification(message); } } /** * Sends app HTML and sandbox settings to the sandbox proxy. */ sendSandboxResourceReady(params) { this.post({ jsonrpc: "2.0", method: "ui/notifications/sandbox-resource-ready", params }); } /** * Sends final tool arguments to the MCP App. */ sendToolInput(input) { this.sendNotification({ method: "ui/notifications/tool-input", params: { arguments: input } }); } /** * Sends a completed MCP tool result to the MCP App. */ sendToolResult(result) { this.sendNotification({ method: "ui/notifications/tool-result", params: result }); } /** * Notifies the MCP App that the related tool call was cancelled. */ sendToolCancelled(reason) { this.sendNotification({ method: "ui/notifications/tool-cancelled", params: reason != null ? { reason } : {} }); } /** * Requests graceful teardown before the host removes the iframe. */ teardownResource() { return this.request("ui/resource-teardown", {}); } /** * Rejects pending bridge requests and clears queued notifications. */ close() { for (const pending of this.pendingResponses.values()) { pending.reject(new Error("MCP App bridge closed")); } this.pendingResponses.clear(); this.pendingNotifications = []; } /** * Resolves or rejects a host-initiated request when the iframe responds. */ handleResponse(response) { const pending = this.pendingResponses.get(response.id); if (pending == null) { return; } this.pendingResponses.delete(response.id); if (response.error != null) { pending.reject(new Error(response.error.message)); } else { pending.resolve(response.result); } } /** * Runs a handler for an iframe request and posts the JSON-RPC response. */ async handleRequest(request) { var _a, _b; try { const result = await this.getRequestResult(request); this.post({ jsonrpc: "2.0", id: request.id, result }); } catch (error) { const normalizedError = toError(error); (_b = (_a = this.handlers).onError) == null ? void 0 : _b.call(_a, normalizedError); this.post({ jsonrpc: "2.0", id: request.id, error: { code: -32603, message: normalizedError.message } }); } } /** * Maps supported iframe request methods to host callbacks. */ async getRequestResult(request) { var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j; switch (request.method) { case "ui/initialize": return { protocolVersion: MCP_APP_PROTOCOL_VERSION, hostCapabilities: { ...this.handlers.callTool != null ? { serverTools: {} } : {}, ...this.handlers.readResource != null ? { serverResources: {} } : {}, ...this.handlers.onLog != null ? { logging: {} } : {} }, hostInfo: this.hostInfo, hostContext: this.hostContext }; case "tools/call": { if (this.handlers.callTool == null) { throw new Error("No tools/call handler configured"); } const params = assertToolCallParams(request.params); if (this.handlers.allowedTools == null || !this.handlers.allowedTools.includes(params.name)) { throw new Error(`Tool is not app-visible: ${params.name}`); } return this.handlers.callTool(params); } case "resources/read": if (this.handlers.readResource == null) { throw new Error("No resources/read handler configured"); } return this.handlers.readResource(request.params); case "resources/list": if (this.handlers.listResources == null) { throw new Error("No resources/list handler configured"); } return this.handlers.listResources(request.params); case "ui/open-link": if (this.handlers.openLink == null) { throw new Error("No ui/open-link handler configured"); } return this.handlers.openLink(request.params); case "ui/message": return (_c = (_b = (_a = this.handlers).sendMessage) == null ? void 0 : _b.call(_a, request.params)) != null ? _c : {}; case "ui/update-model-context": return (_f = (_e = (_d = this.handlers).updateModelContext) == null ? void 0 : _e.call(_d, request.params)) != null ? _f : {}; case "ui/request-display-mode": return (_j = (_h = (_g = this.handlers).requestDisplayMode) == null ? void 0 : _h.call( _g, request.params )) != null ? _j : { mode: (_i = this.hostContext.displayMode) != null ? _i : "inline" }; default: throw new Error(`Unsupported MCP App method: ${request.method}`); } } /** * Handles iframe lifecycle and telemetry notifications. */ handleNotification(notification) { var _a, _b, _c, _d, _e, _f, _g, _h; switch (notification.method) { case "ui/notifications/initialized": this.initialized = true; this.flushNotifications(); (_b = (_a = this.handlers).onInitialized) == null ? void 0 : _b.call(_a); break; case "ui/notifications/size-changed": (_d = (_c = this.handlers).onSizeChange) == null ? void 0 : _d.call( _c, notification.params ); break; case "ui/notifications/request-teardown": (_f = (_e = this.handlers).onRequestTeardown) == null ? void 0 : _f.call(_e, notification.params); break; case "notifications/message": (_h = (_g = this.handlers).onLog) == null ? void 0 : _h.call(_g, notification.params); break; } } /** * Sends a host-to-iframe notification, queueing it until app initialization. */ sendNotification(notification) { const message = { jsonrpc: "2.0", ...notification }; if (!this.initialized && !notification.method.includes("sandbox")) { this.pendingNotifications.push(message); return; } this.post(message); } /** * Sends notifications that were queued before `ui/notifications/initialized`. */ flushNotifications() { const notifications = this.pendingNotifications; this.pendingNotifications = []; for (const notification of notifications) { this.post(notification); } } /** * Sends a host-initiated JSON-RPC request to the iframe. */ request(method, params) { const id = this.nextRequestId++; this.post({ jsonrpc: "2.0", id, method, params }); return new Promise((resolve2, reject) => { this.pendingResponses.set(id, { resolve: resolve2, reject }); }); } /** * Posts a JSON-RPC message to the sandbox proxy iframe. */ post(message) { this.targetWindow.postMessage(message, this.targetOrigin); } }; // src/mcp-apps/sandbox.ts var MCP_APP_DEFAULT_OUTER_SANDBOX = "allow-scripts allow-same-origin allow-forms"; var MCP_APP_DEFAULT_INNER_SANDBOX = "allow-scripts allow-forms"; function getMCPAppCSP(csp) { var _a, _b, _c; if (csp == null) { return void 0; } const connectSrc = ["'self'", ...(_a = csp.connectDomains) != null ? _a : []]; const imgSrc = ["'self'", "data:", ...(_b = csp.resourceDomains) != null ? _b : []]; const frameSrc = ["'self'", ...(_c = csp.frameDomains) != null ? _c : []]; return [ "default-src 'none'", "script-src 'unsafe-inline'", "style-src 'unsafe-inline'", `connect-src ${connectSrc.join(" ")}`, `img-src ${imgSrc.join(" ")}`, `font-src ${imgSrc.join(" ")}`, `frame-src ${frameSrc.join(" ")}` ].join("; "); } function getMCPAppAllowAttribute(permissions) { if (permissions == null) { return void 0; } const allow = []; if (permissions.camera) allow.push("camera"); if (permissions.microphone) allow.push("microphone"); if (permissions.geolocation) allow.push("geolocation"); if (permissions.clipboardWrite) allow.push("clipboard-write"); return allow.length > 0 ? allow.join("; ") : void 0; } // src/mcp-apps/utils.ts import { isJSONObject as isJSONObject2 } from "@ai-sdk/provider"; function getMCPAppFromToolPart(part) { var _a; const rawAppMetadata = (_a = part.toolMetadata) == null ? void 0 : _a.app; const appMetadata = isJSONObject2(rawAppMetadata) ? rawAppMetadata : void 0; 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 void 0; } return appMetadata; } function normalizeMCPAppToolResult(output) { if (output != null && typeof output === "object" && "content" in output) { return output; } return { content: [], structuredContent: output }; } // src/mcp-apps/app-frame.tsx import { jsx } from "react/jsx-runtime"; function sendToolState({ bridge, input, output }) { if (bridge == null) { return; } if (input !== void 0) { bridge.sendToolInput(input); } if (output !== void 0) { bridge.sendToolResult(normalizeMCPAppToolResult(output)); } } function MCPAppFrame({ app, resource, input, output, sandbox, handlers, hostInfo, hostContext }) { var _a, _b, _c, _d, _e, _f; const iframeRef = useRef5(null); const bridgeRef = useRef5(void 0); const inputRef = useRef5(input); const outputRef = useRef5(output); const hostContextRef = useRef5(hostContext); const initializedRef = useRef5(false); inputRef.current = input; outputRef.current = output; hostContextRef.current = hostContext; const targetOrigin = (_a = sandbox.targetOrigin) != null ? _a : "*"; const sandboxUrl = String(sandbox.url); const resourceCSP = getMCPAppCSP((_b = resource.meta) == null ? void 0 : _b.csp); const resourceAllow = getMCPAppAllowAttribute((_c = resource.meta) == null ? void 0 : _c.permissions); const innerSandbox = (_d = sandbox.innerSandbox) != null ? _d : MCP_APP_DEFAULT_INNER_SANDBOX; const bridgeHandlers = useMemo( () => ({ ...handlers, onInitialized: () => { var _a2; initializedRef.current = true; (_a2 = handlers == null ? void 0 : handlers.onInitialized) == null ? void 0 : _a2.call(handlers); sendToolState({ bridge: bridgeRef.current, input: inputRef.current, output: outputRef.current }); } }), [handlers] ); const bridgeHandlersRef = useRef5(bridgeHandlers); bridgeHandlersRef.current = bridgeHandlers; useEffect4(() => { const iframe = iframeRef.current; const targetWindow = iframe == null ? void 0 : iframe.contentWindow; if (targetWindow == null) { return; } initializedRef.current = false; const bridge = new MCPAppBridge({ targetWindow, targetOrigin, handlers: bridgeHandlersRef.current, hostInfo, hostContext: hostContextRef.current }); bridgeRef.current = bridge; const onMessage = (event) => { var _a2; if (event.source === targetWindow && ((_a2 = event.data) == null ? void 0 : _a2.jsonrpc) === "2.0" && event.data.method === "ui/notifications/sandbox-proxy-ready") { bridge.sendSandboxResourceReady({ html: resource.html, csp: resourceCSP, sandbox: innerSandbox, allow: resourceAllow }); return; } bridge.handleMessage(event); }; window.addEventListener("message", onMessage); return () => { initializedRef.current = false; window.removeEventListener("message", onMessage); void bridge.teardownResource().catch(() => { }); bridge.close(); bridgeRef.current = void 0; }; }, [ hostInfo, innerSandbox, resource.html, resourceAllow, resourceCSP, sandboxUrl, targetOrigin ]); useEffect4(() => { var _a2; (_a2 = bridgeRef.current) == null ? void 0 : _a2.setHandlers(bridgeHandlers); }, [bridgeHandlers]); useEffect4(() => { var _a2; if (hostContext != null) { (_a2 = bridgeRef.current) == null ? void 0 : _a2.setHostContext(hostContext); } }, [hostContext]); useEffect4(() => { var _a2; if (initializedRef.current && input !== void 0) { (_a2 = bridgeRef.current) == null ? void 0 : _a2.sendToolInput(input); } }, [input]); useEffect4(() => { var _a2; if (initializedRef.current && output !== void 0) { (_a2 = bridgeRef.current) == null ? void 0 : _a2.sendToolResult(normalizeMCPAppToolResult(output)); } }, [output]); return /* @__PURE__ */ jsx( "iframe", { ref: iframeRef, title: "MCP App", "aria-label": (_e = sandbox.title) != null ? _e : app.resourceUri, src: sandboxUrl, className: sandbox.className, style: sandbox.style, sandbox: (_f = sandbox.outerSandbox) != null ? _f : MCP_APP_DEFAULT_OUTER_SANDBOX } ); } // src/mcp-apps/app-renderer.tsx import { jsx as jsx2 } from "react/jsx-runtime"; function getToolPartOutput(part) { return part.state === "output-available" ? part.output : void 0; } function getToolPartInput(part) { return part.state === "input-available" || part.state === "output-available" ? part.input : void 0; } function MCPAppRenderer({ part, sandbox, resource: resourceProp, loadResource, handlers, hostInfo, hostContext, fallback = null }) { const app = getMCPAppFromToolPart(part); const [cachedApp, setCachedApp] = useState3(); const [loadedResource, setLoadedResource] = useState3(); useEffect5(() => { if (app != null) { setCachedApp( (previous) => (previous == null ? void 0 : previous.resourceUri) === app.resourceUri ? previous : app ); } }, [app == null ? void 0 : app.resourceUri]); const appForRender = app != null ? app : cachedApp; useEffect5(() => { if (appForRender == null || resourceProp != null || loadResource == null) { return; } let cancelled = false; const resourceUri = appForRender.resourceUri; loadResource(appForRender).then((resource2) => { if (!cancelled) { setLoadedResource({ resourceUri, resource: resource2 }); } }).catch((error2) => { if (!cancelled) { setLoadedResource({ resourceUri, error: error2 instanceof Error ? error2 : new Error(String(error2)) }); } }); return () => { cancelled = true; }; }, [appForRender == null ? void 0 : appForRender.resourceUri, loadResource, resourceProp]); const loadedResourceForApp = (loadedResource == null ? void 0 : loadedResource.resourceUri) === (appForRender == null ? void 0 : appForRender.resourceUri) ? loadedResource : void 0; const resource = resourceProp != null ? resourceProp : loadedResourceForApp == null ? void 0 : loadedResourceForApp.resource; const error = resourceProp == null ? loadedResourceForApp == null ? void 0 : loadedResourceForApp.error : void 0; if (appForRender == null || error != null || resource == null) { return fallback; } return /* @__PURE__ */ jsx2( MCPAppFrame, { app: appForRender, resource, input: getToolPartInput(part), output: getToolPartOutput(part), sandbox, handlers, hostInfo, hostContext } ); } // src/index.ts var experimental_useObject = useObject; export { Chat, MCPAppRenderer as experimental_MCPAppRenderer, experimental_useObject, experimental_useRealtime, useChat, useCompletion, useObject }; //# sourceMappingURL=index.js.map