sunpeak 0.16.27 → 0.16.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/commands/build.mjs +0 -1
- package/dist/chatgpt/globals.css +10 -9
- package/dist/chatgpt/index.cjs +55 -24
- package/dist/chatgpt/index.cjs.map +1 -1
- package/dist/chatgpt/index.js +31 -25
- package/dist/chatgpt/index.js.map +1 -1
- package/dist/chunk-9hOWP6kD.cjs +64 -0
- package/dist/chunk-D6g4UhsZ.js +35 -0
- package/dist/claude/index.cjs +4 -4
- package/dist/claude/index.js +3 -5
- package/dist/discovery-BxKCIgG5.cjs +332 -0
- package/dist/discovery-BxKCIgG5.cjs.map +1 -0
- package/dist/discovery-Du4LHrih.js +261 -0
- package/dist/discovery-Du4LHrih.js.map +1 -0
- package/dist/host/chatgpt/index.cjs +171 -65
- package/dist/host/chatgpt/index.cjs.map +1 -1
- package/dist/host/chatgpt/index.js +170 -70
- package/dist/host/chatgpt/index.js.map +1 -1
- package/dist/host/index.cjs +47 -19
- package/dist/host/index.cjs.map +1 -1
- package/dist/host/index.js +47 -24
- package/dist/host/index.js.map +1 -1
- package/dist/index.cjs +3103 -3725
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3026 -3746
- package/dist/index.js.map +1 -1
- package/dist/lib/discovery-cli.cjs +117 -131
- package/dist/lib/discovery-cli.cjs.map +1 -1
- package/dist/lib/discovery-cli.js +107 -111
- package/dist/lib/discovery-cli.js.map +1 -1
- package/dist/mcp/index.cjs +9818 -10283
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.js +9799 -10282
- package/dist/mcp/index.js.map +1 -1
- package/dist/protocol-DJmRaBzO.js +11080 -0
- package/dist/{protocol-DkDHRwOW.cjs.map → protocol-DJmRaBzO.js.map} +1 -1
- package/dist/protocol-jbxhzcnS.cjs +11493 -0
- package/dist/protocol-jbxhzcnS.cjs.map +1 -0
- package/dist/simulator/index.cjs +79 -36
- package/dist/simulator/index.cjs.map +1 -1
- package/dist/simulator/index.js +43 -37
- package/dist/simulator/index.js.map +1 -1
- package/dist/simulator-BYIH-xqQ.cjs +3701 -0
- package/dist/{simulator-Gc6n_fT4.cjs.map → simulator-BYIH-xqQ.cjs.map} +1 -1
- package/dist/simulator-CmgNnWBO.js +3575 -0
- package/dist/simulator-CmgNnWBO.js.map +1 -0
- package/dist/simulator-url-BDGD4vZD.cjs +69 -0
- package/dist/simulator-url-BDGD4vZD.cjs.map +1 -0
- package/dist/simulator-url-Bkxj43yT.js +64 -0
- package/dist/simulator-url-Bkxj43yT.js.map +1 -0
- package/dist/style.css +10 -9
- package/dist/use-app-D2h-aiyr.cjs +940 -0
- package/dist/use-app-D2h-aiyr.cjs.map +1 -0
- package/dist/use-app-X7JbGskk.js +598 -0
- package/dist/use-app-X7JbGskk.js.map +1 -0
- package/package.json +8 -8
- package/template/node_modules/.bin/vite +2 -2
- package/template/node_modules/.bin/vitest +2 -2
- package/template/package.json +5 -5
- package/dist/claude/index.cjs.map +0 -1
- package/dist/claude/index.js.map +0 -1
- package/dist/discovery-BVqD-JsT.js +0 -224
- package/dist/discovery-BVqD-JsT.js.map +0 -1
- package/dist/discovery-D1gpaVz4.cjs +0 -223
- package/dist/discovery-D1gpaVz4.cjs.map +0 -1
- package/dist/index-BEWVLFfB.cjs +0 -28
- package/dist/index-BEWVLFfB.cjs.map +0 -1
- package/dist/index-C6XYFOmh.js +0 -29
- package/dist/index-C6XYFOmh.js.map +0 -1
- package/dist/index-D0FsXP3Y.cjs +0 -40
- package/dist/index-D0FsXP3Y.cjs.map +0 -1
- package/dist/index-Rg7SWjvl.js +0 -41
- package/dist/index-Rg7SWjvl.js.map +0 -1
- package/dist/protocol-DkDHRwOW.cjs +0 -12221
- package/dist/protocol-uge7qFev.js +0 -12223
- package/dist/protocol-uge7qFev.js.map +0 -1
- package/dist/simulator-B-CrMHVs.js +0 -3534
- package/dist/simulator-B-CrMHVs.js.map +0 -1
- package/dist/simulator-Gc6n_fT4.cjs +0 -3549
- package/dist/simulator-url-DcSYRl-P.cjs +0 -53
- package/dist/simulator-url-DcSYRl-P.cjs.map +0 -1
- package/dist/simulator-url-j_XV3EoP.js +0 -54
- package/dist/simulator-url-j_XV3EoP.js.map +0 -1
- package/dist/use-app-C9gpzIQO.js +0 -349
- package/dist/use-app-C9gpzIQO.js.map +0 -1
- package/dist/use-app-D09O2swh.cjs +0 -348
- package/dist/use-app-D09O2swh.cjs.map +0 -1
|
@@ -0,0 +1,3701 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-9hOWP6kD.cjs");
|
|
2
|
+
const require_protocol = require("./protocol-jbxhzcnS.cjs");
|
|
3
|
+
let react = require("react");
|
|
4
|
+
react = require_chunk.__toESM(react);
|
|
5
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
+
//#region src/lib/default-style-variables.ts
|
|
7
|
+
var DEFAULT_STYLE_VARIABLES = {
|
|
8
|
+
"--color-background-primary": "light-dark(#ffffff, #1a1a1a)",
|
|
9
|
+
"--color-background-secondary": "light-dark(#f5f5f5, #2d2d2d)",
|
|
10
|
+
"--color-background-tertiary": "light-dark(#e5e5e5, #404040)",
|
|
11
|
+
"--color-background-inverse": "light-dark(#1a1a1a, #ffffff)",
|
|
12
|
+
"--color-background-ghost": "light-dark(rgba(255,255,255,0), rgba(26,26,26,0))",
|
|
13
|
+
"--color-background-info": "light-dark(#eff6ff, #1e3a5f)",
|
|
14
|
+
"--color-background-danger": "light-dark(#fef2f2, #7f1d1d)",
|
|
15
|
+
"--color-background-success": "light-dark(#f0fdf4, #14532d)",
|
|
16
|
+
"--color-background-warning": "light-dark(#fefce8, #713f12)",
|
|
17
|
+
"--color-background-disabled": "light-dark(rgba(255,255,255,0.5), rgba(26,26,26,0.5))",
|
|
18
|
+
"--color-text-primary": "light-dark(#1f2937, #f3f4f6)",
|
|
19
|
+
"--color-text-secondary": "light-dark(#6b7280, #9ca3af)",
|
|
20
|
+
"--color-text-tertiary": "light-dark(#9ca3af, #6b7280)",
|
|
21
|
+
"--color-text-inverse": "light-dark(#f3f4f6, #1f2937)",
|
|
22
|
+
"--color-text-ghost": "light-dark(rgba(107,114,128,0.5), rgba(156,163,175,0.5))",
|
|
23
|
+
"--color-text-info": "light-dark(#1d4ed8, #60a5fa)",
|
|
24
|
+
"--color-text-danger": "light-dark(#b91c1c, #f87171)",
|
|
25
|
+
"--color-text-success": "light-dark(#15803d, #4ade80)",
|
|
26
|
+
"--color-text-warning": "light-dark(#a16207, #fbbf24)",
|
|
27
|
+
"--color-text-disabled": "light-dark(rgba(31,41,55,0.5), rgba(243,244,246,0.5))",
|
|
28
|
+
"--color-border-primary": "light-dark(#e5e7eb, #404040)",
|
|
29
|
+
"--color-border-secondary": "light-dark(#d1d5db, #525252)",
|
|
30
|
+
"--color-border-tertiary": "light-dark(#f3f4f6, #374151)",
|
|
31
|
+
"--color-border-inverse": "light-dark(rgba(255,255,255,0.3), rgba(0,0,0,0.3))",
|
|
32
|
+
"--color-border-ghost": "light-dark(rgba(229,231,235,0), rgba(64,64,64,0))",
|
|
33
|
+
"--color-border-info": "light-dark(#93c5fd, #1e40af)",
|
|
34
|
+
"--color-border-danger": "light-dark(#fca5a5, #991b1b)",
|
|
35
|
+
"--color-border-success": "light-dark(#86efac, #166534)",
|
|
36
|
+
"--color-border-warning": "light-dark(#fde047, #854d0e)",
|
|
37
|
+
"--color-border-disabled": "light-dark(rgba(229,231,235,0.5), rgba(64,64,64,0.5))",
|
|
38
|
+
"--color-ring-primary": "light-dark(#3b82f6, #60a5fa)",
|
|
39
|
+
"--color-ring-secondary": "light-dark(#6b7280, #9ca3af)",
|
|
40
|
+
"--color-ring-inverse": "light-dark(#ffffff, #1f2937)",
|
|
41
|
+
"--color-ring-info": "light-dark(#2563eb, #3b82f6)",
|
|
42
|
+
"--color-ring-danger": "light-dark(#dc2626, #ef4444)",
|
|
43
|
+
"--color-ring-success": "light-dark(#16a34a, #22c55e)",
|
|
44
|
+
"--color-ring-warning": "light-dark(#ca8a04, #eab308)",
|
|
45
|
+
"--font-sans": "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
46
|
+
"--font-mono": "ui-monospace, 'SF Mono', Monaco, 'Cascadia Code', monospace",
|
|
47
|
+
"--font-weight-normal": "400",
|
|
48
|
+
"--font-weight-medium": "500",
|
|
49
|
+
"--font-weight-semibold": "600",
|
|
50
|
+
"--font-weight-bold": "700",
|
|
51
|
+
"--font-text-xs-size": "0.75rem",
|
|
52
|
+
"--font-text-sm-size": "0.875rem",
|
|
53
|
+
"--font-text-md-size": "1rem",
|
|
54
|
+
"--font-text-lg-size": "1.125rem",
|
|
55
|
+
"--font-heading-xs-size": "0.75rem",
|
|
56
|
+
"--font-heading-sm-size": "0.875rem",
|
|
57
|
+
"--font-heading-md-size": "1rem",
|
|
58
|
+
"--font-heading-lg-size": "1.25rem",
|
|
59
|
+
"--font-heading-xl-size": "1.5rem",
|
|
60
|
+
"--font-heading-2xl-size": "1.875rem",
|
|
61
|
+
"--font-heading-3xl-size": "2.25rem",
|
|
62
|
+
"--font-text-xs-line-height": "1.4",
|
|
63
|
+
"--font-text-sm-line-height": "1.4",
|
|
64
|
+
"--font-text-md-line-height": "1.5",
|
|
65
|
+
"--font-text-lg-line-height": "1.5",
|
|
66
|
+
"--font-heading-xs-line-height": "1.4",
|
|
67
|
+
"--font-heading-sm-line-height": "1.4",
|
|
68
|
+
"--font-heading-md-line-height": "1.4",
|
|
69
|
+
"--font-heading-lg-line-height": "1.3",
|
|
70
|
+
"--font-heading-xl-line-height": "1.25",
|
|
71
|
+
"--font-heading-2xl-line-height": "1.2",
|
|
72
|
+
"--font-heading-3xl-line-height": "1.1",
|
|
73
|
+
"--border-radius-xs": "2px",
|
|
74
|
+
"--border-radius-sm": "4px",
|
|
75
|
+
"--border-radius-md": "6px",
|
|
76
|
+
"--border-radius-lg": "8px",
|
|
77
|
+
"--border-radius-xl": "12px",
|
|
78
|
+
"--border-radius-full": "9999px",
|
|
79
|
+
"--border-width-regular": "1px",
|
|
80
|
+
"--shadow-hairline": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
|
|
81
|
+
"--shadow-sm": "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)",
|
|
82
|
+
"--shadow-md": "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)",
|
|
83
|
+
"--shadow-lg": "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)"
|
|
84
|
+
};
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/simulator/hosts.ts
|
|
87
|
+
var registry = /* @__PURE__ */ new Map();
|
|
88
|
+
/** Register a host shell. Idempotent — re-registering with the same id replaces. */
|
|
89
|
+
function registerHostShell(shell) {
|
|
90
|
+
registry.set(shell.id, shell);
|
|
91
|
+
}
|
|
92
|
+
/** Get a registered host shell by id. */
|
|
93
|
+
function getHostShell(id) {
|
|
94
|
+
return registry.get(id);
|
|
95
|
+
}
|
|
96
|
+
/** Get all registered host shells, in insertion order. */
|
|
97
|
+
function getRegisteredHosts() {
|
|
98
|
+
return Array.from(registry.values());
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/simulator/simulator-types.ts
|
|
102
|
+
var SCREEN_WIDTHS = {
|
|
103
|
+
"mobile-s": 375,
|
|
104
|
+
"mobile-l": 425,
|
|
105
|
+
tablet: 768,
|
|
106
|
+
full: 1024
|
|
107
|
+
};
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/chatgpt/chatgpt-conversation.tsx
|
|
110
|
+
function CloseIcon$1({ className }) {
|
|
111
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
112
|
+
width: "1em",
|
|
113
|
+
height: "1em",
|
|
114
|
+
viewBox: "0 0 24 24",
|
|
115
|
+
fill: "currentColor",
|
|
116
|
+
className,
|
|
117
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
118
|
+
fillRule: "evenodd",
|
|
119
|
+
clipRule: "evenodd",
|
|
120
|
+
d: "M5.83071 5.83077C6.33839 5.32309 7.16151 5.32309 7.66919 5.83077L12 10.1615L16.3307 5.83077C16.8384 5.32309 17.6615 5.32309 18.1692 5.83077C18.6769 6.33845 18.6769 7.16157 18.1692 7.66925L13.8384 12L18.1692 16.3308C18.6769 16.8385 18.6769 17.6616 18.1692 18.1693C17.6615 18.6769 16.8384 18.6769 16.3307 18.1693L12 13.8385L7.66919 18.1693C7.16151 18.6769 6.33839 18.6769 5.83071 18.1693C5.32303 17.6616 5.32303 16.8385 5.83071 16.3308L10.1615 12L5.83071 7.66925C5.32303 7.16157 5.32303 6.33845 5.83071 5.83077Z"
|
|
121
|
+
})
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Conversation layout that renders children (iframe) at a stable tree position.
|
|
126
|
+
*
|
|
127
|
+
* All three display modes (inline, pip, fullscreen) share the same React tree
|
|
128
|
+
* shape so that the iframe never unmounts when switching modes, avoiding a
|
|
129
|
+
* white-flash reload.
|
|
130
|
+
*
|
|
131
|
+
* Visual differences are achieved purely with CSS:
|
|
132
|
+
* - **inline**: content in normal document flow
|
|
133
|
+
* - **pip**: content wrapper becomes `position: fixed` floating overlay
|
|
134
|
+
* - **fullscreen**: content wrapper becomes `position: fixed` covering the viewport;
|
|
135
|
+
* fullscreen chrome (header/footer) rendered as a separate fixed overlay
|
|
136
|
+
*/
|
|
137
|
+
function Conversation({ children, screenWidth, displayMode, platform, onRequestDisplayMode, appName = "Sunpeak", appIcon, userMessage = "What have you got for me today?", headerAction, onContentWidthChange }) {
|
|
138
|
+
const isDesktop = platform === "desktop";
|
|
139
|
+
const containerWidth = screenWidth === "full" ? "100%" : `${SCREEN_WIDTHS[screenWidth]}px`;
|
|
140
|
+
const isFullscreen = displayMode === "fullscreen";
|
|
141
|
+
const isPip = displayMode === "pip";
|
|
142
|
+
const contentRef = (0, react.useRef)(null);
|
|
143
|
+
const onContentWidthChangeRef = (0, react.useRef)(onContentWidthChange);
|
|
144
|
+
(0, react.useEffect)(() => {
|
|
145
|
+
onContentWidthChangeRef.current = onContentWidthChange;
|
|
146
|
+
});
|
|
147
|
+
const setContentRef = (0, react.useCallback)((node) => {
|
|
148
|
+
contentRef.current = node;
|
|
149
|
+
}, []);
|
|
150
|
+
(0, react.useEffect)(() => {
|
|
151
|
+
const el = contentRef.current;
|
|
152
|
+
if (!el) return;
|
|
153
|
+
const observer = new ResizeObserver((entries) => {
|
|
154
|
+
for (const entry of entries) {
|
|
155
|
+
const width = Math.round(entry.contentBoxSize[0]?.inlineSize ?? entry.contentRect.width);
|
|
156
|
+
if (width > 0) onContentWidthChangeRef.current?.(width);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
observer.observe(el);
|
|
160
|
+
return () => observer.disconnect();
|
|
161
|
+
}, []);
|
|
162
|
+
const handleClose = () => onRequestDisplayMode?.("inline");
|
|
163
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
164
|
+
className: "flex flex-col w-full h-full flex-1 items-center relative",
|
|
165
|
+
style: {
|
|
166
|
+
transform: "translate(0)",
|
|
167
|
+
backgroundColor: "var(--sim-bg-conversation, var(--color-background-primary))",
|
|
168
|
+
color: "var(--color-text-primary)"
|
|
169
|
+
},
|
|
170
|
+
children: [
|
|
171
|
+
isFullscreen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
172
|
+
className: "fixed start-0 end-0 top-0 bottom-0 z-[51] mx-auto flex flex-col pointer-events-none",
|
|
173
|
+
style: { maxWidth: containerWidth },
|
|
174
|
+
children: [
|
|
175
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
176
|
+
className: "z-10 grid h-[3.25rem] grid-cols-[1fr_auto_1fr] border-b px-2 pointer-events-auto",
|
|
177
|
+
style: {
|
|
178
|
+
borderColor: "var(--color-border-primary)",
|
|
179
|
+
backgroundColor: "var(--sim-bg-conversation, var(--color-background-primary))"
|
|
180
|
+
},
|
|
181
|
+
children: [
|
|
182
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
183
|
+
className: "flex items-center justify-start gap-3",
|
|
184
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
185
|
+
onClick: handleClose,
|
|
186
|
+
"aria-label": "Close",
|
|
187
|
+
className: "h-7 w-7 flex items-center justify-center rounded-md transition-colors hover:opacity-70",
|
|
188
|
+
type: "button",
|
|
189
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CloseIcon$1, {})
|
|
190
|
+
})
|
|
191
|
+
}),
|
|
192
|
+
isDesktop && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
193
|
+
className: "flex items-center justify-center text-base",
|
|
194
|
+
children: appName
|
|
195
|
+
}),
|
|
196
|
+
isDesktop && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {})
|
|
197
|
+
]
|
|
198
|
+
}),
|
|
199
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "flex-1" }),
|
|
200
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
201
|
+
className: "relative",
|
|
202
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
203
|
+
className: "absolute inset-x-0 bottom-0 h-full -z-10",
|
|
204
|
+
style: {
|
|
205
|
+
maskImage: "linear-gradient(to bottom, transparent 0%, black 50%)",
|
|
206
|
+
WebkitMaskImage: "linear-gradient(to bottom, transparent 0%, black 50%)",
|
|
207
|
+
backdropFilter: "blur(16px)",
|
|
208
|
+
WebkitBackdropFilter: "blur(16px)"
|
|
209
|
+
}
|
|
210
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
211
|
+
className: "max-w-[40rem] min-[1440px]:max-w-[48rem] mx-auto px-4 pt-4 pb-4 pointer-events-auto",
|
|
212
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
213
|
+
className: "relative",
|
|
214
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
215
|
+
type: "text",
|
|
216
|
+
name: "userInput",
|
|
217
|
+
disabled: true,
|
|
218
|
+
placeholder: "Message sunpeak.ai",
|
|
219
|
+
className: "w-full rounded-3xl px-5 py-3 pr-12 shadow-sm",
|
|
220
|
+
style: {
|
|
221
|
+
backgroundColor: "var(--sim-bg-reply-input, var(--color-background-secondary))",
|
|
222
|
+
color: "var(--color-text-primary)",
|
|
223
|
+
border: "1px solid var(--color-border-tertiary)"
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
})]
|
|
228
|
+
})
|
|
229
|
+
]
|
|
230
|
+
}),
|
|
231
|
+
!isFullscreen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
232
|
+
className: "h-12 flex items-center gap-3 px-4 text-lg sticky top-0 z-40 w-full",
|
|
233
|
+
style: {
|
|
234
|
+
maxWidth: containerWidth,
|
|
235
|
+
backgroundColor: "var(--sim-bg-conversation, var(--color-background-primary))"
|
|
236
|
+
},
|
|
237
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "sunpeak.ai" }), headerAction]
|
|
238
|
+
}),
|
|
239
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
240
|
+
className: "flex flex-col flex-1 w-full transition-all duration-200 overflow-hidden",
|
|
241
|
+
style: { maxWidth: containerWidth },
|
|
242
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
|
|
243
|
+
className: "flex-1 overflow-y-auto overflow-x-hidden flex flex-col",
|
|
244
|
+
children: [
|
|
245
|
+
!isFullscreen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
246
|
+
className: "w-full focus:outline-none",
|
|
247
|
+
dir: "auto",
|
|
248
|
+
"data-turn": "user",
|
|
249
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h5", {
|
|
250
|
+
className: "sr-only",
|
|
251
|
+
children: "You said:"
|
|
252
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
253
|
+
className: "text-base my-auto mx-auto md:pt-8 px-4",
|
|
254
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
255
|
+
className: "max-w-[40rem] min-[1440px]:max-w-[48rem] mx-auto flex-1 relative flex w-full min-w-0 flex-col",
|
|
256
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
257
|
+
className: "flex max-w-full flex-col grow",
|
|
258
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
259
|
+
"data-message-author-role": "user",
|
|
260
|
+
className: "min-h-8 relative flex w-full flex-col items-end gap-2 text-start break-words whitespace-normal",
|
|
261
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
262
|
+
className: "flex w-full flex-col gap-1 empty:hidden items-end",
|
|
263
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
264
|
+
className: "relative rounded-[18px] px-4 py-3 max-w-[70%]",
|
|
265
|
+
style: { backgroundColor: "var(--sim-bg-user-bubble, var(--color-background-tertiary))" },
|
|
266
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
267
|
+
className: "whitespace-pre-wrap",
|
|
268
|
+
children: userMessage
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
})
|
|
275
|
+
})]
|
|
276
|
+
}),
|
|
277
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
278
|
+
className: "w-full focus:outline-none flex-1",
|
|
279
|
+
dir: "auto",
|
|
280
|
+
"data-turn": "assistant",
|
|
281
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h6", {
|
|
282
|
+
className: "sr-only",
|
|
283
|
+
children: [appName, " said:"]
|
|
284
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
285
|
+
className: "text-base my-auto mx-auto pb-10 px-4",
|
|
286
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
287
|
+
className: "max-w-[40rem] min-[1440px]:max-w-[48rem] mx-auto flex-1 relative flex w-full min-w-0 flex-col",
|
|
288
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
289
|
+
className: "flex max-w-full flex-col grow",
|
|
290
|
+
children: [!isFullscreen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
291
|
+
className: "flex items-center gap-2 my-3",
|
|
292
|
+
children: [appIcon ? appIcon.startsWith("data:") || appIcon.startsWith("http") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
293
|
+
src: appIcon,
|
|
294
|
+
alt: "",
|
|
295
|
+
className: "size-6 rounded-full object-cover"
|
|
296
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
297
|
+
className: "size-6 flex items-center justify-center text-base",
|
|
298
|
+
children: appIcon
|
|
299
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
300
|
+
className: "size-6 rounded-full flex items-center justify-center font-medium text-xs text-white",
|
|
301
|
+
style: { backgroundColor: "#10a37f" },
|
|
302
|
+
children: "AI"
|
|
303
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
304
|
+
className: "font-semibold text-sm",
|
|
305
|
+
children: appName
|
|
306
|
+
})]
|
|
307
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
308
|
+
"data-message-author-role": "assistant",
|
|
309
|
+
className: "min-h-8 relative flex w-full flex-col items-start gap-2 text-start break-words whitespace-normal",
|
|
310
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
311
|
+
className: "flex w-full flex-col gap-1 empty:hidden",
|
|
312
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
313
|
+
ref: setContentRef,
|
|
314
|
+
className: isPip ? "no-scrollbar @w-xl/main:top-4 fixed start-4 end-4 top-12 z-50 mx-auto max-w-[40rem] lg:max-w-[48rem] sm:start-0 sm:end-0 sm:top-[3.25rem] sm:w-full overflow-visible" : isFullscreen ? "no-scrollbar fixed inset-x-0 top-[3.25rem] bottom-0 z-50 mx-auto" : "no-scrollbar relative mb-2 @w-sm/main:w-full mx-0 max-sm:-mx-[1rem] max-sm:w-[100cqw] max-sm:overflow-hidden overflow-visible",
|
|
315
|
+
style: {
|
|
316
|
+
...isPip ? { maxHeight: "480px" } : {},
|
|
317
|
+
...isFullscreen ? { maxWidth: containerWidth } : {}
|
|
318
|
+
},
|
|
319
|
+
children: [isPip && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
320
|
+
onClick: handleClose,
|
|
321
|
+
className: "absolute -start-2 -top-1.5 z-10 rounded-full bg-[#3a3a3a] p-1.5 text-white shadow-[0px_0px_0px_1px_#fff3,0px_4px_12px_rgba(0,0,0,0.12)] hover:bg-[#6a6a6a]",
|
|
322
|
+
"aria-label": "Close picture-in-picture",
|
|
323
|
+
type: "button",
|
|
324
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CloseIcon$1, { className: "h-4 w-4" })
|
|
325
|
+
}, "pip-close"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
326
|
+
className: isPip ? "relative overflow-hidden h-full rounded-2xl sm:rounded-3xl shadow-[0px_0px_0px_1px_#fff3,0px_6px_20px_rgba(0,0,0,0.1)] md:-mx-4" : "relative overflow-hidden h-full",
|
|
327
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
328
|
+
className: "h-full w-full max-w-full",
|
|
329
|
+
style: { ...isPip ? {
|
|
330
|
+
overflow: "auto",
|
|
331
|
+
backgroundColor: "var(--color-background-primary)"
|
|
332
|
+
} : isFullscreen ? {
|
|
333
|
+
overflow: "auto",
|
|
334
|
+
backgroundColor: "var(--color-background-primary)"
|
|
335
|
+
} : { backgroundColor: "transparent" } },
|
|
336
|
+
children
|
|
337
|
+
})
|
|
338
|
+
}, "content")]
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
})]
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
})]
|
|
345
|
+
}),
|
|
346
|
+
!isFullscreen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
347
|
+
className: "sticky bottom-0 z-10 pointer-events-none",
|
|
348
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
349
|
+
className: "absolute inset-x-0 bottom-0 h-full -z-10",
|
|
350
|
+
style: {
|
|
351
|
+
maskImage: "linear-gradient(to bottom, transparent 0%, black 50%)",
|
|
352
|
+
WebkitMaskImage: "linear-gradient(to bottom, transparent 0%, black 50%)",
|
|
353
|
+
backdropFilter: "blur(16px)",
|
|
354
|
+
WebkitBackdropFilter: "blur(16px)"
|
|
355
|
+
}
|
|
356
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
357
|
+
className: "max-w-[40rem] min-[1440px]:max-w-[48rem] mx-auto px-4 pt-4 pb-4 pointer-events-auto",
|
|
358
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
359
|
+
className: "relative",
|
|
360
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
361
|
+
type: "text",
|
|
362
|
+
name: "userInput",
|
|
363
|
+
disabled: true,
|
|
364
|
+
placeholder: "Message sunpeak.ai",
|
|
365
|
+
className: "w-full rounded-3xl px-5 py-3 pr-12 shadow-sm",
|
|
366
|
+
style: {
|
|
367
|
+
backgroundColor: "var(--sim-bg-reply-input, var(--color-background-secondary))",
|
|
368
|
+
color: "var(--color-text-primary)",
|
|
369
|
+
border: "1px solid var(--color-border-tertiary)"
|
|
370
|
+
}
|
|
371
|
+
})
|
|
372
|
+
})
|
|
373
|
+
})]
|
|
374
|
+
})
|
|
375
|
+
]
|
|
376
|
+
})
|
|
377
|
+
})
|
|
378
|
+
]
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
//#endregion
|
|
382
|
+
//#region src/chatgpt/chatgpt-host.ts
|
|
383
|
+
/**
|
|
384
|
+
* ChatGPT host version info — matches what ChatGPT reports via the MCP protocol.
|
|
385
|
+
* Verified against production ChatGPT on 2026-03-19.
|
|
386
|
+
*/
|
|
387
|
+
var CHATGPT_HOST_INFO = {
|
|
388
|
+
name: "chatgpt",
|
|
389
|
+
version: "0.0.1"
|
|
390
|
+
};
|
|
391
|
+
var CHATGPT_HOST_CAPABILITIES = {
|
|
392
|
+
openLinks: {},
|
|
393
|
+
serverTools: {},
|
|
394
|
+
serverResources: {},
|
|
395
|
+
logging: {},
|
|
396
|
+
updateModelContext: {},
|
|
397
|
+
message: {},
|
|
398
|
+
sandbox: { permissions: { microphone: {} } }
|
|
399
|
+
};
|
|
400
|
+
/**
|
|
401
|
+
* Apply ChatGPT-style theming to the document.
|
|
402
|
+
* Sets data-theme attribute and color-scheme for light-dark() CSS support.
|
|
403
|
+
*/
|
|
404
|
+
function applyChatGPTTheme(theme) {
|
|
405
|
+
document.documentElement.setAttribute("data-theme", theme);
|
|
406
|
+
document.documentElement.style.colorScheme = theme;
|
|
407
|
+
}
|
|
408
|
+
registerHostShell({
|
|
409
|
+
id: "chatgpt",
|
|
410
|
+
label: "ChatGPT",
|
|
411
|
+
Conversation,
|
|
412
|
+
applyTheme: applyChatGPTTheme,
|
|
413
|
+
hostInfo: CHATGPT_HOST_INFO,
|
|
414
|
+
hostCapabilities: CHATGPT_HOST_CAPABILITIES,
|
|
415
|
+
userAgent: "chatgpt",
|
|
416
|
+
styleVariables: { ...DEFAULT_STYLE_VARIABLES },
|
|
417
|
+
pageStyles: {
|
|
418
|
+
"--sim-bg-sidebar": "light-dark(#ffffff, #212121)",
|
|
419
|
+
"--sim-bg-conversation": "light-dark(#ffffff, #212121)",
|
|
420
|
+
"--sim-bg-user-bubble": "light-dark(rgba(233,233,233,0.5), rgba(50,50,50,0.85))",
|
|
421
|
+
"--sim-bg-reply-input": "light-dark(#ffffff, #212121)"
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
//#endregion
|
|
425
|
+
//#region src/claude/claude-conversation.tsx
|
|
426
|
+
function CloseIcon() {
|
|
427
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
428
|
+
width: "16",
|
|
429
|
+
height: "16",
|
|
430
|
+
viewBox: "0 0 16 16",
|
|
431
|
+
fill: "none",
|
|
432
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
433
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
434
|
+
d: "M12 4L4 12M4 4L12 12",
|
|
435
|
+
stroke: "currentColor",
|
|
436
|
+
strokeWidth: "1.5",
|
|
437
|
+
strokeLinecap: "round"
|
|
438
|
+
})
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
function BackIcon() {
|
|
442
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
443
|
+
width: "16",
|
|
444
|
+
height: "16",
|
|
445
|
+
viewBox: "0 0 16 16",
|
|
446
|
+
fill: "none",
|
|
447
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
448
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
449
|
+
d: "M10 3L5 8L10 13",
|
|
450
|
+
stroke: "currentColor",
|
|
451
|
+
strokeWidth: "1.5",
|
|
452
|
+
strokeLinecap: "round",
|
|
453
|
+
strokeLinejoin: "round"
|
|
454
|
+
})
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Claude conversation shell — mimics Claude's chat UI chrome.
|
|
459
|
+
*
|
|
460
|
+
* All three display modes (inline, pip, fullscreen) share the same React tree
|
|
461
|
+
* shape so that the iframe never unmounts when switching modes.
|
|
462
|
+
*/
|
|
463
|
+
function ClaudeConversation({ children, screenWidth, displayMode, platform, onRequestDisplayMode, appName = "Sunpeak", appIcon, userMessage = "What have you got for me today?", headerAction, onContentWidthChange }) {
|
|
464
|
+
const isDesktop = platform === "desktop";
|
|
465
|
+
const containerWidth = screenWidth === "full" ? "100%" : `${SCREEN_WIDTHS[screenWidth]}px`;
|
|
466
|
+
const isFullscreen = displayMode === "fullscreen";
|
|
467
|
+
const isPip = displayMode === "pip";
|
|
468
|
+
const contentRef = (0, react.useRef)(null);
|
|
469
|
+
const onContentWidthChangeRef = (0, react.useRef)(onContentWidthChange);
|
|
470
|
+
(0, react.useEffect)(() => {
|
|
471
|
+
onContentWidthChangeRef.current = onContentWidthChange;
|
|
472
|
+
});
|
|
473
|
+
const setContentRef = (0, react.useCallback)((node) => {
|
|
474
|
+
contentRef.current = node;
|
|
475
|
+
}, []);
|
|
476
|
+
(0, react.useEffect)(() => {
|
|
477
|
+
const el = contentRef.current;
|
|
478
|
+
if (!el) return;
|
|
479
|
+
const observer = new ResizeObserver((entries) => {
|
|
480
|
+
for (const entry of entries) {
|
|
481
|
+
const width = Math.round(entry.contentBoxSize[0]?.inlineSize ?? entry.contentRect.width);
|
|
482
|
+
if (width > 0) onContentWidthChangeRef.current?.(width);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
observer.observe(el);
|
|
486
|
+
return () => observer.disconnect();
|
|
487
|
+
}, []);
|
|
488
|
+
const handleClose = () => onRequestDisplayMode?.("inline");
|
|
489
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
490
|
+
className: "flex flex-col w-full h-full flex-1 items-center relative",
|
|
491
|
+
style: {
|
|
492
|
+
transform: "translate(0)",
|
|
493
|
+
backgroundColor: "var(--sim-bg-conversation, var(--color-background-primary))",
|
|
494
|
+
color: "var(--color-text-primary)"
|
|
495
|
+
},
|
|
496
|
+
children: [
|
|
497
|
+
isFullscreen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
498
|
+
className: "fixed start-0 end-0 top-0 bottom-0 z-[51] mx-auto flex flex-col pointer-events-none",
|
|
499
|
+
style: { maxWidth: containerWidth },
|
|
500
|
+
children: [
|
|
501
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
502
|
+
className: "z-10 flex items-center h-12 border-b px-3 pointer-events-auto",
|
|
503
|
+
style: {
|
|
504
|
+
borderColor: "var(--color-border-primary)",
|
|
505
|
+
backgroundColor: "var(--sim-bg-conversation, var(--color-background-primary))"
|
|
506
|
+
},
|
|
507
|
+
children: [
|
|
508
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
509
|
+
onClick: handleClose,
|
|
510
|
+
"aria-label": "Back",
|
|
511
|
+
className: "h-8 w-8 flex items-center justify-center rounded-lg transition-colors hover:opacity-70",
|
|
512
|
+
type: "button",
|
|
513
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BackIcon, {})
|
|
514
|
+
}),
|
|
515
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
516
|
+
className: "flex-1 text-center text-sm font-medium",
|
|
517
|
+
children: appName
|
|
518
|
+
}),
|
|
519
|
+
isDesktop && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "w-8" })
|
|
520
|
+
]
|
|
521
|
+
}),
|
|
522
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "flex-1" }),
|
|
523
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("footer", {
|
|
524
|
+
className: "pointer-events-auto p-3",
|
|
525
|
+
style: { backgroundColor: "var(--sim-bg-conversation, var(--color-background-primary))" },
|
|
526
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
527
|
+
className: "max-w-[48rem] mx-auto",
|
|
528
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
529
|
+
type: "text",
|
|
530
|
+
name: "userInput",
|
|
531
|
+
disabled: true,
|
|
532
|
+
placeholder: "Reply to sunpeak...",
|
|
533
|
+
className: "w-full rounded-xl px-4 py-2.5 text-sm",
|
|
534
|
+
style: {
|
|
535
|
+
backgroundColor: "var(--sim-bg-reply-input, var(--color-background-secondary))",
|
|
536
|
+
border: "1px solid var(--color-border-primary)",
|
|
537
|
+
color: "var(--color-text-primary)"
|
|
538
|
+
}
|
|
539
|
+
})
|
|
540
|
+
})
|
|
541
|
+
})
|
|
542
|
+
]
|
|
543
|
+
}),
|
|
544
|
+
!isFullscreen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
545
|
+
className: "h-12 flex items-center gap-3 px-4 text-sm font-medium sticky top-0 z-40 w-full",
|
|
546
|
+
style: {
|
|
547
|
+
maxWidth: containerWidth,
|
|
548
|
+
backgroundColor: "var(--sim-bg-conversation, var(--color-background-primary))"
|
|
549
|
+
},
|
|
550
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "sunpeak.ai" }), headerAction]
|
|
551
|
+
}),
|
|
552
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
553
|
+
className: "flex flex-col flex-1 w-full transition-all duration-200 overflow-hidden",
|
|
554
|
+
style: { maxWidth: containerWidth },
|
|
555
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
|
|
556
|
+
className: "flex-1 overflow-y-auto overflow-x-hidden",
|
|
557
|
+
children: [!isFullscreen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("article", {
|
|
558
|
+
className: "w-full",
|
|
559
|
+
dir: "auto",
|
|
560
|
+
"data-turn": "user",
|
|
561
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
562
|
+
className: "px-4 py-4",
|
|
563
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
564
|
+
className: "max-w-[48rem] mx-auto flex justify-end",
|
|
565
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
566
|
+
className: "inline-block rounded-2xl px-4 py-2.5 text-sm max-w-[85%]",
|
|
567
|
+
style: { backgroundColor: "var(--sim-bg-user-bubble, var(--color-background-tertiary))" },
|
|
568
|
+
children: userMessage
|
|
569
|
+
})
|
|
570
|
+
})
|
|
571
|
+
})
|
|
572
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
573
|
+
className: "w-full",
|
|
574
|
+
dir: "auto",
|
|
575
|
+
"data-turn": "assistant",
|
|
576
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h6", {
|
|
577
|
+
className: "sr-only",
|
|
578
|
+
children: [appName, " said:"]
|
|
579
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
580
|
+
className: "px-4 py-2",
|
|
581
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
582
|
+
className: "max-w-[48rem] mx-auto",
|
|
583
|
+
children: [!isFullscreen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
584
|
+
className: "flex items-center gap-2 mb-3",
|
|
585
|
+
children: [appIcon ? appIcon.startsWith("data:") || appIcon.startsWith("http") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
586
|
+
src: appIcon,
|
|
587
|
+
alt: "",
|
|
588
|
+
className: "size-6 rounded-full object-cover"
|
|
589
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
590
|
+
className: "size-6 flex items-center justify-center text-base",
|
|
591
|
+
children: appIcon
|
|
592
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
593
|
+
className: "size-6 rounded-full flex items-center justify-center text-xs font-medium text-white",
|
|
594
|
+
style: { backgroundColor: "#c55a30" },
|
|
595
|
+
children: "C"
|
|
596
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
597
|
+
className: "text-sm font-medium",
|
|
598
|
+
children: appName
|
|
599
|
+
})]
|
|
600
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
601
|
+
ref: setContentRef,
|
|
602
|
+
className: isPip ? "fixed start-4 end-4 top-12 z-50 mx-auto max-w-[40rem] lg:max-w-[48rem] sm:start-0 sm:end-0 sm:top-[3rem] sm:w-full overflow-visible" : isFullscreen ? "fixed inset-x-0 top-[3rem] bottom-0 z-50 mx-auto" : "relative mb-2 w-full overflow-visible",
|
|
603
|
+
style: {
|
|
604
|
+
...isPip ? { maxHeight: "480px" } : {},
|
|
605
|
+
...isFullscreen ? { maxWidth: containerWidth } : {}
|
|
606
|
+
},
|
|
607
|
+
children: [isPip && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
608
|
+
onClick: handleClose,
|
|
609
|
+
className: "absolute -start-2 -top-1.5 z-10 rounded-full p-1.5 text-white shadow-md",
|
|
610
|
+
style: { backgroundColor: "#4a4a4a" },
|
|
611
|
+
"aria-label": "Close picture-in-picture",
|
|
612
|
+
type: "button",
|
|
613
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CloseIcon, {})
|
|
614
|
+
}, "pip-close"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
615
|
+
className: isPip ? "relative overflow-hidden h-full rounded-2xl shadow-lg" : "relative overflow-hidden h-full",
|
|
616
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
617
|
+
className: "h-full w-full max-w-full",
|
|
618
|
+
style: { ...isPip ? {
|
|
619
|
+
overflow: "auto",
|
|
620
|
+
backgroundColor: "var(--color-background-secondary)"
|
|
621
|
+
} : isFullscreen ? {
|
|
622
|
+
overflow: "auto",
|
|
623
|
+
backgroundColor: "var(--color-background-primary)"
|
|
624
|
+
} : { backgroundColor: "transparent" } },
|
|
625
|
+
children
|
|
626
|
+
})
|
|
627
|
+
}, "content")]
|
|
628
|
+
})]
|
|
629
|
+
})
|
|
630
|
+
})]
|
|
631
|
+
})]
|
|
632
|
+
}), !isFullscreen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("footer", {
|
|
633
|
+
style: { backgroundColor: "var(--sim-bg-conversation, var(--color-background-primary))" },
|
|
634
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
635
|
+
className: "max-w-[48rem] mx-auto px-4 py-4",
|
|
636
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
637
|
+
className: "relative",
|
|
638
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
639
|
+
type: "text",
|
|
640
|
+
name: "userInput",
|
|
641
|
+
disabled: true,
|
|
642
|
+
placeholder: "Reply to sunpeak...",
|
|
643
|
+
className: "w-full rounded-xl px-4 py-2.5 text-sm",
|
|
644
|
+
style: {
|
|
645
|
+
backgroundColor: "var(--sim-bg-reply-input, var(--color-background-secondary))",
|
|
646
|
+
border: "1px solid var(--color-border-primary)"
|
|
647
|
+
}
|
|
648
|
+
})
|
|
649
|
+
})
|
|
650
|
+
})
|
|
651
|
+
})]
|
|
652
|
+
})
|
|
653
|
+
]
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
//#endregion
|
|
657
|
+
//#region src/claude/claude-host.ts
|
|
658
|
+
var CLAUDE_HOST_INFO = {
|
|
659
|
+
name: "Claude",
|
|
660
|
+
version: "1.0.0"
|
|
661
|
+
};
|
|
662
|
+
var CLAUDE_HOST_CAPABILITIES = {
|
|
663
|
+
openLinks: {},
|
|
664
|
+
serverTools: {},
|
|
665
|
+
serverResources: {},
|
|
666
|
+
downloadFile: {},
|
|
667
|
+
logging: {},
|
|
668
|
+
updateModelContext: { text: {} },
|
|
669
|
+
message: { text: {} },
|
|
670
|
+
sandbox: {}
|
|
671
|
+
};
|
|
672
|
+
/**
|
|
673
|
+
* Apply Claude-style theming to the document.
|
|
674
|
+
* Sets data-theme attribute and color-scheme so CSS light-dark() resolves correctly.
|
|
675
|
+
*/
|
|
676
|
+
function applyClaudeTheme(theme) {
|
|
677
|
+
document.documentElement.setAttribute("data-theme", theme);
|
|
678
|
+
document.documentElement.style.colorScheme = theme;
|
|
679
|
+
}
|
|
680
|
+
registerHostShell({
|
|
681
|
+
id: "claude",
|
|
682
|
+
label: "Claude",
|
|
683
|
+
Conversation: ClaudeConversation,
|
|
684
|
+
applyTheme: applyClaudeTheme,
|
|
685
|
+
hostInfo: CLAUDE_HOST_INFO,
|
|
686
|
+
hostCapabilities: CLAUDE_HOST_CAPABILITIES,
|
|
687
|
+
userAgent: "claude",
|
|
688
|
+
styleVariables: {
|
|
689
|
+
...DEFAULT_STYLE_VARIABLES,
|
|
690
|
+
"--color-background-primary": "light-dark(#faf9f5, #262624)",
|
|
691
|
+
"--color-background-secondary": "light-dark(#ffffff, #3a3935)",
|
|
692
|
+
"--color-background-tertiary": "light-dark(#e8e4dc, #4a4843)",
|
|
693
|
+
"--color-background-inverse": "light-dark(#2b2a27, #f3f0e8)",
|
|
694
|
+
"--color-text-primary": "light-dark(#2d2b27, #e8e4dc)",
|
|
695
|
+
"--color-text-secondary": "light-dark(#6b6560, #9b9690)",
|
|
696
|
+
"--color-text-tertiary": "light-dark(#9b9690, #6b6560)",
|
|
697
|
+
"--color-text-inverse": "light-dark(#e8e4dc, #2d2b27)",
|
|
698
|
+
"--color-border-primary": "light-dark(#e0ddd5, #4a4843)",
|
|
699
|
+
"--color-border-secondary": "light-dark(#d5d1c8, #5a5753)",
|
|
700
|
+
"--color-border-tertiary": "light-dark(#f0ede5, #3a3935)"
|
|
701
|
+
},
|
|
702
|
+
pageStyles: {
|
|
703
|
+
"--sim-bg-sidebar": "light-dark(#f9f8f3, #252523)",
|
|
704
|
+
"--sim-bg-conversation": "light-dark(#faf9f5, #262624)",
|
|
705
|
+
"--sim-bg-user-bubble": "light-dark(#f1eee6, #141413)",
|
|
706
|
+
"--sim-bg-reply-input": "light-dark(#ffffff, #30302e)"
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
//#endregion
|
|
710
|
+
//#region ../../node_modules/.pnpm/@modelcontextprotocol+ext-apps@1.2.2_@modelcontextprotocol+sdk@1.27.1_zod@4.3.6__react-_e9bf7657371391a829fe8b4e289b253c/node_modules/@modelcontextprotocol/ext-apps/dist/src/app-bridge.js
|
|
711
|
+
var G = "2026-01-26", m = require_protocol.union([require_protocol.literal("light"), require_protocol.literal("dark")]).describe("Color theme preference for the host environment."), D = require_protocol.union([
|
|
712
|
+
require_protocol.literal("inline"),
|
|
713
|
+
require_protocol.literal("fullscreen"),
|
|
714
|
+
require_protocol.literal("pip")
|
|
715
|
+
]).describe("Display mode for UI presentation."), OQ = require_protocol.union([
|
|
716
|
+
require_protocol.literal("--color-background-primary"),
|
|
717
|
+
require_protocol.literal("--color-background-secondary"),
|
|
718
|
+
require_protocol.literal("--color-background-tertiary"),
|
|
719
|
+
require_protocol.literal("--color-background-inverse"),
|
|
720
|
+
require_protocol.literal("--color-background-ghost"),
|
|
721
|
+
require_protocol.literal("--color-background-info"),
|
|
722
|
+
require_protocol.literal("--color-background-danger"),
|
|
723
|
+
require_protocol.literal("--color-background-success"),
|
|
724
|
+
require_protocol.literal("--color-background-warning"),
|
|
725
|
+
require_protocol.literal("--color-background-disabled"),
|
|
726
|
+
require_protocol.literal("--color-text-primary"),
|
|
727
|
+
require_protocol.literal("--color-text-secondary"),
|
|
728
|
+
require_protocol.literal("--color-text-tertiary"),
|
|
729
|
+
require_protocol.literal("--color-text-inverse"),
|
|
730
|
+
require_protocol.literal("--color-text-ghost"),
|
|
731
|
+
require_protocol.literal("--color-text-info"),
|
|
732
|
+
require_protocol.literal("--color-text-danger"),
|
|
733
|
+
require_protocol.literal("--color-text-success"),
|
|
734
|
+
require_protocol.literal("--color-text-warning"),
|
|
735
|
+
require_protocol.literal("--color-text-disabled"),
|
|
736
|
+
require_protocol.literal("--color-border-primary"),
|
|
737
|
+
require_protocol.literal("--color-border-secondary"),
|
|
738
|
+
require_protocol.literal("--color-border-tertiary"),
|
|
739
|
+
require_protocol.literal("--color-border-inverse"),
|
|
740
|
+
require_protocol.literal("--color-border-ghost"),
|
|
741
|
+
require_protocol.literal("--color-border-info"),
|
|
742
|
+
require_protocol.literal("--color-border-danger"),
|
|
743
|
+
require_protocol.literal("--color-border-success"),
|
|
744
|
+
require_protocol.literal("--color-border-warning"),
|
|
745
|
+
require_protocol.literal("--color-border-disabled"),
|
|
746
|
+
require_protocol.literal("--color-ring-primary"),
|
|
747
|
+
require_protocol.literal("--color-ring-secondary"),
|
|
748
|
+
require_protocol.literal("--color-ring-inverse"),
|
|
749
|
+
require_protocol.literal("--color-ring-info"),
|
|
750
|
+
require_protocol.literal("--color-ring-danger"),
|
|
751
|
+
require_protocol.literal("--color-ring-success"),
|
|
752
|
+
require_protocol.literal("--color-ring-warning"),
|
|
753
|
+
require_protocol.literal("--font-sans"),
|
|
754
|
+
require_protocol.literal("--font-mono"),
|
|
755
|
+
require_protocol.literal("--font-weight-normal"),
|
|
756
|
+
require_protocol.literal("--font-weight-medium"),
|
|
757
|
+
require_protocol.literal("--font-weight-semibold"),
|
|
758
|
+
require_protocol.literal("--font-weight-bold"),
|
|
759
|
+
require_protocol.literal("--font-text-xs-size"),
|
|
760
|
+
require_protocol.literal("--font-text-sm-size"),
|
|
761
|
+
require_protocol.literal("--font-text-md-size"),
|
|
762
|
+
require_protocol.literal("--font-text-lg-size"),
|
|
763
|
+
require_protocol.literal("--font-heading-xs-size"),
|
|
764
|
+
require_protocol.literal("--font-heading-sm-size"),
|
|
765
|
+
require_protocol.literal("--font-heading-md-size"),
|
|
766
|
+
require_protocol.literal("--font-heading-lg-size"),
|
|
767
|
+
require_protocol.literal("--font-heading-xl-size"),
|
|
768
|
+
require_protocol.literal("--font-heading-2xl-size"),
|
|
769
|
+
require_protocol.literal("--font-heading-3xl-size"),
|
|
770
|
+
require_protocol.literal("--font-text-xs-line-height"),
|
|
771
|
+
require_protocol.literal("--font-text-sm-line-height"),
|
|
772
|
+
require_protocol.literal("--font-text-md-line-height"),
|
|
773
|
+
require_protocol.literal("--font-text-lg-line-height"),
|
|
774
|
+
require_protocol.literal("--font-heading-xs-line-height"),
|
|
775
|
+
require_protocol.literal("--font-heading-sm-line-height"),
|
|
776
|
+
require_protocol.literal("--font-heading-md-line-height"),
|
|
777
|
+
require_protocol.literal("--font-heading-lg-line-height"),
|
|
778
|
+
require_protocol.literal("--font-heading-xl-line-height"),
|
|
779
|
+
require_protocol.literal("--font-heading-2xl-line-height"),
|
|
780
|
+
require_protocol.literal("--font-heading-3xl-line-height"),
|
|
781
|
+
require_protocol.literal("--border-radius-xs"),
|
|
782
|
+
require_protocol.literal("--border-radius-sm"),
|
|
783
|
+
require_protocol.literal("--border-radius-md"),
|
|
784
|
+
require_protocol.literal("--border-radius-lg"),
|
|
785
|
+
require_protocol.literal("--border-radius-xl"),
|
|
786
|
+
require_protocol.literal("--border-radius-full"),
|
|
787
|
+
require_protocol.literal("--border-width-regular"),
|
|
788
|
+
require_protocol.literal("--shadow-hairline"),
|
|
789
|
+
require_protocol.literal("--shadow-sm"),
|
|
790
|
+
require_protocol.literal("--shadow-md"),
|
|
791
|
+
require_protocol.literal("--shadow-lg")
|
|
792
|
+
]).describe("CSS variable keys available to MCP apps for theming."), VQ = require_protocol.record(OQ.describe(`Style variables for theming MCP apps.
|
|
793
|
+
|
|
794
|
+
Individual style keys are optional - hosts may provide any subset of these values.
|
|
795
|
+
Values are strings containing CSS values (colors, sizes, font stacks, etc.).
|
|
796
|
+
|
|
797
|
+
Note: This type uses \`Record<K, string | undefined>\` rather than \`Partial<Record<K, string>>\`
|
|
798
|
+
for compatibility with Zod schema generation. Both are functionally equivalent for validation.`), require_protocol.union([require_protocol.string(), require_protocol._undefined()]).describe(`Style variables for theming MCP apps.
|
|
799
|
+
|
|
800
|
+
Individual style keys are optional - hosts may provide any subset of these values.
|
|
801
|
+
Values are strings containing CSS values (colors, sizes, font stacks, etc.).
|
|
802
|
+
|
|
803
|
+
Note: This type uses \`Record<K, string | undefined>\` rather than \`Partial<Record<K, string>>\`
|
|
804
|
+
for compatibility with Zod schema generation. Both are functionally equivalent for validation.`)).describe(`Style variables for theming MCP apps.
|
|
805
|
+
|
|
806
|
+
Individual style keys are optional - hosts may provide any subset of these values.
|
|
807
|
+
Values are strings containing CSS values (colors, sizes, font stacks, etc.).
|
|
808
|
+
|
|
809
|
+
Note: This type uses \`Record<K, string | undefined>\` rather than \`Partial<Record<K, string>>\`
|
|
810
|
+
for compatibility with Zod schema generation. Both are functionally equivalent for validation.`), V = require_protocol.object({
|
|
811
|
+
method: require_protocol.literal("ui/open-link"),
|
|
812
|
+
params: require_protocol.object({ url: require_protocol.string().describe("URL to open in the host's browser") })
|
|
813
|
+
});
|
|
814
|
+
require_protocol.object({ isError: require_protocol.boolean().optional().describe("True if the host failed to open the URL (e.g., due to security policy).") }).passthrough();
|
|
815
|
+
require_protocol.object({ isError: require_protocol.boolean().optional().describe("True if the download failed (e.g., user cancelled or host denied).") }).passthrough();
|
|
816
|
+
require_protocol.object({ isError: require_protocol.boolean().optional().describe("True if the host rejected or failed to deliver the message.") }).passthrough();
|
|
817
|
+
var F = require_protocol.object({
|
|
818
|
+
method: require_protocol.literal("ui/notifications/sandbox-proxy-ready"),
|
|
819
|
+
params: require_protocol.object({})
|
|
820
|
+
}), j = require_protocol.object({
|
|
821
|
+
connectDomains: require_protocol.array(require_protocol.string()).optional().describe(`Origins for network requests (fetch/XHR/WebSocket).
|
|
822
|
+
|
|
823
|
+
- Maps to CSP \`connect-src\` directive
|
|
824
|
+
- Empty or omitted → no network connections (secure default)`),
|
|
825
|
+
resourceDomains: require_protocol.array(require_protocol.string()).optional().describe("Origins for static resources (images, scripts, stylesheets, fonts, media).\n\n- Maps to CSP `img-src`, `script-src`, `style-src`, `font-src`, `media-src` directives\n- Wildcard subdomains supported: `https://*.example.com`\n- Empty or omitted → no network resources (secure default)"),
|
|
826
|
+
frameDomains: require_protocol.array(require_protocol.string()).optional().describe("Origins for nested iframes.\n\n- Maps to CSP `frame-src` directive\n- Empty or omitted → no nested iframes allowed (`frame-src 'none'`)"),
|
|
827
|
+
baseUriDomains: require_protocol.array(require_protocol.string()).optional().describe("Allowed base URIs for the document.\n\n- Maps to CSP `base-uri` directive\n- Empty or omitted → only same origin allowed (`base-uri 'self'`)")
|
|
828
|
+
}), E = require_protocol.object({
|
|
829
|
+
camera: require_protocol.object({}).optional().describe("Request camera access.\n\nMaps to Permission Policy `camera` feature."),
|
|
830
|
+
microphone: require_protocol.object({}).optional().describe("Request microphone access.\n\nMaps to Permission Policy `microphone` feature."),
|
|
831
|
+
geolocation: require_protocol.object({}).optional().describe("Request geolocation access.\n\nMaps to Permission Policy `geolocation` feature."),
|
|
832
|
+
clipboardWrite: require_protocol.object({}).optional().describe("Request clipboard write access.\n\nMaps to Permission Policy `clipboard-write` feature.")
|
|
833
|
+
}), P = require_protocol.object({
|
|
834
|
+
method: require_protocol.literal("ui/notifications/size-changed"),
|
|
835
|
+
params: require_protocol.object({
|
|
836
|
+
width: require_protocol.number().optional().describe("New width in pixels."),
|
|
837
|
+
height: require_protocol.number().optional().describe("New height in pixels.")
|
|
838
|
+
})
|
|
839
|
+
});
|
|
840
|
+
require_protocol.object({
|
|
841
|
+
method: require_protocol.literal("ui/notifications/tool-input"),
|
|
842
|
+
params: require_protocol.object({ arguments: require_protocol.record(require_protocol.string(), require_protocol.unknown().describe("Complete tool call arguments as key-value pairs.")).optional().describe("Complete tool call arguments as key-value pairs.") })
|
|
843
|
+
});
|
|
844
|
+
require_protocol.object({
|
|
845
|
+
method: require_protocol.literal("ui/notifications/tool-input-partial"),
|
|
846
|
+
params: require_protocol.object({ arguments: require_protocol.record(require_protocol.string(), require_protocol.unknown().describe("Partial tool call arguments (incomplete, may change).")).optional().describe("Partial tool call arguments (incomplete, may change).") })
|
|
847
|
+
});
|
|
848
|
+
require_protocol.object({
|
|
849
|
+
method: require_protocol.literal("ui/notifications/tool-cancelled"),
|
|
850
|
+
params: require_protocol.object({ reason: require_protocol.string().optional().describe("Optional reason for the cancellation (e.g., \"user action\", \"timeout\").") })
|
|
851
|
+
});
|
|
852
|
+
var p = require_protocol.object({ fonts: require_protocol.string().optional() }), c = require_protocol.object({
|
|
853
|
+
variables: VQ.optional().describe("CSS variables for theming the app."),
|
|
854
|
+
css: p.optional().describe("CSS blocks that apps can inject.")
|
|
855
|
+
});
|
|
856
|
+
require_protocol.object({
|
|
857
|
+
method: require_protocol.literal("ui/resource-teardown"),
|
|
858
|
+
params: require_protocol.object({})
|
|
859
|
+
});
|
|
860
|
+
var R = require_protocol.record(require_protocol.string(), require_protocol.unknown()), O = require_protocol.object({
|
|
861
|
+
text: require_protocol.object({}).optional().describe("Host supports text content blocks."),
|
|
862
|
+
image: require_protocol.object({}).optional().describe("Host supports image content blocks."),
|
|
863
|
+
audio: require_protocol.object({}).optional().describe("Host supports audio content blocks."),
|
|
864
|
+
resource: require_protocol.object({}).optional().describe("Host supports resource content blocks."),
|
|
865
|
+
resourceLink: require_protocol.object({}).optional().describe("Host supports resource link content blocks."),
|
|
866
|
+
structuredContent: require_protocol.object({}).optional().describe("Host supports structured content.")
|
|
867
|
+
}), n = require_protocol.object({
|
|
868
|
+
experimental: require_protocol.object({}).optional().describe("Experimental features (structure TBD)."),
|
|
869
|
+
openLinks: require_protocol.object({}).optional().describe("Host supports opening external URLs."),
|
|
870
|
+
downloadFile: require_protocol.object({}).optional().describe("Host supports file downloads via ui/download-file."),
|
|
871
|
+
serverTools: require_protocol.object({ listChanged: require_protocol.boolean().optional().describe("Host supports tools/list_changed notifications.") }).optional().describe("Host can proxy tool calls to the MCP server."),
|
|
872
|
+
serverResources: require_protocol.object({ listChanged: require_protocol.boolean().optional().describe("Host supports resources/list_changed notifications.") }).optional().describe("Host can proxy resource reads to the MCP server."),
|
|
873
|
+
logging: require_protocol.object({}).optional().describe("Host accepts log messages."),
|
|
874
|
+
sandbox: require_protocol.object({
|
|
875
|
+
permissions: E.optional().describe("Permissions granted by the host (camera, microphone, geolocation)."),
|
|
876
|
+
csp: j.optional().describe("CSP domains approved by the host.")
|
|
877
|
+
}).optional().describe("Sandbox configuration applied by the host."),
|
|
878
|
+
updateModelContext: O.optional().describe("Host accepts context updates (ui/update-model-context) to be included in the model's context for future turns."),
|
|
879
|
+
message: O.optional().describe("Host supports receiving content messages (ui/message) from the view.")
|
|
880
|
+
}), i = require_protocol.object({
|
|
881
|
+
experimental: require_protocol.object({}).optional().describe("Experimental features (structure TBD)."),
|
|
882
|
+
tools: require_protocol.object({ listChanged: require_protocol.boolean().optional().describe("App supports tools/list_changed notifications.") }).optional().describe("App exposes MCP-style tools that the host can call."),
|
|
883
|
+
availableDisplayModes: require_protocol.array(D).optional().describe("Display modes the app supports.")
|
|
884
|
+
}), M = require_protocol.object({
|
|
885
|
+
method: require_protocol.literal("ui/notifications/initialized"),
|
|
886
|
+
params: require_protocol.object({}).optional()
|
|
887
|
+
});
|
|
888
|
+
require_protocol.object({
|
|
889
|
+
csp: j.optional().describe("Content Security Policy configuration for UI resources."),
|
|
890
|
+
permissions: E.optional().describe("Sandbox permissions requested by the UI resource."),
|
|
891
|
+
domain: require_protocol.string().optional().describe(`Dedicated origin for view sandbox.
|
|
892
|
+
|
|
893
|
+
Useful when views need stable, dedicated origins for OAuth callbacks, CORS policies, or API key allowlists.
|
|
894
|
+
|
|
895
|
+
**Host-dependent:** The format and validation rules for this field are determined by each host. Servers MUST consult host-specific documentation for the expected domain format. Common patterns include:
|
|
896
|
+
- Hash-based subdomains (e.g., \`{hash}.claudemcpcontent.com\`)
|
|
897
|
+
- URL-derived subdomains (e.g., \`www-example-com.oaiusercontent.com\`)
|
|
898
|
+
|
|
899
|
+
If omitted, host uses default sandbox origin (typically per-conversation).`),
|
|
900
|
+
prefersBorder: require_protocol.boolean().optional().describe(`Visual boundary preference - true if view prefers a visible border.
|
|
901
|
+
|
|
902
|
+
Boolean requesting whether a visible border and background is provided by the host. Specifying an explicit value for this is recommended because hosts' defaults may vary.
|
|
903
|
+
|
|
904
|
+
- \`true\`: request visible border + background
|
|
905
|
+
- \`false\`: request no visible border + background
|
|
906
|
+
- omitted: host decides border`)
|
|
907
|
+
});
|
|
908
|
+
var W = require_protocol.object({
|
|
909
|
+
method: require_protocol.literal("ui/request-display-mode"),
|
|
910
|
+
params: require_protocol.object({ mode: D.describe("The display mode being requested.") })
|
|
911
|
+
});
|
|
912
|
+
require_protocol.object({ mode: D.describe("The display mode that was actually set. May differ from requested if not supported.") }).passthrough();
|
|
913
|
+
var l = require_protocol.union([require_protocol.literal("model"), require_protocol.literal("app")]).describe("Tool visibility scope - who can access the tool.");
|
|
914
|
+
require_protocol.object({
|
|
915
|
+
resourceUri: require_protocol.string().optional(),
|
|
916
|
+
visibility: require_protocol.array(l).optional().describe(`Who can access this tool. Default: ["model", "app"]
|
|
917
|
+
- "model": Tool visible to and callable by the agent
|
|
918
|
+
- "app": Tool callable by the app from this server only`)
|
|
919
|
+
});
|
|
920
|
+
require_protocol.object({ mimeTypes: require_protocol.array(require_protocol.string()).optional().describe("Array of supported MIME types for UI resources.\nMust include `\"text/html;profile=mcp-app\"` for MCP Apps support.") });
|
|
921
|
+
var S = require_protocol.object({
|
|
922
|
+
method: require_protocol.literal("ui/download-file"),
|
|
923
|
+
params: require_protocol.object({ contents: require_protocol.array(require_protocol.union([require_protocol.EmbeddedResourceSchema, require_protocol.ResourceLinkSchema])).describe("Resource contents to download — embedded (inline data) or linked (host fetches). Uses standard MCP resource types.") })
|
|
924
|
+
}), C = require_protocol.object({
|
|
925
|
+
method: require_protocol.literal("ui/message"),
|
|
926
|
+
params: require_protocol.object({
|
|
927
|
+
role: require_protocol.literal("user").describe("Message role, currently only \"user\" is supported."),
|
|
928
|
+
content: require_protocol.array(require_protocol.ContentBlockSchema).describe("Message content blocks (text, image, etc.).")
|
|
929
|
+
})
|
|
930
|
+
});
|
|
931
|
+
require_protocol.object({
|
|
932
|
+
method: require_protocol.literal("ui/notifications/sandbox-resource-ready"),
|
|
933
|
+
params: require_protocol.object({
|
|
934
|
+
html: require_protocol.string().describe("HTML content to load into the inner iframe."),
|
|
935
|
+
sandbox: require_protocol.string().optional().describe("Optional override for the inner iframe's sandbox attribute."),
|
|
936
|
+
csp: j.optional().describe("CSP configuration from resource metadata."),
|
|
937
|
+
permissions: E.optional().describe("Sandbox permissions from resource metadata.")
|
|
938
|
+
})
|
|
939
|
+
});
|
|
940
|
+
require_protocol.object({
|
|
941
|
+
method: require_protocol.literal("ui/notifications/tool-result"),
|
|
942
|
+
params: require_protocol.CallToolResultSchema.describe("Standard MCP tool execution result.")
|
|
943
|
+
});
|
|
944
|
+
var q = require_protocol.object({
|
|
945
|
+
toolInfo: require_protocol.object({
|
|
946
|
+
id: require_protocol.RequestIdSchema.optional().describe("JSON-RPC id of the tools/call request."),
|
|
947
|
+
tool: require_protocol.ToolSchema.describe("Tool definition including name, inputSchema, etc.")
|
|
948
|
+
}).optional().describe("Metadata of the tool call that instantiated this App."),
|
|
949
|
+
theme: m.optional().describe("Current color theme preference."),
|
|
950
|
+
styles: c.optional().describe("Style configuration for theming the app."),
|
|
951
|
+
displayMode: D.optional().describe("How the UI is currently displayed."),
|
|
952
|
+
availableDisplayModes: require_protocol.array(D).optional().describe("Display modes the host supports."),
|
|
953
|
+
containerDimensions: require_protocol.union([require_protocol.object({ height: require_protocol.number().describe("Fixed container height in pixels.") }), require_protocol.object({ maxHeight: require_protocol.union([require_protocol.number(), require_protocol._undefined()]).optional().describe("Maximum container height in pixels.") })]).and(require_protocol.union([require_protocol.object({ width: require_protocol.number().describe("Fixed container width in pixels.") }), require_protocol.object({ maxWidth: require_protocol.union([require_protocol.number(), require_protocol._undefined()]).optional().describe("Maximum container width in pixels.") })])).optional().describe(`Container dimensions. Represents the dimensions of the iframe or other
|
|
954
|
+
container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),
|
|
955
|
+
locale: require_protocol.string().optional().describe("User's language and region preference in BCP 47 format."),
|
|
956
|
+
timeZone: require_protocol.string().optional().describe("User's timezone in IANA format."),
|
|
957
|
+
userAgent: require_protocol.string().optional().describe("Host application identifier."),
|
|
958
|
+
platform: require_protocol.union([
|
|
959
|
+
require_protocol.literal("web"),
|
|
960
|
+
require_protocol.literal("desktop"),
|
|
961
|
+
require_protocol.literal("mobile")
|
|
962
|
+
]).optional().describe("Platform type for responsive design decisions."),
|
|
963
|
+
deviceCapabilities: require_protocol.object({
|
|
964
|
+
touch: require_protocol.boolean().optional().describe("Whether the device supports touch input."),
|
|
965
|
+
hover: require_protocol.boolean().optional().describe("Whether the device supports hover interactions.")
|
|
966
|
+
}).optional().describe("Device input capabilities."),
|
|
967
|
+
safeAreaInsets: require_protocol.object({
|
|
968
|
+
top: require_protocol.number().describe("Top safe area inset in pixels."),
|
|
969
|
+
right: require_protocol.number().describe("Right safe area inset in pixels."),
|
|
970
|
+
bottom: require_protocol.number().describe("Bottom safe area inset in pixels."),
|
|
971
|
+
left: require_protocol.number().describe("Left safe area inset in pixels.")
|
|
972
|
+
}).optional().describe("Mobile safe area boundaries in pixels.")
|
|
973
|
+
}).passthrough();
|
|
974
|
+
require_protocol.object({
|
|
975
|
+
method: require_protocol.literal("ui/notifications/host-context-changed"),
|
|
976
|
+
params: q.describe("Partial context update containing only changed fields.")
|
|
977
|
+
});
|
|
978
|
+
var y = require_protocol.object({
|
|
979
|
+
method: require_protocol.literal("ui/update-model-context"),
|
|
980
|
+
params: require_protocol.object({
|
|
981
|
+
content: require_protocol.array(require_protocol.ContentBlockSchema).optional().describe("Context content blocks (text, image, etc.)."),
|
|
982
|
+
structuredContent: require_protocol.record(require_protocol.string(), require_protocol.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")
|
|
983
|
+
})
|
|
984
|
+
}), f = require_protocol.object({
|
|
985
|
+
method: require_protocol.literal("ui/initialize"),
|
|
986
|
+
params: require_protocol.object({
|
|
987
|
+
appInfo: require_protocol.ImplementationSchema.describe("App identification (name and version)."),
|
|
988
|
+
appCapabilities: i.describe("Features and capabilities this app provides."),
|
|
989
|
+
protocolVersion: require_protocol.string().describe("Protocol version this app supports.")
|
|
990
|
+
})
|
|
991
|
+
});
|
|
992
|
+
require_protocol.object({
|
|
993
|
+
protocolVersion: require_protocol.string().describe("Negotiated protocol version string (e.g., \"2025-11-21\")."),
|
|
994
|
+
hostInfo: require_protocol.ImplementationSchema.describe("Host application identification and version."),
|
|
995
|
+
hostCapabilities: n.describe("Features and capabilities provided by the host."),
|
|
996
|
+
hostContext: q.describe("Rich context about the host environment.")
|
|
997
|
+
}).passthrough();
|
|
998
|
+
var N = class {
|
|
999
|
+
eventTarget;
|
|
1000
|
+
eventSource;
|
|
1001
|
+
messageListener;
|
|
1002
|
+
constructor(X = window.parent, Y) {
|
|
1003
|
+
this.eventTarget = X;
|
|
1004
|
+
this.eventSource = Y;
|
|
1005
|
+
this.messageListener = (Z) => {
|
|
1006
|
+
if (Y && Z.source !== this.eventSource) {
|
|
1007
|
+
console.debug("Ignoring message from unknown source", Z);
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
let $ = require_protocol.JSONRPCMessageSchema.safeParse(Z.data);
|
|
1011
|
+
if ($.success) console.debug("Parsed message", $.data), this.onmessage?.($.data);
|
|
1012
|
+
else if (Z.data?.jsonrpc !== "2.0") console.debug("Ignoring non-JSON-RPC message", $.error.message, Z);
|
|
1013
|
+
else console.error("Failed to parse message", $.error.message, Z), this.onerror?.(Error("Invalid JSON-RPC message received: " + $.error.message));
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
async start() {
|
|
1017
|
+
window.addEventListener("message", this.messageListener);
|
|
1018
|
+
}
|
|
1019
|
+
async send(X, Y) {
|
|
1020
|
+
if (X.method !== "ui/notifications/tool-input-partial") console.debug("Sending message", X);
|
|
1021
|
+
this.eventTarget.postMessage(X, "*");
|
|
1022
|
+
}
|
|
1023
|
+
async close() {
|
|
1024
|
+
window.removeEventListener("message", this.messageListener), this.onclose?.();
|
|
1025
|
+
}
|
|
1026
|
+
onclose;
|
|
1027
|
+
onerror;
|
|
1028
|
+
onmessage;
|
|
1029
|
+
sessionId;
|
|
1030
|
+
setProtocolVersion;
|
|
1031
|
+
};
|
|
1032
|
+
var rQ = [G];
|
|
1033
|
+
var oQ = class extends require_protocol.Protocol {
|
|
1034
|
+
_client;
|
|
1035
|
+
_hostInfo;
|
|
1036
|
+
_capabilities;
|
|
1037
|
+
_appCapabilities;
|
|
1038
|
+
_hostContext = {};
|
|
1039
|
+
_appInfo;
|
|
1040
|
+
constructor(X, Y, Z, $) {
|
|
1041
|
+
super($);
|
|
1042
|
+
this._client = X;
|
|
1043
|
+
this._hostInfo = Y;
|
|
1044
|
+
this._capabilities = Z;
|
|
1045
|
+
this._hostContext = $?.hostContext || {}, this.setRequestHandler(f, (K) => this._oninitialize(K)), this.setRequestHandler(require_protocol.PingRequestSchema, (K, J) => {
|
|
1046
|
+
return this.onping?.(K.params, J), {};
|
|
1047
|
+
}), this.setRequestHandler(W, (K) => {
|
|
1048
|
+
return { mode: this._hostContext.displayMode ?? "inline" };
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
getAppCapabilities() {
|
|
1052
|
+
return this._appCapabilities;
|
|
1053
|
+
}
|
|
1054
|
+
getAppVersion() {
|
|
1055
|
+
return this._appInfo;
|
|
1056
|
+
}
|
|
1057
|
+
onping;
|
|
1058
|
+
set onsizechange(X) {
|
|
1059
|
+
this.setNotificationHandler(P, (Y) => X(Y.params));
|
|
1060
|
+
}
|
|
1061
|
+
set onsandboxready(X) {
|
|
1062
|
+
this.setNotificationHandler(F, (Y) => X(Y.params));
|
|
1063
|
+
}
|
|
1064
|
+
set oninitialized(X) {
|
|
1065
|
+
this.setNotificationHandler(M, (Y) => X(Y.params));
|
|
1066
|
+
}
|
|
1067
|
+
set onmessage(X) {
|
|
1068
|
+
this.setRequestHandler(C, async (Y, Z) => {
|
|
1069
|
+
return X(Y.params, Z);
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
set onopenlink(X) {
|
|
1073
|
+
this.setRequestHandler(V, async (Y, Z) => {
|
|
1074
|
+
return X(Y.params, Z);
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
set ondownloadfile(X) {
|
|
1078
|
+
this.setRequestHandler(S, async (Y, Z) => {
|
|
1079
|
+
return X(Y.params, Z);
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
set onrequestdisplaymode(X) {
|
|
1083
|
+
this.setRequestHandler(W, async (Y, Z) => {
|
|
1084
|
+
return X(Y.params, Z);
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
set onloggingmessage(X) {
|
|
1088
|
+
this.setNotificationHandler(require_protocol.LoggingMessageNotificationSchema, async (Y) => {
|
|
1089
|
+
X(Y.params);
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
set onupdatemodelcontext(X) {
|
|
1093
|
+
this.setRequestHandler(y, async (Y, Z) => {
|
|
1094
|
+
return X(Y.params, Z);
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
set oncalltool(X) {
|
|
1098
|
+
this.setRequestHandler(require_protocol.CallToolRequestSchema, async (Y, Z) => {
|
|
1099
|
+
return X(Y.params, Z);
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
sendToolListChanged(X = {}) {
|
|
1103
|
+
return this.notification({
|
|
1104
|
+
method: "notifications/tools/list_changed",
|
|
1105
|
+
params: X
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
set onlistresources(X) {
|
|
1109
|
+
this.setRequestHandler(require_protocol.ListResourcesRequestSchema, async (Y, Z) => {
|
|
1110
|
+
return X(Y.params, Z);
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
set onlistresourcetemplates(X) {
|
|
1114
|
+
this.setRequestHandler(require_protocol.ListResourceTemplatesRequestSchema, async (Y, Z) => {
|
|
1115
|
+
return X(Y.params, Z);
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
set onreadresource(X) {
|
|
1119
|
+
this.setRequestHandler(require_protocol.ReadResourceRequestSchema, async (Y, Z) => {
|
|
1120
|
+
return X(Y.params, Z);
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
sendResourceListChanged(X = {}) {
|
|
1124
|
+
return this.notification({
|
|
1125
|
+
method: "notifications/resources/list_changed",
|
|
1126
|
+
params: X
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
set onlistprompts(X) {
|
|
1130
|
+
this.setRequestHandler(require_protocol.ListPromptsRequestSchema, async (Y, Z) => {
|
|
1131
|
+
return X(Y.params, Z);
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
sendPromptListChanged(X = {}) {
|
|
1135
|
+
return this.notification({
|
|
1136
|
+
method: "notifications/prompts/list_changed",
|
|
1137
|
+
params: X
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
assertCapabilityForMethod(X) {}
|
|
1141
|
+
assertRequestHandlerCapability(X) {}
|
|
1142
|
+
assertNotificationCapability(X) {}
|
|
1143
|
+
assertTaskCapability(X) {
|
|
1144
|
+
throw Error("Tasks are not supported in MCP Apps");
|
|
1145
|
+
}
|
|
1146
|
+
assertTaskHandlerCapability(X) {
|
|
1147
|
+
throw Error("Task handlers are not supported in MCP Apps");
|
|
1148
|
+
}
|
|
1149
|
+
getCapabilities() {
|
|
1150
|
+
return this._capabilities;
|
|
1151
|
+
}
|
|
1152
|
+
async _oninitialize(X) {
|
|
1153
|
+
let Y = X.params.protocolVersion;
|
|
1154
|
+
return this._appCapabilities = X.params.appCapabilities, this._appInfo = X.params.appInfo, {
|
|
1155
|
+
protocolVersion: rQ.includes(Y) ? Y : G,
|
|
1156
|
+
hostCapabilities: this.getCapabilities(),
|
|
1157
|
+
hostInfo: this._hostInfo,
|
|
1158
|
+
hostContext: this._hostContext
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
setHostContext(X) {
|
|
1162
|
+
let Y = {}, Z = !1;
|
|
1163
|
+
for (let $ of Object.keys(X)) {
|
|
1164
|
+
let K = this._hostContext[$], J = X[$];
|
|
1165
|
+
if (aQ(K, J)) continue;
|
|
1166
|
+
Y[$] = J, Z = !0;
|
|
1167
|
+
}
|
|
1168
|
+
if (Z) this._hostContext = X, this.sendHostContextChange(Y);
|
|
1169
|
+
}
|
|
1170
|
+
sendHostContextChange(X) {
|
|
1171
|
+
return this.notification({
|
|
1172
|
+
method: "ui/notifications/host-context-changed",
|
|
1173
|
+
params: X
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
sendToolInput(X) {
|
|
1177
|
+
return this.notification({
|
|
1178
|
+
method: "ui/notifications/tool-input",
|
|
1179
|
+
params: X
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
sendToolInputPartial(X) {
|
|
1183
|
+
return this.notification({
|
|
1184
|
+
method: "ui/notifications/tool-input-partial",
|
|
1185
|
+
params: X
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
sendToolResult(X) {
|
|
1189
|
+
return this.notification({
|
|
1190
|
+
method: "ui/notifications/tool-result",
|
|
1191
|
+
params: X
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
sendToolCancelled(X) {
|
|
1195
|
+
return this.notification({
|
|
1196
|
+
method: "ui/notifications/tool-cancelled",
|
|
1197
|
+
params: X
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
sendSandboxResourceReady(X) {
|
|
1201
|
+
return this.notification({
|
|
1202
|
+
method: "ui/notifications/sandbox-resource-ready",
|
|
1203
|
+
params: X
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
teardownResource(X, Y) {
|
|
1207
|
+
return this.request({
|
|
1208
|
+
method: "ui/resource-teardown",
|
|
1209
|
+
params: X
|
|
1210
|
+
}, R, Y);
|
|
1211
|
+
}
|
|
1212
|
+
sendResourceTeardown = this.teardownResource;
|
|
1213
|
+
async connect(X) {
|
|
1214
|
+
if (this.transport) throw Error("AppBridge is already connected. Call close() before connecting again.");
|
|
1215
|
+
if (this._client) {
|
|
1216
|
+
let Y = this._client.getServerCapabilities();
|
|
1217
|
+
if (!Y) throw Error("Client server capabilities not available");
|
|
1218
|
+
if (Y.tools) {
|
|
1219
|
+
if (this.oncalltool = async (Z, $) => {
|
|
1220
|
+
return this._client.request({
|
|
1221
|
+
method: "tools/call",
|
|
1222
|
+
params: Z
|
|
1223
|
+
}, require_protocol.CallToolResultSchema, { signal: $.signal });
|
|
1224
|
+
}, Y.tools.listChanged) this._client.setNotificationHandler(require_protocol.ToolListChangedNotificationSchema, (Z) => this.sendToolListChanged(Z.params));
|
|
1225
|
+
}
|
|
1226
|
+
if (Y.resources) {
|
|
1227
|
+
if (this.onlistresources = async (Z, $) => {
|
|
1228
|
+
return this._client.request({
|
|
1229
|
+
method: "resources/list",
|
|
1230
|
+
params: Z
|
|
1231
|
+
}, require_protocol.ListResourcesResultSchema, { signal: $.signal });
|
|
1232
|
+
}, this.onlistresourcetemplates = async (Z, $) => {
|
|
1233
|
+
return this._client.request({
|
|
1234
|
+
method: "resources/templates/list",
|
|
1235
|
+
params: Z
|
|
1236
|
+
}, require_protocol.ListResourceTemplatesResultSchema, { signal: $.signal });
|
|
1237
|
+
}, this.onreadresource = async (Z, $) => {
|
|
1238
|
+
return this._client.request({
|
|
1239
|
+
method: "resources/read",
|
|
1240
|
+
params: Z
|
|
1241
|
+
}, require_protocol.ReadResourceResultSchema, { signal: $.signal });
|
|
1242
|
+
}, Y.resources.listChanged) this._client.setNotificationHandler(require_protocol.ResourceListChangedNotificationSchema, (Z) => this.sendResourceListChanged(Z.params));
|
|
1243
|
+
}
|
|
1244
|
+
if (Y.prompts) {
|
|
1245
|
+
if (this.onlistprompts = async (Z, $) => {
|
|
1246
|
+
return this._client.request({
|
|
1247
|
+
method: "prompts/list",
|
|
1248
|
+
params: Z
|
|
1249
|
+
}, require_protocol.ListPromptsResultSchema, { signal: $.signal });
|
|
1250
|
+
}, Y.prompts.listChanged) this._client.setNotificationHandler(require_protocol.PromptListChangedNotificationSchema, (Z) => this.sendPromptListChanged(Z.params));
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
return super.connect(X);
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
function aQ(X, Y) {
|
|
1257
|
+
return JSON.stringify(X) === JSON.stringify(Y);
|
|
1258
|
+
}
|
|
1259
|
+
//#endregion
|
|
1260
|
+
//#region src/simulator/mcp-app-host.ts
|
|
1261
|
+
var DEFAULT_HOST_INFO = {
|
|
1262
|
+
name: "SunpeakSimulator",
|
|
1263
|
+
version: "1.0.0"
|
|
1264
|
+
};
|
|
1265
|
+
var DEFAULT_HOST_CAPABILITIES = {
|
|
1266
|
+
openLinks: {},
|
|
1267
|
+
serverTools: {},
|
|
1268
|
+
serverResources: {},
|
|
1269
|
+
downloadFile: {},
|
|
1270
|
+
logging: {},
|
|
1271
|
+
updateModelContext: { text: {} },
|
|
1272
|
+
message: { text: {} },
|
|
1273
|
+
sandbox: {}
|
|
1274
|
+
};
|
|
1275
|
+
/**
|
|
1276
|
+
* MCP Apps host for the Sunpeak simulator.
|
|
1277
|
+
* Wraps AppBridge to provide a simpler API for the simulator.
|
|
1278
|
+
* Connects to an iframe via PostMessageTransport.
|
|
1279
|
+
*/
|
|
1280
|
+
var McpAppHost = class {
|
|
1281
|
+
bridge;
|
|
1282
|
+
options;
|
|
1283
|
+
_initialized = false;
|
|
1284
|
+
_contentWindow = null;
|
|
1285
|
+
_fenceId = 0;
|
|
1286
|
+
_fenceCleanup = null;
|
|
1287
|
+
_prevDisplayMode;
|
|
1288
|
+
_pendingToolInput = null;
|
|
1289
|
+
_pendingToolResult = null;
|
|
1290
|
+
constructor(options = {}) {
|
|
1291
|
+
this.options = options;
|
|
1292
|
+
this._prevDisplayMode = options.hostContext?.displayMode;
|
|
1293
|
+
this.bridge = new oQ(null, options.hostInfo ?? DEFAULT_HOST_INFO, options.hostCapabilities ?? DEFAULT_HOST_CAPABILITIES, { hostContext: options.hostContext });
|
|
1294
|
+
this.bridge.oninitialized = () => {
|
|
1295
|
+
this._initialized = true;
|
|
1296
|
+
if (this._pendingToolInput) {
|
|
1297
|
+
this.bridge.sendToolInput(this._pendingToolInput);
|
|
1298
|
+
this._pendingToolInput = null;
|
|
1299
|
+
}
|
|
1300
|
+
if (this._pendingToolResult) {
|
|
1301
|
+
this.bridge.sendToolResult(this._pendingToolResult);
|
|
1302
|
+
this._pendingToolResult = null;
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
this.bridge.onopenlink = async ({ url }) => {
|
|
1306
|
+
console.log("[MCP App] openLink:", url);
|
|
1307
|
+
if (this.options.onOpenLink) this.options.onOpenLink(url);
|
|
1308
|
+
else {
|
|
1309
|
+
try {
|
|
1310
|
+
const parsed = new URL(url);
|
|
1311
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
1312
|
+
console.warn("[MCP App] openLink blocked non-http(s) URL:", url);
|
|
1313
|
+
return {};
|
|
1314
|
+
}
|
|
1315
|
+
} catch {
|
|
1316
|
+
console.warn("[MCP App] openLink blocked invalid URL:", url);
|
|
1317
|
+
return {};
|
|
1318
|
+
}
|
|
1319
|
+
window.open(url, "_blank");
|
|
1320
|
+
}
|
|
1321
|
+
return {};
|
|
1322
|
+
};
|
|
1323
|
+
this.bridge.onmessage = async ({ role, content }) => {
|
|
1324
|
+
if (this.options.onMessage) this.options.onMessage(role, content);
|
|
1325
|
+
else console.log("[MCP App] sendMessage:", {
|
|
1326
|
+
role,
|
|
1327
|
+
content
|
|
1328
|
+
});
|
|
1329
|
+
return {};
|
|
1330
|
+
};
|
|
1331
|
+
this.bridge.onrequestdisplaymode = async ({ mode }) => {
|
|
1332
|
+
this.options.onDisplayModeChange?.(mode);
|
|
1333
|
+
return { mode };
|
|
1334
|
+
};
|
|
1335
|
+
this.bridge.onupdatemodelcontext = async ({ content, structuredContent }) => {
|
|
1336
|
+
this.options.onUpdateModelContext?.(content ?? [], structuredContent);
|
|
1337
|
+
return {};
|
|
1338
|
+
};
|
|
1339
|
+
this.bridge.onsizechange = (params) => {
|
|
1340
|
+
this.options.onSizeChanged?.(params);
|
|
1341
|
+
};
|
|
1342
|
+
this.bridge.onloggingmessage = (params) => {
|
|
1343
|
+
if (this.options.onLog) this.options.onLog(params);
|
|
1344
|
+
else {
|
|
1345
|
+
const level = params.level ?? "info";
|
|
1346
|
+
const prefix = `[MCP App${params.logger ? ` ${params.logger}` : ""}]`;
|
|
1347
|
+
if (level === "error" || level === "critical" || level === "alert" || level === "emergency") console.error(prefix, params.data);
|
|
1348
|
+
else if (level === "warning") console.warn(prefix, params.data);
|
|
1349
|
+
else if (level === "debug") console.debug(prefix, params.data);
|
|
1350
|
+
else console.log(prefix, params.data);
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
this.bridge.oncalltool = async (params) => {
|
|
1354
|
+
if (this.options.onCallTool) return this.options.onCallTool(params);
|
|
1355
|
+
console.log("[MCP App] callServerTool:", params.name, params.arguments);
|
|
1356
|
+
return { content: [{
|
|
1357
|
+
type: "text",
|
|
1358
|
+
text: `[Simulator] Tool "${params.name}" called (no handler configured)`
|
|
1359
|
+
}] };
|
|
1360
|
+
};
|
|
1361
|
+
this.bridge.ondownloadfile = async ({ contents }) => {
|
|
1362
|
+
if (this.options.onDownloadFile) this.options.onDownloadFile(contents);
|
|
1363
|
+
else console.log("[MCP App] downloadFile:", contents.length, "item(s)");
|
|
1364
|
+
return {};
|
|
1365
|
+
};
|
|
1366
|
+
this.bridge.onsandboxready = () => {
|
|
1367
|
+
this.options.onSandboxReady?.();
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Connect to an iframe's contentWindow.
|
|
1372
|
+
*/
|
|
1373
|
+
async connectToIframe(contentWindow) {
|
|
1374
|
+
this._contentWindow = contentWindow;
|
|
1375
|
+
const transport = new N(contentWindow, contentWindow);
|
|
1376
|
+
await this.bridge.connect(transport);
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Wait for the iframe to process all pending messages and commit its DOM.
|
|
1380
|
+
*
|
|
1381
|
+
* Uses a postMessage fence: since messages to the same target are delivered
|
|
1382
|
+
* in FIFO order, a fence message sent after setHostContext is guaranteed to
|
|
1383
|
+
* be processed after the host context change. The iframe's fence responder
|
|
1384
|
+
* waits for requestAnimationFrame before acking, ensuring the DOM has been
|
|
1385
|
+
* committed for the re-render triggered by the context change.
|
|
1386
|
+
*
|
|
1387
|
+
* Returns immediately if the iframe is not connected.
|
|
1388
|
+
*/
|
|
1389
|
+
waitForPaint() {
|
|
1390
|
+
const win = this._contentWindow;
|
|
1391
|
+
if (!win) return Promise.resolve();
|
|
1392
|
+
this._fenceCleanup?.();
|
|
1393
|
+
const id = ++this._fenceId;
|
|
1394
|
+
return new Promise((resolve) => {
|
|
1395
|
+
const handler = (event) => {
|
|
1396
|
+
if (event.source !== win) return;
|
|
1397
|
+
if (event.data?.method === "sunpeak/fence-ack" && event.data.params?.fenceId === id) {
|
|
1398
|
+
cleanup();
|
|
1399
|
+
resolve();
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
const cleanup = () => {
|
|
1403
|
+
window.removeEventListener("message", handler);
|
|
1404
|
+
if (this._fenceCleanup === cleanup) this._fenceCleanup = null;
|
|
1405
|
+
};
|
|
1406
|
+
this._fenceCleanup = cleanup;
|
|
1407
|
+
window.addEventListener("message", handler);
|
|
1408
|
+
try {
|
|
1409
|
+
win.postMessage({
|
|
1410
|
+
jsonrpc: "2.0",
|
|
1411
|
+
method: "sunpeak/fence",
|
|
1412
|
+
params: { fenceId: id }
|
|
1413
|
+
}, "*");
|
|
1414
|
+
} catch {
|
|
1415
|
+
cleanup();
|
|
1416
|
+
resolve();
|
|
1417
|
+
}
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Update the host context and notify the connected app.
|
|
1422
|
+
* Automatically detects display mode changes and waits for the iframe
|
|
1423
|
+
* to commit its DOM before firing onDisplayModeReady.
|
|
1424
|
+
*/
|
|
1425
|
+
setHostContext(context) {
|
|
1426
|
+
this.bridge.setHostContext(context);
|
|
1427
|
+
const currentMode = context.displayMode;
|
|
1428
|
+
if (currentMode && currentMode !== this._prevDisplayMode) {
|
|
1429
|
+
this._prevDisplayMode = currentMode;
|
|
1430
|
+
const mode = currentMode;
|
|
1431
|
+
this.waitForPaint().then(() => {
|
|
1432
|
+
this.options.onDisplayModeReady?.(mode);
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Send tool input to the app.
|
|
1438
|
+
* If the app hasn't initialized yet, the input is queued.
|
|
1439
|
+
*/
|
|
1440
|
+
sendToolInput(args) {
|
|
1441
|
+
const params = { arguments: args };
|
|
1442
|
+
if (this._initialized) this.bridge.sendToolInput(params);
|
|
1443
|
+
else this._pendingToolInput = params;
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Send tool result to the app.
|
|
1447
|
+
* If the app hasn't initialized yet, the result is queued.
|
|
1448
|
+
*/
|
|
1449
|
+
sendToolResult(result) {
|
|
1450
|
+
if (this._initialized) this.bridge.sendToolResult(result);
|
|
1451
|
+
else this._pendingToolResult = result;
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
* Send partial/streaming tool input to the app.
|
|
1455
|
+
* Useful for simulating streaming tool arguments.
|
|
1456
|
+
*/
|
|
1457
|
+
sendToolInputPartial(args) {
|
|
1458
|
+
const params = { arguments: args };
|
|
1459
|
+
if (this._initialized) this.bridge.sendToolInputPartial(params);
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Send tool cancellation notification to the app.
|
|
1463
|
+
* Simulates user or host cancelling a tool execution.
|
|
1464
|
+
*/
|
|
1465
|
+
sendToolCancelled(reason) {
|
|
1466
|
+
const params = reason ? { reason } : {};
|
|
1467
|
+
if (this._initialized) this.bridge.sendToolCancelled(params);
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Send HTML resource to the sandbox proxy for secure loading.
|
|
1471
|
+
* Used in the double-iframe architecture after the proxy signals readiness.
|
|
1472
|
+
*/
|
|
1473
|
+
sendSandboxResourceReady(params) {
|
|
1474
|
+
this.bridge.sendSandboxResourceReady(params);
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Send a custom message to the connected iframe (for sandbox proxy commands).
|
|
1478
|
+
*/
|
|
1479
|
+
sendRawMessage(data) {
|
|
1480
|
+
const win = this._contentWindow;
|
|
1481
|
+
if (!win) return;
|
|
1482
|
+
try {
|
|
1483
|
+
win.postMessage(data, "*");
|
|
1484
|
+
} catch {}
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Close the connection.
|
|
1488
|
+
*/
|
|
1489
|
+
async close() {
|
|
1490
|
+
this._fenceCleanup?.();
|
|
1491
|
+
this._fenceCleanup = null;
|
|
1492
|
+
try {
|
|
1493
|
+
if (this._initialized) await this.bridge.teardownResource({});
|
|
1494
|
+
} catch {}
|
|
1495
|
+
await this.bridge.close();
|
|
1496
|
+
this._initialized = false;
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Debug: Inject state directly into the app's useAppState hook.
|
|
1500
|
+
* This bypasses the normal MCP Apps protocol and is intended for
|
|
1501
|
+
* simulator testing/debugging only.
|
|
1502
|
+
*/
|
|
1503
|
+
injectState(state) {
|
|
1504
|
+
const win = this._contentWindow;
|
|
1505
|
+
if (!win) return;
|
|
1506
|
+
try {
|
|
1507
|
+
win.postMessage({
|
|
1508
|
+
jsonrpc: "2.0",
|
|
1509
|
+
method: "sunpeak/injectState",
|
|
1510
|
+
params: { state }
|
|
1511
|
+
}, "*");
|
|
1512
|
+
} catch {}
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Update mutable options (callbacks) after construction.
|
|
1516
|
+
* Allows the simulator to swap handlers (e.g. onCallTool) without
|
|
1517
|
+
* recreating the host and tearing down the iframe connection.
|
|
1518
|
+
*/
|
|
1519
|
+
updateOptions(partial) {
|
|
1520
|
+
Object.assign(this.options, partial);
|
|
1521
|
+
}
|
|
1522
|
+
get initialized() {
|
|
1523
|
+
return this._initialized;
|
|
1524
|
+
}
|
|
1525
|
+
};
|
|
1526
|
+
//#endregion
|
|
1527
|
+
//#region src/simulator/mock-openai-runtime.ts
|
|
1528
|
+
/**
|
|
1529
|
+
* Mock OpenAI runtime for the simulator.
|
|
1530
|
+
*
|
|
1531
|
+
* ChatGPT-specific hooks (useUploadFile, useRequestModal, etc.) call
|
|
1532
|
+
* `window.openai` directly — they don't use the MCP protocol. When the
|
|
1533
|
+
* ChatGPT host is selected in the simulator, we inject this mock into
|
|
1534
|
+
* the iframe's window so those hooks work during local development.
|
|
1535
|
+
*/
|
|
1536
|
+
/**
|
|
1537
|
+
* Inline script that sets up a mock `window.openai` for srcdoc iframes.
|
|
1538
|
+
* Embedded in the generated HTML *before* the app's script so that
|
|
1539
|
+
* `isChatGPT()` and hooks work from the very first render.
|
|
1540
|
+
*/
|
|
1541
|
+
var MOCK_OPENAI_RUNTIME_SCRIPT = [
|
|
1542
|
+
"window.openai={",
|
|
1543
|
+
"uploadFile:function(f){console.log(\"[Simulator] uploadFile:\",f.name);",
|
|
1544
|
+
"return Promise.resolve({fileId:\"sim_file_\"+Date.now()})},",
|
|
1545
|
+
"getFileDownloadUrl:function(p){console.log(\"[Simulator] getFileDownloadUrl:\",p.fileId);",
|
|
1546
|
+
"return Promise.resolve({downloadUrl:\"https://simulator.local/files/\"+p.fileId})},",
|
|
1547
|
+
"requestModal:function(p){console.log(\"[Simulator] requestModal:\",JSON.stringify(p));",
|
|
1548
|
+
"return Promise.resolve()},",
|
|
1549
|
+
"requestCheckout:function(s){console.log(\"[Simulator] requestCheckout:\",JSON.stringify(s));",
|
|
1550
|
+
"return Promise.resolve({id:\"sim_order_\"+Date.now(),checkout_session_id:s.id||\"sim_session\",status:\"completed\"})},",
|
|
1551
|
+
"requestClose:function(){console.log(\"[Simulator] requestClose\")},",
|
|
1552
|
+
"requestDisplayMode:function(p){console.log(\"[Simulator] requestDisplayMode:\",p.mode);",
|
|
1553
|
+
"return Promise.resolve()},",
|
|
1554
|
+
"sendFollowUpMessage:function(p){console.log(\"[Simulator] sendFollowUpMessage:\",p.prompt)},",
|
|
1555
|
+
"openExternal:function(p){console.log(\"[Simulator] openExternal:\",p.href);window.open(p.href,\"_blank\")}",
|
|
1556
|
+
"};"
|
|
1557
|
+
].join("");
|
|
1558
|
+
//#endregion
|
|
1559
|
+
//#region src/simulator/sandbox-proxy.ts
|
|
1560
|
+
/**
|
|
1561
|
+
* Sandbox proxy for the double-iframe architecture.
|
|
1562
|
+
*
|
|
1563
|
+
* Real hosts (ChatGPT, Claude) use a two-level iframe structure:
|
|
1564
|
+
* 1. Outer iframe (sandbox proxy) — acts as a message relay on a separate origin
|
|
1565
|
+
* 2. Inner iframe — loads the untrusted app HTML
|
|
1566
|
+
*
|
|
1567
|
+
* The proxy relays PostMessage between the host (parent) and the app (inner iframe),
|
|
1568
|
+
* providing origin isolation and security boundaries.
|
|
1569
|
+
*
|
|
1570
|
+
* The simulator replicates this architecture so apps are tested under the same
|
|
1571
|
+
* iframe nesting they'll encounter in production.
|
|
1572
|
+
*
|
|
1573
|
+
* Protocol:
|
|
1574
|
+
* 1. Host creates outer iframe with proxy HTML (srcdoc)
|
|
1575
|
+
* 2. Proxy sends `ui/notifications/sandbox-proxy-ready` to parent
|
|
1576
|
+
* 3. Host sends `ui/notifications/sandbox-resource-ready` with { html, sandbox, csp, permissions }
|
|
1577
|
+
* OR `sunpeak/sandbox-load-src` with { src } for dev mode
|
|
1578
|
+
* 4. Proxy creates inner iframe and loads the content
|
|
1579
|
+
* 5. All subsequent messages relay transparently between parent and inner iframe
|
|
1580
|
+
*/
|
|
1581
|
+
/**
|
|
1582
|
+
* Generate the sandbox proxy HTML.
|
|
1583
|
+
*
|
|
1584
|
+
* This HTML is loaded into the outer iframe via srcdoc. It:
|
|
1585
|
+
* - Signals readiness via `ui/notifications/sandbox-proxy-ready`
|
|
1586
|
+
* - Listens for resource content or URL to load into the inner iframe
|
|
1587
|
+
* - Relays all PostMessage between parent and inner iframe
|
|
1588
|
+
* - Optionally injects platform runtime scripts (e.g., mock window.openai)
|
|
1589
|
+
*
|
|
1590
|
+
* @param platformScript - Optional JS to inject into the inner iframe before the app loads
|
|
1591
|
+
*/
|
|
1592
|
+
function generateSandboxProxyHtml(platformScript) {
|
|
1593
|
+
const escapedPlatformScript = platformScript ? JSON.stringify(platformScript) : "null";
|
|
1594
|
+
const colorScheme = "dark";
|
|
1595
|
+
return `<!DOCTYPE html>
|
|
1596
|
+
<html style="color-scheme:${colorScheme}">
|
|
1597
|
+
<head>
|
|
1598
|
+
<meta name="color-scheme" content="${colorScheme}" />
|
|
1599
|
+
<style>
|
|
1600
|
+
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; }
|
|
1601
|
+
iframe { border: none; width: 100%; height: 100%; display: block; }
|
|
1602
|
+
</style>
|
|
1603
|
+
</head>
|
|
1604
|
+
<body>
|
|
1605
|
+
<script>
|
|
1606
|
+
(function() {
|
|
1607
|
+
var innerFrame = null;
|
|
1608
|
+
var innerWindow = null;
|
|
1609
|
+
var platformScript = ${escapedPlatformScript};
|
|
1610
|
+
|
|
1611
|
+
// Relay messages between parent (host) and inner iframe (app)
|
|
1612
|
+
window.addEventListener('message', function(event) {
|
|
1613
|
+
var data = event.data;
|
|
1614
|
+
if (!data || typeof data !== 'object') return;
|
|
1615
|
+
|
|
1616
|
+
if (event.source === window.parent) {
|
|
1617
|
+
// ── Messages from the host ──
|
|
1618
|
+
|
|
1619
|
+
// sandbox-resource-ready: load HTML into inner iframe (scriptSrc/prod mode)
|
|
1620
|
+
if (data.method === 'ui/notifications/sandbox-resource-ready' && data.params) {
|
|
1621
|
+
createInnerFrame(data.params);
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
// sunpeak/sandbox-load-src: load URL into inner iframe (src/dev mode)
|
|
1626
|
+
if (data.method === 'sunpeak/sandbox-load-src' && data.params) {
|
|
1627
|
+
createInnerFrameWithSrc(data.params);
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
// Handle paint fence. Forward to the inner iframe and wait for its ack.
|
|
1632
|
+
// If the inner iframe has a fence responder (same-origin, script injected),
|
|
1633
|
+
// the ack is deterministic. Otherwise fall back to a 150ms timeout.
|
|
1634
|
+
if (data.method === 'sunpeak/fence' && data.params) {
|
|
1635
|
+
var fenceId = data.params.fenceId;
|
|
1636
|
+
var acked = false;
|
|
1637
|
+
var onAck = function(e) {
|
|
1638
|
+
if (e.source !== innerWindow) return;
|
|
1639
|
+
if (e.data && e.data.method === 'sunpeak/fence-ack' &&
|
|
1640
|
+
e.data.params && e.data.params.fenceId === fenceId) {
|
|
1641
|
+
acked = true;
|
|
1642
|
+
window.removeEventListener('message', onAck);
|
|
1643
|
+
window.parent.postMessage(e.data, '*');
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
window.addEventListener('message', onAck);
|
|
1647
|
+
if (innerWindow) {
|
|
1648
|
+
try { innerWindow.postMessage(data, '*'); } catch(e) {}
|
|
1649
|
+
}
|
|
1650
|
+
setTimeout(function() {
|
|
1651
|
+
if (!acked) {
|
|
1652
|
+
window.removeEventListener('message', onAck);
|
|
1653
|
+
window.parent.postMessage({
|
|
1654
|
+
jsonrpc: '2.0',
|
|
1655
|
+
method: 'sunpeak/fence-ack',
|
|
1656
|
+
params: { fenceId: fenceId }
|
|
1657
|
+
}, '*');
|
|
1658
|
+
}
|
|
1659
|
+
}, 150);
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
// Forward all other messages to the inner iframe
|
|
1664
|
+
if (innerWindow) {
|
|
1665
|
+
try { innerWindow.postMessage(data, '*'); } catch(e) { /* detached */ }
|
|
1666
|
+
}
|
|
1667
|
+
} else if (innerWindow && event.source === innerWindow) {
|
|
1668
|
+
// ── Messages from the app → forward to host ──
|
|
1669
|
+
try { window.parent.postMessage(data, '*'); } catch(e) { /* detached */ }
|
|
1670
|
+
}
|
|
1671
|
+
});
|
|
1672
|
+
|
|
1673
|
+
function createInnerFrame(params) {
|
|
1674
|
+
if (innerFrame) innerFrame.remove();
|
|
1675
|
+
|
|
1676
|
+
innerFrame = document.createElement('iframe');
|
|
1677
|
+
innerFrame.sandbox = params.sandbox ||
|
|
1678
|
+
'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox';
|
|
1679
|
+
if (params.allow) innerFrame.allow = params.allow;
|
|
1680
|
+
document.body.appendChild(innerFrame);
|
|
1681
|
+
innerWindow = innerFrame.contentWindow;
|
|
1682
|
+
|
|
1683
|
+
// Write HTML content into the inner iframe
|
|
1684
|
+
var doc = innerFrame.contentDocument;
|
|
1685
|
+
if (doc && params.html) {
|
|
1686
|
+
doc.open();
|
|
1687
|
+
doc.write(params.html);
|
|
1688
|
+
doc.close();
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
function createInnerFrameWithSrc(params) {
|
|
1693
|
+
if (innerFrame) innerFrame.remove();
|
|
1694
|
+
|
|
1695
|
+
innerFrame = document.createElement('iframe');
|
|
1696
|
+
innerFrame.sandbox =
|
|
1697
|
+
'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox';
|
|
1698
|
+
if (params.allow) innerFrame.allow = params.allow;
|
|
1699
|
+
innerFrame.src = params.src;
|
|
1700
|
+
innerFrame.style.height = '100%';
|
|
1701
|
+
|
|
1702
|
+
// Set color-scheme on the inner iframe to match the host theme.
|
|
1703
|
+
// This ensures prefers-color-scheme resolves correctly inside.
|
|
1704
|
+
if (params.theme) {
|
|
1705
|
+
innerFrame.style.colorScheme = params.theme;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
// After load, inject helpers into the inner iframe
|
|
1709
|
+
innerFrame.addEventListener('load', function() {
|
|
1710
|
+
innerWindow = innerFrame.contentWindow;
|
|
1711
|
+
|
|
1712
|
+
// Inject platform runtime (e.g., mock window.openai for ChatGPT)
|
|
1713
|
+
if (platformScript && innerWindow) {
|
|
1714
|
+
try {
|
|
1715
|
+
var pScript = innerFrame.contentDocument.createElement('script');
|
|
1716
|
+
pScript.textContent = platformScript;
|
|
1717
|
+
innerFrame.contentDocument.head.appendChild(pScript);
|
|
1718
|
+
} catch(e) { /* cross-origin */ }
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// Inject paint fence responder
|
|
1722
|
+
try {
|
|
1723
|
+
var fenceScript = innerFrame.contentDocument.createElement('script');
|
|
1724
|
+
fenceScript.setAttribute('data-sunpeak-fence', '');
|
|
1725
|
+
fenceScript.textContent = PAINT_FENCE_SCRIPT;
|
|
1726
|
+
innerFrame.contentDocument.head.appendChild(fenceScript);
|
|
1727
|
+
} catch(e) { /* cross-origin */ }
|
|
1728
|
+
|
|
1729
|
+
// Inject background rule
|
|
1730
|
+
if (params.theme) {
|
|
1731
|
+
try {
|
|
1732
|
+
innerFrame.contentDocument.documentElement.style.colorScheme = params.theme;
|
|
1733
|
+
var bgStyle = innerFrame.contentDocument.createElement('style');
|
|
1734
|
+
bgStyle.setAttribute('data-sunpeak-bg', '');
|
|
1735
|
+
bgStyle.textContent = 'html { background-color: var(--color-background-primary, Canvas); }';
|
|
1736
|
+
innerFrame.contentDocument.head.appendChild(bgStyle);
|
|
1737
|
+
} catch(e) { /* cross-origin */ }
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
// Inject style variables for immediate rendering
|
|
1741
|
+
if (params.styleVars) {
|
|
1742
|
+
try {
|
|
1743
|
+
var root = innerFrame.contentDocument.documentElement;
|
|
1744
|
+
for (var key in params.styleVars) {
|
|
1745
|
+
if (params.styleVars[key]) root.style.setProperty(key, params.styleVars[key]);
|
|
1746
|
+
}
|
|
1747
|
+
} catch(e) { /* cross-origin */ }
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// Signal that load is complete — fade in
|
|
1751
|
+
innerFrame.style.opacity = '1';
|
|
1752
|
+
innerFrame.style.transition = 'opacity 100ms';
|
|
1753
|
+
});
|
|
1754
|
+
|
|
1755
|
+
// Start hidden for smooth fade-in
|
|
1756
|
+
innerFrame.style.opacity = '0';
|
|
1757
|
+
document.body.appendChild(innerFrame);
|
|
1758
|
+
innerWindow = innerFrame.contentWindow;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// Paint fence responder — same as in iframe-resource.ts
|
|
1762
|
+
var PAINT_FENCE_SCRIPT = 'window.addEventListener("message",function(e){' +
|
|
1763
|
+
'if(e.data&&e.data.method==="sunpeak/fence"){' +
|
|
1764
|
+
'var fid=e.data.params&&e.data.params.fenceId;' +
|
|
1765
|
+
'requestAnimationFrame(function(){' +
|
|
1766
|
+
'e.source.postMessage({jsonrpc:"2.0",method:"sunpeak/fence-ack",params:{fenceId:fid}},"*");' +
|
|
1767
|
+
'});}});';
|
|
1768
|
+
|
|
1769
|
+
// Signal to the host that the proxy is ready.
|
|
1770
|
+
// Use setTimeout to ensure the host's PostMessageTransport listener
|
|
1771
|
+
// is set up before we send the ready notification. The srcdoc is parsed
|
|
1772
|
+
// synchronously by the browser, which can race with React's ref callback.
|
|
1773
|
+
setTimeout(function() {
|
|
1774
|
+
window.parent.postMessage({
|
|
1775
|
+
jsonrpc: '2.0',
|
|
1776
|
+
method: 'ui/notifications/sandbox-proxy-ready',
|
|
1777
|
+
params: {}
|
|
1778
|
+
}, '*');
|
|
1779
|
+
}, 0);
|
|
1780
|
+
})();
|
|
1781
|
+
<\/script>
|
|
1782
|
+
</body>
|
|
1783
|
+
</html>`;
|
|
1784
|
+
}
|
|
1785
|
+
//#endregion
|
|
1786
|
+
//#region src/simulator/iframe-resource.tsx
|
|
1787
|
+
/**
|
|
1788
|
+
* Allowed origins for cross-origin script loading.
|
|
1789
|
+
* - Local development: localhost, 127.0.0.1, file://
|
|
1790
|
+
* - Production: sunpeak-prod-app-storage.s3.us-east-2.amazonaws.com (serves user scripts)
|
|
1791
|
+
*/
|
|
1792
|
+
var ALLOWED_SCRIPT_ORIGINS = [
|
|
1793
|
+
"https://sunpeak-prod-app-storage.s3.us-east-2.amazonaws.com",
|
|
1794
|
+
"http://localhost",
|
|
1795
|
+
"https://localhost",
|
|
1796
|
+
"http://127.0.0.1",
|
|
1797
|
+
"https://127.0.0.1"
|
|
1798
|
+
];
|
|
1799
|
+
/**
|
|
1800
|
+
* Escapes HTML special characters to prevent XSS via attribute injection.
|
|
1801
|
+
*/
|
|
1802
|
+
function escapeHtml(str) {
|
|
1803
|
+
return str.replace(/[&<>"']/g, (c) => {
|
|
1804
|
+
return {
|
|
1805
|
+
"&": "&",
|
|
1806
|
+
"<": "<",
|
|
1807
|
+
">": ">",
|
|
1808
|
+
"\"": """,
|
|
1809
|
+
"'": "'"
|
|
1810
|
+
}[c] ?? c;
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Validates that a URL is from an allowed origin.
|
|
1815
|
+
* Allows same-origin URLs and URLs from whitelisted domains.
|
|
1816
|
+
*/
|
|
1817
|
+
function isAllowedUrl(src) {
|
|
1818
|
+
if (!src) return false;
|
|
1819
|
+
if (src.startsWith("/") && !src.startsWith("//")) return true;
|
|
1820
|
+
if (!src.includes("://")) return false;
|
|
1821
|
+
try {
|
|
1822
|
+
const url = new URL(src);
|
|
1823
|
+
if (url.origin === window.location.origin) return true;
|
|
1824
|
+
if (url.hostname === "localhost" || url.hostname === "127.0.0.1") return true;
|
|
1825
|
+
return ALLOWED_SCRIPT_ORIGINS.some((allowed) => {
|
|
1826
|
+
try {
|
|
1827
|
+
return url.origin === new URL(allowed).origin;
|
|
1828
|
+
} catch {
|
|
1829
|
+
return false;
|
|
1830
|
+
}
|
|
1831
|
+
});
|
|
1832
|
+
} catch {
|
|
1833
|
+
return false;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Extract CSP configuration from a resource's _meta.ui.csp field.
|
|
1838
|
+
*/
|
|
1839
|
+
function extractResourceCSP(resource) {
|
|
1840
|
+
return (resource._meta?.ui)?.csp;
|
|
1841
|
+
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Validates a CSP source entry is a safe origin URL (scheme + host + optional port).
|
|
1844
|
+
* Rejects wildcards, CSP keywords, and whitespace that could inject extra directives.
|
|
1845
|
+
*/
|
|
1846
|
+
function isValidCspSource(source) {
|
|
1847
|
+
if (!source || /[\s;,']/.test(source) || source === "*") return false;
|
|
1848
|
+
try {
|
|
1849
|
+
const url = new URL(source);
|
|
1850
|
+
return url.protocol === "http:" || url.protocol === "https:" || url.protocol === "ws:" || url.protocol === "wss:";
|
|
1851
|
+
} catch {
|
|
1852
|
+
return false;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* Generates a Content Security Policy string.
|
|
1857
|
+
*/
|
|
1858
|
+
function generateCSP(csp, scriptSrc) {
|
|
1859
|
+
let scriptOrigin = "";
|
|
1860
|
+
try {
|
|
1861
|
+
scriptOrigin = new URL(scriptSrc, window.location.origin).origin;
|
|
1862
|
+
} catch {}
|
|
1863
|
+
const frameSources = /* @__PURE__ */ new Set();
|
|
1864
|
+
if (csp?.frameDomains) for (const domain of csp.frameDomains) if (isValidCspSource(domain)) frameSources.add(domain);
|
|
1865
|
+
else console.warn("[IframeResource] Ignoring invalid CSP frame domain:", domain);
|
|
1866
|
+
const frameSrc = frameSources.size > 0 ? `frame-src ${Array.from(frameSources).join(" ")}` : "frame-src 'none'";
|
|
1867
|
+
const baseSources = new Set(["'self'"]);
|
|
1868
|
+
if (csp?.baseUriDomains) for (const domain of csp.baseUriDomains) if (isValidCspSource(domain)) baseSources.add(domain);
|
|
1869
|
+
else console.warn("[IframeResource] Ignoring invalid CSP base-uri domain:", domain);
|
|
1870
|
+
const directives = [
|
|
1871
|
+
"default-src 'none'",
|
|
1872
|
+
`script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: ${scriptOrigin}`.trim(),
|
|
1873
|
+
`style-src 'self' 'unsafe-inline' ${scriptOrigin}`.trim(),
|
|
1874
|
+
frameSrc,
|
|
1875
|
+
"object-src 'none'",
|
|
1876
|
+
"form-action 'none'",
|
|
1877
|
+
`base-uri ${Array.from(baseSources).join(" ")}`
|
|
1878
|
+
];
|
|
1879
|
+
const connectSources = new Set(["'self'"]);
|
|
1880
|
+
if (scriptOrigin) connectSources.add(scriptOrigin);
|
|
1881
|
+
if (csp?.connectDomains) for (const domain of csp.connectDomains) if (isValidCspSource(domain)) connectSources.add(domain);
|
|
1882
|
+
else console.warn("[IframeResource] Ignoring invalid CSP connect domain:", domain);
|
|
1883
|
+
directives.push(`connect-src ${Array.from(connectSources).join(" ")}`);
|
|
1884
|
+
const resourceSources = new Set([
|
|
1885
|
+
"'self'",
|
|
1886
|
+
"data:",
|
|
1887
|
+
"blob:"
|
|
1888
|
+
]);
|
|
1889
|
+
if (scriptOrigin) resourceSources.add(scriptOrigin);
|
|
1890
|
+
if (csp?.resourceDomains) for (const domain of csp.resourceDomains) if (isValidCspSource(domain)) resourceSources.add(domain);
|
|
1891
|
+
else console.warn("[IframeResource] Ignoring invalid CSP resource domain:", domain);
|
|
1892
|
+
const resourceList = Array.from(resourceSources).join(" ");
|
|
1893
|
+
directives.push(`img-src ${resourceList}`);
|
|
1894
|
+
directives.push(`font-src ${resourceList}`);
|
|
1895
|
+
directives.push(`media-src ${resourceList}`);
|
|
1896
|
+
return directives.join("; ");
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Paint fence responder script body.
|
|
1900
|
+
* Embedded in the generated HTML for scriptSrc mode. The sandbox proxy
|
|
1901
|
+
* handles fence injection for src-mode iframes.
|
|
1902
|
+
*/
|
|
1903
|
+
var PAINT_FENCE_SCRIPT = `window.addEventListener("message",function(e){
|
|
1904
|
+
if(e.data&&e.data.method==="sunpeak/fence"){
|
|
1905
|
+
var fid=e.data.params&&e.data.params.fenceId;
|
|
1906
|
+
requestAnimationFrame(function(){
|
|
1907
|
+
e.source.postMessage({jsonrpc:"2.0",method:"sunpeak/fence-ack",params:{fenceId:fid}},"*");
|
|
1908
|
+
});}});`;
|
|
1909
|
+
/**
|
|
1910
|
+
* Generates HTML wrapper for a script URL.
|
|
1911
|
+
* The MCP Apps SDK in the loaded script handles communication via PostMessageTransport.
|
|
1912
|
+
*/
|
|
1913
|
+
function generateScriptHtml(scriptSrc, theme, cspPolicy, platformScript) {
|
|
1914
|
+
const safeScriptSrc = escapeHtml(scriptSrc);
|
|
1915
|
+
const safeCsp = escapeHtml(cspPolicy);
|
|
1916
|
+
const safeTheme = escapeHtml(theme);
|
|
1917
|
+
return `<!DOCTYPE html>
|
|
1918
|
+
<html lang="en" data-theme="${safeTheme}" style="color-scheme:${safeTheme};background:Canvas">
|
|
1919
|
+
<head>
|
|
1920
|
+
<meta charset="UTF-8" />
|
|
1921
|
+
<meta name="color-scheme" content="${safeTheme}" />
|
|
1922
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1923
|
+
<meta http-equiv="Content-Security-Policy" content="${safeCsp}" />
|
|
1924
|
+
<title>Resource</title>
|
|
1925
|
+
<style>
|
|
1926
|
+
html {
|
|
1927
|
+
/* Use the MCP App background variable once JS sets it, otherwise
|
|
1928
|
+
Canvas (the system color that auto-adapts to color-scheme). */
|
|
1929
|
+
background-color: var(--color-background-primary, Canvas);
|
|
1930
|
+
}
|
|
1931
|
+
body, #root {
|
|
1932
|
+
margin: 0;
|
|
1933
|
+
padding: 0;
|
|
1934
|
+
width: 100%;
|
|
1935
|
+
background: transparent;
|
|
1936
|
+
}
|
|
1937
|
+
</style>
|
|
1938
|
+
<script>${PAINT_FENCE_SCRIPT}<\/script>${platformScript ? `\n <script>${platformScript}<\/script>` : ""}
|
|
1939
|
+
</head>
|
|
1940
|
+
<body>
|
|
1941
|
+
<div id="root"></div>
|
|
1942
|
+
<script src="${safeScriptSrc}"><\/script>
|
|
1943
|
+
</body>
|
|
1944
|
+
</html>`;
|
|
1945
|
+
}
|
|
1946
|
+
/**
|
|
1947
|
+
* Build the iframe `allow` attribute from resource-declared permissions.
|
|
1948
|
+
* Maps McpUiResourcePermissions to Permission Policy directives and
|
|
1949
|
+
* combines them with simulator baseline permissions.
|
|
1950
|
+
*/
|
|
1951
|
+
function buildIframeAllow(permissions) {
|
|
1952
|
+
const parts = ["local-network-access *"];
|
|
1953
|
+
for (const [key, directive] of [
|
|
1954
|
+
["camera", "camera"],
|
|
1955
|
+
["microphone", "microphone"],
|
|
1956
|
+
["geolocation", "geolocation"],
|
|
1957
|
+
["clipboardWrite", "clipboard-write"]
|
|
1958
|
+
]) if (permissions?.[key]) parts.push(directive);
|
|
1959
|
+
return parts.join("; ");
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* IframeResource renders MCP Apps in an iframe, communicating via the
|
|
1963
|
+
* MCP Apps protocol (PostMessageTransport + AppBridge).
|
|
1964
|
+
*
|
|
1965
|
+
* Supports two modes:
|
|
1966
|
+
* - `src`: Load an HTML page directly (dev mode with Vite HMR)
|
|
1967
|
+
* - `scriptSrc`: Generate HTML wrapper for a JS file (production builds)
|
|
1968
|
+
*
|
|
1969
|
+
* The loaded app uses the MCP Apps SDK's useApp() hook which automatically
|
|
1970
|
+
* connects via PostMessageTransport to window.parent. The parent side uses
|
|
1971
|
+
* McpAppHost (wrapping AppBridge) to communicate.
|
|
1972
|
+
*/
|
|
1973
|
+
function IframeResource({ src, scriptSrc, hostContext, toolInput, toolInputPartial, toolResult, hostOptions, csp, permissions, prefersBorder, className, style, onDisplayModeReady, debugInjectState, injectOpenAIRuntime, sandboxUrl }) {
|
|
1974
|
+
const iframeRef = (0, react.useRef)(null);
|
|
1975
|
+
const hostRef = (0, react.useRef)(null);
|
|
1976
|
+
const resourceUrl = src ?? scriptSrc;
|
|
1977
|
+
const srcRef = (0, react.useRef)(src);
|
|
1978
|
+
srcRef.current = src;
|
|
1979
|
+
const scriptSrcRef = (0, react.useRef)(scriptSrc);
|
|
1980
|
+
scriptSrcRef.current = scriptSrc;
|
|
1981
|
+
const cspRef = (0, react.useRef)(csp);
|
|
1982
|
+
cspRef.current = csp;
|
|
1983
|
+
const hostContextRef = (0, react.useRef)(hostContext);
|
|
1984
|
+
hostContextRef.current = hostContext;
|
|
1985
|
+
const injectOpenAIRuntimeRef = (0, react.useRef)(injectOpenAIRuntime);
|
|
1986
|
+
injectOpenAIRuntimeRef.current = injectOpenAIRuntime;
|
|
1987
|
+
const permissionsRef = (0, react.useRef)(permissions);
|
|
1988
|
+
permissionsRef.current = permissions;
|
|
1989
|
+
const hasReceivedSizeRef = (0, react.useRef)(false);
|
|
1990
|
+
const displayModeRef = (0, react.useRef)(hostContext?.displayMode);
|
|
1991
|
+
displayModeRef.current = hostContext?.displayMode;
|
|
1992
|
+
const lastContentHeightRef = (0, react.useRef)(null);
|
|
1993
|
+
const host = (0, react.useMemo)(() => new McpAppHost({
|
|
1994
|
+
hostContext,
|
|
1995
|
+
...hostOptions,
|
|
1996
|
+
onSizeChanged: (params) => {
|
|
1997
|
+
hostOptions?.onSizeChanged?.(params);
|
|
1998
|
+
if (params.height != null) lastContentHeightRef.current = params.height;
|
|
1999
|
+
if (displayModeRef.current === "fullscreen") return;
|
|
2000
|
+
const iframe = iframeRef.current;
|
|
2001
|
+
if (!iframe) return;
|
|
2002
|
+
const style = getComputedStyle(iframe);
|
|
2003
|
+
const isBorderBox = style.boxSizing === "border-box";
|
|
2004
|
+
const from = {};
|
|
2005
|
+
const to = {};
|
|
2006
|
+
if (params.width != null) {
|
|
2007
|
+
let w = params.width;
|
|
2008
|
+
if (isBorderBox) w += parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth);
|
|
2009
|
+
from.minWidth = `${iframe.offsetWidth}px`;
|
|
2010
|
+
iframe.style.minWidth = `min(${w}px, 100%)`;
|
|
2011
|
+
to.minWidth = `min(${w}px, 100%)`;
|
|
2012
|
+
}
|
|
2013
|
+
if (params.height != null) {
|
|
2014
|
+
hasReceivedSizeRef.current = true;
|
|
2015
|
+
let h = params.height;
|
|
2016
|
+
if (isBorderBox) h += parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
|
|
2017
|
+
from.height = `${iframe.offsetHeight}px`;
|
|
2018
|
+
iframe.style.height = `${h}px`;
|
|
2019
|
+
to.height = `${h}px`;
|
|
2020
|
+
}
|
|
2021
|
+
if (Object.keys(from).length > 0) iframe.animate([from, to], {
|
|
2022
|
+
duration: 300,
|
|
2023
|
+
easing: "ease-out"
|
|
2024
|
+
});
|
|
2025
|
+
},
|
|
2026
|
+
onDisplayModeReady: (mode) => onDisplayModeReady?.(mode),
|
|
2027
|
+
onSandboxReady: () => {
|
|
2028
|
+
const currentSrc = srcRef.current;
|
|
2029
|
+
const currentScriptSrc = scriptSrcRef.current;
|
|
2030
|
+
const currentHost = hostRef.current;
|
|
2031
|
+
if (!currentHost) return;
|
|
2032
|
+
if (currentScriptSrc) {
|
|
2033
|
+
const absoluteScriptSrc = currentScriptSrc.startsWith("/") ? `${window.location.origin}${currentScriptSrc}` : currentScriptSrc;
|
|
2034
|
+
const cspPolicy = generateCSP(cspRef.current, absoluteScriptSrc);
|
|
2035
|
+
const appHtml = generateScriptHtml(absoluteScriptSrc, hostContextRef.current?.theme ?? "dark", cspPolicy, injectOpenAIRuntimeRef.current ? MOCK_OPENAI_RUNTIME_SCRIPT : void 0);
|
|
2036
|
+
currentHost.sendSandboxResourceReady({
|
|
2037
|
+
html: appHtml,
|
|
2038
|
+
sandbox: "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
|
|
2039
|
+
});
|
|
2040
|
+
if (iframeRef.current) {
|
|
2041
|
+
iframeRef.current.style.opacity = "1";
|
|
2042
|
+
iframeRef.current.style.transition = "opacity 100ms";
|
|
2043
|
+
}
|
|
2044
|
+
} else if (currentSrc) {
|
|
2045
|
+
const absoluteSrc = currentSrc.startsWith("/") ? `${window.location.origin}${currentSrc}` : currentSrc;
|
|
2046
|
+
const allowAttr = buildIframeAllow(permissionsRef.current);
|
|
2047
|
+
currentHost.sendRawMessage({
|
|
2048
|
+
jsonrpc: "2.0",
|
|
2049
|
+
method: "sunpeak/sandbox-load-src",
|
|
2050
|
+
params: {
|
|
2051
|
+
src: absoluteSrc,
|
|
2052
|
+
allow: allowAttr,
|
|
2053
|
+
theme: hostContextRef.current?.theme,
|
|
2054
|
+
styleVars: hostContextRef.current?.styles?.variables
|
|
2055
|
+
}
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}), []);
|
|
2060
|
+
hostRef.current = host;
|
|
2061
|
+
const setIframeRef = (0, react.useCallback)((node) => {
|
|
2062
|
+
iframeRef.current = node;
|
|
2063
|
+
if (node?.contentWindow) host.connectToIframe(node.contentWindow);
|
|
2064
|
+
}, [host]);
|
|
2065
|
+
const handleLoad = (0, react.useCallback)(() => {
|
|
2066
|
+
if (iframeRef.current) {
|
|
2067
|
+
iframeRef.current.style.opacity = "1";
|
|
2068
|
+
iframeRef.current.style.transition = "opacity 100ms";
|
|
2069
|
+
}
|
|
2070
|
+
}, []);
|
|
2071
|
+
(0, react.useEffect)(() => {
|
|
2072
|
+
if (hostOptions) host.updateOptions(hostOptions);
|
|
2073
|
+
}, [host, hostOptions]);
|
|
2074
|
+
(0, react.useEffect)(() => {
|
|
2075
|
+
if (hostContext) host.setHostContext(hostContext);
|
|
2076
|
+
if (iframeRef.current) {
|
|
2077
|
+
if (hostContext?.displayMode === "fullscreen") iframeRef.current.style.height = "100%";
|
|
2078
|
+
else if (lastContentHeightRef.current != null) iframeRef.current.style.height = `${lastContentHeightRef.current}px`;
|
|
2079
|
+
}
|
|
2080
|
+
}, [host, hostContext]);
|
|
2081
|
+
(0, react.useEffect)(() => {
|
|
2082
|
+
if (toolInput) host.sendToolInput(toolInput);
|
|
2083
|
+
}, [host, toolInput]);
|
|
2084
|
+
(0, react.useEffect)(() => {
|
|
2085
|
+
if (toolInputPartial) host.sendToolInputPartial(toolInputPartial);
|
|
2086
|
+
}, [host, toolInputPartial]);
|
|
2087
|
+
(0, react.useEffect)(() => {
|
|
2088
|
+
if (toolResult) host.sendToolResult(toolResult);
|
|
2089
|
+
}, [host, toolResult]);
|
|
2090
|
+
(0, react.useEffect)(() => {
|
|
2091
|
+
if (debugInjectState != null) host.injectState(debugInjectState);
|
|
2092
|
+
}, [host, debugInjectState]);
|
|
2093
|
+
(0, react.useEffect)(() => {
|
|
2094
|
+
return () => {
|
|
2095
|
+
hostRef.current?.close();
|
|
2096
|
+
};
|
|
2097
|
+
}, []);
|
|
2098
|
+
const isValidUrl = (0, react.useMemo)(() => resourceUrl && isAllowedUrl(resourceUrl), [resourceUrl]);
|
|
2099
|
+
const allowAttribute = (0, react.useMemo)(() => buildIframeAllow(permissions), [permissions]);
|
|
2100
|
+
const borderStyle = prefersBorder ? { border: "1px solid var(--color-border-primary, #e5e7eb)" } : { border: "none" };
|
|
2101
|
+
const sandboxSrc = (0, react.useMemo)(() => {
|
|
2102
|
+
if (!sandboxUrl) return void 0;
|
|
2103
|
+
const url = new URL("/proxy", sandboxUrl);
|
|
2104
|
+
if (injectOpenAIRuntime) url.searchParams.set("platform", "chatgpt");
|
|
2105
|
+
return url.toString();
|
|
2106
|
+
}, [sandboxUrl, injectOpenAIRuntime]);
|
|
2107
|
+
const proxyHtml = (0, react.useMemo)(() => {
|
|
2108
|
+
if (sandboxSrc) return void 0;
|
|
2109
|
+
return generateSandboxProxyHtml(injectOpenAIRuntime ? MOCK_OPENAI_RUNTIME_SCRIPT : void 0);
|
|
2110
|
+
}, [sandboxSrc, injectOpenAIRuntime]);
|
|
2111
|
+
const iframeStyle = {
|
|
2112
|
+
...borderStyle,
|
|
2113
|
+
background: "transparent",
|
|
2114
|
+
colorScheme: hostContext?.theme === "light" ? "light dark" : "dark light",
|
|
2115
|
+
opacity: 0,
|
|
2116
|
+
width: "100%",
|
|
2117
|
+
...hostContext?.displayMode === "fullscreen" ? { height: "100%" } : { minHeight: "200px" },
|
|
2118
|
+
...style
|
|
2119
|
+
};
|
|
2120
|
+
const dims = hostContext?.containerDimensions;
|
|
2121
|
+
const isFullscreenMode = hostContext?.displayMode === "fullscreen";
|
|
2122
|
+
const wrapperStyle = {};
|
|
2123
|
+
if (dims && !isFullscreenMode) {
|
|
2124
|
+
const h = "height" in dims ? dims.height : void 0;
|
|
2125
|
+
const mh = "maxHeight" in dims ? dims.maxHeight : void 0;
|
|
2126
|
+
const w = "width" in dims ? dims.width : void 0;
|
|
2127
|
+
const mw = "maxWidth" in dims ? dims.maxWidth : void 0;
|
|
2128
|
+
if (h != null) {
|
|
2129
|
+
wrapperStyle.height = h;
|
|
2130
|
+
wrapperStyle.overflow = "hidden";
|
|
2131
|
+
}
|
|
2132
|
+
if (mh != null) {
|
|
2133
|
+
wrapperStyle.maxHeight = mh;
|
|
2134
|
+
wrapperStyle.overflow = "hidden";
|
|
2135
|
+
}
|
|
2136
|
+
if (w != null) wrapperStyle.width = w;
|
|
2137
|
+
if (mw != null) wrapperStyle.maxWidth = mw;
|
|
2138
|
+
}
|
|
2139
|
+
if (src && !isValidUrl) {
|
|
2140
|
+
console.error("[IframeResource] URL not allowed:", src);
|
|
2141
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2142
|
+
style: {
|
|
2143
|
+
color: "red",
|
|
2144
|
+
padding: 20
|
|
2145
|
+
},
|
|
2146
|
+
children: ["Error: URL not allowed: ", src]
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
if (scriptSrc && !isValidUrl) {
|
|
2150
|
+
console.error("[IframeResource] Script source not allowed:", scriptSrc);
|
|
2151
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2152
|
+
style: {
|
|
2153
|
+
color: "red",
|
|
2154
|
+
padding: 20
|
|
2155
|
+
},
|
|
2156
|
+
children: "Error: Script source not allowed."
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2160
|
+
className,
|
|
2161
|
+
style: wrapperStyle,
|
|
2162
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
|
|
2163
|
+
ref: setIframeRef,
|
|
2164
|
+
onLoad: handleLoad,
|
|
2165
|
+
style: iframeStyle,
|
|
2166
|
+
title: "Resource Preview",
|
|
2167
|
+
sandbox: "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox",
|
|
2168
|
+
allow: allowAttribute,
|
|
2169
|
+
src: sandboxSrc ?? void 0,
|
|
2170
|
+
srcDoc: sandboxSrc ? void 0 : proxyHtml
|
|
2171
|
+
})
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
//#endregion
|
|
2175
|
+
//#region src/simulator/use-simulator-state.ts
|
|
2176
|
+
var DEFAULT_THEME = "dark";
|
|
2177
|
+
var DEFAULT_DISPLAY_MODE = "inline";
|
|
2178
|
+
var DEFAULT_PLATFORM = "desktop";
|
|
2179
|
+
/**
|
|
2180
|
+
* Parse URL params for initial simulator values.
|
|
2181
|
+
* Supported params:
|
|
2182
|
+
* - simulation: simulation name (e.g., 'show-albums')
|
|
2183
|
+
* - theme: 'light' | 'dark'
|
|
2184
|
+
* - displayMode: 'inline' | 'pip' | 'fullscreen'
|
|
2185
|
+
* - locale: e.g., 'en-US'
|
|
2186
|
+
* - maxHeight: number (containerDimensions.maxHeight)
|
|
2187
|
+
* - maxWidth: number (containerDimensions.maxWidth)
|
|
2188
|
+
* - deviceType: 'mobile' | 'tablet' | 'desktop' → maps to platform
|
|
2189
|
+
* - hover: 'true' | 'false'
|
|
2190
|
+
* - touch: 'true' | 'false'
|
|
2191
|
+
* - safeAreaTop, safeAreaBottom, safeAreaLeft, safeAreaRight: number
|
|
2192
|
+
* - host: 'chatgpt' | 'claude'
|
|
2193
|
+
* - prodTools: 'true' | 'false'
|
|
2194
|
+
* - prodResources: 'true' | 'false'
|
|
2195
|
+
*/
|
|
2196
|
+
function parseUrlParams() {
|
|
2197
|
+
if (typeof window === "undefined") return {};
|
|
2198
|
+
const params = new URLSearchParams(window.location.search);
|
|
2199
|
+
const simulation = params.get("simulation") ?? void 0;
|
|
2200
|
+
const theme = params.get("theme");
|
|
2201
|
+
const displayMode = params.get("displayMode");
|
|
2202
|
+
const locale = params.get("locale");
|
|
2203
|
+
const maxHeightParam = params.get("maxHeight");
|
|
2204
|
+
const containerMaxHeight = maxHeightParam ? Number(maxHeightParam) : void 0;
|
|
2205
|
+
const maxWidthParam = params.get("maxWidth");
|
|
2206
|
+
const containerMaxWidth = maxWidthParam ? Number(maxWidthParam) : void 0;
|
|
2207
|
+
const host = params.get("host") ?? void 0;
|
|
2208
|
+
const prodToolsParam = params.get("prodTools");
|
|
2209
|
+
const prodTools = prodToolsParam === "true" ? true : prodToolsParam === "false" ? false : void 0;
|
|
2210
|
+
const prodResourcesParam = params.get("prodResources");
|
|
2211
|
+
const prodResources = prodResourcesParam === "true" ? true : prodResourcesParam === "false" ? false : void 0;
|
|
2212
|
+
const deviceType = params.get("deviceType");
|
|
2213
|
+
let platform;
|
|
2214
|
+
if (deviceType === "mobile" || deviceType === "tablet") platform = "mobile";
|
|
2215
|
+
else if (deviceType === "desktop") platform = "desktop";
|
|
2216
|
+
const hoverParam = params.get("hover");
|
|
2217
|
+
const touchParam = params.get("touch");
|
|
2218
|
+
const deviceCapabilities = hoverParam || touchParam ? {
|
|
2219
|
+
hover: hoverParam === "false" ? false : true,
|
|
2220
|
+
touch: touchParam === "true" ? true : false
|
|
2221
|
+
} : void 0;
|
|
2222
|
+
const safeAreaTop = params.get("safeAreaTop");
|
|
2223
|
+
const safeAreaBottom = params.get("safeAreaBottom");
|
|
2224
|
+
const safeAreaLeft = params.get("safeAreaLeft");
|
|
2225
|
+
const safeAreaRight = params.get("safeAreaRight");
|
|
2226
|
+
const safeAreaInsets = safeAreaTop || safeAreaBottom || safeAreaLeft || safeAreaRight ? {
|
|
2227
|
+
top: safeAreaTop ? Number(safeAreaTop) : 0,
|
|
2228
|
+
bottom: safeAreaBottom ? Number(safeAreaBottom) : 0,
|
|
2229
|
+
left: safeAreaLeft ? Number(safeAreaLeft) : 0,
|
|
2230
|
+
right: safeAreaRight ? Number(safeAreaRight) : 0
|
|
2231
|
+
} : void 0;
|
|
2232
|
+
return {
|
|
2233
|
+
simulation,
|
|
2234
|
+
theme: theme ?? void 0,
|
|
2235
|
+
displayMode: displayMode ?? void 0,
|
|
2236
|
+
locale: locale ?? void 0,
|
|
2237
|
+
containerMaxHeight,
|
|
2238
|
+
containerMaxWidth,
|
|
2239
|
+
platform,
|
|
2240
|
+
deviceCapabilities,
|
|
2241
|
+
safeAreaInsets,
|
|
2242
|
+
host: host ?? void 0,
|
|
2243
|
+
prodTools,
|
|
2244
|
+
prodResources
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
function useSimulatorState({ simulations, defaultHost = "chatgpt" }) {
|
|
2248
|
+
const simulationNames = Object.keys(simulations).filter((name) => simulations[name].resource).sort((a, b) => {
|
|
2249
|
+
const simA = simulations[a];
|
|
2250
|
+
const simB = simulations[b];
|
|
2251
|
+
const resourceLabelA = simA.resource.title || simA.resource.name;
|
|
2252
|
+
const resourceLabelB = simB.resource.title || simB.resource.name;
|
|
2253
|
+
const labelA = `${resourceLabelA}: ${simA.tool.title || simA.tool.name}`;
|
|
2254
|
+
const labelB = `${resourceLabelB}: ${simB.tool.title || simB.tool.name}`;
|
|
2255
|
+
return labelA.localeCompare(labelB);
|
|
2256
|
+
});
|
|
2257
|
+
const urlParams = (0, react.useMemo)(() => parseUrlParams(), []);
|
|
2258
|
+
const [screenWidth, setScreenWidth] = (0, react.useState)("full");
|
|
2259
|
+
const isMobileWidth = (width) => width === "mobile-s" || width === "mobile-l";
|
|
2260
|
+
const [activeHost, setActiveHost] = (0, react.useState)(urlParams.host ?? defaultHost);
|
|
2261
|
+
const [selectedSimulationName, setSelectedSimulationName] = (0, react.useState)((0, react.useMemo)(() => {
|
|
2262
|
+
const defaultName = simulationNames[0] ?? "";
|
|
2263
|
+
if (!urlParams.simulation) return defaultName;
|
|
2264
|
+
return urlParams.simulation in simulations ? urlParams.simulation : defaultName;
|
|
2265
|
+
}, [
|
|
2266
|
+
urlParams.simulation,
|
|
2267
|
+
simulations,
|
|
2268
|
+
simulationNames
|
|
2269
|
+
]));
|
|
2270
|
+
const selectedSim = simulations[selectedSimulationName];
|
|
2271
|
+
const [theme, setTheme] = (0, react.useState)(urlParams.theme ?? DEFAULT_THEME);
|
|
2272
|
+
const [displayMode, _setDisplayMode] = (0, react.useState)(urlParams.displayMode ?? DEFAULT_DISPLAY_MODE);
|
|
2273
|
+
const [locale, setLocale] = (0, react.useState)(urlParams.locale ?? "en-US");
|
|
2274
|
+
const [containerHeight, setContainerHeight] = (0, react.useState)(void 0);
|
|
2275
|
+
const [containerWidth, setContainerWidth] = (0, react.useState)(void 0);
|
|
2276
|
+
const [containerMaxHeight, setContainerMaxHeight] = (0, react.useState)(urlParams.containerMaxHeight);
|
|
2277
|
+
const [containerMaxWidth, setContainerMaxWidth] = (0, react.useState)(urlParams.containerMaxWidth);
|
|
2278
|
+
const [platform, setPlatform] = (0, react.useState)(urlParams.platform ?? DEFAULT_PLATFORM);
|
|
2279
|
+
const [hover, setHover] = (0, react.useState)(urlParams.deviceCapabilities?.hover ?? true);
|
|
2280
|
+
const [touch, setTouch] = (0, react.useState)(urlParams.deviceCapabilities?.touch ?? false);
|
|
2281
|
+
const [safeAreaInsets, setSafeAreaInsets] = (0, react.useState)(urlParams.safeAreaInsets ?? {
|
|
2282
|
+
top: 0,
|
|
2283
|
+
bottom: 0,
|
|
2284
|
+
left: 0,
|
|
2285
|
+
right: 0
|
|
2286
|
+
});
|
|
2287
|
+
const [timeZone, setTimeZone] = (0, react.useState)(() => Intl.DateTimeFormat().resolvedOptions().timeZone);
|
|
2288
|
+
const [measuredContentWidth, setMeasuredContentWidth] = (0, react.useState)(void 0);
|
|
2289
|
+
const handleContentWidthChange = (0, react.useCallback)((width) => {
|
|
2290
|
+
setMeasuredContentWidth(width);
|
|
2291
|
+
}, []);
|
|
2292
|
+
const setDisplayMode = (mode) => {
|
|
2293
|
+
if (isMobileWidth(screenWidth) && mode === "pip") _setDisplayMode("fullscreen");
|
|
2294
|
+
else _setDisplayMode(mode);
|
|
2295
|
+
};
|
|
2296
|
+
const handleDisplayModeReady = (0, react.useCallback)((_mode) => {}, []);
|
|
2297
|
+
const containerDimensions = (0, react.useMemo)(() => {
|
|
2298
|
+
if (containerHeight != null || containerWidth != null || containerMaxHeight != null || containerMaxWidth != null) return {
|
|
2299
|
+
...containerHeight != null ? { height: containerHeight } : {},
|
|
2300
|
+
...containerWidth != null ? { width: containerWidth } : {},
|
|
2301
|
+
...containerMaxHeight != null ? { maxHeight: containerMaxHeight } : {},
|
|
2302
|
+
...containerMaxWidth != null ? { maxWidth: containerMaxWidth } : {}
|
|
2303
|
+
};
|
|
2304
|
+
if (displayMode === "fullscreen") return {
|
|
2305
|
+
height: typeof window !== "undefined" ? window.innerHeight - 52 : 800,
|
|
2306
|
+
width: measuredContentWidth ?? (typeof window !== "undefined" ? window.innerWidth : 1280)
|
|
2307
|
+
};
|
|
2308
|
+
if (displayMode === "pip") return {
|
|
2309
|
+
height: 480,
|
|
2310
|
+
...measuredContentWidth != null ? { maxWidth: measuredContentWidth } : {}
|
|
2311
|
+
};
|
|
2312
|
+
if (measuredContentWidth != null) return { maxWidth: measuredContentWidth };
|
|
2313
|
+
}, [
|
|
2314
|
+
containerHeight,
|
|
2315
|
+
containerWidth,
|
|
2316
|
+
containerMaxHeight,
|
|
2317
|
+
containerMaxWidth,
|
|
2318
|
+
measuredContentWidth,
|
|
2319
|
+
displayMode
|
|
2320
|
+
]);
|
|
2321
|
+
const hostContext = (0, react.useMemo)(() => ({
|
|
2322
|
+
theme,
|
|
2323
|
+
displayMode,
|
|
2324
|
+
availableDisplayModes: [
|
|
2325
|
+
"inline",
|
|
2326
|
+
"pip",
|
|
2327
|
+
"fullscreen"
|
|
2328
|
+
],
|
|
2329
|
+
locale,
|
|
2330
|
+
timeZone,
|
|
2331
|
+
platform,
|
|
2332
|
+
deviceCapabilities: {
|
|
2333
|
+
hover,
|
|
2334
|
+
touch
|
|
2335
|
+
},
|
|
2336
|
+
safeAreaInsets,
|
|
2337
|
+
...containerDimensions ? { containerDimensions } : {}
|
|
2338
|
+
}), [
|
|
2339
|
+
theme,
|
|
2340
|
+
displayMode,
|
|
2341
|
+
locale,
|
|
2342
|
+
timeZone,
|
|
2343
|
+
platform,
|
|
2344
|
+
hover,
|
|
2345
|
+
touch,
|
|
2346
|
+
safeAreaInsets,
|
|
2347
|
+
containerDimensions
|
|
2348
|
+
]);
|
|
2349
|
+
const [toolInput, setToolInput] = (0, react.useState)(() => selectedSim?.toolInput ?? {});
|
|
2350
|
+
const [toolResult, setToolResult] = (0, react.useState)(() => selectedSim?.toolResult);
|
|
2351
|
+
const [toolInputJson, setToolInputJson] = (0, react.useState)(() => JSON.stringify(toolInput, null, 2));
|
|
2352
|
+
const [toolResultJson, setToolResultJson] = (0, react.useState)(() => JSON.stringify(toolResult ?? null, null, 2));
|
|
2353
|
+
const [modelContextJson, setModelContextJson] = (0, react.useState)("null");
|
|
2354
|
+
const [modelContext, setModelContext] = (0, react.useState)(null);
|
|
2355
|
+
const [editingField, setEditingField] = (0, react.useState)(null);
|
|
2356
|
+
const [toolInputError, setToolInputError] = (0, react.useState)("");
|
|
2357
|
+
const [toolResultError, setToolResultError] = (0, react.useState)("");
|
|
2358
|
+
const [modelContextError, setModelContextError] = (0, react.useState)("");
|
|
2359
|
+
(0, react.useEffect)(() => {
|
|
2360
|
+
const newInput = selectedSim?.toolInput ?? {};
|
|
2361
|
+
const newResult = selectedSim?.toolResult ?? void 0;
|
|
2362
|
+
setToolInput(newInput);
|
|
2363
|
+
setToolResult(newResult);
|
|
2364
|
+
if (editingField !== "toolInput") {
|
|
2365
|
+
setToolInputJson(JSON.stringify(newInput, null, 2));
|
|
2366
|
+
setToolInputError("");
|
|
2367
|
+
}
|
|
2368
|
+
if (editingField !== "toolResult") {
|
|
2369
|
+
setToolResultJson(JSON.stringify(newResult ?? null, null, 2));
|
|
2370
|
+
setToolResultError("");
|
|
2371
|
+
}
|
|
2372
|
+
if (editingField !== "modelContext") {
|
|
2373
|
+
setModelContextJson("null");
|
|
2374
|
+
setModelContext(null);
|
|
2375
|
+
setModelContextError("");
|
|
2376
|
+
}
|
|
2377
|
+
}, [selectedSimulationName, selectedSim]);
|
|
2378
|
+
(0, react.useEffect)(() => {
|
|
2379
|
+
if (isMobileWidth(screenWidth) && displayMode === "pip") _setDisplayMode("fullscreen");
|
|
2380
|
+
}, [screenWidth, displayMode]);
|
|
2381
|
+
const handleDisplayModeChange = (mode) => {
|
|
2382
|
+
setDisplayMode(mode);
|
|
2383
|
+
};
|
|
2384
|
+
const handleUpdateModelContext = (content, structuredContent) => {
|
|
2385
|
+
setModelContextJson(JSON.stringify(structuredContent ?? content, null, 2));
|
|
2386
|
+
};
|
|
2387
|
+
const validateJSON = (json, setJson, setError) => {
|
|
2388
|
+
setJson(json);
|
|
2389
|
+
try {
|
|
2390
|
+
if (json.trim() !== "") JSON.parse(json);
|
|
2391
|
+
setError("");
|
|
2392
|
+
} catch (e) {
|
|
2393
|
+
setError(e instanceof Error ? e.message : "Invalid JSON");
|
|
2394
|
+
}
|
|
2395
|
+
};
|
|
2396
|
+
const commitJSON = (json, setError, updateFn) => {
|
|
2397
|
+
try {
|
|
2398
|
+
const parsed = json.trim() === "" ? null : JSON.parse(json);
|
|
2399
|
+
setError("");
|
|
2400
|
+
updateFn(parsed);
|
|
2401
|
+
} catch (e) {
|
|
2402
|
+
setError(e instanceof Error ? e.message : "Invalid JSON");
|
|
2403
|
+
} finally {
|
|
2404
|
+
setEditingField(null);
|
|
2405
|
+
}
|
|
2406
|
+
};
|
|
2407
|
+
const effectiveToolResult = (0, react.useMemo)(() => {
|
|
2408
|
+
if (!toolResult && !modelContext) return void 0;
|
|
2409
|
+
if (!modelContext) return toolResult;
|
|
2410
|
+
const baseResult = toolResult ?? { content: [] };
|
|
2411
|
+
const baseStructured = baseResult.structuredContent ?? {};
|
|
2412
|
+
return {
|
|
2413
|
+
...baseResult,
|
|
2414
|
+
structuredContent: {
|
|
2415
|
+
...baseStructured,
|
|
2416
|
+
...modelContext
|
|
2417
|
+
}
|
|
2418
|
+
};
|
|
2419
|
+
}, [toolResult, modelContext]);
|
|
2420
|
+
const resourceUrl = selectedSim?.resourceUrl;
|
|
2421
|
+
const resourceScript = selectedSim?.resourceScript;
|
|
2422
|
+
const csp = selectedSim?.resource ? extractResourceCSP(selectedSim.resource) : void 0;
|
|
2423
|
+
const resourceMeta = (selectedSim?.resource?._meta)?.ui;
|
|
2424
|
+
return {
|
|
2425
|
+
simulationNames,
|
|
2426
|
+
selectedSimulationName,
|
|
2427
|
+
setSelectedSimulationName,
|
|
2428
|
+
selectedSim,
|
|
2429
|
+
activeHost,
|
|
2430
|
+
setActiveHost,
|
|
2431
|
+
screenWidth,
|
|
2432
|
+
setScreenWidth,
|
|
2433
|
+
theme,
|
|
2434
|
+
setTheme,
|
|
2435
|
+
displayMode,
|
|
2436
|
+
setDisplayMode,
|
|
2437
|
+
locale,
|
|
2438
|
+
setLocale,
|
|
2439
|
+
containerHeight,
|
|
2440
|
+
setContainerHeight,
|
|
2441
|
+
containerWidth,
|
|
2442
|
+
setContainerWidth,
|
|
2443
|
+
containerMaxHeight,
|
|
2444
|
+
setContainerMaxHeight,
|
|
2445
|
+
containerMaxWidth,
|
|
2446
|
+
setContainerMaxWidth,
|
|
2447
|
+
platform,
|
|
2448
|
+
setPlatform,
|
|
2449
|
+
hover,
|
|
2450
|
+
setHover,
|
|
2451
|
+
touch,
|
|
2452
|
+
setTouch,
|
|
2453
|
+
safeAreaInsets,
|
|
2454
|
+
setSafeAreaInsets,
|
|
2455
|
+
timeZone,
|
|
2456
|
+
setTimeZone,
|
|
2457
|
+
hostContext,
|
|
2458
|
+
handleDisplayModeReady,
|
|
2459
|
+
toolInput,
|
|
2460
|
+
setToolInput,
|
|
2461
|
+
toolResult,
|
|
2462
|
+
setToolResult,
|
|
2463
|
+
effectiveToolResult,
|
|
2464
|
+
modelContext,
|
|
2465
|
+
setModelContext,
|
|
2466
|
+
toolInputJson,
|
|
2467
|
+
setToolInputJson,
|
|
2468
|
+
toolInputError,
|
|
2469
|
+
setToolInputError,
|
|
2470
|
+
toolResultJson,
|
|
2471
|
+
setToolResultJson,
|
|
2472
|
+
toolResultError,
|
|
2473
|
+
setToolResultError,
|
|
2474
|
+
modelContextJson,
|
|
2475
|
+
setModelContextJson,
|
|
2476
|
+
modelContextError,
|
|
2477
|
+
setModelContextError,
|
|
2478
|
+
editingField,
|
|
2479
|
+
setEditingField,
|
|
2480
|
+
validateJSON,
|
|
2481
|
+
commitJSON,
|
|
2482
|
+
measuredContentWidth,
|
|
2483
|
+
handleContentWidthChange,
|
|
2484
|
+
handleDisplayModeChange,
|
|
2485
|
+
handleUpdateModelContext,
|
|
2486
|
+
resourceUrl,
|
|
2487
|
+
resourceScript,
|
|
2488
|
+
csp,
|
|
2489
|
+
permissions: resourceMeta?.permissions,
|
|
2490
|
+
prefersBorder: resourceMeta?.prefersBorder ?? false,
|
|
2491
|
+
urlProdTools: urlParams.prodTools,
|
|
2492
|
+
urlProdResources: urlParams.prodResources
|
|
2493
|
+
};
|
|
2494
|
+
}
|
|
2495
|
+
//#endregion
|
|
2496
|
+
//#region src/simulator/theme-provider.tsx
|
|
2497
|
+
var ThemeProviderContext = react.createContext(void 0);
|
|
2498
|
+
/** Default theme applier: sets data-theme attribute on document.documentElement */
|
|
2499
|
+
function defaultApplyTheme(theme) {
|
|
2500
|
+
if (typeof document !== "undefined") document.documentElement.setAttribute("data-theme", theme);
|
|
2501
|
+
}
|
|
2502
|
+
function ThemeProvider({ children, defaultTheme = "light", theme: controlledTheme, applyTheme, ...props }) {
|
|
2503
|
+
const [internalTheme] = react.useState(defaultTheme);
|
|
2504
|
+
const theme = controlledTheme ?? internalTheme;
|
|
2505
|
+
const applier = applyTheme ?? defaultApplyTheme;
|
|
2506
|
+
react.useLayoutEffect(() => {
|
|
2507
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") try {
|
|
2508
|
+
applier(theme);
|
|
2509
|
+
} catch (error) {
|
|
2510
|
+
console.warn("Failed to apply document theme:", error);
|
|
2511
|
+
}
|
|
2512
|
+
}, [theme, applier]);
|
|
2513
|
+
const value = { theme };
|
|
2514
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThemeProviderContext.Provider, {
|
|
2515
|
+
...props,
|
|
2516
|
+
value,
|
|
2517
|
+
children
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
var useThemeContext = () => {
|
|
2521
|
+
const context = react.useContext(ThemeProviderContext);
|
|
2522
|
+
if (context === void 0) throw new Error("useThemeContext must be used within a ThemeProvider");
|
|
2523
|
+
return context;
|
|
2524
|
+
};
|
|
2525
|
+
//#endregion
|
|
2526
|
+
//#region src/simulator/simple-sidebar.tsx
|
|
2527
|
+
var DEFAULT_SIDEBAR_WIDTH = 224;
|
|
2528
|
+
function ChevronRightIcon() {
|
|
2529
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2530
|
+
width: "1em",
|
|
2531
|
+
height: "1em",
|
|
2532
|
+
viewBox: "0 0 24 24",
|
|
2533
|
+
fill: "currentColor",
|
|
2534
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
2535
|
+
fillRule: "evenodd",
|
|
2536
|
+
d: "M8.293 4.293a1 1 0 0 1 1.414 0l7 7a1 1 0 0 1 0 1.414l-7 7a1 1 0 0 1-1.414-1.414L14.586 12 8.293 5.707a1 1 0 0 1 0-1.414Z",
|
|
2537
|
+
clipRule: "evenodd"
|
|
2538
|
+
})
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
function SimpleSidebar({ children, controls, headerRight }) {
|
|
2542
|
+
const [isDrawerOpen, setIsDrawerOpen] = react.useState(false);
|
|
2543
|
+
const [sidebarWidth, setSidebarWidth] = react.useState(DEFAULT_SIDEBAR_WIDTH);
|
|
2544
|
+
const [isResizing, setIsResizing] = react.useState(false);
|
|
2545
|
+
const handleMouseDown = react.useCallback((e) => {
|
|
2546
|
+
e.preventDefault();
|
|
2547
|
+
setIsResizing(true);
|
|
2548
|
+
}, []);
|
|
2549
|
+
react.useEffect(() => {
|
|
2550
|
+
if (!isResizing) return;
|
|
2551
|
+
const handleMouseMove = (e) => {
|
|
2552
|
+
const maxWidth = Math.floor(window.innerWidth / 3);
|
|
2553
|
+
setSidebarWidth(Math.min(maxWidth, Math.max(DEFAULT_SIDEBAR_WIDTH, e.clientX)));
|
|
2554
|
+
};
|
|
2555
|
+
const handleMouseUp = () => {
|
|
2556
|
+
setIsResizing(false);
|
|
2557
|
+
};
|
|
2558
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
2559
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
2560
|
+
return () => {
|
|
2561
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
2562
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
2563
|
+
};
|
|
2564
|
+
}, [isResizing]);
|
|
2565
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2566
|
+
className: "sunpeak-simulator-root flex h-screen w-full overflow-hidden relative",
|
|
2567
|
+
children: [
|
|
2568
|
+
isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "fixed inset-0 z-50 cursor-col-resize" }),
|
|
2569
|
+
isDrawerOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2570
|
+
className: "md:hidden fixed inset-0 bg-black/50 z-40 pointer-events-auto",
|
|
2571
|
+
onClick: (e) => {
|
|
2572
|
+
if (e.target === e.currentTarget) setIsDrawerOpen(false);
|
|
2573
|
+
}
|
|
2574
|
+
}),
|
|
2575
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
2576
|
+
className: `
|
|
2577
|
+
relative flex flex-col bg-sidebar
|
|
2578
|
+
md:z-auto
|
|
2579
|
+
max-md:fixed max-md:inset-y-0 max-md:left-0 max-md:z-[100]
|
|
2580
|
+
max-md:transition-transform max-md:duration-300 max-md:!w-2/3
|
|
2581
|
+
${isDrawerOpen ? "max-md:translate-x-0" : "max-md:-translate-x-full"}
|
|
2582
|
+
`,
|
|
2583
|
+
style: {
|
|
2584
|
+
width: sidebarWidth,
|
|
2585
|
+
borderRight: "1px solid var(--color-border-primary)"
|
|
2586
|
+
},
|
|
2587
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2588
|
+
className: "flex-1 overflow-y-auto min-h-0 px-3 pb-3 pt-0",
|
|
2589
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2590
|
+
className: "space-y-3",
|
|
2591
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2592
|
+
className: "flex items-center justify-between sticky top-0 bg-sidebar z-10 py-2",
|
|
2593
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
2594
|
+
className: "text-xs font-semibold",
|
|
2595
|
+
style: { color: "var(--color-text-primary)" },
|
|
2596
|
+
children: "Controls"
|
|
2597
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2598
|
+
className: "flex items-center gap-2",
|
|
2599
|
+
children: [headerRight, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2600
|
+
onClick: () => setIsDrawerOpen(false),
|
|
2601
|
+
className: "md:hidden p-1 transition-colors",
|
|
2602
|
+
style: { color: "var(--color-text-secondary)" },
|
|
2603
|
+
type: "button",
|
|
2604
|
+
"aria-label": "Close sidebar",
|
|
2605
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2606
|
+
width: "16",
|
|
2607
|
+
height: "16",
|
|
2608
|
+
viewBox: "0 0 16 16",
|
|
2609
|
+
fill: "none",
|
|
2610
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2611
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
2612
|
+
d: "M12 4L4 12M4 4L12 12",
|
|
2613
|
+
stroke: "currentColor",
|
|
2614
|
+
strokeWidth: "2",
|
|
2615
|
+
strokeLinecap: "round"
|
|
2616
|
+
})
|
|
2617
|
+
})
|
|
2618
|
+
})]
|
|
2619
|
+
})]
|
|
2620
|
+
}), controls] })
|
|
2621
|
+
})
|
|
2622
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2623
|
+
onMouseDown: handleMouseDown,
|
|
2624
|
+
className: "hidden md:block absolute top-0 right-0 w-1 h-full cursor-col-resize hover:bg-black/10 dark:hover:bg-white/10 active:bg-black/20 dark:active:bg-white/20 transition-colors"
|
|
2625
|
+
})]
|
|
2626
|
+
}),
|
|
2627
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
|
|
2628
|
+
className: "flex-1 overflow-auto relative",
|
|
2629
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2630
|
+
onClick: () => setIsDrawerOpen(true),
|
|
2631
|
+
className: "md:hidden fixed top-18 left-0 z-30 bg-sidebar rounded-r-md p-2 shadow-lg hover:bg-black/5 dark:hover:bg-white/10 transition-colors",
|
|
2632
|
+
style: {
|
|
2633
|
+
borderRight: "1px solid var(--color-border-primary)",
|
|
2634
|
+
borderTop: "1px solid var(--color-border-primary)",
|
|
2635
|
+
borderBottom: "1px solid var(--color-border-primary)"
|
|
2636
|
+
},
|
|
2637
|
+
type: "button",
|
|
2638
|
+
"aria-label": "Open sidebar",
|
|
2639
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronRightIcon, {})
|
|
2640
|
+
}), children]
|
|
2641
|
+
})
|
|
2642
|
+
]
|
|
2643
|
+
});
|
|
2644
|
+
}
|
|
2645
|
+
var DOCS_BASE_URL = "https://sunpeak.ai/docs";
|
|
2646
|
+
function HelpIcon({ tooltip, docsPath }) {
|
|
2647
|
+
const [pos, setPos] = react.useState(null);
|
|
2648
|
+
const ref = react.useRef(null);
|
|
2649
|
+
const showTooltip = () => {
|
|
2650
|
+
const rect = ref.current?.getBoundingClientRect();
|
|
2651
|
+
if (rect) setPos({
|
|
2652
|
+
top: rect.top + rect.height / 2,
|
|
2653
|
+
left: rect.right + 6
|
|
2654
|
+
});
|
|
2655
|
+
};
|
|
2656
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
|
|
2657
|
+
ref,
|
|
2658
|
+
href: `${DOCS_BASE_URL}/${docsPath}`,
|
|
2659
|
+
target: "_blank",
|
|
2660
|
+
rel: "noopener noreferrer",
|
|
2661
|
+
className: "inline-flex items-center justify-center no-underline flex-shrink-0 transition-colors",
|
|
2662
|
+
style: { color: "var(--color-text-tertiary, var(--color-text-secondary))" },
|
|
2663
|
+
onClick: (e) => e.stopPropagation(),
|
|
2664
|
+
onMouseEnter: showTooltip,
|
|
2665
|
+
onMouseLeave: () => setPos(null),
|
|
2666
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
2667
|
+
width: "12",
|
|
2668
|
+
height: "12",
|
|
2669
|
+
viewBox: "0 0 12 12",
|
|
2670
|
+
fill: "none",
|
|
2671
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2672
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
2673
|
+
cx: "6",
|
|
2674
|
+
cy: "6",
|
|
2675
|
+
r: "5.25",
|
|
2676
|
+
stroke: "currentColor",
|
|
2677
|
+
strokeWidth: "1"
|
|
2678
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
|
|
2679
|
+
x: "6.25",
|
|
2680
|
+
y: "8.5",
|
|
2681
|
+
textAnchor: "middle",
|
|
2682
|
+
fill: "currentColor",
|
|
2683
|
+
fontSize: "7.5",
|
|
2684
|
+
fontWeight: "600",
|
|
2685
|
+
fontFamily: "system-ui, sans-serif",
|
|
2686
|
+
children: "?"
|
|
2687
|
+
})]
|
|
2688
|
+
}), pos && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2689
|
+
className: "pointer-events-none fixed whitespace-nowrap rounded px-2 py-1 text-[11px] font-normal leading-tight z-[200]",
|
|
2690
|
+
style: {
|
|
2691
|
+
top: pos.top,
|
|
2692
|
+
left: pos.left,
|
|
2693
|
+
transform: "translateY(-50%)",
|
|
2694
|
+
backgroundColor: "var(--color-text-primary)",
|
|
2695
|
+
color: "var(--color-background-primary)"
|
|
2696
|
+
},
|
|
2697
|
+
children: tooltip
|
|
2698
|
+
})]
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
function SidebarControl({ label, children, tooltip, docsPath }) {
|
|
2702
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2703
|
+
className: "space-y-1",
|
|
2704
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2705
|
+
className: "text-[10px] font-medium leading-tight inline-flex items-center gap-1",
|
|
2706
|
+
style: { color: "var(--color-text-secondary)" },
|
|
2707
|
+
children: [label, tooltip && docsPath && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HelpIcon, {
|
|
2708
|
+
tooltip,
|
|
2709
|
+
docsPath
|
|
2710
|
+
})]
|
|
2711
|
+
}), children]
|
|
2712
|
+
});
|
|
2713
|
+
}
|
|
2714
|
+
function SidebarCollapsibleControl({ label, children, defaultCollapsed = true, tooltip, docsPath }) {
|
|
2715
|
+
const [isCollapsed, setIsCollapsed] = react.useState(defaultCollapsed);
|
|
2716
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2717
|
+
className: "space-y-1",
|
|
2718
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2719
|
+
onClick: () => setIsCollapsed(!isCollapsed),
|
|
2720
|
+
className: "w-full flex items-center justify-between text-[10px] font-medium leading-tight transition-colors py-1 cursor-pointer",
|
|
2721
|
+
style: { color: "var(--color-text-secondary)" },
|
|
2722
|
+
type: "button",
|
|
2723
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2724
|
+
className: "inline-flex items-center gap-1",
|
|
2725
|
+
children: [label, tooltip && docsPath && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HelpIcon, {
|
|
2726
|
+
tooltip,
|
|
2727
|
+
docsPath
|
|
2728
|
+
})]
|
|
2729
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2730
|
+
className: "text-[8px]",
|
|
2731
|
+
children: isCollapsed ? "▶" : "▼"
|
|
2732
|
+
})]
|
|
2733
|
+
}), !isCollapsed && children]
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
var formElementStyle = {
|
|
2737
|
+
color: "var(--color-text-primary)",
|
|
2738
|
+
backgroundColor: "var(--color-background-primary)",
|
|
2739
|
+
cursor: "pointer"
|
|
2740
|
+
};
|
|
2741
|
+
function SidebarSelect({ value, onChange, options, placeholder }) {
|
|
2742
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
2743
|
+
value,
|
|
2744
|
+
onChange: (e) => onChange(e.target.value),
|
|
2745
|
+
className: "w-full h-7 text-xs rounded-full px-2 outline-none appearance-none bg-no-repeat bg-[length:12px] bg-[right_6px_center]",
|
|
2746
|
+
style: {
|
|
2747
|
+
...formElementStyle,
|
|
2748
|
+
backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236b7280'%3e%3cpath d='M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z'/%3e%3c/svg%3e")`,
|
|
2749
|
+
paddingRight: "1.5rem"
|
|
2750
|
+
},
|
|
2751
|
+
children: [placeholder && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2752
|
+
value: "",
|
|
2753
|
+
disabled: true,
|
|
2754
|
+
children: placeholder
|
|
2755
|
+
}), options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2756
|
+
value: option.value,
|
|
2757
|
+
children: option.label
|
|
2758
|
+
}, option.value))]
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
function SidebarInput({ value, onChange, applyOnBlur = false, placeholder, type = "text", disabled = false }) {
|
|
2762
|
+
const [draft, setDraft] = (0, react.useState)(value);
|
|
2763
|
+
const [isEditing, setIsEditing] = (0, react.useState)(false);
|
|
2764
|
+
const [prevValue, setPrevValue] = (0, react.useState)(value);
|
|
2765
|
+
if (!isEditing && value !== prevValue) {
|
|
2766
|
+
setPrevValue(value);
|
|
2767
|
+
setDraft(value);
|
|
2768
|
+
}
|
|
2769
|
+
if (applyOnBlur) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2770
|
+
type,
|
|
2771
|
+
value: draft,
|
|
2772
|
+
onChange: (e) => setDraft(e.target.value),
|
|
2773
|
+
onFocus: () => setIsEditing(true),
|
|
2774
|
+
onBlur: () => {
|
|
2775
|
+
setIsEditing(false);
|
|
2776
|
+
onChange(draft);
|
|
2777
|
+
},
|
|
2778
|
+
placeholder,
|
|
2779
|
+
disabled,
|
|
2780
|
+
className: "w-full h-7 text-xs rounded-md px-2 outline-none disabled:opacity-50 disabled:cursor-not-allowed",
|
|
2781
|
+
style: {
|
|
2782
|
+
...formElementStyle,
|
|
2783
|
+
cursor: disabled ? void 0 : "text"
|
|
2784
|
+
}
|
|
2785
|
+
});
|
|
2786
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2787
|
+
type,
|
|
2788
|
+
value,
|
|
2789
|
+
onChange: (e) => onChange(e.target.value),
|
|
2790
|
+
placeholder,
|
|
2791
|
+
disabled,
|
|
2792
|
+
className: "w-full h-7 text-xs rounded-md px-2 outline-none disabled:opacity-50 disabled:cursor-not-allowed",
|
|
2793
|
+
style: {
|
|
2794
|
+
...formElementStyle,
|
|
2795
|
+
cursor: disabled ? void 0 : "text"
|
|
2796
|
+
}
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
2799
|
+
function SidebarCheckbox({ checked, onChange, label, tooltip, docsPath }) {
|
|
2800
|
+
const id = react.useId();
|
|
2801
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2802
|
+
className: "flex items-center gap-1.5",
|
|
2803
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2804
|
+
id,
|
|
2805
|
+
type: "checkbox",
|
|
2806
|
+
checked,
|
|
2807
|
+
onChange: (e) => onChange(e.target.checked),
|
|
2808
|
+
className: "outline-none cursor-pointer"
|
|
2809
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2810
|
+
htmlFor: id,
|
|
2811
|
+
className: "text-[11px] select-none cursor-pointer leading-tight inline-flex items-center gap-1",
|
|
2812
|
+
style: { color: "var(--color-text-primary)" },
|
|
2813
|
+
children: [label, tooltip && docsPath && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HelpIcon, {
|
|
2814
|
+
tooltip,
|
|
2815
|
+
docsPath
|
|
2816
|
+
})]
|
|
2817
|
+
})]
|
|
2818
|
+
});
|
|
2819
|
+
}
|
|
2820
|
+
function SidebarTextarea({ value, onChange, onFocus, onBlur, placeholder, maxRows = 8, error }) {
|
|
2821
|
+
const contentRows = value?.split("\n").length ?? 1;
|
|
2822
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2823
|
+
className: "space-y-0.5",
|
|
2824
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
2825
|
+
value,
|
|
2826
|
+
onChange: (e) => onChange(e.target.value),
|
|
2827
|
+
onFocus,
|
|
2828
|
+
onBlur,
|
|
2829
|
+
placeholder,
|
|
2830
|
+
rows: Math.min(contentRows, maxRows),
|
|
2831
|
+
className: "w-full text-[10px] font-mono rounded-md px-2 py-1.5 outline-none resize-y",
|
|
2832
|
+
style: {
|
|
2833
|
+
...formElementStyle,
|
|
2834
|
+
cursor: "text",
|
|
2835
|
+
whiteSpace: "pre",
|
|
2836
|
+
overflowX: "auto",
|
|
2837
|
+
overflowWrap: "normal",
|
|
2838
|
+
...error ? { boxShadow: "inset 0 0 0 1px var(--color-text-danger, #dc2626)" } : {}
|
|
2839
|
+
},
|
|
2840
|
+
"aria-invalid": !!error
|
|
2841
|
+
}), error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2842
|
+
className: "text-[9px]",
|
|
2843
|
+
style: { color: "var(--color-text-danger, #dc2626)" },
|
|
2844
|
+
children: error
|
|
2845
|
+
})]
|
|
2846
|
+
});
|
|
2847
|
+
}
|
|
2848
|
+
function SidebarToggle({ value, onChange, options }) {
|
|
2849
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2850
|
+
className: "inline-flex w-full rounded-full p-[3px] gap-0.5",
|
|
2851
|
+
style: { backgroundColor: "var(--color-background-tertiary)" },
|
|
2852
|
+
role: "group",
|
|
2853
|
+
"aria-label": "Toggle options",
|
|
2854
|
+
children: options.map((option) => {
|
|
2855
|
+
const isSelected = value === option.value;
|
|
2856
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2857
|
+
onClick: () => onChange(option.value),
|
|
2858
|
+
"aria-pressed": isSelected,
|
|
2859
|
+
className: "flex-1 text-[10px] font-medium h-[22px] px-2 rounded-full outline-none transition-all duration-150 cursor-pointer",
|
|
2860
|
+
style: {
|
|
2861
|
+
backgroundColor: isSelected ? "var(--color-background-primary)" : "transparent",
|
|
2862
|
+
color: isSelected ? "var(--color-text-primary)" : "var(--color-text-secondary)",
|
|
2863
|
+
boxShadow: isSelected ? "0 1px 2px 0 rgba(0,0,0,0.06)" : "none"
|
|
2864
|
+
},
|
|
2865
|
+
type: "button",
|
|
2866
|
+
children: option.label
|
|
2867
|
+
}, option.value);
|
|
2868
|
+
})
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
2871
|
+
//#endregion
|
|
2872
|
+
//#region src/types/simulation.ts
|
|
2873
|
+
/**
|
|
2874
|
+
* Resolve a `ServerToolMock` to a concrete `CallToolResult` given the call arguments.
|
|
2875
|
+
*
|
|
2876
|
+
* - Simple form (single result): returns it directly.
|
|
2877
|
+
* - Conditional form (array of `{ when, result }`): returns the first entry
|
|
2878
|
+
* whose `when` keys all shallow-equal the corresponding values in `args`.
|
|
2879
|
+
* Falls back to `undefined` if no condition matches.
|
|
2880
|
+
*/
|
|
2881
|
+
function resolveServerToolResult(mock, args) {
|
|
2882
|
+
if (!Array.isArray(mock)) return mock;
|
|
2883
|
+
for (const entry of mock) if (Object.entries(entry.when).every(([key, value]) => args != null && args[key] === value)) return entry.result;
|
|
2884
|
+
}
|
|
2885
|
+
//#endregion
|
|
2886
|
+
//#region src/simulator/simulator.tsx
|
|
2887
|
+
function Simulator({ children, simulations = {}, appName = "Sunpeak", appIcon, defaultHost = "chatgpt", onCallTool, defaultProdTools = false, defaultProdResources = false, hideSimulatorModes = false, sandboxUrl }) {
|
|
2888
|
+
const state = useSimulatorState({
|
|
2889
|
+
simulations,
|
|
2890
|
+
defaultHost
|
|
2891
|
+
});
|
|
2892
|
+
const [prodTools, setProdTools] = react.useState(state.urlProdTools ?? defaultProdTools);
|
|
2893
|
+
const [prodResources, setProdResources] = react.useState(state.urlProdResources ?? defaultProdResources);
|
|
2894
|
+
const [isRunning, setIsRunning] = react.useState(false);
|
|
2895
|
+
const [hasRun, setHasRun] = react.useState(false);
|
|
2896
|
+
const [showCheck, setShowCheck] = react.useState(false);
|
|
2897
|
+
const checkTimerRef = react.useRef(void 0);
|
|
2898
|
+
react.useEffect(() => {
|
|
2899
|
+
if (prodTools) setHasRun(false);
|
|
2900
|
+
else {
|
|
2901
|
+
const simResult = state.selectedSim?.toolResult ?? void 0;
|
|
2902
|
+
state.setToolResult(simResult);
|
|
2903
|
+
}
|
|
2904
|
+
}, [prodTools, state.selectedSimulationName]);
|
|
2905
|
+
react.useEffect(() => () => clearTimeout(checkTimerRef.current), []);
|
|
2906
|
+
const toolOptions = react.useMemo(() => {
|
|
2907
|
+
if (!prodTools) return [];
|
|
2908
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2909
|
+
for (const simName of state.simulationNames) {
|
|
2910
|
+
const toolName = simulations[simName].tool.name;
|
|
2911
|
+
if (!seen.has(toolName)) seen.set(toolName, simName);
|
|
2912
|
+
}
|
|
2913
|
+
return Array.from(seen.entries()).map(([toolName, simName]) => ({
|
|
2914
|
+
value: simName,
|
|
2915
|
+
label: simulations[simName].tool.title || toolName
|
|
2916
|
+
}));
|
|
2917
|
+
}, [
|
|
2918
|
+
prodTools,
|
|
2919
|
+
state.simulationNames,
|
|
2920
|
+
simulations
|
|
2921
|
+
]);
|
|
2922
|
+
const handleRun = react.useCallback(async () => {
|
|
2923
|
+
if (!onCallTool || !state.selectedSim) return;
|
|
2924
|
+
const toolName = state.selectedSim.tool.name;
|
|
2925
|
+
setIsRunning(true);
|
|
2926
|
+
try {
|
|
2927
|
+
const result = await onCallTool({
|
|
2928
|
+
name: toolName,
|
|
2929
|
+
arguments: state.toolInput
|
|
2930
|
+
});
|
|
2931
|
+
state.setToolResult(result);
|
|
2932
|
+
state.setToolResultJson(JSON.stringify(result, null, 2));
|
|
2933
|
+
state.setToolResultError("");
|
|
2934
|
+
setHasRun(true);
|
|
2935
|
+
setShowCheck(true);
|
|
2936
|
+
clearTimeout(checkTimerRef.current);
|
|
2937
|
+
checkTimerRef.current = setTimeout(() => setShowCheck(false), 2e3);
|
|
2938
|
+
} catch (err) {
|
|
2939
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2940
|
+
state.setToolResult({
|
|
2941
|
+
content: [{
|
|
2942
|
+
type: "text",
|
|
2943
|
+
text: `Error: ${message}`
|
|
2944
|
+
}],
|
|
2945
|
+
isError: true
|
|
2946
|
+
});
|
|
2947
|
+
state.setToolResultJson(JSON.stringify({
|
|
2948
|
+
content: [{
|
|
2949
|
+
type: "text",
|
|
2950
|
+
text: `Error: ${message}`
|
|
2951
|
+
}],
|
|
2952
|
+
isError: true
|
|
2953
|
+
}, null, 2));
|
|
2954
|
+
} finally {
|
|
2955
|
+
setIsRunning(false);
|
|
2956
|
+
}
|
|
2957
|
+
}, [onCallTool, state]);
|
|
2958
|
+
const activeShell = getHostShell(state.activeHost);
|
|
2959
|
+
const registeredHosts = getRegisteredHosts();
|
|
2960
|
+
const ShellConversation = activeShell?.Conversation;
|
|
2961
|
+
const hostContext = react.useMemo(() => {
|
|
2962
|
+
const styleVars = activeShell?.styleVariables;
|
|
2963
|
+
const userAgent = activeShell?.userAgent;
|
|
2964
|
+
const ctx = { ...state.hostContext };
|
|
2965
|
+
if (styleVars) ctx.styles = { variables: styleVars };
|
|
2966
|
+
if (userAgent) ctx.userAgent = userAgent;
|
|
2967
|
+
return ctx;
|
|
2968
|
+
}, [state.hostContext, activeShell]);
|
|
2969
|
+
react.useEffect(() => {
|
|
2970
|
+
const vars = activeShell?.styleVariables;
|
|
2971
|
+
if (!vars) return;
|
|
2972
|
+
const root = document.documentElement;
|
|
2973
|
+
for (const [key, value] of Object.entries(vars)) if (value) root.style.setProperty(key, value);
|
|
2974
|
+
}, [activeShell]);
|
|
2975
|
+
const prevPageStyleKeysRef = react.useRef([]);
|
|
2976
|
+
react.useEffect(() => {
|
|
2977
|
+
const root = document.documentElement;
|
|
2978
|
+
for (const key of prevPageStyleKeysRef.current) root.style.removeProperty(key);
|
|
2979
|
+
const pageStyles = activeShell?.pageStyles;
|
|
2980
|
+
if (pageStyles) {
|
|
2981
|
+
const keys = [];
|
|
2982
|
+
for (const [key, value] of Object.entries(pageStyles)) {
|
|
2983
|
+
root.style.setProperty(key, value);
|
|
2984
|
+
keys.push(key);
|
|
2985
|
+
}
|
|
2986
|
+
prevPageStyleKeysRef.current = keys;
|
|
2987
|
+
} else prevPageStyleKeysRef.current = [];
|
|
2988
|
+
}, [activeShell]);
|
|
2989
|
+
const handleCallTool = react.useCallback((params) => {
|
|
2990
|
+
if (onCallTool) return onCallTool(params);
|
|
2991
|
+
const mock = state.selectedSim?.serverTools?.[params.name];
|
|
2992
|
+
if (mock) {
|
|
2993
|
+
const result = resolveServerToolResult(mock, params.arguments);
|
|
2994
|
+
if (result) return result;
|
|
2995
|
+
}
|
|
2996
|
+
return { content: [{
|
|
2997
|
+
type: "text",
|
|
2998
|
+
text: `[Simulator] Tool "${params.name}" called — no serverTools mock found in simulation "${state.selectedSimulationName}".`
|
|
2999
|
+
}] };
|
|
3000
|
+
}, [
|
|
3001
|
+
onCallTool,
|
|
3002
|
+
state.selectedSim,
|
|
3003
|
+
state.selectedSimulationName
|
|
3004
|
+
]);
|
|
3005
|
+
const prodToolsUserMessage = prodTools && state.selectedSim ? `Call my ${state.selectedSim.tool.title || state.selectedSim.tool.name} tool` : void 0;
|
|
3006
|
+
const prodResourcesPath = react.useMemo(() => {
|
|
3007
|
+
if (!prodResources || !state.selectedSim?.resource) return void 0;
|
|
3008
|
+
const name = state.selectedSim.resource.name;
|
|
3009
|
+
return `/dist/${name}/${name}.html`;
|
|
3010
|
+
}, [prodResources, state.selectedSim?.resource]);
|
|
3011
|
+
const [prodResourcesReady, setProdResourcesReady] = react.useState(false);
|
|
3012
|
+
const [prodResourcesGeneration, setProdResourcesGeneration] = react.useState(0);
|
|
3013
|
+
const prodResourcesWasReady = react.useRef(false);
|
|
3014
|
+
react.useEffect(() => {
|
|
3015
|
+
if (!prodResourcesPath) {
|
|
3016
|
+
setProdResourcesReady(false);
|
|
3017
|
+
prodResourcesWasReady.current = false;
|
|
3018
|
+
return;
|
|
3019
|
+
}
|
|
3020
|
+
let cancelled = false;
|
|
3021
|
+
let timer;
|
|
3022
|
+
const check = async () => {
|
|
3023
|
+
let ok = false;
|
|
3024
|
+
try {
|
|
3025
|
+
ok = (await fetch(prodResourcesPath, { method: "HEAD" })).ok;
|
|
3026
|
+
} catch {}
|
|
3027
|
+
if (cancelled) return;
|
|
3028
|
+
if (ok) {
|
|
3029
|
+
if (!prodResourcesWasReady.current) setProdResourcesGeneration((g) => g + 1);
|
|
3030
|
+
prodResourcesWasReady.current = true;
|
|
3031
|
+
setProdResourcesReady(true);
|
|
3032
|
+
} else {
|
|
3033
|
+
prodResourcesWasReady.current = false;
|
|
3034
|
+
setProdResourcesReady(false);
|
|
3035
|
+
}
|
|
3036
|
+
timer = setTimeout(check, 1e3);
|
|
3037
|
+
};
|
|
3038
|
+
check();
|
|
3039
|
+
return () => {
|
|
3040
|
+
cancelled = true;
|
|
3041
|
+
clearTimeout(timer);
|
|
3042
|
+
};
|
|
3043
|
+
}, [prodResourcesPath]);
|
|
3044
|
+
const effectiveResourceUrl = (prodResourcesPath && prodResourcesReady ? prodResourcesPath : void 0) ?? state.resourceUrl;
|
|
3045
|
+
const prodResourcesLoading = !!prodResourcesPath && !prodResourcesReady;
|
|
3046
|
+
const showEmptyState = prodTools && !hasRun;
|
|
3047
|
+
let content;
|
|
3048
|
+
const iframeBg = "var(--sim-bg-conversation, var(--color-background-primary, transparent))";
|
|
3049
|
+
if (showEmptyState) content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3050
|
+
className: "h-full w-full flex items-center justify-center",
|
|
3051
|
+
style: { background: iframeBg },
|
|
3052
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3053
|
+
className: "text-sm",
|
|
3054
|
+
style: { color: "var(--color-text-secondary)" },
|
|
3055
|
+
children: [
|
|
3056
|
+
"Press ",
|
|
3057
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Run" }),
|
|
3058
|
+
" to call the tool"
|
|
3059
|
+
]
|
|
3060
|
+
})
|
|
3061
|
+
});
|
|
3062
|
+
else if (prodResourcesLoading) content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3063
|
+
className: "h-full w-full flex items-center justify-center",
|
|
3064
|
+
style: { background: iframeBg },
|
|
3065
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3066
|
+
className: "text-sm",
|
|
3067
|
+
style: { color: "var(--color-text-secondary)" },
|
|
3068
|
+
children: "Building…"
|
|
3069
|
+
})
|
|
3070
|
+
});
|
|
3071
|
+
else if (effectiveResourceUrl) content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3072
|
+
className: "h-full w-full",
|
|
3073
|
+
style: { background: iframeBg },
|
|
3074
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IframeResource, {
|
|
3075
|
+
src: effectiveResourceUrl,
|
|
3076
|
+
hostContext,
|
|
3077
|
+
toolInput: state.toolInput,
|
|
3078
|
+
toolResult: state.effectiveToolResult,
|
|
3079
|
+
hostOptions: {
|
|
3080
|
+
hostInfo: activeShell?.hostInfo,
|
|
3081
|
+
hostCapabilities: activeShell?.hostCapabilities,
|
|
3082
|
+
onDisplayModeChange: state.handleDisplayModeChange,
|
|
3083
|
+
onUpdateModelContext: state.handleUpdateModelContext,
|
|
3084
|
+
onCallTool: handleCallTool
|
|
3085
|
+
},
|
|
3086
|
+
permissions: state.permissions,
|
|
3087
|
+
prefersBorder: state.prefersBorder,
|
|
3088
|
+
onDisplayModeReady: state.handleDisplayModeReady,
|
|
3089
|
+
debugInjectState: state.modelContext,
|
|
3090
|
+
injectOpenAIRuntime: state.activeHost === "chatgpt",
|
|
3091
|
+
sandboxUrl,
|
|
3092
|
+
className: "h-full w-full"
|
|
3093
|
+
}, `${state.activeHost}-${state.selectedSimulationName}-${prodResources}-${prodResourcesGeneration}`)
|
|
3094
|
+
});
|
|
3095
|
+
else if (!prodResources && state.resourceScript) content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3096
|
+
className: "h-full w-full",
|
|
3097
|
+
style: { background: iframeBg },
|
|
3098
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IframeResource, {
|
|
3099
|
+
scriptSrc: state.resourceScript,
|
|
3100
|
+
hostContext,
|
|
3101
|
+
toolInput: state.toolInput,
|
|
3102
|
+
toolResult: state.effectiveToolResult,
|
|
3103
|
+
csp: state.csp,
|
|
3104
|
+
hostOptions: {
|
|
3105
|
+
hostInfo: activeShell?.hostInfo,
|
|
3106
|
+
hostCapabilities: activeShell?.hostCapabilities,
|
|
3107
|
+
onDisplayModeChange: state.handleDisplayModeChange,
|
|
3108
|
+
onUpdateModelContext: state.handleUpdateModelContext,
|
|
3109
|
+
onCallTool: handleCallTool
|
|
3110
|
+
},
|
|
3111
|
+
permissions: state.permissions,
|
|
3112
|
+
prefersBorder: state.prefersBorder,
|
|
3113
|
+
onDisplayModeReady: state.handleDisplayModeReady,
|
|
3114
|
+
debugInjectState: state.modelContext,
|
|
3115
|
+
injectOpenAIRuntime: state.activeHost === "chatgpt",
|
|
3116
|
+
sandboxUrl,
|
|
3117
|
+
className: "h-full w-full"
|
|
3118
|
+
}, `${state.activeHost}-${state.selectedSimulationName}`)
|
|
3119
|
+
});
|
|
3120
|
+
else content = children;
|
|
3121
|
+
const applyTheme = activeShell?.applyTheme;
|
|
3122
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThemeProvider, {
|
|
3123
|
+
theme: state.theme,
|
|
3124
|
+
applyTheme,
|
|
3125
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SimpleSidebar, {
|
|
3126
|
+
controls: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3127
|
+
className: "space-y-1",
|
|
3128
|
+
children: [
|
|
3129
|
+
!hideSimulatorModes && onCallTool && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCheckbox, {
|
|
3130
|
+
checked: prodTools,
|
|
3131
|
+
onChange: setProdTools,
|
|
3132
|
+
label: "Prod Tools",
|
|
3133
|
+
tooltip: "Use real tool handlers instead of simulations",
|
|
3134
|
+
docsPath: "api-reference/cli/dev#prod-tools-and-prod-resources-flags"
|
|
3135
|
+
}),
|
|
3136
|
+
!hideSimulatorModes && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCheckbox, {
|
|
3137
|
+
checked: prodResources,
|
|
3138
|
+
onChange: setProdResources,
|
|
3139
|
+
label: "Prod Resources",
|
|
3140
|
+
tooltip: "Load resources from dist/ builds instead of HMR",
|
|
3141
|
+
docsPath: "api-reference/cli/dev#prod-tools-and-prod-resources-flags"
|
|
3142
|
+
}),
|
|
3143
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3144
|
+
className: "grid grid-cols-2 gap-2",
|
|
3145
|
+
children: [registeredHosts.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3146
|
+
label: "Host",
|
|
3147
|
+
tooltip: "Host runtime to simulate",
|
|
3148
|
+
docsPath: "api-reference/functions/host-detection",
|
|
3149
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSelect, {
|
|
3150
|
+
value: state.activeHost,
|
|
3151
|
+
onChange: (value) => state.setActiveHost(value),
|
|
3152
|
+
options: registeredHosts.map((h) => ({
|
|
3153
|
+
value: h.id,
|
|
3154
|
+
label: h.label
|
|
3155
|
+
}))
|
|
3156
|
+
})
|
|
3157
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3158
|
+
label: "Width",
|
|
3159
|
+
tooltip: "Chat width",
|
|
3160
|
+
docsPath: "api-reference/simulations/simulator",
|
|
3161
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSelect, {
|
|
3162
|
+
value: state.screenWidth,
|
|
3163
|
+
onChange: (value) => state.setScreenWidth(value),
|
|
3164
|
+
options: [
|
|
3165
|
+
{
|
|
3166
|
+
value: "mobile-s",
|
|
3167
|
+
label: "Mobile S (375px)"
|
|
3168
|
+
},
|
|
3169
|
+
{
|
|
3170
|
+
value: "mobile-l",
|
|
3171
|
+
label: "Mobile L (425px)"
|
|
3172
|
+
},
|
|
3173
|
+
{
|
|
3174
|
+
value: "tablet",
|
|
3175
|
+
label: "Tablet (768px)"
|
|
3176
|
+
},
|
|
3177
|
+
{
|
|
3178
|
+
value: "full",
|
|
3179
|
+
label: "100% (Full)"
|
|
3180
|
+
}
|
|
3181
|
+
]
|
|
3182
|
+
})
|
|
3183
|
+
})]
|
|
3184
|
+
}),
|
|
3185
|
+
prodTools && toolOptions.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3186
|
+
label: "Tool",
|
|
3187
|
+
tooltip: "Tool to call with prod handler",
|
|
3188
|
+
docsPath: "api-reference/cli/dev",
|
|
3189
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSelect, {
|
|
3190
|
+
value: state.selectedSimulationName,
|
|
3191
|
+
onChange: (value) => state.setSelectedSimulationName(value),
|
|
3192
|
+
options: toolOptions
|
|
3193
|
+
})
|
|
3194
|
+
}),
|
|
3195
|
+
!prodTools && state.simulationNames.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3196
|
+
label: "Simulation",
|
|
3197
|
+
tooltip: "Test fixture to render",
|
|
3198
|
+
docsPath: "api-reference/simulations/simulation",
|
|
3199
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSelect, {
|
|
3200
|
+
value: state.selectedSimulationName,
|
|
3201
|
+
onChange: (value) => state.setSelectedSimulationName(value),
|
|
3202
|
+
options: state.simulationNames.map((name) => {
|
|
3203
|
+
const sim = simulations[name];
|
|
3204
|
+
const resourceTitle = sim.resource ? sim.resource.title || sim.resource.name : void 0;
|
|
3205
|
+
const toolTitle = sim.tool.title || sim.tool.name;
|
|
3206
|
+
return {
|
|
3207
|
+
value: name,
|
|
3208
|
+
label: resourceTitle ? `${resourceTitle}: ${toolTitle}` : toolTitle
|
|
3209
|
+
};
|
|
3210
|
+
})
|
|
3211
|
+
})
|
|
3212
|
+
}),
|
|
3213
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCollapsibleControl, {
|
|
3214
|
+
label: "Host Context",
|
|
3215
|
+
defaultCollapsed: false,
|
|
3216
|
+
tooltip: "Host-provided environment",
|
|
3217
|
+
docsPath: "api-reference/hooks/use-host-context",
|
|
3218
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3219
|
+
className: "space-y-1",
|
|
3220
|
+
children: [
|
|
3221
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3222
|
+
className: "grid grid-cols-[2fr_1fr] gap-2",
|
|
3223
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3224
|
+
label: "Theme",
|
|
3225
|
+
tooltip: "Host color theme",
|
|
3226
|
+
docsPath: "api-reference/hooks/use-theme",
|
|
3227
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarToggle, {
|
|
3228
|
+
value: state.theme,
|
|
3229
|
+
onChange: (value) => state.setTheme(value),
|
|
3230
|
+
options: [{
|
|
3231
|
+
value: "light",
|
|
3232
|
+
label: "Light"
|
|
3233
|
+
}, {
|
|
3234
|
+
value: "dark",
|
|
3235
|
+
label: "Dark"
|
|
3236
|
+
}]
|
|
3237
|
+
})
|
|
3238
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3239
|
+
label: "Locale",
|
|
3240
|
+
tooltip: "BCP 47 language tag",
|
|
3241
|
+
docsPath: "api-reference/hooks/use-locale",
|
|
3242
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarInput, {
|
|
3243
|
+
applyOnBlur: true,
|
|
3244
|
+
value: state.locale,
|
|
3245
|
+
onChange: (value) => state.setLocale(value),
|
|
3246
|
+
placeholder: "en-US"
|
|
3247
|
+
})
|
|
3248
|
+
})]
|
|
3249
|
+
}),
|
|
3250
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3251
|
+
label: "Display Mode",
|
|
3252
|
+
tooltip: "Host resource rendering paradigm",
|
|
3253
|
+
docsPath: "api-reference/hooks/use-display-mode",
|
|
3254
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarToggle, {
|
|
3255
|
+
value: state.displayMode,
|
|
3256
|
+
onChange: (value) => state.setDisplayMode(value),
|
|
3257
|
+
options: [
|
|
3258
|
+
{
|
|
3259
|
+
value: "inline",
|
|
3260
|
+
label: "Inline"
|
|
3261
|
+
},
|
|
3262
|
+
{
|
|
3263
|
+
value: "pip",
|
|
3264
|
+
label: "PiP"
|
|
3265
|
+
},
|
|
3266
|
+
{
|
|
3267
|
+
value: "fullscreen",
|
|
3268
|
+
label: "Full"
|
|
3269
|
+
}
|
|
3270
|
+
]
|
|
3271
|
+
})
|
|
3272
|
+
}),
|
|
3273
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3274
|
+
className: "grid grid-cols-7 gap-2",
|
|
3275
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3276
|
+
className: "col-span-3",
|
|
3277
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3278
|
+
label: "Platform",
|
|
3279
|
+
tooltip: "End user device platform",
|
|
3280
|
+
docsPath: "api-reference/hooks/use-platform",
|
|
3281
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSelect, {
|
|
3282
|
+
value: state.platform,
|
|
3283
|
+
onChange: (value) => {
|
|
3284
|
+
const p = value;
|
|
3285
|
+
state.setPlatform(p);
|
|
3286
|
+
if (p === "mobile") {
|
|
3287
|
+
state.setHover(false);
|
|
3288
|
+
state.setTouch(true);
|
|
3289
|
+
} else if (p === "desktop") {
|
|
3290
|
+
state.setHover(true);
|
|
3291
|
+
state.setTouch(false);
|
|
3292
|
+
} else {
|
|
3293
|
+
state.setHover(true);
|
|
3294
|
+
state.setTouch(false);
|
|
3295
|
+
}
|
|
3296
|
+
},
|
|
3297
|
+
options: [
|
|
3298
|
+
{
|
|
3299
|
+
value: "mobile",
|
|
3300
|
+
label: "Mobile"
|
|
3301
|
+
},
|
|
3302
|
+
{
|
|
3303
|
+
value: "desktop",
|
|
3304
|
+
label: "Desktop"
|
|
3305
|
+
},
|
|
3306
|
+
{
|
|
3307
|
+
value: "web",
|
|
3308
|
+
label: "Web"
|
|
3309
|
+
}
|
|
3310
|
+
]
|
|
3311
|
+
})
|
|
3312
|
+
})
|
|
3313
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3314
|
+
className: "col-span-4",
|
|
3315
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3316
|
+
label: "Capabilities",
|
|
3317
|
+
tooltip: "End user device capabilities",
|
|
3318
|
+
docsPath: "api-reference/hooks/use-device-capabilities",
|
|
3319
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3320
|
+
className: "flex gap-2",
|
|
3321
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCheckbox, {
|
|
3322
|
+
checked: state.hover,
|
|
3323
|
+
onChange: state.setHover,
|
|
3324
|
+
label: "Hover"
|
|
3325
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCheckbox, {
|
|
3326
|
+
checked: state.touch,
|
|
3327
|
+
onChange: state.setTouch,
|
|
3328
|
+
label: "Touch"
|
|
3329
|
+
})]
|
|
3330
|
+
})
|
|
3331
|
+
})
|
|
3332
|
+
})]
|
|
3333
|
+
}),
|
|
3334
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3335
|
+
label: "Time Zone",
|
|
3336
|
+
tooltip: "End user IANA time zone",
|
|
3337
|
+
docsPath: "api-reference/hooks/use-time-zone",
|
|
3338
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarInput, {
|
|
3339
|
+
applyOnBlur: true,
|
|
3340
|
+
value: state.timeZone,
|
|
3341
|
+
onChange: (value) => state.setTimeZone(value),
|
|
3342
|
+
placeholder: "e.g. America/New_York"
|
|
3343
|
+
})
|
|
3344
|
+
}),
|
|
3345
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3346
|
+
label: "Container Dimensions",
|
|
3347
|
+
tooltip: "Host-enforced size constraints (px)",
|
|
3348
|
+
docsPath: "api-reference/hooks/use-viewport",
|
|
3349
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3350
|
+
className: "grid grid-cols-4 gap-1",
|
|
3351
|
+
children: [
|
|
3352
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3353
|
+
label: "Height",
|
|
3354
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarInput, {
|
|
3355
|
+
type: "number",
|
|
3356
|
+
applyOnBlur: true,
|
|
3357
|
+
placeholder: "-",
|
|
3358
|
+
value: state.containerHeight != null ? String(state.containerHeight) : "",
|
|
3359
|
+
onChange: (value) => state.setContainerHeight(value ? Number(value) : void 0)
|
|
3360
|
+
})
|
|
3361
|
+
}),
|
|
3362
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3363
|
+
label: "Width",
|
|
3364
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarInput, {
|
|
3365
|
+
type: "number",
|
|
3366
|
+
applyOnBlur: true,
|
|
3367
|
+
placeholder: "-",
|
|
3368
|
+
value: state.containerWidth != null ? String(state.containerWidth) : "",
|
|
3369
|
+
onChange: (value) => state.setContainerWidth(value ? Number(value) : void 0)
|
|
3370
|
+
})
|
|
3371
|
+
}),
|
|
3372
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3373
|
+
label: "Max H",
|
|
3374
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarInput, {
|
|
3375
|
+
type: "number",
|
|
3376
|
+
applyOnBlur: true,
|
|
3377
|
+
placeholder: "-",
|
|
3378
|
+
value: state.containerMaxHeight != null ? String(state.containerMaxHeight) : "",
|
|
3379
|
+
onChange: (value) => state.setContainerMaxHeight(value ? Number(value) : void 0)
|
|
3380
|
+
})
|
|
3381
|
+
}),
|
|
3382
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3383
|
+
label: "Max W",
|
|
3384
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarInput, {
|
|
3385
|
+
type: "number",
|
|
3386
|
+
applyOnBlur: true,
|
|
3387
|
+
placeholder: state.measuredContentWidth != null ? String(state.measuredContentWidth) : "-",
|
|
3388
|
+
value: state.containerMaxWidth != null ? String(state.containerMaxWidth) : "",
|
|
3389
|
+
onChange: (value) => state.setContainerMaxWidth(value ? Number(value) : void 0)
|
|
3390
|
+
})
|
|
3391
|
+
})
|
|
3392
|
+
]
|
|
3393
|
+
})
|
|
3394
|
+
}),
|
|
3395
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
|
|
3396
|
+
label: "Safe Area Insets",
|
|
3397
|
+
tooltip: "Device safe area padding (px)",
|
|
3398
|
+
docsPath: "api-reference/hooks/use-safe-area",
|
|
3399
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3400
|
+
className: "grid grid-cols-4 gap-1",
|
|
3401
|
+
children: [
|
|
3402
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3403
|
+
className: "flex items-center gap-0.5",
|
|
3404
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3405
|
+
className: "text-[10px]",
|
|
3406
|
+
style: { color: "var(--color-text-secondary)" },
|
|
3407
|
+
children: "↑"
|
|
3408
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarInput, {
|
|
3409
|
+
type: "number",
|
|
3410
|
+
applyOnBlur: true,
|
|
3411
|
+
placeholder: "-",
|
|
3412
|
+
value: state.safeAreaInsets.top ? String(state.safeAreaInsets.top) : "",
|
|
3413
|
+
onChange: (value) => state.setSafeAreaInsets((prev) => ({
|
|
3414
|
+
...prev,
|
|
3415
|
+
top: Number(value) || 0
|
|
3416
|
+
}))
|
|
3417
|
+
})]
|
|
3418
|
+
}),
|
|
3419
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3420
|
+
className: "flex items-center gap-0.5",
|
|
3421
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3422
|
+
className: "text-[10px]",
|
|
3423
|
+
style: { color: "var(--color-text-secondary)" },
|
|
3424
|
+
children: "↓"
|
|
3425
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarInput, {
|
|
3426
|
+
type: "number",
|
|
3427
|
+
applyOnBlur: true,
|
|
3428
|
+
placeholder: "-",
|
|
3429
|
+
value: state.safeAreaInsets.bottom ? String(state.safeAreaInsets.bottom) : "",
|
|
3430
|
+
onChange: (value) => state.setSafeAreaInsets((prev) => ({
|
|
3431
|
+
...prev,
|
|
3432
|
+
bottom: Number(value) || 0
|
|
3433
|
+
}))
|
|
3434
|
+
})]
|
|
3435
|
+
}),
|
|
3436
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3437
|
+
className: "flex items-center gap-0.5",
|
|
3438
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3439
|
+
className: "text-[10px]",
|
|
3440
|
+
style: { color: "var(--color-text-secondary)" },
|
|
3441
|
+
children: "←"
|
|
3442
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarInput, {
|
|
3443
|
+
type: "number",
|
|
3444
|
+
applyOnBlur: true,
|
|
3445
|
+
placeholder: "-",
|
|
3446
|
+
value: state.safeAreaInsets.left ? String(state.safeAreaInsets.left) : "",
|
|
3447
|
+
onChange: (value) => state.setSafeAreaInsets((prev) => ({
|
|
3448
|
+
...prev,
|
|
3449
|
+
left: Number(value) || 0
|
|
3450
|
+
}))
|
|
3451
|
+
})]
|
|
3452
|
+
}),
|
|
3453
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3454
|
+
className: "flex items-center gap-0.5",
|
|
3455
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3456
|
+
className: "text-[10px]",
|
|
3457
|
+
style: { color: "var(--color-text-secondary)" },
|
|
3458
|
+
children: "→"
|
|
3459
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarInput, {
|
|
3460
|
+
type: "number",
|
|
3461
|
+
applyOnBlur: true,
|
|
3462
|
+
placeholder: "-",
|
|
3463
|
+
value: state.safeAreaInsets.right ? String(state.safeAreaInsets.right) : "",
|
|
3464
|
+
onChange: (value) => state.setSafeAreaInsets((prev) => ({
|
|
3465
|
+
...prev,
|
|
3466
|
+
right: Number(value) || 0
|
|
3467
|
+
}))
|
|
3468
|
+
})]
|
|
3469
|
+
})
|
|
3470
|
+
]
|
|
3471
|
+
})
|
|
3472
|
+
})
|
|
3473
|
+
]
|
|
3474
|
+
})
|
|
3475
|
+
}),
|
|
3476
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCollapsibleControl, {
|
|
3477
|
+
label: "App Context",
|
|
3478
|
+
defaultCollapsed: true,
|
|
3479
|
+
tooltip: "App-provided context shared with the model",
|
|
3480
|
+
docsPath: "api-reference/hooks/use-app-state",
|
|
3481
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarTextarea, {
|
|
3482
|
+
value: state.modelContextJson,
|
|
3483
|
+
onChange: (json) => state.validateJSON(json, state.setModelContextJson, state.setModelContextError),
|
|
3484
|
+
onFocus: () => state.setEditingField("modelContext"),
|
|
3485
|
+
onBlur: () => state.commitJSON(state.modelContextJson, state.setModelContextError, (parsed) => {
|
|
3486
|
+
state.setModelContext(parsed);
|
|
3487
|
+
}),
|
|
3488
|
+
error: state.modelContextError,
|
|
3489
|
+
maxRows: 8
|
|
3490
|
+
})
|
|
3491
|
+
}),
|
|
3492
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCollapsibleControl, {
|
|
3493
|
+
label: "Tool Input (JSON)",
|
|
3494
|
+
defaultCollapsed: !prodTools,
|
|
3495
|
+
tooltip: "Arguments passed to the tool",
|
|
3496
|
+
docsPath: "api-reference/hooks/use-tool-data",
|
|
3497
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarTextarea, {
|
|
3498
|
+
value: state.toolInputJson,
|
|
3499
|
+
onChange: (json) => state.validateJSON(json, state.setToolInputJson, state.setToolInputError),
|
|
3500
|
+
onFocus: () => state.setEditingField("toolInput"),
|
|
3501
|
+
onBlur: () => state.commitJSON(state.toolInputJson, state.setToolInputError, (parsed) => state.setToolInput(parsed ?? {})),
|
|
3502
|
+
error: state.toolInputError,
|
|
3503
|
+
maxRows: 8
|
|
3504
|
+
})
|
|
3505
|
+
}, `tool-input-${prodTools}`),
|
|
3506
|
+
!prodTools && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCollapsibleControl, {
|
|
3507
|
+
label: "Tool Result (JSON)",
|
|
3508
|
+
defaultCollapsed: false,
|
|
3509
|
+
tooltip: "Structured content returned by the tool",
|
|
3510
|
+
docsPath: "api-reference/hooks/use-tool-data",
|
|
3511
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarTextarea, {
|
|
3512
|
+
value: state.toolResultJson,
|
|
3513
|
+
onChange: (json) => state.validateJSON(json, state.setToolResultJson, state.setToolResultError),
|
|
3514
|
+
onFocus: () => state.setEditingField("toolResult"),
|
|
3515
|
+
onBlur: () => state.commitJSON(state.toolResultJson, state.setToolResultError, (parsed) => {
|
|
3516
|
+
if (parsed === null) state.setToolResult(void 0);
|
|
3517
|
+
else {
|
|
3518
|
+
const result = parsed;
|
|
3519
|
+
if ("content" in result || "structuredContent" in result) state.setToolResult(result);
|
|
3520
|
+
else state.setToolResult({
|
|
3521
|
+
content: [],
|
|
3522
|
+
structuredContent: result
|
|
3523
|
+
});
|
|
3524
|
+
}
|
|
3525
|
+
}),
|
|
3526
|
+
error: state.toolResultError,
|
|
3527
|
+
maxRows: 8
|
|
3528
|
+
})
|
|
3529
|
+
})
|
|
3530
|
+
]
|
|
3531
|
+
}),
|
|
3532
|
+
children: ShellConversation ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShellConversation, {
|
|
3533
|
+
screenWidth: state.screenWidth,
|
|
3534
|
+
displayMode: state.displayMode,
|
|
3535
|
+
platform: state.platform,
|
|
3536
|
+
onRequestDisplayMode: state.handleDisplayModeChange,
|
|
3537
|
+
appName,
|
|
3538
|
+
appIcon,
|
|
3539
|
+
userMessage: prodToolsUserMessage ?? state.selectedSim?.userMessage,
|
|
3540
|
+
onContentWidthChange: state.handleContentWidthChange,
|
|
3541
|
+
headerAction: prodTools && onCallTool ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3542
|
+
type: "button",
|
|
3543
|
+
onClick: handleRun,
|
|
3544
|
+
disabled: isRunning,
|
|
3545
|
+
className: "rounded-full px-3 py-1 text-sm font-medium transition-opacity disabled:opacity-40 flex items-center gap-1.5 cursor-pointer",
|
|
3546
|
+
style: {
|
|
3547
|
+
backgroundColor: "var(--color-text-primary)",
|
|
3548
|
+
color: "var(--color-background-primary)"
|
|
3549
|
+
},
|
|
3550
|
+
children: [showCheck ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3551
|
+
width: "12",
|
|
3552
|
+
height: "12",
|
|
3553
|
+
viewBox: "0 0 12 12",
|
|
3554
|
+
fill: "none",
|
|
3555
|
+
stroke: "currentColor",
|
|
3556
|
+
strokeWidth: "2",
|
|
3557
|
+
strokeLinecap: "round",
|
|
3558
|
+
strokeLinejoin: "round",
|
|
3559
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2 6L5 9L10 3" })
|
|
3560
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3561
|
+
width: "10",
|
|
3562
|
+
height: "12",
|
|
3563
|
+
viewBox: "0 0 10 12",
|
|
3564
|
+
fill: "currentColor",
|
|
3565
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M0 0L10 6L0 12V0Z" })
|
|
3566
|
+
}), "Run"]
|
|
3567
|
+
}) : void 0,
|
|
3568
|
+
children: content
|
|
3569
|
+
}) : content
|
|
3570
|
+
})
|
|
3571
|
+
});
|
|
3572
|
+
}
|
|
3573
|
+
//#endregion
|
|
3574
|
+
Object.defineProperty(exports, "DEFAULT_STYLE_VARIABLES", {
|
|
3575
|
+
enumerable: true,
|
|
3576
|
+
get: function() {
|
|
3577
|
+
return DEFAULT_STYLE_VARIABLES;
|
|
3578
|
+
}
|
|
3579
|
+
});
|
|
3580
|
+
Object.defineProperty(exports, "IframeResource", {
|
|
3581
|
+
enumerable: true,
|
|
3582
|
+
get: function() {
|
|
3583
|
+
return IframeResource;
|
|
3584
|
+
}
|
|
3585
|
+
});
|
|
3586
|
+
Object.defineProperty(exports, "McpAppHost", {
|
|
3587
|
+
enumerable: true,
|
|
3588
|
+
get: function() {
|
|
3589
|
+
return McpAppHost;
|
|
3590
|
+
}
|
|
3591
|
+
});
|
|
3592
|
+
Object.defineProperty(exports, "SCREEN_WIDTHS", {
|
|
3593
|
+
enumerable: true,
|
|
3594
|
+
get: function() {
|
|
3595
|
+
return SCREEN_WIDTHS;
|
|
3596
|
+
}
|
|
3597
|
+
});
|
|
3598
|
+
Object.defineProperty(exports, "SidebarCheckbox", {
|
|
3599
|
+
enumerable: true,
|
|
3600
|
+
get: function() {
|
|
3601
|
+
return SidebarCheckbox;
|
|
3602
|
+
}
|
|
3603
|
+
});
|
|
3604
|
+
Object.defineProperty(exports, "SidebarCollapsibleControl", {
|
|
3605
|
+
enumerable: true,
|
|
3606
|
+
get: function() {
|
|
3607
|
+
return SidebarCollapsibleControl;
|
|
3608
|
+
}
|
|
3609
|
+
});
|
|
3610
|
+
Object.defineProperty(exports, "SidebarControl", {
|
|
3611
|
+
enumerable: true,
|
|
3612
|
+
get: function() {
|
|
3613
|
+
return SidebarControl;
|
|
3614
|
+
}
|
|
3615
|
+
});
|
|
3616
|
+
Object.defineProperty(exports, "SidebarInput", {
|
|
3617
|
+
enumerable: true,
|
|
3618
|
+
get: function() {
|
|
3619
|
+
return SidebarInput;
|
|
3620
|
+
}
|
|
3621
|
+
});
|
|
3622
|
+
Object.defineProperty(exports, "SidebarSelect", {
|
|
3623
|
+
enumerable: true,
|
|
3624
|
+
get: function() {
|
|
3625
|
+
return SidebarSelect;
|
|
3626
|
+
}
|
|
3627
|
+
});
|
|
3628
|
+
Object.defineProperty(exports, "SidebarTextarea", {
|
|
3629
|
+
enumerable: true,
|
|
3630
|
+
get: function() {
|
|
3631
|
+
return SidebarTextarea;
|
|
3632
|
+
}
|
|
3633
|
+
});
|
|
3634
|
+
Object.defineProperty(exports, "SidebarToggle", {
|
|
3635
|
+
enumerable: true,
|
|
3636
|
+
get: function() {
|
|
3637
|
+
return SidebarToggle;
|
|
3638
|
+
}
|
|
3639
|
+
});
|
|
3640
|
+
Object.defineProperty(exports, "SimpleSidebar", {
|
|
3641
|
+
enumerable: true,
|
|
3642
|
+
get: function() {
|
|
3643
|
+
return SimpleSidebar;
|
|
3644
|
+
}
|
|
3645
|
+
});
|
|
3646
|
+
Object.defineProperty(exports, "Simulator", {
|
|
3647
|
+
enumerable: true,
|
|
3648
|
+
get: function() {
|
|
3649
|
+
return Simulator;
|
|
3650
|
+
}
|
|
3651
|
+
});
|
|
3652
|
+
Object.defineProperty(exports, "ThemeProvider", {
|
|
3653
|
+
enumerable: true,
|
|
3654
|
+
get: function() {
|
|
3655
|
+
return ThemeProvider;
|
|
3656
|
+
}
|
|
3657
|
+
});
|
|
3658
|
+
Object.defineProperty(exports, "extractResourceCSP", {
|
|
3659
|
+
enumerable: true,
|
|
3660
|
+
get: function() {
|
|
3661
|
+
return extractResourceCSP;
|
|
3662
|
+
}
|
|
3663
|
+
});
|
|
3664
|
+
Object.defineProperty(exports, "getHostShell", {
|
|
3665
|
+
enumerable: true,
|
|
3666
|
+
get: function() {
|
|
3667
|
+
return getHostShell;
|
|
3668
|
+
}
|
|
3669
|
+
});
|
|
3670
|
+
Object.defineProperty(exports, "getRegisteredHosts", {
|
|
3671
|
+
enumerable: true,
|
|
3672
|
+
get: function() {
|
|
3673
|
+
return getRegisteredHosts;
|
|
3674
|
+
}
|
|
3675
|
+
});
|
|
3676
|
+
Object.defineProperty(exports, "registerHostShell", {
|
|
3677
|
+
enumerable: true,
|
|
3678
|
+
get: function() {
|
|
3679
|
+
return registerHostShell;
|
|
3680
|
+
}
|
|
3681
|
+
});
|
|
3682
|
+
Object.defineProperty(exports, "resolveServerToolResult", {
|
|
3683
|
+
enumerable: true,
|
|
3684
|
+
get: function() {
|
|
3685
|
+
return resolveServerToolResult;
|
|
3686
|
+
}
|
|
3687
|
+
});
|
|
3688
|
+
Object.defineProperty(exports, "useSimulatorState", {
|
|
3689
|
+
enumerable: true,
|
|
3690
|
+
get: function() {
|
|
3691
|
+
return useSimulatorState;
|
|
3692
|
+
}
|
|
3693
|
+
});
|
|
3694
|
+
Object.defineProperty(exports, "useThemeContext", {
|
|
3695
|
+
enumerable: true,
|
|
3696
|
+
get: function() {
|
|
3697
|
+
return useThemeContext;
|
|
3698
|
+
}
|
|
3699
|
+
});
|
|
3700
|
+
|
|
3701
|
+
//# sourceMappingURL=simulator-BYIH-xqQ.cjs.map
|