sunpeak 0.18.7 → 0.18.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +12 -0
  2. package/bin/commands/dev.mjs +3 -3
  3. package/bin/commands/inspect.mjs +4 -1
  4. package/bin/lib/sandbox-server.mjs +31 -1
  5. package/dist/chatgpt/index.cjs +1 -2
  6. package/dist/chatgpt/index.js +1 -2
  7. package/dist/claude/index.cjs +1 -2
  8. package/dist/claude/index.js +1 -2
  9. package/dist/host/chatgpt/index.cjs +1 -2
  10. package/dist/host/chatgpt/index.cjs.map +1 -1
  11. package/dist/host/chatgpt/index.js +1 -2
  12. package/dist/host/chatgpt/index.js.map +1 -1
  13. package/dist/index.cjs +93 -93
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.js +54 -54
  16. package/dist/index.js.map +1 -1
  17. package/dist/inspector/hosts.d.ts +12 -0
  18. package/dist/inspector/index.cjs +1 -2
  19. package/dist/inspector/index.js +1 -2
  20. package/dist/{inspector-CKc58UuI.cjs → inspector-8nPV2A-z.cjs} +104 -54
  21. package/dist/inspector-8nPV2A-z.cjs.map +1 -0
  22. package/dist/{inspector-DZrN0kej.js → inspector-Cdo5BK2D.js} +103 -53
  23. package/dist/inspector-Cdo5BK2D.js.map +1 -0
  24. package/dist/mcp/index.cjs +85 -71
  25. package/dist/mcp/index.cjs.map +1 -1
  26. package/dist/mcp/index.js +79 -65
  27. package/dist/mcp/index.js.map +1 -1
  28. package/dist/{protocol-DJmRaBzO.js → protocol-BfAACnv0.js} +14 -9
  29. package/dist/{protocol-DJmRaBzO.js.map → protocol-BfAACnv0.js.map} +1 -1
  30. package/dist/{protocol-jbxhzcnS.cjs → protocol-C7kTcBr_.cjs} +14 -9
  31. package/dist/{protocol-jbxhzcnS.cjs.map → protocol-C7kTcBr_.cjs.map} +1 -1
  32. package/dist/style.css +36 -1
  33. package/dist/{use-app-BNbz1uzj.js → use-app-CfP9VypY.js} +107 -56
  34. package/dist/use-app-CfP9VypY.js.map +1 -0
  35. package/dist/{use-app-Dqh20JPP.cjs → use-app-CzcYw1Kz.cjs} +165 -114
  36. package/dist/use-app-CzcYw1Kz.cjs.map +1 -0
  37. package/package.json +7 -7
  38. package/template/README.md +12 -0
  39. package/template/dist/albums/albums.html +16 -15
  40. package/template/dist/albums/albums.json +1 -1
  41. package/template/dist/carousel/carousel.html +20 -19
  42. package/template/dist/carousel/carousel.json +1 -1
  43. package/template/dist/map/map.html +7 -6
  44. package/template/dist/map/map.json +1 -1
  45. package/template/dist/review/review.html +6 -5
  46. package/template/dist/review/review.json +1 -1
  47. package/template/node_modules/.bin/vite +2 -2
  48. package/template/node_modules/.bin/vitest +2 -2
  49. package/template/node_modules/.vite/deps/_metadata.json +4 -4
  50. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps.js +49 -49
  51. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps.js.map +1 -1
  52. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_app-bridge.js +49 -49
  53. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_app-bridge.js.map +1 -1
  54. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_react.js +89 -89
  55. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_react.js.map +1 -1
  56. package/template/node_modules/.vite-mcp/deps/@testing-library_react.js +9 -6
  57. package/template/node_modules/.vite-mcp/deps/@testing-library_react.js.map +1 -1
  58. package/template/node_modules/.vite-mcp/deps/_metadata.json +26 -26
  59. package/template/node_modules/.vite-mcp/deps/{protocol-CTflwIfG.js → protocol-B_qKkui_.js} +14 -9
  60. package/template/node_modules/.vite-mcp/deps/protocol-B_qKkui_.js.map +1 -0
  61. package/template/node_modules/.vite-mcp/deps/vitest.js +366 -128
  62. package/template/node_modules/.vite-mcp/deps/vitest.js.map +1 -1
  63. package/template/package.json +2 -2
  64. package/template/src/resources/carousel/carousel.test.tsx +16 -2
  65. package/template/src/resources/carousel/carousel.tsx +42 -14
  66. package/template/src/resources/carousel/components/carousel.tsx +20 -0
  67. package/template/src/resources/carousel/components/index.ts +1 -0
  68. package/template/src/resources/carousel/components/place-detail.tsx +153 -0
  69. package/template/tests/e2e/carousel.spec.ts +125 -0
  70. package/template/tests/live/carousel.spec.ts +2 -2
  71. package/template/tests/simulations/show-carousel.json +54 -5
  72. package/dist/inspector-CKc58UuI.cjs.map +0 -1
  73. package/dist/inspector-DZrN0kej.js.map +0 -1
  74. package/dist/use-app-BNbz1uzj.js.map +0 -1
  75. package/dist/use-app-Dqh20JPP.cjs.map +0 -1
  76. package/template/node_modules/.vite-mcp/deps/protocol-CTflwIfG.js.map +0 -1
package/README.md CHANGED
@@ -230,6 +230,18 @@ Install the `create-sunpeak-app` skill to give your coding agent (Claude Code, C
230
230
  npx skills add Sunpeak-AI/sunpeak@create-sunpeak-app
231
231
  ```
232
232
 
233
+ ## Troubleshooting
234
+
235
+ If your app doesn't render in ChatGPT or Claude:
236
+
237
+ 1. **Check your tunnel** is running and pointing to the correct port
238
+ 2. **Restart `sunpeak dev`** to clear stale connections
239
+ 3. **Refresh or re-add the MCP server** in the host's settings (Settings > MCP Servers)
240
+ 4. **Hard refresh** the host page (`Cmd+Shift+R` / `Ctrl+Shift+R`)
241
+ 5. **Open a new chat** in the host (cached iframes persist per-conversation)
242
+
243
+ Full guide: [sunpeak.ai/docs/guides/troubleshooting](https://sunpeak.ai/docs/guides/troubleshooting)
244
+
233
245
  ## Resources
234
246
 
235
247
  - [MCP Apps Documentation](https://sunpeak.ai/docs/mcp-apps/introduction)
@@ -239,7 +239,7 @@ export async function dev(projectRoot = process.cwd(), args = []) {
239
239
  const { tool } = await extractToolExport(toolPath);
240
240
  toolMap.set(toolName, { tool, path: toolPath });
241
241
  } catch (err) {
242
- console.warn(`Warning: Could not extract metadata from tool ${toolName}: ${err.message}`);
242
+ console.warn(`Warning: Could not extract metadata from tool "${toolName}" (${toolPath}):\n ${err.message}\n Expected: export const tool: AppToolConfig = { ... }`);
243
243
  }
244
244
  }
245
245
 
@@ -271,7 +271,7 @@ export async function dev(projectRoot = process.cwd(), args = []) {
271
271
  toolHandlerMap.set(toolName, { handler: mod.default, outputSchema: mod.outputSchema });
272
272
  }
273
273
  } catch (err) {
274
- console.warn(`Warning: Could not load handler for tool "${toolName}": ${err.message}`);
274
+ console.warn(`Warning: Could not load handler for tool "${toolName}" (${relativePath}):\n ${err.message}`);
275
275
  }
276
276
  }
277
277
 
@@ -287,7 +287,7 @@ export async function dev(projectRoot = process.cwd(), args = []) {
287
287
  const toolEntry = toolMap.get(toolName);
288
288
  const tool = toolEntry?.tool;
289
289
  if (!tool) {
290
- console.warn(`Warning: Tool "${toolName}" not found for simulation "${simName}". Skipping.`);
290
+ console.warn(`Warning: Tool "${toolName}" not found for simulation "${simName}". Expected file: src/tools/${toolName}.ts`);
291
291
  continue;
292
292
  }
293
293
 
@@ -1144,10 +1144,13 @@ export async function inspectServer(opts) {
1144
1144
  server.printUrls();
1145
1145
  server.bindCLIShortcuts({ print: true });
1146
1146
 
1147
+ // Print troubleshooting link (dimmed)
1148
+ console.log('\n \x1b[2mApp not loading? \u2192 https://sunpeak.ai/docs/guides/troubleshooting\x1b[0m');
1149
+
1147
1150
  // Print star-begging message unless suppressed
1148
1151
  if (!noBegging) {
1149
1152
  // #FFB800 in 24-bit ANSI color
1150
- console.log('\n\n\x1b[38;2;255;184;0m\u2b50\ufe0f \u2192 \u2764\ufe0f https://github.com/Sunpeak-AI/sunpeak\x1b[0m\n');
1153
+ console.log('\n\x1b[38;2;255;184;0m\u2b50\ufe0f \u2192 \u2764\ufe0f https://github.com/Sunpeak-AI/sunpeak\x1b[0m\n');
1151
1154
  }
1152
1155
 
1153
1156
  // Cleanup on exit
@@ -59,6 +59,25 @@ export async function startSandboxServer({ preferredPort = 24680 } = {}) {
59
59
  res.end('Not found');
60
60
  });
61
61
 
62
+ server.on('clientError', (err, socket) => {
63
+ if (err.code === 'ECONNRESET') {
64
+ // Normal when browser tabs close abruptly
65
+ } else if (
66
+ err.code === 'HPE_INVALID_METHOD' &&
67
+ err.rawPacket instanceof Buffer &&
68
+ err.rawPacket[0] === 0x16
69
+ ) {
70
+ console.error(
71
+ 'Received HTTPS request on sandbox server (port ' + port + '). ' +
72
+ 'If you\'re using ngrok, make sure the upstream is http:// (not https://). ' +
73
+ 'Example: ngrok http 8000'
74
+ );
75
+ } else {
76
+ console.error('Sandbox server client error', err);
77
+ }
78
+ socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
79
+ });
80
+
62
81
  await new Promise((resolve, reject) => {
63
82
  server.listen(port, () => resolve());
64
83
  server.on('error', reject);
@@ -189,6 +208,7 @@ iframe { border: none; width: 100%; height: 100%; display: block; }
189
208
  });
190
209
 
191
210
  function createInnerFrame(params) {
211
+ clearInterval(readyInterval);
192
212
  if (innerFrame) innerFrame.remove();
193
213
 
194
214
  innerFrame = document.createElement('iframe');
@@ -207,6 +227,7 @@ iframe { border: none; width: 100%; height: 100%; display: block; }
207
227
  }
208
228
 
209
229
  function createInnerFrameWithSrc(params) {
230
+ clearInterval(readyInterval);
210
231
  if (innerFrame) innerFrame.remove();
211
232
 
212
233
  innerFrame = document.createElement('iframe');
@@ -277,7 +298,15 @@ iframe { border: none; width: 100%; height: 100%; display: block; }
277
298
  'e.source.postMessage({jsonrpc:"2.0",method:"sunpeak/fence-ack",params:{fenceId:fid}},"*");' +
278
299
  '});}});';
279
300
 
280
- // Signal readiness to the host
301
+ // Signal readiness to the host. Retry every 200ms in case the host's
302
+ // PostMessage listener isn't attached yet (srcdoc race with React refs).
303
+ var readyInterval = setInterval(function() {
304
+ window.parent.postMessage({
305
+ jsonrpc: '2.0',
306
+ method: 'ui/notifications/sandbox-proxy-ready',
307
+ params: {}
308
+ }, '*');
309
+ }, 200);
281
310
  setTimeout(function() {
282
311
  window.parent.postMessage({
283
312
  jsonrpc: '2.0',
@@ -285,6 +314,7 @@ iframe { border: none; width: 100%; height: 100%; display: block; }
285
314
  params: {}
286
315
  }, '*');
287
316
  }, 0);
317
+ setTimeout(function() { clearInterval(readyInterval); }, 10000);
288
318
  })();
289
319
  </script>
290
320
  </body>
@@ -1,7 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../chunk-9hOWP6kD.cjs");
3
- require("../protocol-jbxhzcnS.cjs");
4
- const require_inspector = require("../inspector-CKc58UuI.cjs");
3
+ const require_inspector = require("../inspector-8nPV2A-z.cjs");
5
4
  const require_inspector_url = require("../inspector-url-C3LTKgXt.cjs");
6
5
  const require_discovery = require("../discovery-Clu4uHp1.cjs");
7
6
  //#region src/chatgpt/index.ts
@@ -1,6 +1,5 @@
1
1
  import { r as __exportAll } from "../chunk-D6g4UhsZ.js";
2
- import "../protocol-DJmRaBzO.js";
3
- import { _ as McpAppHost, d as ThemeProvider, f as useThemeContext, g as extractResourceCSP, h as IframeResource, n as resolveServerToolResult, t as Inspector, v as SCREEN_WIDTHS } from "../inspector-DZrN0kej.js";
2
+ import { _ as McpAppHost, d as ThemeProvider, f as useThemeContext, g as extractResourceCSP, h as IframeResource, n as resolveServerToolResult, t as Inspector, v as SCREEN_WIDTHS } from "../inspector-Cdo5BK2D.js";
4
3
  import { t as createInspectorUrl } from "../inspector-url-CyQcuBI9.js";
5
4
  import { c as toPascalCase, i as findResourceKey, n as extractSimulationKey, r as findResourceDirs, s as getComponentName, t as extractResourceKey } from "../discovery-Cgoegt62.js";
6
5
  //#region src/chatgpt/index.ts
@@ -1,5 +1,4 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  require("../chunk-9hOWP6kD.cjs");
3
- require("../protocol-jbxhzcnS.cjs");
4
- const require_inspector = require("../inspector-CKc58UuI.cjs");
3
+ const require_inspector = require("../inspector-8nPV2A-z.cjs");
5
4
  exports.Inspector = require_inspector.Inspector;
@@ -1,3 +1,2 @@
1
- import "../protocol-DJmRaBzO.js";
2
- import { t as Inspector } from "../inspector-DZrN0kej.js";
1
+ import { t as Inspector } from "../inspector-Cdo5BK2D.js";
3
2
  export { Inspector };
@@ -1,7 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  require("../../chunk-9hOWP6kD.cjs");
3
- require("../../protocol-jbxhzcnS.cjs");
4
- const require_use_app = require("../../use-app-Dqh20JPP.cjs");
3
+ const require_use_app = require("../../use-app-CzcYw1Kz.cjs");
5
4
  let react = require("react");
6
5
  //#region src/host/chatgpt/openai-types.ts
7
6
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/host/chatgpt/openai-types.ts","../../../src/host/chatgpt/use-create-file.ts","../../../src/host/chatgpt/use-open-modal.ts","../../../src/host/chatgpt/use-request-checkout.ts"],"sourcesContent":["/**\n * TypeScript declarations for the `window.openai` ChatGPT runtime.\n *\n * These APIs are available inside ChatGPT-hosted iframes and provide\n * platform-specific capabilities beyond the MCP Apps standard.\n *\n * @see https://developers.openai.com/apps-sdk/mcp-apps-in-chatgpt\n */\n\nexport interface OpenAIUploadFileResult {\n fileId: string;\n}\n\nexport interface OpenAIFileDownloadUrlResult {\n downloadUrl: string;\n}\n\nexport interface OpenAIRequestModalParams {\n /** URL of an alternate UI template to load in the modal. Omit to reuse the current UI. */\n template?: string;\n /** Arbitrary params forwarded to the modal content. */\n params?: Record<string, unknown>;\n}\n\nexport interface OpenAICheckoutPaymentProvider {\n provider: string;\n merchant_id: string;\n supported_payment_methods: string[];\n}\n\nexport interface OpenAICheckoutTotal {\n type: string;\n display_text: string;\n amount: number;\n}\n\nexport interface OpenAICheckoutLink {\n type: 'terms_of_use' | 'privacy_policy' | string;\n url: string;\n}\n\nexport interface OpenAICheckoutSession {\n id: string;\n payment_provider: OpenAICheckoutPaymentProvider;\n status: 'ready_for_payment';\n currency: string;\n totals: OpenAICheckoutTotal[];\n links: OpenAICheckoutLink[];\n payment_mode: 'live' | 'test';\n}\n\nexport interface OpenAICheckoutOrder {\n id: string;\n checkout_session_id: string;\n status: 'completed' | string;\n permalink_url?: string;\n}\n\n/**\n * The `window.openai` runtime object injected by ChatGPT into hosted iframes.\n */\nexport interface OpenAIRuntime {\n // --- File APIs ---\n uploadFile?(file: File): Promise<OpenAIUploadFileResult>;\n getFileDownloadUrl?(params: { fileId: string }): Promise<OpenAIFileDownloadUrlResult>;\n\n // --- Modal API ---\n requestModal?(params: OpenAIRequestModalParams): Promise<void>;\n\n // --- Checkout API ---\n requestCheckout?(session: OpenAICheckoutSession): Promise<OpenAICheckoutOrder>;\n\n // --- Display ---\n requestClose?(): void;\n requestDisplayMode?(params: { mode: 'inline' | 'PiP' | 'fullscreen' }): Promise<void>;\n\n // --- Messaging ---\n sendFollowUpMessage?(params: { prompt: string }): void;\n\n // --- Other ---\n openExternal?(params: { href: string }): void;\n}\n\n/**\n * Get the `window.openai` runtime if available.\n * Returns `undefined` outside of ChatGPT or in SSR.\n */\nexport function getOpenAIRuntime(): OpenAIRuntime | undefined {\n if (typeof window !== 'undefined' && 'openai' in window) {\n return (window as unknown as { openai: OpenAIRuntime }).openai;\n }\n return undefined;\n}\n","import { useCallback } from 'react';\nimport { useApp } from '../../hooks/use-app';\nimport { getOpenAIRuntime, type OpenAIUploadFileResult } from './openai-types';\n\nexport type { OpenAIUploadFileResult as CreateFileResult };\n\n/**\n * Upload a file from the app UI into the ChatGPT conversation.\n *\n * Wraps `window.openai.uploadFile` which is only available inside ChatGPT.\n * Supported formats: `image/png`, `image/jpeg`, `image/webp`.\n *\n * Import from `sunpeak/host/chatgpt`:\n *\n * @example\n * ```tsx\n * import { useUploadFile } from 'sunpeak/host/chatgpt';\n *\n * function ImageUploader() {\n * const uploadFile = useUploadFile();\n *\n * const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {\n * const file = e.currentTarget.files?.[0];\n * if (!file) return;\n * const { fileId } = await uploadFile(file);\n * console.log('Uploaded:', fileId);\n * };\n *\n * return <input type=\"file\" accept=\"image/*\" onChange={handleChange} />;\n * }\n * ```\n */\nexport function useUploadFile(): (file: File) => Promise<OpenAIUploadFileResult> {\n const app = useApp();\n return useCallback(\n async (file: File) => {\n if (!app) {\n throw new Error('[useUploadFile] App not connected');\n }\n const runtime = getOpenAIRuntime();\n if (!runtime?.uploadFile) {\n throw new Error('[useUploadFile] window.openai.uploadFile not available');\n }\n return runtime.uploadFile(file);\n },\n [app]\n );\n}\n","import { useCallback } from 'react';\nimport { useApp } from '../../hooks/use-app';\nimport { getOpenAIRuntime, type OpenAIRequestModalParams } from './openai-types';\n\nexport type { OpenAIRequestModalParams as OpenModalParams };\n\n/**\n * Open a host-controlled modal in ChatGPT.\n *\n * Wraps `window.openai.requestModal` which is only available inside ChatGPT.\n * Pass a `template` URL to load alternate UI content in the modal, or omit\n * it to reuse the current UI.\n *\n * Import from `sunpeak/host/chatgpt`:\n *\n * @example\n * ```tsx\n * import { useRequestModal } from 'sunpeak/host/chatgpt';\n *\n * function CheckoutButton() {\n * const requestModal = useRequestModal();\n *\n * return (\n * <button onClick={() => requestModal({ template: 'ui://widget/checkout.html' })}>\n * Checkout\n * </button>\n * );\n * }\n * ```\n */\nexport function useRequestModal(): (params: OpenAIRequestModalParams) => Promise<void> {\n const app = useApp();\n return useCallback(\n async (params: OpenAIRequestModalParams) => {\n if (!app) {\n console.warn('[useRequestModal] App not connected');\n return;\n }\n const runtime = getOpenAIRuntime();\n if (!runtime?.requestModal) {\n throw new Error('[useRequestModal] window.openai.requestModal not available');\n }\n return runtime.requestModal(params);\n },\n [app]\n );\n}\n","import { useCallback } from 'react';\nimport { useApp } from '../../hooks/use-app';\nimport {\n getOpenAIRuntime,\n type OpenAICheckoutSession,\n type OpenAICheckoutOrder,\n} from './openai-types';\n\nexport type { OpenAICheckoutSession as CheckoutSession };\nexport type { OpenAICheckoutOrder as CheckoutOrder };\n\n/**\n * Trigger the ChatGPT instant checkout flow.\n *\n * Wraps `window.openai.requestCheckout` which is only available inside\n * ChatGPT. Opens the host checkout UI, handles payment display, and\n * resolves with the finalized order. Rejects on error or user cancel.\n *\n * Import from `sunpeak/host/chatgpt`:\n *\n * @example\n * ```tsx\n * import { useRequestCheckout } from 'sunpeak/host/chatgpt';\n *\n * function BuyButton() {\n * const requestCheckout = useRequestCheckout();\n *\n * const handleBuy = async () => {\n * try {\n * const order = await requestCheckout({\n * id: 'session-1',\n * payment_provider: {\n * provider: 'stripe',\n * merchant_id: 'acct_xxx',\n * supported_payment_methods: ['card', 'apple_pay'],\n * },\n * status: 'ready_for_payment',\n * currency: 'USD',\n * totals: [{ type: 'total', display_text: 'Total', amount: 999 }],\n * links: [],\n * payment_mode: 'live',\n * });\n * console.log('Order completed:', order.id);\n * } catch {\n * console.log('Checkout cancelled or failed');\n * }\n * };\n *\n * return <button onClick={handleBuy}>Buy Now</button>;\n * }\n * ```\n */\nexport function useRequestCheckout(): (\n session: OpenAICheckoutSession\n) => Promise<OpenAICheckoutOrder> {\n const app = useApp();\n return useCallback(\n async (session: OpenAICheckoutSession) => {\n if (!app) {\n throw new Error('[useRequestCheckout] App not connected');\n }\n const runtime = getOpenAIRuntime();\n if (!runtime?.requestCheckout) {\n throw new Error('[useRequestCheckout] window.openai.requestCheckout not available');\n }\n return runtime.requestCheckout(session);\n },\n [app]\n );\n}\n"],"mappings":";;;;;;;;;;AAuFA,SAAgB,mBAA8C;AAC5D,KAAI,OAAO,WAAW,eAAe,YAAY,OAC/C,QAAQ,OAAgD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACzD5D,SAAgB,gBAAiE;CAC/E,MAAM,MAAM,gBAAA,QAAQ;AACpB,SAAA,GAAA,MAAA,aACE,OAAO,SAAe;AACpB,MAAI,CAAC,IACH,OAAM,IAAI,MAAM,oCAAoC;EAEtD,MAAM,UAAU,kBAAkB;AAClC,MAAI,CAAC,SAAS,WACZ,OAAM,IAAI,MAAM,yDAAyD;AAE3E,SAAO,QAAQ,WAAW,KAAK;IAEjC,CAAC,IAAI,CACN;;;;;;;;;;;;;;;;;;;;;;;;;;;;AChBH,SAAgB,kBAAuE;CACrF,MAAM,MAAM,gBAAA,QAAQ;AACpB,SAAA,GAAA,MAAA,aACE,OAAO,WAAqC;AAC1C,MAAI,CAAC,KAAK;AACR,WAAQ,KAAK,sCAAsC;AACnD;;EAEF,MAAM,UAAU,kBAAkB;AAClC,MAAI,CAAC,SAAS,aACZ,OAAM,IAAI,MAAM,6DAA6D;AAE/E,SAAO,QAAQ,aAAa,OAAO;IAErC,CAAC,IAAI,CACN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACOH,SAAgB,qBAEkB;CAChC,MAAM,MAAM,gBAAA,QAAQ;AACpB,SAAA,GAAA,MAAA,aACE,OAAO,YAAmC;AACxC,MAAI,CAAC,IACH,OAAM,IAAI,MAAM,yCAAyC;EAE3D,MAAM,UAAU,kBAAkB;AAClC,MAAI,CAAC,SAAS,gBACZ,OAAM,IAAI,MAAM,mEAAmE;AAErF,SAAO,QAAQ,gBAAgB,QAAQ;IAEzC,CAAC,IAAI,CACN"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/host/chatgpt/openai-types.ts","../../../src/host/chatgpt/use-create-file.ts","../../../src/host/chatgpt/use-open-modal.ts","../../../src/host/chatgpt/use-request-checkout.ts"],"sourcesContent":["/**\n * TypeScript declarations for the `window.openai` ChatGPT runtime.\n *\n * These APIs are available inside ChatGPT-hosted iframes and provide\n * platform-specific capabilities beyond the MCP Apps standard.\n *\n * @see https://developers.openai.com/apps-sdk/mcp-apps-in-chatgpt\n */\n\nexport interface OpenAIUploadFileResult {\n fileId: string;\n}\n\nexport interface OpenAIFileDownloadUrlResult {\n downloadUrl: string;\n}\n\nexport interface OpenAIRequestModalParams {\n /** URL of an alternate UI template to load in the modal. Omit to reuse the current UI. */\n template?: string;\n /** Arbitrary params forwarded to the modal content. */\n params?: Record<string, unknown>;\n}\n\nexport interface OpenAICheckoutPaymentProvider {\n provider: string;\n merchant_id: string;\n supported_payment_methods: string[];\n}\n\nexport interface OpenAICheckoutTotal {\n type: string;\n display_text: string;\n amount: number;\n}\n\nexport interface OpenAICheckoutLink {\n type: 'terms_of_use' | 'privacy_policy' | string;\n url: string;\n}\n\nexport interface OpenAICheckoutSession {\n id: string;\n payment_provider: OpenAICheckoutPaymentProvider;\n status: 'ready_for_payment';\n currency: string;\n totals: OpenAICheckoutTotal[];\n links: OpenAICheckoutLink[];\n payment_mode: 'live' | 'test';\n}\n\nexport interface OpenAICheckoutOrder {\n id: string;\n checkout_session_id: string;\n status: 'completed' | string;\n permalink_url?: string;\n}\n\n/**\n * The `window.openai` runtime object injected by ChatGPT into hosted iframes.\n */\nexport interface OpenAIRuntime {\n // --- File APIs ---\n uploadFile?(file: File): Promise<OpenAIUploadFileResult>;\n getFileDownloadUrl?(params: { fileId: string }): Promise<OpenAIFileDownloadUrlResult>;\n\n // --- Modal API ---\n requestModal?(params: OpenAIRequestModalParams): Promise<void>;\n\n // --- Checkout API ---\n requestCheckout?(session: OpenAICheckoutSession): Promise<OpenAICheckoutOrder>;\n\n // --- Display ---\n requestClose?(): void;\n requestDisplayMode?(params: { mode: 'inline' | 'PiP' | 'fullscreen' }): Promise<void>;\n\n // --- Messaging ---\n sendFollowUpMessage?(params: { prompt: string }): void;\n\n // --- Other ---\n openExternal?(params: { href: string }): void;\n}\n\n/**\n * Get the `window.openai` runtime if available.\n * Returns `undefined` outside of ChatGPT or in SSR.\n */\nexport function getOpenAIRuntime(): OpenAIRuntime | undefined {\n if (typeof window !== 'undefined' && 'openai' in window) {\n return (window as unknown as { openai: OpenAIRuntime }).openai;\n }\n return undefined;\n}\n","import { useCallback } from 'react';\nimport { useApp } from '../../hooks/use-app';\nimport { getOpenAIRuntime, type OpenAIUploadFileResult } from './openai-types';\n\nexport type { OpenAIUploadFileResult as CreateFileResult };\n\n/**\n * Upload a file from the app UI into the ChatGPT conversation.\n *\n * Wraps `window.openai.uploadFile` which is only available inside ChatGPT.\n * Supported formats: `image/png`, `image/jpeg`, `image/webp`.\n *\n * Import from `sunpeak/host/chatgpt`:\n *\n * @example\n * ```tsx\n * import { useUploadFile } from 'sunpeak/host/chatgpt';\n *\n * function ImageUploader() {\n * const uploadFile = useUploadFile();\n *\n * const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {\n * const file = e.currentTarget.files?.[0];\n * if (!file) return;\n * const { fileId } = await uploadFile(file);\n * console.log('Uploaded:', fileId);\n * };\n *\n * return <input type=\"file\" accept=\"image/*\" onChange={handleChange} />;\n * }\n * ```\n */\nexport function useUploadFile(): (file: File) => Promise<OpenAIUploadFileResult> {\n const app = useApp();\n return useCallback(\n async (file: File) => {\n if (!app) {\n throw new Error('[useUploadFile] App not connected');\n }\n const runtime = getOpenAIRuntime();\n if (!runtime?.uploadFile) {\n throw new Error('[useUploadFile] window.openai.uploadFile not available');\n }\n return runtime.uploadFile(file);\n },\n [app]\n );\n}\n","import { useCallback } from 'react';\nimport { useApp } from '../../hooks/use-app';\nimport { getOpenAIRuntime, type OpenAIRequestModalParams } from './openai-types';\n\nexport type { OpenAIRequestModalParams as OpenModalParams };\n\n/**\n * Open a host-controlled modal in ChatGPT.\n *\n * Wraps `window.openai.requestModal` which is only available inside ChatGPT.\n * Pass a `template` URL to load alternate UI content in the modal, or omit\n * it to reuse the current UI.\n *\n * Import from `sunpeak/host/chatgpt`:\n *\n * @example\n * ```tsx\n * import { useRequestModal } from 'sunpeak/host/chatgpt';\n *\n * function CheckoutButton() {\n * const requestModal = useRequestModal();\n *\n * return (\n * <button onClick={() => requestModal({ template: 'ui://widget/checkout.html' })}>\n * Checkout\n * </button>\n * );\n * }\n * ```\n */\nexport function useRequestModal(): (params: OpenAIRequestModalParams) => Promise<void> {\n const app = useApp();\n return useCallback(\n async (params: OpenAIRequestModalParams) => {\n if (!app) {\n console.warn('[useRequestModal] App not connected');\n return;\n }\n const runtime = getOpenAIRuntime();\n if (!runtime?.requestModal) {\n throw new Error('[useRequestModal] window.openai.requestModal not available');\n }\n return runtime.requestModal(params);\n },\n [app]\n );\n}\n","import { useCallback } from 'react';\nimport { useApp } from '../../hooks/use-app';\nimport {\n getOpenAIRuntime,\n type OpenAICheckoutSession,\n type OpenAICheckoutOrder,\n} from './openai-types';\n\nexport type { OpenAICheckoutSession as CheckoutSession };\nexport type { OpenAICheckoutOrder as CheckoutOrder };\n\n/**\n * Trigger the ChatGPT instant checkout flow.\n *\n * Wraps `window.openai.requestCheckout` which is only available inside\n * ChatGPT. Opens the host checkout UI, handles payment display, and\n * resolves with the finalized order. Rejects on error or user cancel.\n *\n * Import from `sunpeak/host/chatgpt`:\n *\n * @example\n * ```tsx\n * import { useRequestCheckout } from 'sunpeak/host/chatgpt';\n *\n * function BuyButton() {\n * const requestCheckout = useRequestCheckout();\n *\n * const handleBuy = async () => {\n * try {\n * const order = await requestCheckout({\n * id: 'session-1',\n * payment_provider: {\n * provider: 'stripe',\n * merchant_id: 'acct_xxx',\n * supported_payment_methods: ['card', 'apple_pay'],\n * },\n * status: 'ready_for_payment',\n * currency: 'USD',\n * totals: [{ type: 'total', display_text: 'Total', amount: 999 }],\n * links: [],\n * payment_mode: 'live',\n * });\n * console.log('Order completed:', order.id);\n * } catch {\n * console.log('Checkout cancelled or failed');\n * }\n * };\n *\n * return <button onClick={handleBuy}>Buy Now</button>;\n * }\n * ```\n */\nexport function useRequestCheckout(): (\n session: OpenAICheckoutSession\n) => Promise<OpenAICheckoutOrder> {\n const app = useApp();\n return useCallback(\n async (session: OpenAICheckoutSession) => {\n if (!app) {\n throw new Error('[useRequestCheckout] App not connected');\n }\n const runtime = getOpenAIRuntime();\n if (!runtime?.requestCheckout) {\n throw new Error('[useRequestCheckout] window.openai.requestCheckout not available');\n }\n return runtime.requestCheckout(session);\n },\n [app]\n );\n}\n"],"mappings":";;;;;;;;;AAuFA,SAAgB,mBAA8C;AAC5D,KAAI,OAAO,WAAW,eAAe,YAAY,OAC/C,QAAQ,OAAgD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACzD5D,SAAgB,gBAAiE;CAC/E,MAAM,MAAM,gBAAA,QAAQ;AACpB,SAAA,GAAA,MAAA,aACE,OAAO,SAAe;AACpB,MAAI,CAAC,IACH,OAAM,IAAI,MAAM,oCAAoC;EAEtD,MAAM,UAAU,kBAAkB;AAClC,MAAI,CAAC,SAAS,WACZ,OAAM,IAAI,MAAM,yDAAyD;AAE3E,SAAO,QAAQ,WAAW,KAAK;IAEjC,CAAC,IAAI,CACN;;;;;;;;;;;;;;;;;;;;;;;;;;;;AChBH,SAAgB,kBAAuE;CACrF,MAAM,MAAM,gBAAA,QAAQ;AACpB,SAAA,GAAA,MAAA,aACE,OAAO,WAAqC;AAC1C,MAAI,CAAC,KAAK;AACR,WAAQ,KAAK,sCAAsC;AACnD;;EAEF,MAAM,UAAU,kBAAkB;AAClC,MAAI,CAAC,SAAS,aACZ,OAAM,IAAI,MAAM,6DAA6D;AAE/E,SAAO,QAAQ,aAAa,OAAO;IAErC,CAAC,IAAI,CACN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACOH,SAAgB,qBAEkB;CAChC,MAAM,MAAM,gBAAA,QAAQ;AACpB,SAAA,GAAA,MAAA,aACE,OAAO,YAAmC;AACxC,MAAI,CAAC,IACH,OAAM,IAAI,MAAM,yCAAyC;EAE3D,MAAM,UAAU,kBAAkB;AAClC,MAAI,CAAC,SAAS,gBACZ,OAAM,IAAI,MAAM,mEAAmE;AAErF,SAAO,QAAQ,gBAAgB,QAAQ;IAEzC,CAAC,IAAI,CACN"}
@@ -1,5 +1,4 @@
1
- import "../../protocol-DJmRaBzO.js";
2
- import { t as useApp } from "../../use-app-BNbz1uzj.js";
1
+ import { t as useApp } from "../../use-app-CfP9VypY.js";
3
2
  import { useCallback } from "react";
4
3
  //#region src/host/chatgpt/openai-types.ts
5
4
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/host/chatgpt/openai-types.ts","../../../src/host/chatgpt/use-create-file.ts","../../../src/host/chatgpt/use-open-modal.ts","../../../src/host/chatgpt/use-request-checkout.ts"],"sourcesContent":["/**\n * TypeScript declarations for the `window.openai` ChatGPT runtime.\n *\n * These APIs are available inside ChatGPT-hosted iframes and provide\n * platform-specific capabilities beyond the MCP Apps standard.\n *\n * @see https://developers.openai.com/apps-sdk/mcp-apps-in-chatgpt\n */\n\nexport interface OpenAIUploadFileResult {\n fileId: string;\n}\n\nexport interface OpenAIFileDownloadUrlResult {\n downloadUrl: string;\n}\n\nexport interface OpenAIRequestModalParams {\n /** URL of an alternate UI template to load in the modal. Omit to reuse the current UI. */\n template?: string;\n /** Arbitrary params forwarded to the modal content. */\n params?: Record<string, unknown>;\n}\n\nexport interface OpenAICheckoutPaymentProvider {\n provider: string;\n merchant_id: string;\n supported_payment_methods: string[];\n}\n\nexport interface OpenAICheckoutTotal {\n type: string;\n display_text: string;\n amount: number;\n}\n\nexport interface OpenAICheckoutLink {\n type: 'terms_of_use' | 'privacy_policy' | string;\n url: string;\n}\n\nexport interface OpenAICheckoutSession {\n id: string;\n payment_provider: OpenAICheckoutPaymentProvider;\n status: 'ready_for_payment';\n currency: string;\n totals: OpenAICheckoutTotal[];\n links: OpenAICheckoutLink[];\n payment_mode: 'live' | 'test';\n}\n\nexport interface OpenAICheckoutOrder {\n id: string;\n checkout_session_id: string;\n status: 'completed' | string;\n permalink_url?: string;\n}\n\n/**\n * The `window.openai` runtime object injected by ChatGPT into hosted iframes.\n */\nexport interface OpenAIRuntime {\n // --- File APIs ---\n uploadFile?(file: File): Promise<OpenAIUploadFileResult>;\n getFileDownloadUrl?(params: { fileId: string }): Promise<OpenAIFileDownloadUrlResult>;\n\n // --- Modal API ---\n requestModal?(params: OpenAIRequestModalParams): Promise<void>;\n\n // --- Checkout API ---\n requestCheckout?(session: OpenAICheckoutSession): Promise<OpenAICheckoutOrder>;\n\n // --- Display ---\n requestClose?(): void;\n requestDisplayMode?(params: { mode: 'inline' | 'PiP' | 'fullscreen' }): Promise<void>;\n\n // --- Messaging ---\n sendFollowUpMessage?(params: { prompt: string }): void;\n\n // --- Other ---\n openExternal?(params: { href: string }): void;\n}\n\n/**\n * Get the `window.openai` runtime if available.\n * Returns `undefined` outside of ChatGPT or in SSR.\n */\nexport function getOpenAIRuntime(): OpenAIRuntime | undefined {\n if (typeof window !== 'undefined' && 'openai' in window) {\n return (window as unknown as { openai: OpenAIRuntime }).openai;\n }\n return undefined;\n}\n","import { useCallback } from 'react';\nimport { useApp } from '../../hooks/use-app';\nimport { getOpenAIRuntime, type OpenAIUploadFileResult } from './openai-types';\n\nexport type { OpenAIUploadFileResult as CreateFileResult };\n\n/**\n * Upload a file from the app UI into the ChatGPT conversation.\n *\n * Wraps `window.openai.uploadFile` which is only available inside ChatGPT.\n * Supported formats: `image/png`, `image/jpeg`, `image/webp`.\n *\n * Import from `sunpeak/host/chatgpt`:\n *\n * @example\n * ```tsx\n * import { useUploadFile } from 'sunpeak/host/chatgpt';\n *\n * function ImageUploader() {\n * const uploadFile = useUploadFile();\n *\n * const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {\n * const file = e.currentTarget.files?.[0];\n * if (!file) return;\n * const { fileId } = await uploadFile(file);\n * console.log('Uploaded:', fileId);\n * };\n *\n * return <input type=\"file\" accept=\"image/*\" onChange={handleChange} />;\n * }\n * ```\n */\nexport function useUploadFile(): (file: File) => Promise<OpenAIUploadFileResult> {\n const app = useApp();\n return useCallback(\n async (file: File) => {\n if (!app) {\n throw new Error('[useUploadFile] App not connected');\n }\n const runtime = getOpenAIRuntime();\n if (!runtime?.uploadFile) {\n throw new Error('[useUploadFile] window.openai.uploadFile not available');\n }\n return runtime.uploadFile(file);\n },\n [app]\n );\n}\n","import { useCallback } from 'react';\nimport { useApp } from '../../hooks/use-app';\nimport { getOpenAIRuntime, type OpenAIRequestModalParams } from './openai-types';\n\nexport type { OpenAIRequestModalParams as OpenModalParams };\n\n/**\n * Open a host-controlled modal in ChatGPT.\n *\n * Wraps `window.openai.requestModal` which is only available inside ChatGPT.\n * Pass a `template` URL to load alternate UI content in the modal, or omit\n * it to reuse the current UI.\n *\n * Import from `sunpeak/host/chatgpt`:\n *\n * @example\n * ```tsx\n * import { useRequestModal } from 'sunpeak/host/chatgpt';\n *\n * function CheckoutButton() {\n * const requestModal = useRequestModal();\n *\n * return (\n * <button onClick={() => requestModal({ template: 'ui://widget/checkout.html' })}>\n * Checkout\n * </button>\n * );\n * }\n * ```\n */\nexport function useRequestModal(): (params: OpenAIRequestModalParams) => Promise<void> {\n const app = useApp();\n return useCallback(\n async (params: OpenAIRequestModalParams) => {\n if (!app) {\n console.warn('[useRequestModal] App not connected');\n return;\n }\n const runtime = getOpenAIRuntime();\n if (!runtime?.requestModal) {\n throw new Error('[useRequestModal] window.openai.requestModal not available');\n }\n return runtime.requestModal(params);\n },\n [app]\n );\n}\n","import { useCallback } from 'react';\nimport { useApp } from '../../hooks/use-app';\nimport {\n getOpenAIRuntime,\n type OpenAICheckoutSession,\n type OpenAICheckoutOrder,\n} from './openai-types';\n\nexport type { OpenAICheckoutSession as CheckoutSession };\nexport type { OpenAICheckoutOrder as CheckoutOrder };\n\n/**\n * Trigger the ChatGPT instant checkout flow.\n *\n * Wraps `window.openai.requestCheckout` which is only available inside\n * ChatGPT. Opens the host checkout UI, handles payment display, and\n * resolves with the finalized order. Rejects on error or user cancel.\n *\n * Import from `sunpeak/host/chatgpt`:\n *\n * @example\n * ```tsx\n * import { useRequestCheckout } from 'sunpeak/host/chatgpt';\n *\n * function BuyButton() {\n * const requestCheckout = useRequestCheckout();\n *\n * const handleBuy = async () => {\n * try {\n * const order = await requestCheckout({\n * id: 'session-1',\n * payment_provider: {\n * provider: 'stripe',\n * merchant_id: 'acct_xxx',\n * supported_payment_methods: ['card', 'apple_pay'],\n * },\n * status: 'ready_for_payment',\n * currency: 'USD',\n * totals: [{ type: 'total', display_text: 'Total', amount: 999 }],\n * links: [],\n * payment_mode: 'live',\n * });\n * console.log('Order completed:', order.id);\n * } catch {\n * console.log('Checkout cancelled or failed');\n * }\n * };\n *\n * return <button onClick={handleBuy}>Buy Now</button>;\n * }\n * ```\n */\nexport function useRequestCheckout(): (\n session: OpenAICheckoutSession\n) => Promise<OpenAICheckoutOrder> {\n const app = useApp();\n return useCallback(\n async (session: OpenAICheckoutSession) => {\n if (!app) {\n throw new Error('[useRequestCheckout] App not connected');\n }\n const runtime = getOpenAIRuntime();\n if (!runtime?.requestCheckout) {\n throw new Error('[useRequestCheckout] window.openai.requestCheckout not available');\n }\n return runtime.requestCheckout(session);\n },\n [app]\n );\n}\n"],"mappings":";;;;;;;;AAuFA,SAAgB,mBAA8C;AAC5D,KAAI,OAAO,WAAW,eAAe,YAAY,OAC/C,QAAQ,OAAgD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACzD5D,SAAgB,gBAAiE;CAC/E,MAAM,MAAM,QAAQ;AACpB,QAAO,YACL,OAAO,SAAe;AACpB,MAAI,CAAC,IACH,OAAM,IAAI,MAAM,oCAAoC;EAEtD,MAAM,UAAU,kBAAkB;AAClC,MAAI,CAAC,SAAS,WACZ,OAAM,IAAI,MAAM,yDAAyD;AAE3E,SAAO,QAAQ,WAAW,KAAK;IAEjC,CAAC,IAAI,CACN;;;;;;;;;;;;;;;;;;;;;;;;;;;;AChBH,SAAgB,kBAAuE;CACrF,MAAM,MAAM,QAAQ;AACpB,QAAO,YACL,OAAO,WAAqC;AAC1C,MAAI,CAAC,KAAK;AACR,WAAQ,KAAK,sCAAsC;AACnD;;EAEF,MAAM,UAAU,kBAAkB;AAClC,MAAI,CAAC,SAAS,aACZ,OAAM,IAAI,MAAM,6DAA6D;AAE/E,SAAO,QAAQ,aAAa,OAAO;IAErC,CAAC,IAAI,CACN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACOH,SAAgB,qBAEkB;CAChC,MAAM,MAAM,QAAQ;AACpB,QAAO,YACL,OAAO,YAAmC;AACxC,MAAI,CAAC,IACH,OAAM,IAAI,MAAM,yCAAyC;EAE3D,MAAM,UAAU,kBAAkB;AAClC,MAAI,CAAC,SAAS,gBACZ,OAAM,IAAI,MAAM,mEAAmE;AAErF,SAAO,QAAQ,gBAAgB,QAAQ;IAEzC,CAAC,IAAI,CACN"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/host/chatgpt/openai-types.ts","../../../src/host/chatgpt/use-create-file.ts","../../../src/host/chatgpt/use-open-modal.ts","../../../src/host/chatgpt/use-request-checkout.ts"],"sourcesContent":["/**\n * TypeScript declarations for the `window.openai` ChatGPT runtime.\n *\n * These APIs are available inside ChatGPT-hosted iframes and provide\n * platform-specific capabilities beyond the MCP Apps standard.\n *\n * @see https://developers.openai.com/apps-sdk/mcp-apps-in-chatgpt\n */\n\nexport interface OpenAIUploadFileResult {\n fileId: string;\n}\n\nexport interface OpenAIFileDownloadUrlResult {\n downloadUrl: string;\n}\n\nexport interface OpenAIRequestModalParams {\n /** URL of an alternate UI template to load in the modal. Omit to reuse the current UI. */\n template?: string;\n /** Arbitrary params forwarded to the modal content. */\n params?: Record<string, unknown>;\n}\n\nexport interface OpenAICheckoutPaymentProvider {\n provider: string;\n merchant_id: string;\n supported_payment_methods: string[];\n}\n\nexport interface OpenAICheckoutTotal {\n type: string;\n display_text: string;\n amount: number;\n}\n\nexport interface OpenAICheckoutLink {\n type: 'terms_of_use' | 'privacy_policy' | string;\n url: string;\n}\n\nexport interface OpenAICheckoutSession {\n id: string;\n payment_provider: OpenAICheckoutPaymentProvider;\n status: 'ready_for_payment';\n currency: string;\n totals: OpenAICheckoutTotal[];\n links: OpenAICheckoutLink[];\n payment_mode: 'live' | 'test';\n}\n\nexport interface OpenAICheckoutOrder {\n id: string;\n checkout_session_id: string;\n status: 'completed' | string;\n permalink_url?: string;\n}\n\n/**\n * The `window.openai` runtime object injected by ChatGPT into hosted iframes.\n */\nexport interface OpenAIRuntime {\n // --- File APIs ---\n uploadFile?(file: File): Promise<OpenAIUploadFileResult>;\n getFileDownloadUrl?(params: { fileId: string }): Promise<OpenAIFileDownloadUrlResult>;\n\n // --- Modal API ---\n requestModal?(params: OpenAIRequestModalParams): Promise<void>;\n\n // --- Checkout API ---\n requestCheckout?(session: OpenAICheckoutSession): Promise<OpenAICheckoutOrder>;\n\n // --- Display ---\n requestClose?(): void;\n requestDisplayMode?(params: { mode: 'inline' | 'PiP' | 'fullscreen' }): Promise<void>;\n\n // --- Messaging ---\n sendFollowUpMessage?(params: { prompt: string }): void;\n\n // --- Other ---\n openExternal?(params: { href: string }): void;\n}\n\n/**\n * Get the `window.openai` runtime if available.\n * Returns `undefined` outside of ChatGPT or in SSR.\n */\nexport function getOpenAIRuntime(): OpenAIRuntime | undefined {\n if (typeof window !== 'undefined' && 'openai' in window) {\n return (window as unknown as { openai: OpenAIRuntime }).openai;\n }\n return undefined;\n}\n","import { useCallback } from 'react';\nimport { useApp } from '../../hooks/use-app';\nimport { getOpenAIRuntime, type OpenAIUploadFileResult } from './openai-types';\n\nexport type { OpenAIUploadFileResult as CreateFileResult };\n\n/**\n * Upload a file from the app UI into the ChatGPT conversation.\n *\n * Wraps `window.openai.uploadFile` which is only available inside ChatGPT.\n * Supported formats: `image/png`, `image/jpeg`, `image/webp`.\n *\n * Import from `sunpeak/host/chatgpt`:\n *\n * @example\n * ```tsx\n * import { useUploadFile } from 'sunpeak/host/chatgpt';\n *\n * function ImageUploader() {\n * const uploadFile = useUploadFile();\n *\n * const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {\n * const file = e.currentTarget.files?.[0];\n * if (!file) return;\n * const { fileId } = await uploadFile(file);\n * console.log('Uploaded:', fileId);\n * };\n *\n * return <input type=\"file\" accept=\"image/*\" onChange={handleChange} />;\n * }\n * ```\n */\nexport function useUploadFile(): (file: File) => Promise<OpenAIUploadFileResult> {\n const app = useApp();\n return useCallback(\n async (file: File) => {\n if (!app) {\n throw new Error('[useUploadFile] App not connected');\n }\n const runtime = getOpenAIRuntime();\n if (!runtime?.uploadFile) {\n throw new Error('[useUploadFile] window.openai.uploadFile not available');\n }\n return runtime.uploadFile(file);\n },\n [app]\n );\n}\n","import { useCallback } from 'react';\nimport { useApp } from '../../hooks/use-app';\nimport { getOpenAIRuntime, type OpenAIRequestModalParams } from './openai-types';\n\nexport type { OpenAIRequestModalParams as OpenModalParams };\n\n/**\n * Open a host-controlled modal in ChatGPT.\n *\n * Wraps `window.openai.requestModal` which is only available inside ChatGPT.\n * Pass a `template` URL to load alternate UI content in the modal, or omit\n * it to reuse the current UI.\n *\n * Import from `sunpeak/host/chatgpt`:\n *\n * @example\n * ```tsx\n * import { useRequestModal } from 'sunpeak/host/chatgpt';\n *\n * function CheckoutButton() {\n * const requestModal = useRequestModal();\n *\n * return (\n * <button onClick={() => requestModal({ template: 'ui://widget/checkout.html' })}>\n * Checkout\n * </button>\n * );\n * }\n * ```\n */\nexport function useRequestModal(): (params: OpenAIRequestModalParams) => Promise<void> {\n const app = useApp();\n return useCallback(\n async (params: OpenAIRequestModalParams) => {\n if (!app) {\n console.warn('[useRequestModal] App not connected');\n return;\n }\n const runtime = getOpenAIRuntime();\n if (!runtime?.requestModal) {\n throw new Error('[useRequestModal] window.openai.requestModal not available');\n }\n return runtime.requestModal(params);\n },\n [app]\n );\n}\n","import { useCallback } from 'react';\nimport { useApp } from '../../hooks/use-app';\nimport {\n getOpenAIRuntime,\n type OpenAICheckoutSession,\n type OpenAICheckoutOrder,\n} from './openai-types';\n\nexport type { OpenAICheckoutSession as CheckoutSession };\nexport type { OpenAICheckoutOrder as CheckoutOrder };\n\n/**\n * Trigger the ChatGPT instant checkout flow.\n *\n * Wraps `window.openai.requestCheckout` which is only available inside\n * ChatGPT. Opens the host checkout UI, handles payment display, and\n * resolves with the finalized order. Rejects on error or user cancel.\n *\n * Import from `sunpeak/host/chatgpt`:\n *\n * @example\n * ```tsx\n * import { useRequestCheckout } from 'sunpeak/host/chatgpt';\n *\n * function BuyButton() {\n * const requestCheckout = useRequestCheckout();\n *\n * const handleBuy = async () => {\n * try {\n * const order = await requestCheckout({\n * id: 'session-1',\n * payment_provider: {\n * provider: 'stripe',\n * merchant_id: 'acct_xxx',\n * supported_payment_methods: ['card', 'apple_pay'],\n * },\n * status: 'ready_for_payment',\n * currency: 'USD',\n * totals: [{ type: 'total', display_text: 'Total', amount: 999 }],\n * links: [],\n * payment_mode: 'live',\n * });\n * console.log('Order completed:', order.id);\n * } catch {\n * console.log('Checkout cancelled or failed');\n * }\n * };\n *\n * return <button onClick={handleBuy}>Buy Now</button>;\n * }\n * ```\n */\nexport function useRequestCheckout(): (\n session: OpenAICheckoutSession\n) => Promise<OpenAICheckoutOrder> {\n const app = useApp();\n return useCallback(\n async (session: OpenAICheckoutSession) => {\n if (!app) {\n throw new Error('[useRequestCheckout] App not connected');\n }\n const runtime = getOpenAIRuntime();\n if (!runtime?.requestCheckout) {\n throw new Error('[useRequestCheckout] window.openai.requestCheckout not available');\n }\n return runtime.requestCheckout(session);\n },\n [app]\n );\n}\n"],"mappings":";;;;;;;AAuFA,SAAgB,mBAA8C;AAC5D,KAAI,OAAO,WAAW,eAAe,YAAY,OAC/C,QAAQ,OAAgD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACzD5D,SAAgB,gBAAiE;CAC/E,MAAM,MAAM,QAAQ;AACpB,QAAO,YACL,OAAO,SAAe;AACpB,MAAI,CAAC,IACH,OAAM,IAAI,MAAM,oCAAoC;EAEtD,MAAM,UAAU,kBAAkB;AAClC,MAAI,CAAC,SAAS,WACZ,OAAM,IAAI,MAAM,yDAAyD;AAE3E,SAAO,QAAQ,WAAW,KAAK;IAEjC,CAAC,IAAI,CACN;;;;;;;;;;;;;;;;;;;;;;;;;;;;AChBH,SAAgB,kBAAuE;CACrF,MAAM,MAAM,QAAQ;AACpB,QAAO,YACL,OAAO,WAAqC;AAC1C,MAAI,CAAC,KAAK;AACR,WAAQ,KAAK,sCAAsC;AACnD;;EAEF,MAAM,UAAU,kBAAkB;AAClC,MAAI,CAAC,SAAS,aACZ,OAAM,IAAI,MAAM,6DAA6D;AAE/E,SAAO,QAAQ,aAAa,OAAO;IAErC,CAAC,IAAI,CACN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACOH,SAAgB,qBAEkB;CAChC,MAAM,MAAM,QAAQ;AACpB,QAAO,YACL,OAAO,YAAmC;AACxC,MAAI,CAAC,IACH,OAAM,IAAI,MAAM,yCAAyC;EAE3D,MAAM,UAAU,kBAAkB;AAClC,MAAI,CAAC,SAAS,gBACZ,OAAM,IAAI,MAAM,mEAAmE;AAErF,SAAO,QAAQ,gBAAgB,QAAQ;IAEzC,CAAC,IAAI,CACN"}