sunpeak 0.18.6 → 0.18.9

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 (63) hide show
  1. package/bin/commands/dev.mjs +9 -5
  2. package/bin/commands/inspect.mjs +13 -1
  3. package/bin/commands/new.mjs +5 -0
  4. package/bin/lib/dev-overlay.mjs +50 -0
  5. package/bin/lib/live/live-config.d.mts +3 -0
  6. package/bin/lib/live/live-config.mjs +3 -1
  7. package/bin/lib/sandbox-server.mjs +19 -0
  8. package/dist/chatgpt/index.cjs +2 -3
  9. package/dist/chatgpt/index.js +2 -3
  10. package/dist/claude/index.cjs +1 -2
  11. package/dist/claude/index.js +1 -2
  12. package/dist/host/chatgpt/index.cjs +0 -1
  13. package/dist/host/chatgpt/index.cjs.map +1 -1
  14. package/dist/host/chatgpt/index.js +0 -1
  15. package/dist/host/chatgpt/index.js.map +1 -1
  16. package/dist/index.cjs +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/inspector/index.cjs +2 -3
  19. package/dist/inspector/index.js +2 -3
  20. package/dist/inspector/inspector-url.d.ts +13 -0
  21. package/dist/inspector/use-inspector-state.d.ts +2 -0
  22. package/dist/{inspector-DRD_Q66E.cjs → inspector-CTMccsz9.cjs} +71 -22
  23. package/dist/inspector-CTMccsz9.cjs.map +1 -0
  24. package/dist/{inspector-CjSoXm6N.js → inspector-DkS75JCk.js} +71 -22
  25. package/dist/inspector-DkS75JCk.js.map +1 -0
  26. package/dist/{inspector-url-7qhtJwY6.cjs → inspector-url-C3LTKgXt.cjs} +3 -1
  27. package/dist/inspector-url-C3LTKgXt.cjs.map +1 -0
  28. package/dist/{inspector-url-DuEFmxLP.js → inspector-url-CyQcuBI9.js} +3 -1
  29. package/dist/inspector-url-CyQcuBI9.js.map +1 -0
  30. package/dist/mcp/index.cjs +98 -15
  31. package/dist/mcp/index.cjs.map +1 -1
  32. package/dist/mcp/index.js +98 -15
  33. package/dist/mcp/index.js.map +1 -1
  34. package/dist/style.css +4 -0
  35. package/package.json +5 -5
  36. package/template/dist/albums/albums.html +1 -1
  37. package/template/dist/albums/albums.json +1 -1
  38. package/template/dist/carousel/carousel.html +1 -1
  39. package/template/dist/carousel/carousel.json +1 -1
  40. package/template/dist/map/map.html +1 -1
  41. package/template/dist/map/map.json +1 -1
  42. package/template/dist/review/review.html +1 -1
  43. package/template/dist/review/review.json +1 -1
  44. package/template/node_modules/.bin/vite +2 -2
  45. package/template/node_modules/.bin/vitest +2 -2
  46. package/template/node_modules/.vite/deps/_metadata.json +4 -4
  47. package/template/node_modules/.vite-mcp/deps/@testing-library_react.js +9 -6
  48. package/template/node_modules/.vite-mcp/deps/@testing-library_react.js.map +1 -1
  49. package/template/node_modules/.vite-mcp/deps/_metadata.json +21 -21
  50. package/template/node_modules/.vite-mcp/deps/vitest.js +366 -128
  51. package/template/node_modules/.vite-mcp/deps/vitest.js.map +1 -1
  52. package/template/package.json +2 -2
  53. package/template/tests/e2e/albums.spec.ts +1 -1
  54. package/template/tests/e2e/carousel.spec.ts +1 -1
  55. package/template/tests/e2e/dev-overlay.spec.ts +118 -0
  56. package/template/tests/e2e/helpers.ts +13 -0
  57. package/template/tests/e2e/map.spec.ts +1 -1
  58. package/template/tests/e2e/review.spec.ts +1 -1
  59. package/template/tests/live/playwright.config.ts +1 -1
  60. package/dist/inspector-CjSoXm6N.js.map +0 -1
  61. package/dist/inspector-DRD_Q66E.cjs.map +0 -1
  62. package/dist/inspector-url-7qhtJwY6.cjs.map +0 -1
  63. package/dist/inspector-url-DuEFmxLP.js.map +0 -1
@@ -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
 
@@ -412,6 +412,7 @@ if (!Component) {
412
412
  if (import.meta.hot) {
413
413
  import.meta.hot.accept();
414
414
  }
415
+
415
416
  `;
416
417
  }
417
418
  },
@@ -547,11 +548,14 @@ if (import.meta.hot) {
547
548
  if (typeof mod.default !== 'function') {
548
549
  throw new Error(`Tool "${name}" has no default export handler`);
549
550
  }
551
+ const startTime = performance.now();
550
552
  const result = await mod.default(args, {});
553
+ const durationMs = Math.round((performance.now() - startTime) * 10) / 10;
551
554
  if (typeof result === 'string') {
552
- return { content: [{ type: 'text', text: result }] };
555
+ return { content: [{ type: 'text', text: result }], _meta: { _sunpeak: { requestTimeMs: durationMs } } };
553
556
  }
554
- return result;
557
+ const typed = result ?? {};
558
+ return { ...typed, _meta: { ...typed._meta, _sunpeak: { requestTimeMs: durationMs } } };
555
559
  }
556
560
  throw new Error(`Tool "${name}" not found`);
557
561
  },
@@ -20,6 +20,7 @@ const { join, resolve, dirname } = path;
20
20
  import { fileURLToPath, pathToFileURL } from 'url';
21
21
  import { getPort } from '../lib/get-port.mjs';
22
22
  import { startSandboxServer } from '../lib/sandbox-server.mjs';
23
+ import { getDevOverlayScript } from '../lib/dev-overlay.mjs';
23
24
 
24
25
  const __dirname = dirname(fileURLToPath(import.meta.url));
25
26
  const SUNPEAK_PKG_DIR = resolve(__dirname, '..', '..');
@@ -850,7 +851,18 @@ function sunpeakInspectEndpointsPlugin(getClient, setClient, pluginOpts = {}) {
850
851
  'X-Content-Type-Options': 'nosniff',
851
852
  });
852
853
  if (typeof content.text === 'string') {
853
- res.end(content.text);
854
+ const stripOverlay = url.searchParams.get('devOverlay') === 'false';
855
+ let text = content.text;
856
+ if (stripOverlay) {
857
+ // Strip dev overlay (e.g., for e2e tests)
858
+ text = text.replace(/<script>(?:(?!<\/script>)[\s\S])*?__sunpeak-dev-timing(?:(?!<\/script>)[\s\S])*?<\/script>/g, '');
859
+ } else if (process.env.SUNPEAK_DEV_OVERLAY !== 'false' && !text.includes('__sunpeak-dev-timing') && text.includes('</body>')) {
860
+ // Inject dev overlay into resources from non-sunpeak servers.
861
+ // The overlay shows resource served timestamp and tool timing (from
862
+ // _meta._sunpeak.requestTimeMs on the PostMessage tool-result notification).
863
+ text = text.replace('</body>', `${getDevOverlayScript(Date.now(), null)}\n</body>`);
864
+ }
865
+ res.end(text);
854
866
  } else if (content.blob) {
855
867
  res.end(Buffer.from(content.blob, 'base64'));
856
868
  } else {
@@ -175,6 +175,11 @@ export async function init(projectName, resourcesArg, deps = defaultDeps) {
175
175
  return false;
176
176
  }
177
177
 
178
+ // Skip framework-internal test files (dev overlay tests are for sunpeak development, not user projects)
179
+ if ((src.includes('/tests/e2e/') || src.includes('/tests/live/')) && name.startsWith('dev-')) {
180
+ return false;
181
+ }
182
+
178
183
  // Skip deps.json files (build-time metadata, not needed in scaffolded projects)
179
184
  if (name === 'deps.json' && src.includes('/resources/')) {
180
185
  return false;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Generate an inline script that shows a dev overlay with resource served timestamp
3
+ * and tool call request timing.
4
+ *
5
+ * The resource timestamp is baked into the HTML at readResource time. Tool timing
6
+ * arrives two ways:
7
+ * 1. Baked-in `toolMs` from readResource time (works when the tool call precedes the
8
+ * resource read, which is the case for Claude and the inspector's initial render).
9
+ * 2. `_meta._sunpeak.requestTimeMs` on the tool result PostMessage (handles inspector
10
+ * Re-run and hosts that pass `_meta` through to the resource iframe).
11
+ *
12
+ * @param {number} servedAt - Unix timestamp (ms) when the resource HTML was generated/served.
13
+ * @param {number | null} toolMs - Most recent tool call duration (ms), or null if no call yet.
14
+ * @returns {string} HTML script tag with the dev overlay.
15
+ */
16
+ export function getDevOverlayScript(servedAt, toolMs) {
17
+ return `<script>
18
+ (function(){
19
+ var servedAt=${servedAt};
20
+ var el=null,hidden=false,lastMs=${toolMs ?? 'null'};
21
+ function fmt(ts){var d=new Date(ts);var h=d.getHours(),m=d.getMinutes(),s=d.getSeconds();return (h<10?'0':'')+h+':'+(m<10?'0':'')+m+':'+(s<10?'0':'')+s}
22
+ function make(){
23
+ var existing=document.getElementById('__sunpeak-dev-timing');
24
+ if(existing)return existing;
25
+ var b=document.createElement('button');b.id='__sunpeak-dev-timing';
26
+ b.style.cssText='position:fixed;bottom:8px;right:8px;z-index:2147483647;display:grid;grid-template-columns:auto auto;gap:0 6px;align-items:baseline;padding:5px 8px;border-radius:6px;border:1px solid rgba(128,128,128,0.25);background:rgba(0,0,0,0.75);backdrop-filter:blur(8px);color:#e5e5e5;font-size:11px;font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;line-height:1.4;cursor:pointer;user-select:none;opacity:0.85;transition:opacity 150ms;';
27
+ b.onmouseenter=function(){b.style.opacity='1'};
28
+ b.onmouseleave=function(){b.style.opacity='0.85'};
29
+ b.onclick=function(){hidden=!hidden;upd()};
30
+ document.body.appendChild(b);return b;
31
+ }
32
+ function upd(){
33
+ if(!el)el=make();
34
+ if(hidden){el.title='Show dev info';el.innerHTML='<span style="grid-column:1/-1;font-size:9px;text-align:center">DEV</span>';return}
35
+ var h='';
36
+ h+='<span style="text-align:right;color:rgba(255,255,255,0.5);white-space:nowrap">Resource:</span><span style="white-space:nowrap">'+fmt(servedAt)+'</span>';
37
+ if(lastMs!=null)h+='<span style="text-align:right;color:rgba(255,255,255,0.5);white-space:nowrap">Tool:</span><span style="white-space:nowrap">'+(lastMs%1===0?lastMs:lastMs.toFixed(1))+'ms</span>';
38
+ el.title='Hide dev info';el.innerHTML=h;
39
+ }
40
+ upd();
41
+ window.addEventListener('message',function(e){
42
+ var d=e.data;if(!d||typeof d!=='object')return;
43
+ if(d.method!=='ui/notifications/tool-result')return;
44
+ var p=d.params;if(!p)return;
45
+ var ms=p._meta&&p._meta._sunpeak&&p._meta._sunpeak.requestTimeMs;
46
+ if(typeof ms==='number'){lastMs=ms;upd()}
47
+ });
48
+ })();
49
+ </script>`;
50
+ }
@@ -23,6 +23,9 @@ export interface LiveConfigOptions {
23
23
  /** Browser permissions to grant (e.g., ['geolocation']). */
24
24
  permissions?: string[];
25
25
 
26
+ /** Show the dev overlay (resource timestamp + tool timing) in resources. Default: true */
27
+ devOverlay?: boolean;
28
+
26
29
  /** Additional Playwright `use` options, merged with defaults. */
27
30
  use?: Record<string, unknown>;
28
31
  }
@@ -32,6 +32,7 @@ const GLOBAL_SETUP_PATH = join(__dirname, 'global-setup.mjs');
32
32
  * @param {string} [options.timezoneId] - Timezone (e.g., 'America/New_York')
33
33
  * @param {{ latitude: number, longitude: number }} [options.geolocation] - Geolocation coordinates
34
34
  * @param {string[]} [options.permissions] - Browser permissions to grant (e.g., ['geolocation'])
35
+ * @param {boolean} [options.devOverlay=true] - Show the dev overlay (resource timestamp + tool timing) in resources
35
36
  * @param {Object} [options.use] - Additional Playwright `use` options (merged with defaults)
36
37
  */
37
38
  export function createLiveConfig(hostOptions, options = {}) {
@@ -40,6 +41,7 @@ export function createLiveConfig(hostOptions, options = {}) {
40
41
  testDir = '.',
41
42
  authDir,
42
43
  vitePort = getPortSync(3456),
44
+ devOverlay = true,
43
45
  colorScheme,
44
46
  viewport,
45
47
  locale,
@@ -89,7 +91,7 @@ export function createLiveConfig(hostOptions, options = {}) {
89
91
  },
90
92
  ],
91
93
  webServer: {
92
- command: `SUNPEAK_LIVE_TEST=1 SUNPEAK_SANDBOX_PORT=${getPortSync(24680)} pnpm dev -- --prod-resources --port ${vitePort}`,
94
+ command: `SUNPEAK_LIVE_TEST=1 SUNPEAK_SANDBOX_PORT=${getPortSync(24680)}${devOverlay ? '' : ' SUNPEAK_DEV_OVERLAY=false'} pnpm dev -- --prod-resources --port ${vitePort}`,
93
95
  url: `http://localhost:${vitePort}/health`,
94
96
  reuseExistingServer: !process.env.CI,
95
97
  timeout: 60_000,
@@ -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);
@@ -1,8 +1,7 @@
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-DRD_Q66E.cjs");
5
- const require_inspector_url = require("../inspector-url-7qhtJwY6.cjs");
3
+ const require_inspector = require("../inspector-CTMccsz9.cjs");
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
8
7
  var chatgpt_exports = /* @__PURE__ */ require_chunk.__exportAll({
@@ -1,7 +1,6 @@
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-CjSoXm6N.js";
4
- import { t as createInspectorUrl } from "../inspector-url-DuEFmxLP.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-DkS75JCk.js";
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
7
6
  var chatgpt_exports = /* @__PURE__ */ __exportAll({
@@ -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-DRD_Q66E.cjs");
3
+ const require_inspector = require("../inspector-CTMccsz9.cjs");
5
4
  exports.Inspector = require_inspector.Inspector;
@@ -1,3 +1,2 @@
1
- import "../protocol-DJmRaBzO.js";
2
- import { t as Inspector } from "../inspector-CjSoXm6N.js";
1
+ import { t as Inspector } from "../inspector-DkS75JCk.js";
3
2
  export { Inspector };
@@ -1,6 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  require("../../chunk-9hOWP6kD.cjs");
3
- require("../../protocol-jbxhzcnS.cjs");
4
3
  const require_use_app = require("../../use-app-Dqh20JPP.cjs");
5
4
  let react = require("react");
6
5
  //#region src/host/chatgpt/openai-types.ts
@@ -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,4 +1,3 @@
1
- import "../../protocol-DJmRaBzO.js";
2
1
  import { t as useApp } from "../../use-app-BNbz1uzj.js";
3
2
  import { useCallback } from "react";
4
3
  //#region src/host/chatgpt/openai-types.ts
@@ -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"}
package/dist/index.cjs CHANGED
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-9hOWP6kD.cjs");
3
3
  const require_protocol = require("./protocol-jbxhzcnS.cjs");
4
4
  const require_use_app = require("./use-app-Dqh20JPP.cjs");
5
- const require_inspector = require("./inspector-DRD_Q66E.cjs");
5
+ const require_inspector = require("./inspector-CTMccsz9.cjs");
6
6
  const require_host_index = require("./host/index.cjs");
7
7
  const require_inspector_index = require("./inspector/index.cjs");
8
8
  const require_chatgpt_index = require("./chatgpt/index.cjs");
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { $ as literal, L as RequestIdSchema, Q as boolean, R as ResourceLinkSchema, U as ToolSchema, X as _undefined, Z as array, _ as ImplementationSchema, a as CallToolResultSchema, at as unknown, et as number, it as union, nt as record, p as EmbeddedResourceSchema, rt as string, s as ContentBlockSchema, tt as object } from "./protocol-DJmRaBzO.js";
2
2
  import { $ as s, A as V, B as e, C as QQ, D as T, E as S, F as _Q, G as k, H as gQ, I as a, J as o, K as m, L as b, M as W, N as WQ, O as TQ, P as _, Q as r, R as c, S as PQ, T as RQ, U as h, V as g, W as jQ, X as q, Y as p, Z as qQ, _ as LQ, a as B, at as zQ, b as OQ, c as EQ, d as G, et as t, f as I, g as L, h as K, i as AQ, it as z, j as VQ, k as U, l as F, m as J, n as AppProvider, nt as w, o as BQ, p as IQ, q as n, r as A, rt as x, s as C, t as useApp, tt as vX, u as FQ, v as MX, w as R, x as P, y as O, z as d } from "./use-app-BNbz1uzj.js";
3
- import { S as DEFAULT_STYLE_VARIABLES } from "./inspector-CjSoXm6N.js";
3
+ import { S as DEFAULT_STYLE_VARIABLES } from "./inspector-DkS75JCk.js";
4
4
  import { detectHost, isChatGPT, isClaude } from "./host/index.js";
5
5
  import { t as inspector_exports } from "./inspector/index.js";
6
6
  import { t as chatgpt_exports } from "./chatgpt/index.js";
@@ -1,8 +1,7 @@
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-DRD_Q66E.cjs");
5
- const require_inspector_url = require("../inspector-url-7qhtJwY6.cjs");
3
+ const require_inspector = require("../inspector-CTMccsz9.cjs");
4
+ const require_inspector_url = require("../inspector-url-C3LTKgXt.cjs");
6
5
  const require_discovery = require("../discovery-Clu4uHp1.cjs");
7
6
  //#region src/inspector/index.ts
8
7
  var inspector_exports = /* @__PURE__ */ require_chunk.__exportAll({
@@ -1,7 +1,6 @@
1
1
  import { r as __exportAll } from "../chunk-D6g4UhsZ.js";
2
- import "../protocol-DJmRaBzO.js";
3
- import { _ as McpAppHost, a as SidebarControl, b as getRegisteredHosts, c as SidebarTextarea, d as ThemeProvider, f as useThemeContext, g as extractResourceCSP, h as IframeResource, i as SidebarCollapsibleControl, l as SidebarToggle, m as useInspectorState, n as resolveServerToolResult, o as SidebarInput, p as useMcpConnection, r as SidebarCheckbox, s as SidebarSelect, t as Inspector, u as SimpleSidebar, v as SCREEN_WIDTHS, x as registerHostShell, y as getHostShell } from "../inspector-CjSoXm6N.js";
4
- import { t as createInspectorUrl } from "../inspector-url-DuEFmxLP.js";
2
+ import { _ as McpAppHost, a as SidebarControl, b as getRegisteredHosts, c as SidebarTextarea, d as ThemeProvider, f as useThemeContext, g as extractResourceCSP, h as IframeResource, i as SidebarCollapsibleControl, l as SidebarToggle, m as useInspectorState, n as resolveServerToolResult, o as SidebarInput, p as useMcpConnection, r as SidebarCheckbox, s as SidebarSelect, t as Inspector, u as SimpleSidebar, v as SCREEN_WIDTHS, x as registerHostShell, y as getHostShell } from "../inspector-DkS75JCk.js";
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/inspector/index.ts
7
6
  var inspector_exports = /* @__PURE__ */ __exportAll({
@@ -109,6 +109,19 @@ export interface InspectorUrlParams {
109
109
  * Enable Prod Resources mode (production dist/ bundles instead of HMR).
110
110
  */
111
111
  prodResources?: boolean;
112
+ /**
113
+ * Show the inspector sidebar. Useful for headless testing or embedding
114
+ * the inspector as a pure resource viewer.
115
+ * @default true
116
+ */
117
+ sidebar?: boolean;
118
+ /**
119
+ * Show the dev overlay (resource timestamp + tool timing) inside resources.
120
+ * Set to false to hide it during e2e tests so it doesn't interfere with
121
+ * element assertions.
122
+ * @default true
123
+ */
124
+ devOverlay?: boolean;
112
125
  }
113
126
  /**
114
127
  * Creates a URL path with query parameters for the Inspector.
@@ -88,6 +88,8 @@ export interface InspectorState {
88
88
  prefersBorder: boolean;
89
89
  urlTool: string | undefined;
90
90
  urlProdResources: boolean | undefined;
91
+ urlSidebar: boolean | undefined;
92
+ urlDevOverlay: boolean | undefined;
91
93
  }
92
94
  export declare function useInspectorState({ simulations, defaultHost, }: UseInspectorStateOptions): InspectorState;
93
95
  export {};
@@ -2338,6 +2338,10 @@ function parseUrlParams() {
2338
2338
  const host = params.get("host") ?? void 0;
2339
2339
  const prodResourcesParam = params.get("prodResources");
2340
2340
  const prodResources = prodResourcesParam === "true" ? true : prodResourcesParam === "false" ? false : void 0;
2341
+ const sidebarParam = params.get("sidebar");
2342
+ const sidebar = sidebarParam === "false" ? false : sidebarParam === "true" ? true : void 0;
2343
+ const devOverlayParam = params.get("devOverlay");
2344
+ const devOverlay = devOverlayParam === "false" ? false : devOverlayParam === "true" ? true : void 0;
2341
2345
  const deviceType = params.get("deviceType");
2342
2346
  let platform;
2343
2347
  if (deviceType === "mobile" || deviceType === "tablet") platform = "mobile";
@@ -2370,7 +2374,9 @@ function parseUrlParams() {
2370
2374
  deviceCapabilities,
2371
2375
  safeAreaInsets,
2372
2376
  host: host ?? void 0,
2373
- prodResources
2377
+ prodResources,
2378
+ sidebar,
2379
+ devOverlay
2374
2380
  };
2375
2381
  }
2376
2382
  function useInspectorState({ simulations, defaultHost = "chatgpt" }) {
@@ -2621,7 +2627,9 @@ function useInspectorState({ simulations, defaultHost = "chatgpt" }) {
2621
2627
  permissions: resourceMeta?.permissions,
2622
2628
  prefersBorder: resourceMeta?.prefersBorder ?? false,
2623
2629
  urlTool: urlParams.tool,
2624
- urlProdResources: urlParams.prodResources
2630
+ urlProdResources: urlParams.prodResources,
2631
+ urlSidebar: urlParams.sidebar,
2632
+ urlDevOverlay: urlParams.devOverlay
2625
2633
  };
2626
2634
  }
2627
2635
  //#endregion
@@ -2655,18 +2663,23 @@ function useMcpConnection(initialServerUrl) {
2655
2663
  body: JSON.stringify(body)
2656
2664
  });
2657
2665
  if (!res.ok) {
2658
- let message = `Connection failed (${res.status})`;
2666
+ let message;
2659
2667
  try {
2660
2668
  const json = await res.json();
2661
2669
  if (json.error) message = json.error;
2662
2670
  } catch {}
2671
+ if (!message) if (res.status === 404) message = "Server not found at this URL. Check the URL and make sure the server is running.";
2672
+ else if (res.status >= 500) message = `Server error (${res.status}). Check the MCP server logs for details.`;
2673
+ else message = `Connection failed (${res.status})`;
2663
2674
  throw new Error(message);
2664
2675
  }
2665
2676
  const data = await res.json();
2666
2677
  setStatus("connected");
2667
2678
  setSimulations(data.simulations ?? void 0);
2668
2679
  } catch (err) {
2669
- setError(err instanceof Error ? err.message : String(err));
2680
+ let message = err instanceof Error ? err.message : String(err);
2681
+ if (err instanceof TypeError && message === "Failed to fetch") message = "Cannot reach MCP server. Is it running?";
2682
+ setError(message);
2670
2683
  setStatus("error");
2671
2684
  setSimulations(void 0);
2672
2685
  }
@@ -2685,7 +2698,10 @@ function useMcpConnection(initialServerUrl) {
2685
2698
  try {
2686
2699
  const res = await fetch("/__sunpeak/list-tools");
2687
2700
  if (cancelled) return;
2688
- if (!res.ok) throw new Error(`Health check failed (${res.status})`);
2701
+ if (!res.ok) {
2702
+ const msg = res.status === 404 ? "MCP server not reachable. Is it running?" : `Health check failed (${res.status}). Check the MCP server logs.`;
2703
+ throw new Error(msg);
2704
+ }
2689
2705
  setStatus("connected");
2690
2706
  } catch (err) {
2691
2707
  if (cancelled) return;
@@ -3196,6 +3212,8 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
3196
3212
  const [oauthError, setOauthError] = react.useState();
3197
3213
  const connection = useMcpConnection(mcpServerUrl || void 0);
3198
3214
  const [prodResources, setProdResources] = react.useState(state.urlProdResources ?? defaultProdResources);
3215
+ const showSidebar = state.urlSidebar !== false;
3216
+ const showDevOverlay = state.urlDevOverlay !== false;
3199
3217
  const [isRunning, setIsRunning] = react.useState(false);
3200
3218
  const [hasRun, setHasRun] = react.useState(false);
3201
3219
  const [showCheck, setShowCheck] = react.useState(false);
@@ -3366,26 +3384,47 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
3366
3384
  if (!caller || !sim) return;
3367
3385
  const toolName = sim.tool.name;
3368
3386
  setIsRunning(true);
3387
+ const startTime = performance.now();
3369
3388
  try {
3370
3389
  const result = await caller({
3371
3390
  name: toolName,
3372
3391
  arguments: state.toolInput
3373
3392
  });
3374
- state.setToolResult(result);
3375
- state.setToolResultJson(JSON.stringify(result, null, 2));
3393
+ const clientMs = Math.round((performance.now() - startTime) * 10) / 10;
3394
+ const resultMeta = result?._meta;
3395
+ const serverMs = (resultMeta?._sunpeak)?.requestTimeMs;
3396
+ const durationMs = typeof serverMs === "number" ? serverMs : clientMs;
3397
+ const resultWithTiming = {
3398
+ ...result,
3399
+ _meta: {
3400
+ ...resultMeta,
3401
+ _sunpeak: { requestTimeMs: durationMs }
3402
+ }
3403
+ };
3404
+ state.setToolResult(resultWithTiming);
3405
+ const displayResult = resultMeta?._sunpeak ? (() => {
3406
+ const { _sunpeak: _, ...cleanMeta } = resultMeta;
3407
+ const clean = { ...result };
3408
+ clean._meta = Object.keys(cleanMeta).length > 0 ? cleanMeta : void 0;
3409
+ if (clean._meta === void 0) delete clean._meta;
3410
+ return clean;
3411
+ })() : result;
3412
+ state.setToolResultJson(JSON.stringify(displayResult, null, 2));
3376
3413
  state.setToolResultError("");
3377
3414
  setHasRun(true);
3378
3415
  setShowCheck(true);
3379
3416
  clearTimeout(checkTimerRef.current);
3380
3417
  checkTimerRef.current = setTimeout(() => setShowCheck(false), 2e3);
3381
3418
  } catch (err) {
3419
+ const durationMs = Math.round((performance.now() - startTime) * 10) / 10;
3382
3420
  const message = err instanceof Error ? err.message : String(err);
3383
3421
  state.setToolResult({
3384
3422
  content: [{
3385
3423
  type: "text",
3386
3424
  text: `Error: ${message}`
3387
3425
  }],
3388
- isError: true
3426
+ isError: true,
3427
+ _meta: { _sunpeak: { requestTimeMs: durationMs } }
3389
3428
  });
3390
3429
  state.setToolResultJson(JSON.stringify({
3391
3430
  content: [{
@@ -3520,7 +3559,8 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
3520
3559
  clearTimeout(timer);
3521
3560
  };
3522
3561
  }, [prodResourcesPath]);
3523
- const effectiveResourceUrl = (prodResourcesPath && prodResourcesReady ? prodResourcesPath : void 0) ?? state.resourceUrl;
3562
+ const baseResourceUrl = (prodResourcesPath && prodResourcesReady ? prodResourcesPath : void 0) ?? state.resourceUrl;
3563
+ const effectiveResourceUrl = baseResourceUrl && !showDevOverlay ? `${baseResourceUrl}${baseResourceUrl.includes("?") ? "&" : "?"}devOverlay=false` : baseResourceUrl;
3524
3564
  const prodResourcesLoading = !!prodResourcesPath && !prodResourcesReady;
3525
3565
  const hasTools = toolNames.length > 0;
3526
3566
  const showEmptyState = !(activeSimulationName !== null && currentSim?.toolResult != null) && !hasRun;
@@ -3638,6 +3678,26 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
3638
3678
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M0 0L10 6L0 12V0Z" })
3639
3679
  }), "Run"]
3640
3680
  }) : void 0;
3681
+ const conversationContent = ShellConversation ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShellConversation, {
3682
+ screenWidth: state.screenWidth,
3683
+ displayMode: state.displayMode,
3684
+ platform: state.platform,
3685
+ onRequestDisplayMode: state.handleDisplayModeChange,
3686
+ appName,
3687
+ appIcon,
3688
+ userMessage,
3689
+ onContentWidthChange: state.handleContentWidthChange,
3690
+ headerAction: runButton,
3691
+ children: content
3692
+ }) : content;
3693
+ if (!showSidebar) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThemeProvider, {
3694
+ theme: state.theme,
3695
+ applyTheme,
3696
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3697
+ className: "flex h-screen w-screen",
3698
+ children: conversationContent
3699
+ })
3700
+ });
3641
3701
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThemeProvider, {
3642
3702
  theme: state.theme,
3643
3703
  applyTheme,
@@ -4170,18 +4230,7 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
4170
4230
  })
4171
4231
  ]
4172
4232
  }),
4173
- children: ShellConversation ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShellConversation, {
4174
- screenWidth: state.screenWidth,
4175
- displayMode: state.displayMode,
4176
- platform: state.platform,
4177
- onRequestDisplayMode: state.handleDisplayModeChange,
4178
- appName,
4179
- appIcon,
4180
- userMessage,
4181
- onContentWidthChange: state.handleContentWidthChange,
4182
- headerAction: runButton,
4183
- children: content
4184
- }) : content
4233
+ children: conversationContent
4185
4234
  })
4186
4235
  });
4187
4236
  }
@@ -4319,4 +4368,4 @@ Object.defineProperty(exports, "useThemeContext", {
4319
4368
  }
4320
4369
  });
4321
4370
 
4322
- //# sourceMappingURL=inspector-DRD_Q66E.cjs.map
4371
+ //# sourceMappingURL=inspector-CTMccsz9.cjs.map