import { useSyncExternalStore } from "react"; export type CallToolResponse = { result: string; }; export type UnknownObject = Record; declare global { interface Window { openai: API & OpenAiGlobals; HOST: string; } interface WindowEventMap { [SET_GLOBALS_EVENT_TYPE]: SetGlobalsEvent; } } type OpenAiGlobals< ToolInput extends UnknownObject = UnknownObject, ToolOutput extends UnknownObject = UnknownObject, ToolResponseMetadata extends UnknownObject = UnknownObject, WidgetState extends UnknownObject = UnknownObject > = { theme: Theme; userAgent: UserAgent; locale: string; // layout maxHeight: number; displayMode: DisplayMode; safeArea: SafeArea; // state toolInput: ToolInput; toolOutput: ToolOutput | null; toolResponseMetadata: ToolResponseMetadata | null; widgetState: WidgetState | null; }; type API = { /** Calls a tool on your MCP. Returns the full response. */ callTool: ( name: string, args: Record ) => Promise; /** Triggers a followup turn in the ChatGPT conversation */ sendFollowUpMessage: (args: { prompt: string }) => Promise; /** Opens an external link, redirects web page or mobile app */ openExternal(payload: { href: string }): void; /** For transitioning an app from inline to fullscreen or pip */ requestDisplayMode: (args: { mode: DisplayMode }) => Promise<{ /** * The granted display mode. The host may reject the request. * For mobile, PiP is always coerced to fullscreen. */ mode: DisplayMode; }>; setWidgetState: (state: WidgetState) => Promise; }; // Dispatched when any global changes in the host page export const SET_GLOBALS_EVENT_TYPE = "openai:set_globals"; export class SetGlobalsEvent extends CustomEvent<{ globals: Partial; }> { readonly type = SET_GLOBALS_EVENT_TYPE; } export type CallTool = ( name: string, args: Record ) => Promise; export type DisplayMode = "pip" | "inline" | "fullscreen"; export type Theme = "light" | "dark"; export type SafeAreaInsets = { top: number; bottom: number; left: number; right: number; }; export type SafeArea = { insets: SafeAreaInsets; }; export type DeviceType = "mobile" | "tablet" | "desktop" | "unknown"; export type UserAgent = { device: { type: DeviceType }; capabilities: { hover: boolean; touch: boolean; }; }; export function useOpenAiGlobal( key: K ): OpenAiGlobals[K] { return useSyncExternalStore( (onChange) => { const handleSetGlobal = (event: SetGlobalsEvent) => { const value = event.detail.globals[key]; if (value === undefined) { return; } onChange(); }; window.addEventListener(SET_GLOBALS_EVENT_TYPE, handleSetGlobal, { passive: true }); return () => { window.removeEventListener(SET_GLOBALS_EVENT_TYPE, handleSetGlobal); }; }, () => window.openai[key] ); } export function useToolInput() { return useOpenAiGlobal("toolInput"); } export function useToolOutput() { return useOpenAiGlobal("toolOutput"); } export function useToolResponseMetadata() { if (typeof window.openai === "undefined") { return null; } return useOpenAiGlobal("toolResponseMetadata"); }